From 3fcf01d821cafc54c0fecde78eb3bd045ce9793d Mon Sep 17 00:00:00 2001
From: hexiaodong <1@1.com>
Date: Thu, 11 Sep 2025 20:49:36 +0800
Subject: [PATCH] hxd202509112049
---
.DS_Store | Bin 18436 -> 0 bytes
antis-ncc-admin/.env.development | 4 ++--
netcore/netcore/.DS_Store | Bin 6148 -> 0 bytes
netcore/netcore/src/.DS_Store | Bin 6148 -> 0 bytes
netcore/netcore/src/Modularity/.DS_Store | Bin 6148 -> 0 bytes
netcore/src/.DS_Store | Bin 10244 -> 0 bytes
netcore/src/Application/.DS_Store | Bin 10244 -> 0 bytes
netcore/src/Application/NCC.API/appsettings.json | 2 +-
service/LqSalaryCalculationService/Dockerfile | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/LqSalaryCalculationService.csproj | 31 +++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/Models/EmployeeSalary.cs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/Models/SalaryCalculationConfig.cs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/Program.cs | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/README.md | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/Services/ISalaryCalculationService.cs | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/Services/SalaryCalculationService.cs | 600 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/appsettings.json | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/docker-compose.yml | 23 +++++++++++++++++++++++
service/LqSalaryCalculationService/start.bat | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
service/LqSalaryCalculationService/start.sh | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
20 files changed, 1838 insertions(+), 3 deletions(-)
create mode 100644 service/LqSalaryCalculationService/Dockerfile
create mode 100644 service/LqSalaryCalculationService/LqSalaryCalculationService.csproj
create mode 100644 service/LqSalaryCalculationService/Models/EmployeeSalary.cs
create mode 100644 service/LqSalaryCalculationService/Models/SalaryCalculationConfig.cs
create mode 100644 service/LqSalaryCalculationService/Program.cs
create mode 100644 service/LqSalaryCalculationService/README.md
create mode 100644 service/LqSalaryCalculationService/Services/ISalaryCalculationService.cs
create mode 100644 service/LqSalaryCalculationService/Services/SalaryCalculationService.cs
create mode 100644 service/LqSalaryCalculationService/appsettings.json
create mode 100644 service/LqSalaryCalculationService/docker-compose.yml
create mode 100644 service/LqSalaryCalculationService/start.bat
create mode 100755 service/LqSalaryCalculationService/start.sh
diff --git a/.DS_Store b/.DS_Store
index dcd23dd..5f85f05 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/antis-ncc-admin/.env.development b/antis-ncc-admin/.env.development
index 92ac187..f2eb5cf 100644
--- a/antis-ncc-admin/.env.development
+++ b/antis-ncc-admin/.env.development
@@ -1,5 +1,5 @@
# 开发
VUE_CLI_BABEL_TRANSPILE_MODULES = true
-VUE_APP_BASE_API = 'http://localhost:5000'
-VUE_APP_BASE_WSS = 'ws://192.168.110.45:5000/websocket'
+VUE_APP_BASE_API = 'http://localhost:2011'
+VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
diff --git a/netcore/netcore/.DS_Store b/netcore/netcore/.DS_Store
index 95a02e7..5128fb4 100644
Binary files a/netcore/netcore/.DS_Store and b/netcore/netcore/.DS_Store differ
diff --git a/netcore/netcore/src/.DS_Store b/netcore/netcore/src/.DS_Store
index c66fa50..b82cfb6 100644
Binary files a/netcore/netcore/src/.DS_Store and b/netcore/netcore/src/.DS_Store differ
diff --git a/netcore/netcore/src/Modularity/.DS_Store b/netcore/netcore/src/Modularity/.DS_Store
index 3a7fe57..77bc87c 100644
Binary files a/netcore/netcore/src/Modularity/.DS_Store and b/netcore/netcore/src/Modularity/.DS_Store differ
diff --git a/netcore/src/.DS_Store b/netcore/src/.DS_Store
index 804c075..35396ae 100644
Binary files a/netcore/src/.DS_Store and b/netcore/src/.DS_Store differ
diff --git a/netcore/src/Application/.DS_Store b/netcore/src/Application/.DS_Store
index 7413767..5f772a8 100644
Binary files a/netcore/src/Application/.DS_Store and b/netcore/src/Application/.DS_Store differ
diff --git a/netcore/src/Application/NCC.API/appsettings.json b/netcore/src/Application/NCC.API/appsettings.json
index fabb2c9..655c370 100644
--- a/netcore/src/Application/NCC.API/appsettings.json
+++ b/netcore/src/Application/NCC.API/appsettings.json
@@ -77,7 +77,7 @@
},
"CorsAccessorSettings": {
"PolicyName": "NCCCorsAccessor",
- "WithOrigins": [ "http://192.168.0.138:8080", "http://localhost:8080", "http://localhost:2015", "http://localhost:2016", "http://localhost:3000", "http://localhost:9528", "http://localhost:8200", "http://localhost:3001", "http://localhost:8080" ],
+ "WithOrigins": [ "http://192.168.0.138:8080", "http://localhost:8080", "http://localhost:2015", "http://localhost:2011", "http://localhost:3000", "http://localhost:9528", "http://localhost:8200", "http://localhost:3001", "http://localhost:8080" ],
"WithExposedHeaders": [ "access-token", "x-access-token", "Content-Disposition" ]
},
"PaymentSettings": {
diff --git a/service/LqSalaryCalculationService/Dockerfile b/service/LqSalaryCalculationService/Dockerfile
new file mode 100644
index 0000000..e8d4cd5
--- /dev/null
+++ b/service/LqSalaryCalculationService/Dockerfile
@@ -0,0 +1,54 @@
+# 使用官方.NET 6运行时作为基础镜像
+FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
+WORKDIR /app
+
+# 安装必要的系统依赖
+RUN apt-get update && apt-get install -y \
+ curl \
+ && rm -rf /var/lib/apt/lists/*
+
+# 创建必要的目录
+RUN mkdir -p /app/logs /app/output /app/backup
+
+# 使用官方.NET 6 SDK作为构建镜像
+FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
+WORKDIR /src
+
+# 复制项目文件
+COPY ["LqSalaryCalculationService.csproj", "./"]
+RUN dotnet restore "LqSalaryCalculationService.csproj"
+
+# 复制所有源代码
+COPY . .
+
+# 构建应用程序
+RUN dotnet build "LqSalaryCalculationService.csproj" -c Release -o /app/build
+
+# 发布应用程序
+FROM build AS publish
+RUN dotnet publish "LqSalaryCalculationService.csproj" -c Release -o /app/publish \
+ --self-contained true \
+ --runtime linux-x64
+
+# 最终运行镜像
+FROM base AS final
+WORKDIR /app
+
+# 复制发布的应用程序
+COPY --from=publish /app/publish .
+
+# 设置环境变量
+ENV ASPNETCORE_ENVIRONMENT=Production
+ENV DOTNET_RUNNING_IN_CONTAINER=true
+
+# 创建非root用户
+RUN groupadd -r appuser && useradd -r -g appuser appuser
+RUN chown -R appuser:appuser /app
+USER appuser
+
+# 设置入口点
+ENTRYPOINT ["dotnet", "LqSalaryCalculationService.dll"]
+
+# 健康检查
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:8080/health || exit 1
diff --git a/service/LqSalaryCalculationService/LqSalaryCalculationService.csproj b/service/LqSalaryCalculationService/LqSalaryCalculationService.csproj
new file mode 100644
index 0000000..0f64f93
--- /dev/null
+++ b/service/LqSalaryCalculationService/LqSalaryCalculationService.csproj
@@ -0,0 +1,31 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/service/LqSalaryCalculationService/Models/EmployeeSalary.cs b/service/LqSalaryCalculationService/Models/EmployeeSalary.cs
new file mode 100644
index 0000000..aae9b0e
--- /dev/null
+++ b/service/LqSalaryCalculationService/Models/EmployeeSalary.cs
@@ -0,0 +1,136 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace LqSalaryCalculationService.Models
+{
+ ///
+ /// 员工工资计算结果模型
+ ///
+ public class EmployeeSalary
+ {
+ ///
+ /// 主键ID
+ ///
+ public string Id { get; set; } = string.Empty;
+
+ ///
+ /// 员工姓名
+ ///
+ public string EmployeeName { get; set; } = string.Empty;
+
+ ///
+ /// 员工ID
+ ///
+ public string EmployeeId { get; set; } = string.Empty;
+
+ ///
+ /// 门店名称
+ ///
+ public string StoreName { get; set; } = string.Empty;
+
+ ///
+ /// 门店ID
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 职位
+ ///
+ public string Position { get; set; } = string.Empty;
+
+ ///
+ /// 岗位分类
+ ///
+ public string PositionCategory { get; set; } = string.Empty;
+
+ ///
+ /// 计算月份
+ ///
+ public string CalculationMonth { get; set; } = string.Empty;
+
+ ///
+ /// 底薪
+ ///
+ public decimal BaseSalary { get; set; }
+
+ ///
+ /// 总业绩
+ ///
+ public decimal TotalPerformance { get; set; }
+
+ ///
+ /// 消耗业绩
+ ///
+ public decimal ConsumptionPerformance { get; set; }
+
+ ///
+ /// 项目数
+ ///
+ public int ProjectCount { get; set; }
+
+ ///
+ /// 人头数
+ ///
+ public int CustomerCount { get; set; }
+
+ ///
+ /// 提成金额
+ ///
+ public decimal CommissionAmount { get; set; }
+
+ ///
+ /// 奖金金额
+ ///
+ public decimal BonusAmount { get; set; }
+
+ ///
+ /// 扣款金额
+ ///
+ public decimal DeductionAmount { get; set; }
+
+ ///
+ /// 应发工资
+ ///
+ public decimal GrossSalary { get; set; }
+
+ ///
+ /// 实发工资
+ ///
+ public decimal NetSalary { get; set; }
+
+ ///
+ /// 金三角业绩
+ ///
+ public decimal TeamPerformance { get; set; }
+
+ ///
+ /// 金三角提成
+ ///
+ public decimal TeamCommission { get; set; }
+
+ ///
+ /// 是否新店
+ ///
+ public bool IsNewStore { get; set; }
+
+ ///
+ /// 门店生命线
+ ///
+ public decimal StoreLifeLine { get; set; }
+
+ ///
+ /// 是否达标
+ ///
+ public bool IsTargetAchieved { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedTime { get; set; } = DateTime.Now;
+
+ ///
+ /// 备注
+ ///
+ public string Remarks { get; set; } = string.Empty;
+ }
+}
diff --git a/service/LqSalaryCalculationService/Models/SalaryCalculationConfig.cs b/service/LqSalaryCalculationService/Models/SalaryCalculationConfig.cs
new file mode 100644
index 0000000..b7bb765
--- /dev/null
+++ b/service/LqSalaryCalculationService/Models/SalaryCalculationConfig.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+
+namespace LqSalaryCalculationService.Models
+{
+ ///
+ /// 工资核算配置模型
+ ///
+ public class SalaryCalculationConfig
+ {
+ ///
+ /// 计算月份
+ ///
+ public string CalculationMonth { get; set; } = string.Empty;
+
+ ///
+ /// 输出路径
+ ///
+ public string OutputPath { get; set; } = "./output";
+
+ ///
+ /// 备份路径
+ ///
+ public string BackupPath { get; set; } = "./backup";
+
+ ///
+ /// 日志路径
+ ///
+ public string LogPath { get; set; } = "./logs";
+
+ ///
+ /// 是否启用邮件通知
+ ///
+ public bool EnableEmailNotification { get; set; } = true;
+
+ ///
+ /// 邮件设置
+ ///
+ public EmailSettings EmailSettings { get; set; } = new EmailSettings();
+ }
+
+ ///
+ /// 邮件设置
+ ///
+ public class EmailSettings
+ {
+ ///
+ /// SMTP服务器
+ ///
+ public string SmtpServer { get; set; } = string.Empty;
+
+ ///
+ /// SMTP端口
+ ///
+ public int SmtpPort { get; set; } = 587;
+
+ ///
+ /// 用户名
+ ///
+ public string Username { get; set; } = string.Empty;
+
+ ///
+ /// 密码
+ ///
+ public string Password { get; set; } = string.Empty;
+
+ ///
+ /// 发件人邮箱
+ ///
+ public string FromEmail { get; set; } = string.Empty;
+
+ ///
+ /// 收件人邮箱列表
+ ///
+ public List ToEmails { get; set; } = new List();
+ }
+}
diff --git a/service/LqSalaryCalculationService/Program.cs b/service/LqSalaryCalculationService/Program.cs
new file mode 100644
index 0000000..206f2a2
--- /dev/null
+++ b/service/LqSalaryCalculationService/Program.cs
@@ -0,0 +1,115 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Serilog;
+using SqlSugar;
+using LqSalaryCalculationService.Services;
+using LqSalaryCalculationService.Models;
+
+namespace LqSalaryCalculationService
+{
+ class Program
+ {
+ static async Task Main(string[] args)
+ {
+ // 配置Serilog
+ var configuration = GetConfiguration();
+ Log.Logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration)
+ .CreateLogger();
+
+ try
+ {
+ Log.Information("绿纤美业ERP工资核算服务启动中...");
+
+ var host = CreateHostBuilder(args).Build();
+
+ // 获取服务
+ var salaryService = host.Services.GetRequiredService();
+ var logger = host.Services.GetRequiredService>();
+
+ // 获取计算月份参数
+ var calculationMonth = GetCalculationMonth(args);
+ if (string.IsNullOrEmpty(calculationMonth))
+ {
+ logger.LogError("请提供计算月份参数,格式: yyyy-MM");
+ Console.WriteLine("使用方法: dotnet run -- 2024-09");
+ return;
+ }
+
+ logger.LogInformation($"开始执行工资核算,计算月份: {calculationMonth}");
+
+ // 获取用户基础信息并插入到工资表
+ logger.LogInformation("开始获取用户基础信息并插入到工资表...");
+ var userInfoResult = await salaryService.InitializeUserInfoAsync(calculationMonth);
+
+ if (userInfoResult.Success)
+ {
+ logger.LogInformation($"✅ 成功初始化 {userInfoResult.UserCount} 名用户信息");
+ Console.WriteLine($"✅ 用户信息初始化完成!");
+ Console.WriteLine($"📊 共处理 {userInfoResult.UserCount} 名用户");
+ Console.WriteLine($"📅 计算月份: {calculationMonth}");
+ }
+ else
+ {
+ logger.LogError($"初始化用户信息失败: {userInfoResult.Message}");
+ Console.WriteLine($"❌ 初始化用户信息失败: {userInfoResult.Message}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "服务启动失败");
+ Console.WriteLine($"❌ 服务启动失败: {ex.Message}");
+ }
+ finally
+ {
+ Log.CloseAndFlush();
+ }
+ }
+
+ static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .UseSerilog()
+ .ConfigureServices((context, services) =>
+ {
+ // 配置SqlSugar
+ services.AddSingleton(provider =>
+ {
+ var configuration = provider.GetRequiredService();
+ var connectionString = configuration.GetConnectionString("DefaultConnection");
+
+ return new SqlSugarClient(new ConnectionConfig()
+ {
+ ConnectionString = connectionString,
+ DbType = DbType.MySql,
+ IsAutoCloseConnection = true,
+ InitKeyType = InitKeyType.Attribute
+ });
+ });
+
+ // 注册服务
+ services.AddScoped();
+ });
+
+ static IConfiguration GetConfiguration()
+ {
+ return new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
+ .AddEnvironmentVariables()
+ .Build();
+ }
+
+ static string GetCalculationMonth(string[] args)
+ {
+ if (args.Length > 0)
+ {
+ return args[0];
+ }
+
+ // 如果没有提供参数,使用当前月份
+ return DateTime.Now.ToString("yyyy-MM");
+ }
+ }
+}
diff --git a/service/LqSalaryCalculationService/README.md b/service/LqSalaryCalculationService/README.md
new file mode 100644
index 0000000..54abe10
--- /dev/null
+++ b/service/LqSalaryCalculationService/README.md
@@ -0,0 +1,287 @@
+# 绿纤美业ERP工资核算服务
+
+## 项目简介
+
+这是一个基于.NET 6开发的跨平台控制台服务,专门用于绿纤美业ERP系统的月末财务工资核算。该服务支持Windows、Linux、CentOS等操作系统,能够自动计算员工工资、生成工资报表并发送邮件通知。
+
+## 功能特性
+
+- ✅ **跨平台支持**: 支持Windows、Linux、CentOS等操作系统
+- ✅ **用户信息初始化**: 自动从系统用户表获取用户信息并插入到工资表
+- ✅ **自动工资核算**: 根据绿纤美业薪酬规则自动计算员工工资
+- ✅ **多角色支持**: 支持健康师、店长、主任、店助等不同岗位的工资计算
+- ✅ **金三角提成**: 支持金三角(战队)提成计算
+- ✅ **门店生命线**: 基于门店生命线的考核机制
+- ✅ **新店保护**: 支持新店特殊薪酬规则
+- ✅ **报表导出**: 自动生成Excel/CSV格式的工资报表
+- ✅ **邮件通知**: 支持邮件通知功能
+- ✅ **日志记录**: 完整的操作日志记录
+- ✅ **Docker支持**: 支持Docker容器化部署
+
+## 技术栈
+
+- **.NET 6.0**: 跨平台运行时
+- **SqlSugar**: ORM框架,支持MySQL数据库
+- **Serilog**: 结构化日志记录
+- **Microsoft.Extensions**: 依赖注入和配置管理
+- **Yitter.IdGenerator**: 分布式ID生成
+
+## 系统要求
+
+### 开发环境
+- .NET 6.0 SDK
+- Visual Studio 2022 或 VS Code
+- MySQL 5.7+ 或 8.0+
+
+### 运行环境
+- .NET 6.0 Runtime
+- MySQL数据库连接
+- 网络连接(用于数据库访问)
+
+## 快速开始
+
+### 1. 克隆项目
+```bash
+git clone [项目地址]
+cd service/LqSalaryCalculationService
+```
+
+### 2. 配置数据库
+编辑 `appsettings.json` 文件,配置数据库连接字符串:
+
+```json
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Database=lqerp;Data Source=your-server;Port=3306;User Id=your-username;Password=your-password;Charset=utf8;"
+ }
+}
+```
+
+### 3. 运行服务
+
+#### Windows
+```cmd
+# 计算当前月份工资
+start.bat
+
+# 计算指定月份工资
+start.bat 2024-09
+
+# 显示帮助
+start.bat --help
+```
+
+#### Linux/CentOS
+```bash
+# 计算当前月份工资
+./start.sh
+
+# 计算指定月份工资
+./start.sh 2024-09
+
+# 显示帮助
+./start.sh --help
+```
+
+#### 直接使用dotnet命令
+```bash
+# 还原包
+dotnet restore
+
+# 构建项目
+dotnet build -c Release
+
+# 运行服务
+dotnet run --configuration Release -- 2024-09
+```
+
+## Docker部署
+
+### 1. 构建Docker镜像
+```bash
+docker build -t lq-salary-calculation-service .
+```
+
+### 2. 运行容器
+```bash
+# 运行容器
+docker run -d \
+ --name salary-service \
+ -v $(pwd)/logs:/app/logs \
+ -v $(pwd)/output:/app/output \
+ -v $(pwd)/backup:/app/backup \
+ lq-salary-calculation-service 2024-09
+
+# 查看日志
+docker logs salary-service
+```
+
+### 3. 使用Docker Compose
+```bash
+# 启动服务
+docker-compose up -d
+
+# 查看日志
+docker-compose logs -f
+
+# 停止服务
+docker-compose down
+```
+
+## 配置说明
+
+### appsettings.json配置项
+
+```json
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "数据库连接字符串"
+ },
+ "SalaryCalculation": {
+ "CalculationMonth": "计算月份",
+ "OutputPath": "./output",
+ "BackupPath": "./backup",
+ "LogPath": "./logs",
+ "EnableEmailNotification": true,
+ "EmailSettings": {
+ "SmtpServer": "smtp.163.com",
+ "SmtpPort": 587,
+ "Username": "邮箱用户名",
+ "Password": "邮箱密码",
+ "FromEmail": "发件人邮箱",
+ "ToEmails": ["收件人邮箱列表"]
+ }
+ }
+}
+```
+
+## 工作流程
+
+### 1. 用户信息初始化
+服务启动时首先执行以下步骤:
+
+1. **获取用户基础信息**:
+ - 从 `BASE_USER` 表获取 `F_Id`(用户编号)、`F_REALNAME`(姓名)、`F_GW`(岗位)
+ - 通过 `F_MDID`(门店编号)关联 `lq_mdxx` 表获取 `dm`(门店名称)
+
+2. **获取金三角信息**:
+ - 通过用户ID在 `lq_jinsanjiao_user` 表中查找对应月份的金三角ID
+ - 通过金三角ID在 `lq_ycsd_jsj` 表中获取 `jsj`(金三角名称)
+
+3. **插入工资表**:
+ - 将用户信息插入到 `lq_gz`(工资表)的对应字段:
+ - `userid` ← `F_Id`(用户编号)
+ - `xm` ← `F_REALNAME`(姓名)
+ - `hsgw` ← `F_GW`(岗位)
+ - `md` ← `dm`(门店名称)
+ - `jsjzd` ← `jsj`(金三角名称)
+
+### 2. 工资核算
+完成用户信息初始化后,执行工资核算:
+
+## 薪酬规则
+
+### 健康师薪酬规则
+- **一星**: 月消耗≥10000元 且 项目数≥96个 → 底薪2000元
+- **二星**: 月消耗≥20000元 且 项目数≥126个 → 底薪2200元
+- **三星**: 月消耗≥40000元 且 项目数≥156个 → 底薪2400元
+- **0星**: 未达到最低标准 → 底薪1800元
+
+### 金三角提成规则
+- **3人战队**: 业绩30000-150000元,提成比例3%-7%
+- **2人战队**: 业绩20000-80000元,提成比例3%-6%
+- **1人战队**: 业绩10000-60000元,提成比例3%-6%
+
+### 管理岗位薪酬规则
+- **店长**: 底薪4000元,基于毛利的提成计算
+- **主任**: 底薪3500元,基于毛利的提成计算
+- **店助**: 底薪3000元,基于毛利的提成计算
+
+## 输出文件
+
+服务运行后会在以下目录生成文件:
+
+- `./output/`: 工资报表文件(Excel/CSV格式)
+- `./logs/`: 日志文件
+- `./backup/`: 备份文件
+
+## 日志说明
+
+日志文件位置:`./logs/salary-calculation-YYYY-MM-DD.log`
+
+日志级别:
+- **Information**: 一般信息
+- **Warning**: 警告信息
+- **Error**: 错误信息
+- **Fatal**: 致命错误
+
+## 故障排除
+
+### 常见问题
+
+1. **数据库连接失败**
+ - 检查数据库连接字符串是否正确
+ - 确认数据库服务是否运行
+ - 检查网络连接
+
+2. **权限不足**
+ - 确保有足够的文件系统权限
+ - 检查输出目录是否可写
+
+3. **依赖包问题**
+ - 运行 `dotnet restore` 还原包
+ - 检查网络连接
+
+### 调试模式
+
+```bash
+# 启用详细日志
+export ASPNETCORE_ENVIRONMENT=Development
+dotnet run --configuration Debug -- 2024-09
+```
+
+## 开发指南
+
+### 项目结构
+```
+LqSalaryCalculationService/
+├── Models/ # 数据模型
+│ ├── EmployeeSalary.cs # 员工工资模型
+│ └── SalaryCalculationConfig.cs # 配置模型
+├── Services/ # 服务层
+│ ├── ISalaryCalculationService.cs # 服务接口
+│ └── SalaryCalculationService.cs # 服务实现
+├── Program.cs # 程序入口
+├── appsettings.json # 配置文件
+├── Dockerfile # Docker配置
+├── docker-compose.yml # Docker Compose配置
+├── start.sh # Linux启动脚本
+├── start.bat # Windows启动脚本
+└── README.md # 说明文档
+```
+
+### 添加新的薪酬规则
+
+1. 在 `SalaryCalculationService.cs` 中添加新的计算方法
+2. 在 `CalculateSalaryAsync` 方法中添加新的岗位判断
+3. 更新配置文件以支持新的参数
+
+## 版本历史
+
+### v1.0.0 (2024-09-11)
+- 初始版本发布
+- 支持基础工资核算功能
+- 支持跨平台部署
+- 支持Docker容器化
+
+## 许可证
+
+本项目仅供内部使用,请勿用于商业用途。
+
+## 技术支持
+
+如有技术问题,请联系开发团队或查看项目文档。
+
+---
+
+**注意**: 请确保在生产环境中正确配置数据库连接和邮件设置。
diff --git a/service/LqSalaryCalculationService/Services/ISalaryCalculationService.cs b/service/LqSalaryCalculationService/Services/ISalaryCalculationService.cs
new file mode 100644
index 0000000..4ff648d
--- /dev/null
+++ b/service/LqSalaryCalculationService/Services/ISalaryCalculationService.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using LqSalaryCalculationService.Models;
+
+namespace LqSalaryCalculationService.Services
+{
+ ///
+ /// 工资核算服务接口
+ ///
+ public interface ISalaryCalculationService
+ {
+ ///
+ /// 初始化用户信息到工资表
+ ///
+ /// 计算月份 (格式: yyyy-MM)
+ /// 初始化结果
+ Task InitializeUserInfoAsync(string calculationMonth);
+
+ ///
+ /// 执行工资核算
+ ///
+ /// 计算月份 (格式: yyyy-MM)
+ /// 核算结果
+ Task CalculateSalaryAsync(string calculationMonth);
+
+ ///
+ /// 获取员工业绩数据
+ ///
+ /// 计算月份
+ /// 员工业绩列表
+ Task> GetEmployeePerformanceAsync(string calculationMonth);
+
+ ///
+ /// 计算健康师工资
+ ///
+ /// 业绩数据
+ /// 工资计算结果
+ Task CalculateHealthWorkerSalaryAsync(EmployeePerformance performance);
+
+ ///
+ /// 计算金三角提成
+ ///
+ /// 团队业绩
+ /// 团队成员数
+ /// 提成比例
+ decimal CalculateTeamCommissionRate(decimal teamPerformance, int memberCount);
+
+ ///
+ /// 计算门店管理员工资
+ ///
+ /// 业绩数据
+ /// 工资计算结果
+ Task CalculateManagerSalaryAsync(EmployeePerformance performance);
+
+ ///
+ /// 导出工资报表
+ ///
+ /// 工资列表
+ /// 输出路径
+ /// 导出文件路径
+ Task ExportSalaryReportAsync(List salaries, string outputPath);
+
+ ///
+ /// 发送邮件通知
+ ///
+ /// 核算结果
+ /// 发送结果
+ Task SendEmailNotificationAsync(SalaryCalculationResult result);
+ }
+
+ ///
+ /// 员工业绩数据模型
+ ///
+ public class EmployeePerformance
+ {
+ public string EmployeeId { get; set; } = string.Empty;
+ public string EmployeeName { get; set; } = string.Empty;
+ public string StoreId { get; set; } = string.Empty;
+ public string StoreName { get; set; } = string.Empty;
+ public string Position { get; set; } = string.Empty;
+ public string PositionCategory { get; set; } = string.Empty;
+ public decimal TotalPerformance { get; set; }
+ public decimal ConsumptionPerformance { get; set; }
+ public int ProjectCount { get; set; }
+ public int CustomerCount { get; set; }
+ public decimal TeamPerformance { get; set; }
+ public bool IsNewStore { get; set; }
+ public decimal StoreLifeLine { get; set; }
+ public string TeamId { get; set; } = string.Empty;
+ public int TeamMemberCount { get; set; }
+ }
+
+ ///
+ /// 工资核算结果
+ ///
+ public class SalaryCalculationResult
+ {
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public List Salaries { get; set; } = new List();
+ public string OutputFilePath { get; set; } = string.Empty;
+ public DateTime CalculationTime { get; set; } = DateTime.Now;
+ public string CalculationMonth { get; set; } = string.Empty;
+ }
+
+ ///
+ /// 用户信息初始化结果
+ ///
+ public class UserInfoInitResult
+ {
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public int UserCount { get; set; }
+ public DateTime InitTime { get; set; } = DateTime.Now;
+ public string CalculationMonth { get; set; } = string.Empty;
+ }
+}
diff --git a/service/LqSalaryCalculationService/Services/SalaryCalculationService.cs b/service/LqSalaryCalculationService/Services/SalaryCalculationService.cs
new file mode 100644
index 0000000..72623e0
--- /dev/null
+++ b/service/LqSalaryCalculationService/Services/SalaryCalculationService.cs
@@ -0,0 +1,600 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using SqlSugar;
+using LqSalaryCalculationService.Models;
+using Yitter.IdGenerator;
+
+namespace LqSalaryCalculationService.Services
+{
+ ///
+ /// 工资核算服务实现
+ ///
+ public class SalaryCalculationService : ISalaryCalculationService
+ {
+ private readonly SqlSugarClient _db;
+ private readonly ILogger _logger;
+ private readonly IConfiguration _configuration;
+ private readonly SalaryCalculationConfig _config;
+
+ public SalaryCalculationService(
+ SqlSugarClient db,
+ ILogger logger,
+ IConfiguration configuration)
+ {
+ _db = db;
+ _logger = logger;
+ _configuration = configuration;
+ _config = _configuration.GetSection("SalaryCalculation").Get() ?? new SalaryCalculationConfig();
+ }
+
+ ///
+ /// 初始化用户信息到工资表
+ ///
+ public async Task InitializeUserInfoAsync(string calculationMonth)
+ {
+ try
+ {
+ _logger.LogInformation($"开始初始化用户信息,计算月份: {calculationMonth}");
+
+ // 1. 从base_user表获取所有用户基础信息(包括没有门店信息的)
+ var userInfoSql = @"
+ SELECT
+ u.F_Id as UserId,
+ u.F_REALNAME as UserName,
+ u.F_GW as Position,
+ u.F_MDID as StoreId,
+ COALESCE(m.dm, '无门店') as StoreName
+ FROM BASE_USER u
+ LEFT JOIN lq_mdxx m ON u.F_MDID = m.F_Id
+ WHERE u.F_ENABLEDMARK = 1
+ AND u.F_DELETEMARK IS NULL";
+
+ var userInfos = await _db.Ado.SqlQueryAsync(userInfoSql);
+ _logger.LogInformation($"获取到 {userInfos.Count()} 名用户基础信息");
+
+ // 2. 清理工资表数据
+ _logger.LogInformation("开始清理工资表数据...");
+ var clearSql = "DELETE FROM lq_gz";
+ await _db.Ado.ExecuteCommandAsync(clearSql);
+ _logger.LogInformation("工资表数据清理完成");
+
+ // 3. 先插入所有用户到工资表
+ var insertCount = 0;
+ foreach (var userInfo in userInfos)
+ {
+ try
+ {
+ // 插入到lq_gz工资表 - 先插入基本字段
+ var insertSql = @"
+ INSERT INTO lq_gz (F_Id, userid, xm, hsgw, md, jsjzd)
+ VALUES (@fid, @userid, @xm, @hsgw, @md, @jsjzd)
+ ON DUPLICATE KEY UPDATE
+ xm = VALUES(xm),
+ hsgw = VALUES(hsgw),
+ md = VALUES(md)";
+
+ await _db.Ado.ExecuteCommandAsync(insertSql, new
+ {
+ fid = Guid.NewGuid().ToString(),
+ userid = userInfo.UserId,
+ xm = userInfo.UserName,
+ hsgw = userInfo.Position,
+ md = userInfo.StoreName,
+ jsjzd = "" // 先插入空值
+ });
+
+ insertCount++;
+ _logger.LogDebug($"已插入用户: {userInfo.UserName} - {userInfo.StoreName}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, $"插入用户 {userInfo.UserName} 时出错: {ex.Message}");
+ }
+ }
+
+ _logger.LogInformation($"第一阶段完成:已插入 {insertCount} 名用户到工资表");
+
+ // 4. 更新门店信息(重新查询有门店信息的用户)
+ var updateStoreCount = 0;
+ var storeUpdateSql = @"
+ SELECT
+ u.F_Id as UserId,
+ u.F_REALNAME as UserName,
+ m.dm as StoreName
+ FROM BASE_USER u
+ LEFT JOIN lq_mdxx m ON u.F_MDID = m.F_Id
+ WHERE u.F_ENABLEDMARK = 1
+ AND u.F_DELETEMARK IS NULL
+ AND u.F_MDID IS NOT NULL
+ AND m.dm IS NOT NULL";
+
+ var storeInfos = await _db.Ado.SqlQueryAsync(storeUpdateSql);
+
+ foreach (var storeInfo in storeInfos)
+ {
+ try
+ {
+ var updateStoreSql = @"
+ UPDATE lq_gz
+ SET md = @storeName
+ WHERE userid = @userId";
+
+ await _db.Ado.ExecuteCommandAsync(updateStoreSql, new
+ {
+ storeName = storeInfo.StoreName,
+ userId = storeInfo.UserId
+ });
+
+ updateStoreCount++;
+ _logger.LogDebug($"已更新门店信息: {storeInfo.UserName} - {storeInfo.StoreName}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, $"更新门店信息 {storeInfo.UserName} 时出错: {ex.Message}");
+ }
+ }
+
+ _logger.LogInformation($"第二阶段完成:已更新 {updateStoreCount} 名用户的门店信息");
+
+ // 5. 更新金三角信息
+ var updateTeamCount = 0;
+ foreach (var userInfo in userInfos)
+ {
+ try
+ {
+ // 通过用户ID在lq_jinsanjiao_user中获取对应月份的金三角ID
+ var monthFormat = calculationMonth.Replace("-", "");
+ var teamSql = @"
+ SELECT jsj_user.jsj_id, jsj.jsj as TeamName
+ FROM lq_jinsanjiao_user jsj_user
+ LEFT JOIN lq_ycsd_jsj jsj ON jsj_user.jsj_id = jsj.F_Id
+ WHERE jsj_user.user_id = @userId
+ AND jsj_user.status = 'ACTIVE'
+ AND jsj_user.F_Month = @monthFormat
+ LIMIT 1";
+
+ var teamInfo = await _db.Ado.SqlQueryAsync(teamSql, new { userId = userInfo.UserId, monthFormat = monthFormat });
+ var teamName = teamInfo.FirstOrDefault()?.TeamName ?? "";
+
+ if (!string.IsNullOrEmpty(teamName))
+ {
+ var updateTeamSql = @"
+ UPDATE lq_gz
+ SET jsjzd = @teamName
+ WHERE userid = @userId";
+
+ await _db.Ado.ExecuteCommandAsync(updateTeamSql, new
+ {
+ teamName = teamName,
+ userId = userInfo.UserId
+ });
+
+ updateTeamCount++;
+ _logger.LogInformation($"已更新金三角信息: {userInfo.UserName} - {teamName}");
+ }
+ else
+ {
+ _logger.LogDebug($"用户 {userInfo.UserName} 没有找到金三角信息");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, $"更新金三角信息 {userInfo.UserName} 时出错: {ex.Message}");
+ }
+ }
+
+ _logger.LogInformation($"第三阶段完成:已更新 {updateTeamCount} 名用户的金三角信息");
+ _logger.LogInformation($"用户信息初始化完成,共处理 {insertCount} 名用户");
+
+ return new UserInfoInitResult
+ {
+ Success = true,
+ Message = "用户信息初始化完成",
+ UserCount = insertCount,
+ CalculationMonth = calculationMonth
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"初始化用户信息失败: {ex.Message}");
+ return new UserInfoInitResult
+ {
+ Success = false,
+ Message = $"初始化用户信息失败: {ex.Message}",
+ CalculationMonth = calculationMonth
+ };
+ }
+ }
+
+ ///
+ /// 执行工资核算
+ ///
+ public async Task CalculateSalaryAsync(string calculationMonth)
+ {
+ try
+ {
+ _logger.LogInformation($"开始执行工资核算,计算月份: {calculationMonth}");
+
+ // 1. 获取员工业绩数据
+ var performances = await GetEmployeePerformanceAsync(calculationMonth);
+ _logger.LogInformation($"获取到 {performances.Count} 条员工业绩数据");
+
+ // 2. 计算每个员工的工资
+ var salaries = new List();
+ foreach (var performance in performances)
+ {
+ EmployeeSalary salary;
+ switch (performance.PositionCategory)
+ {
+ case "健康师":
+ salary = await CalculateHealthWorkerSalaryAsync(performance);
+ break;
+ case "店长":
+ case "主任":
+ case "店助":
+ salary = await CalculateManagerSalaryAsync(performance);
+ break;
+ default:
+ salary = await CalculateDefaultSalaryAsync(performance);
+ break;
+ }
+ salaries.Add(salary);
+ }
+
+ // 3. 导出工资报表
+ var outputPath = Path.Combine(_config.OutputPath, $"工资核算_{calculationMonth}.xlsx");
+ var exportPath = await ExportSalaryReportAsync(salaries, outputPath);
+
+ // 4. 发送邮件通知
+ if (_config.EnableEmailNotification)
+ {
+ await SendEmailNotificationAsync(new SalaryCalculationResult
+ {
+ Success = true,
+ Message = "工资核算完成",
+ Salaries = salaries,
+ OutputFilePath = exportPath,
+ CalculationMonth = calculationMonth
+ });
+ }
+
+ _logger.LogInformation($"工资核算完成,共计算 {salaries.Count} 名员工工资");
+
+ return new SalaryCalculationResult
+ {
+ Success = true,
+ Message = "工资核算完成",
+ Salaries = salaries,
+ OutputFilePath = exportPath,
+ CalculationMonth = calculationMonth
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"工资核算失败: {ex.Message}");
+ return new SalaryCalculationResult
+ {
+ Success = false,
+ Message = $"工资核算失败: {ex.Message}",
+ CalculationMonth = calculationMonth
+ };
+ }
+ }
+
+ ///
+ /// 获取员工业绩数据
+ ///
+ public async Task> GetEmployeePerformanceAsync(string calculationMonth)
+ {
+ var startDate = DateTime.Parse($"{calculationMonth}-01");
+ var endDate = startDate.AddMonths(1).AddDays(-1);
+
+ var sql = @"
+ SELECT
+ u.F_Id as EmployeeId,
+ u.F_REALNAME as EmployeeName,
+ u.F_MDID as StoreId,
+ m.dm as StoreName,
+ u.F_GW as Position,
+ u.F_GWFL as PositionCategory,
+ COALESCE(SUM(CAST(yj.ssyj as DECIMAL(18,2))), 0) as TotalPerformance,
+ COALESCE(SUM(CAST(xh.ssyj as DECIMAL(18,2))), 0) as ConsumptionPerformance,
+ COUNT(DISTINCT yj.xmbh) as ProjectCount,
+ COUNT(DISTINCT yj.khbh) as CustomerCount,
+ COALESCE(SUM(CAST(jsj.team_performance as DECIMAL(18,2))), 0) as TeamPerformance,
+ CASE WHEN m.kysj >= DATE_SUB(NOW(), INTERVAL 6 MONTH) THEN 1 ELSE 0 END as IsNewStore,
+ COALESCE(m.xsyj, 0) as StoreLifeLine,
+ COALESCE(jsj.team_id, '') as TeamId,
+ COALESCE(jsj.team_member_count, 1) as TeamMemberCount
+ FROM BASE_USER u
+ LEFT JOIN lq_mdxx m ON u.F_MDID = m.F_Id
+ LEFT JOIN lq_yjmxb yj ON u.F_REALNAME = yj.jks
+ AND DATE_FORMAT(yj.fssj, '%Y-%m') = @month
+ LEFT JOIN lq_xhmxb xh ON u.F_REALNAME = xh.jks
+ AND DATE_FORMAT(xh.fssj, '%Y-%m') = @month
+ LEFT JOIN (
+ SELECT
+ jsj.F_Id as team_id,
+ jsj.jsj,
+ COUNT(jsj_user.user_id) as team_member_count,
+ SUM(CAST(yj2.ssyj as DECIMAL(18,2))) as team_performance
+ FROM lq_ycsd_jsj jsj
+ LEFT JOIN lq_jinsanjiao_user jsj_user ON jsj.F_Id = jsj_user.jsj_id
+ LEFT JOIN lq_yjmxb yj2 ON jsj_user.user_name = yj2.jks
+ AND DATE_FORMAT(yj2.fssj, '%Y-%m') = @month
+ WHERE jsj.yf = @month
+ GROUP BY jsj.F_Id, jsj.jsj
+ ) jsj ON u.F_REALNAME = jsj.jsj
+ WHERE u.F_ENABLEDMARK = 1
+ AND u.F_DELETEMARK IS NULL
+ GROUP BY u.F_Id, u.F_REALNAME, u.F_MDID, m.dm, u.F_GW, u.F_GWFL,
+ m.kysj, m.xsyj, jsj.team_id, jsj.team_member_count";
+
+ var performances = await _db.Ado.SqlQueryAsync(sql, new { month = calculationMonth });
+ return performances.ToList();
+ }
+
+ ///
+ /// 计算健康师工资
+ ///
+ public Task CalculateHealthWorkerSalaryAsync(EmployeePerformance performance)
+ {
+ var salary = new EmployeeSalary
+ {
+ Id = Guid.NewGuid().ToString(),
+ EmployeeId = performance.EmployeeId,
+ EmployeeName = performance.EmployeeName,
+ StoreId = performance.StoreId,
+ StoreName = performance.StoreName,
+ Position = performance.Position,
+ PositionCategory = performance.PositionCategory,
+ CalculationMonth = performance.StoreId, // 这里应该传计算月份
+ TotalPerformance = performance.TotalPerformance,
+ ConsumptionPerformance = performance.ConsumptionPerformance,
+ ProjectCount = performance.ProjectCount,
+ CustomerCount = performance.CustomerCount,
+ TeamPerformance = performance.TeamPerformance,
+ IsNewStore = performance.IsNewStore,
+ StoreLifeLine = performance.StoreLifeLine,
+ CreatedTime = DateTime.Now
+ };
+
+ // 根据健康师薪酬规则计算底薪
+ if (performance.ConsumptionPerformance >= 40000 && performance.ProjectCount >= 156)
+ {
+ salary.BaseSalary = 2400; // 三星
+ }
+ else if (performance.ConsumptionPerformance >= 20000 && performance.ProjectCount >= 126)
+ {
+ salary.BaseSalary = 2200; // 二星
+ }
+ else if (performance.ConsumptionPerformance >= 10000 && performance.ProjectCount >= 96)
+ {
+ salary.BaseSalary = 2000; // 一星
+ }
+ else
+ {
+ salary.BaseSalary = 1800; // 0星
+ }
+
+ // 计算金三角提成
+ if (performance.TeamMemberCount >= 1)
+ {
+ var commissionRate = CalculateTeamCommissionRate(performance.TeamPerformance, performance.TeamMemberCount);
+ salary.TeamCommission = performance.TeamPerformance * commissionRate / 100;
+ }
+
+ // 计算个人提成(基础业绩提成)
+ if (performance.TotalPerformance > 6000)
+ {
+ salary.CommissionAmount = performance.TotalPerformance * 0.95m * 0.03m; // 3%提成点
+ }
+
+ salary.GrossSalary = salary.BaseSalary + salary.CommissionAmount + salary.TeamCommission + salary.BonusAmount - salary.DeductionAmount;
+ salary.NetSalary = salary.GrossSalary;
+
+ return Task.FromResult(salary);
+ }
+
+ ///
+ /// 计算金三角提成比例
+ ///
+ public decimal CalculateTeamCommissionRate(decimal teamPerformance, int memberCount)
+ {
+ switch (memberCount)
+ {
+ case 3: // 3人战队
+ if (teamPerformance >= 150000) return 7m;
+ if (teamPerformance >= 120000) return 6m;
+ if (teamPerformance >= 90000) return 5m;
+ if (teamPerformance >= 60000) return 4m;
+ if (teamPerformance >= 30000) return 3m;
+ break;
+ case 2: // 2人战队
+ if (teamPerformance >= 80000) return 6m;
+ if (teamPerformance >= 60000) return 5m;
+ if (teamPerformance >= 40000) return 4m;
+ if (teamPerformance >= 20000) return 3m;
+ break;
+ case 1: // 1人战队
+ if (teamPerformance >= 60000) return 6m;
+ if (teamPerformance >= 40000) return 5m;
+ if (teamPerformance >= 20000) return 4m;
+ if (teamPerformance >= 10000) return 3m;
+ break;
+ }
+ return 0m;
+ }
+
+ ///
+ /// 计算门店管理员工资
+ ///
+ public Task CalculateManagerSalaryAsync(EmployeePerformance performance)
+ {
+ var salary = new EmployeeSalary
+ {
+ Id = Guid.NewGuid().ToString(),
+ EmployeeId = performance.EmployeeId,
+ EmployeeName = performance.EmployeeName,
+ StoreId = performance.StoreId,
+ StoreName = performance.StoreName,
+ Position = performance.Position,
+ PositionCategory = performance.PositionCategory,
+ CalculationMonth = performance.StoreId, // 这里应该传计算月份
+ TotalPerformance = performance.TotalPerformance,
+ ConsumptionPerformance = performance.ConsumptionPerformance,
+ ProjectCount = performance.ProjectCount,
+ CustomerCount = performance.CustomerCount,
+ TeamPerformance = performance.TeamPerformance,
+ IsNewStore = performance.IsNewStore,
+ StoreLifeLine = performance.StoreLifeLine,
+ CreatedTime = DateTime.Now
+ };
+
+ // 根据职位设置底薪
+ switch (performance.Position)
+ {
+ case "店长":
+ salary.BaseSalary = 4000;
+ break;
+ case "主任":
+ salary.BaseSalary = 3500;
+ break;
+ case "店助":
+ salary.BaseSalary = 3000;
+ break;
+ default:
+ salary.BaseSalary = 3000;
+ break;
+ }
+
+ // 计算提成(基于毛利)
+ var grossProfit = performance.TotalPerformance * 0.6m; // 假设毛利率60%
+ var commissionRate = 0.03m; // 3%提成率
+ salary.CommissionAmount = grossProfit * commissionRate;
+
+ // 检查是否达标
+ salary.IsTargetAchieved = performance.TotalPerformance >= performance.StoreLifeLine;
+
+ salary.GrossSalary = salary.BaseSalary + salary.CommissionAmount + salary.BonusAmount - salary.DeductionAmount;
+ salary.NetSalary = salary.GrossSalary;
+
+ return Task.FromResult(salary);
+ }
+
+ ///
+ /// 计算默认工资
+ ///
+ private Task CalculateDefaultSalaryAsync(EmployeePerformance performance)
+ {
+ var salary = new EmployeeSalary
+ {
+ Id = Guid.NewGuid().ToString(),
+ EmployeeId = performance.EmployeeId,
+ EmployeeName = performance.EmployeeName,
+ StoreId = performance.StoreId,
+ StoreName = performance.StoreName,
+ Position = performance.Position,
+ PositionCategory = performance.PositionCategory,
+ CalculationMonth = performance.StoreId,
+ TotalPerformance = performance.TotalPerformance,
+ ConsumptionPerformance = performance.ConsumptionPerformance,
+ ProjectCount = performance.ProjectCount,
+ CustomerCount = performance.CustomerCount,
+ TeamPerformance = performance.TeamPerformance,
+ IsNewStore = performance.IsNewStore,
+ StoreLifeLine = performance.StoreLifeLine,
+ CreatedTime = DateTime.Now,
+ BaseSalary = 2000, // 默认底薪
+ GrossSalary = 2000,
+ NetSalary = 2000
+ };
+
+ return Task.FromResult(salary);
+ }
+
+ ///
+ /// 导出工资报表
+ ///
+ public async Task ExportSalaryReportAsync(List salaries, string outputPath)
+ {
+ try
+ {
+ // 确保输出目录存在
+ var directory = Path.GetDirectoryName(outputPath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory!);
+ }
+
+ // 这里可以使用EPPlus或其他Excel库来生成Excel文件
+ // 为了简化,这里生成CSV文件
+ var csvPath = outputPath.Replace(".xlsx", ".csv");
+
+ using var writer = new StreamWriter(csvPath);
+ await writer.WriteLineAsync("员工姓名,门店名称,职位,底薪,总业绩,消耗业绩,提成金额,应发工资,实发工资,计算月份");
+
+ foreach (var salary in salaries)
+ {
+ await writer.WriteLineAsync($"{salary.EmployeeName},{salary.StoreName},{salary.Position}," +
+ $"{salary.BaseSalary},{salary.TotalPerformance},{salary.ConsumptionPerformance}," +
+ $"{salary.CommissionAmount},{salary.GrossSalary},{salary.NetSalary},{salary.CalculationMonth}");
+ }
+
+ _logger.LogInformation($"工资报表已导出到: {csvPath}");
+ return csvPath;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"导出工资报表失败: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// 发送邮件通知
+ ///
+ public Task SendEmailNotificationAsync(SalaryCalculationResult result)
+ {
+ try
+ {
+ // 这里可以实现邮件发送逻辑
+ // 可以使用MailKit或其他邮件库
+ _logger.LogInformation($"邮件通知功能待实现,核算结果: {result.Message}");
+ return Task.FromResult(true);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"发送邮件通知失败: {ex.Message}");
+ return Task.FromResult(false);
+ }
+ }
+ }
+
+ ///
+ /// 用户信息DTO
+ ///
+ public class UserInfoDto
+ {
+ public string UserId { get; set; } = string.Empty;
+ public string UserName { get; set; } = string.Empty;
+ public string Position { get; set; } = string.Empty;
+ public string StoreId { get; set; } = string.Empty;
+ public string StoreName { get; set; } = string.Empty;
+ }
+
+ ///
+ /// 团队信息DTO
+ ///
+ public class TeamInfoDto
+ {
+ public string JsjId { get; set; } = string.Empty;
+ public string TeamName { get; set; } = string.Empty;
+ }
+}
diff --git a/service/LqSalaryCalculationService/appsettings.json b/service/LqSalaryCalculationService/appsettings.json
new file mode 100644
index 0000000..804a947
--- /dev/null
+++ b/service/LqSalaryCalculationService/appsettings.json
@@ -0,0 +1,52 @@
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Database=lqerp;Data Source=rm-bp19ohrgc6111ynzh1o.mysql.rds.aliyuncs.com;Port=3306;User Id=netteam;Password=netteam;Charset=utf8;TreatTinyAsBoolean=true;"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "SalaryCalculation": {
+ "CalculationMonth": "",
+ "OutputPath": "./output",
+ "BackupPath": "./backup",
+ "LogPath": "./logs",
+ "EnableEmailNotification": true,
+ "EmailSettings": {
+ "SmtpServer": "smtp.163.com",
+ "SmtpPort": 587,
+ "Username": "",
+ "Password": "",
+ "FromEmail": "",
+ "ToEmails": []
+ }
+ },
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "System": "Warning",
+ "Microsoft": "Warning"
+ }
+ },
+ "WriteTo": [
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}"
+ }
+ },
+ {
+ "Name": "File",
+ "Args": {
+ "path": "./logs/salary-calculation-.log",
+ "rollingInterval": "Day",
+ "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}"
+ }
+ }
+ ]
+ }
+}
diff --git a/service/LqSalaryCalculationService/docker-compose.yml b/service/LqSalaryCalculationService/docker-compose.yml
new file mode 100644
index 0000000..ae6a37e
--- /dev/null
+++ b/service/LqSalaryCalculationService/docker-compose.yml
@@ -0,0 +1,23 @@
+version: '3.8'
+
+services:
+ salary-calculation-service:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: lq-salary-calculation-service
+ restart: unless-stopped
+ environment:
+ - ASPNETCORE_ENVIRONMENT=Production
+ - ConnectionStrings__DefaultConnection=Database=lqerp;Data Source=rm-bp19ohrgc6111ynzh1o.mysql.rds.aliyuncs.com;Port=3306;User Id=netteam;Password=netteam;Charset=utf8;TreatTinyAsBoolean=true;
+ volumes:
+ - ./logs:/app/logs
+ - ./output:/app/output
+ - ./backup:/app/backup
+ networks:
+ - salary-network
+ command: ["2024-09"] # 默认计算2024年9月工资
+
+networks:
+ salary-network:
+ driver: bridge
diff --git a/service/LqSalaryCalculationService/start.bat b/service/LqSalaryCalculationService/start.bat
new file mode 100644
index 0000000..963e0bf
--- /dev/null
+++ b/service/LqSalaryCalculationService/start.bat
@@ -0,0 +1,163 @@
+@echo off
+chcp 65001 >nul
+setlocal enabledelayedexpansion
+
+REM 绿纤美业ERP工资核算服务启动脚本 (Windows)
+REM 支持Windows 10/11, Windows Server 2016+
+
+echo.
+echo ========================================
+echo 绿纤美业ERP工资核算服务
+echo ========================================
+echo.
+
+REM 检查参数
+if "%1"=="--help" goto :help
+if "%1"=="-h" goto :help
+if "%1"=="--version" goto :version
+if "%1"=="-v" goto :version
+if "%1"=="--build" goto :build
+if "%1"=="--clean" goto :clean
+
+REM 检查.NET 6是否安装
+echo [INFO] 检查.NET 6环境...
+dotnet --version >nul 2>&1
+if errorlevel 1 (
+ echo [ERROR] .NET 6 未安装,请先安装 .NET 6 SDK
+ echo [INFO] 下载地址: https://dotnet.microsoft.com/download/dotnet/6.0
+ pause
+ exit /b 1
+)
+
+for /f "tokens=*" %%i in ('dotnet --version') do set DOTNET_VERSION=%%i
+echo [INFO] 检测到 .NET 版本: %DOTNET_VERSION%
+
+REM 检查配置文件
+if not exist "appsettings.json" (
+ echo [ERROR] 配置文件 appsettings.json 不存在
+ pause
+ exit /b 1
+)
+echo [INFO] 配置文件检查通过
+
+REM 创建必要目录
+echo [INFO] 创建必要目录...
+if not exist "logs" mkdir logs
+if not exist "output" mkdir output
+if not exist "backup" mkdir backup
+echo [INFO] 目录创建完成
+
+REM 还原NuGet包
+echo [INFO] 还原NuGet包...
+dotnet restore
+if errorlevel 1 (
+ echo [ERROR] NuGet包还原失败
+ pause
+ exit /b 1
+)
+echo [INFO] NuGet包还原成功
+
+REM 构建项目
+echo [INFO] 构建项目...
+dotnet build -c Release
+if errorlevel 1 (
+ echo [ERROR] 项目构建失败
+ pause
+ exit /b 1
+)
+echo [INFO] 项目构建成功
+
+REM 运行服务
+set MONTH=%1
+if "%MONTH%"=="" (
+ for /f "tokens=1,2 delims=-" %%a in ('date /t') do set MONTH=%%a-%%b
+ echo [WARN] 未指定计算月份,使用当前月份: %MONTH%
+ echo [INFO] 使用方法: %0 [YYYY-MM]
+ echo [INFO] 示例: %0 2024-09
+)
+
+echo [INFO] 启动工资核算服务,计算月份: %MONTH%
+dotnet run --configuration Release -- %MONTH%
+
+goto :end
+
+:help
+echo.
+echo 绿纤美业ERP工资核算服务
+echo.
+echo 使用方法:
+echo %0 [月份] 运行工资核算服务
+echo %0 --help, -h 显示帮助信息
+echo %0 --version, -v 显示版本信息
+echo %0 --build 仅构建项目
+echo %0 --clean 清理构建文件
+echo.
+echo 参数:
+echo 月份 计算月份,格式: YYYY-MM (例如: 2024-09)
+echo.
+echo 示例:
+echo %0 2024-09 计算2024年9月工资
+echo %0 计算当前月份工资
+echo.
+echo 环境要求:
+echo - .NET 6.0 SDK
+echo - MySQL数据库连接
+echo - 网络连接
+goto :end
+
+:version
+echo 绿纤美业ERP工资核算服务 v1.0.0
+echo 构建时间: %date% %time%
+for /f "tokens=*" %%i in ('dotnet --version') do echo .NET版本: %%i
+goto :end
+
+:build
+echo [INFO] 检查.NET 6环境...
+dotnet --version >nul 2>&1
+if errorlevel 1 (
+ echo [ERROR] .NET 6 未安装
+ pause
+ exit /b 1
+)
+
+echo [INFO] 检查配置文件...
+if not exist "appsettings.json" (
+ echo [ERROR] 配置文件不存在
+ pause
+ exit /b 1
+)
+
+echo [INFO] 创建目录...
+if not exist "logs" mkdir logs
+if not exist "output" mkdir output
+if not exist "backup" mkdir backup
+
+echo [INFO] 还原包...
+dotnet restore
+if errorlevel 1 (
+ echo [ERROR] 还原失败
+ pause
+ exit /b 1
+)
+
+echo [INFO] 构建项目...
+dotnet build -c Release
+if errorlevel 1 (
+ echo [ERROR] 构建失败
+ pause
+ exit /b 1
+)
+
+echo [INFO] 构建完成
+goto :end
+
+:clean
+echo [INFO] 清理构建文件...
+dotnet clean
+if exist "bin" rmdir /s /q "bin"
+if exist "obj" rmdir /s /q "obj"
+echo [INFO] 清理完成
+goto :end
+
+:end
+pause
diff --git a/service/LqSalaryCalculationService/start.sh b/service/LqSalaryCalculationService/start.sh
new file mode 100755
index 0000000..92986a3
--- /dev/null
+++ b/service/LqSalaryCalculationService/start.sh
@@ -0,0 +1,179 @@
+#!/bin/bash
+
+# 绿纤美业ERP工资核算服务启动脚本
+# 支持Windows、Linux、CentOS等操作系统
+
+set -e
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# 日志函数
+log_info() {
+ echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+log_warn() {
+ echo -e "${YELLOW}[WARN]${NC} $1"
+}
+
+log_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+log_debug() {
+ echo -e "${BLUE}[DEBUG]${NC} $1"
+}
+
+# 检查.NET 6是否安装
+check_dotnet() {
+ if ! command -v dotnet &> /dev/null; then
+ log_error ".NET 6 未安装,请先安装 .NET 6 SDK"
+ log_info "下载地址: https://dotnet.microsoft.com/download/dotnet/6.0"
+ exit 1
+ fi
+
+ local dotnet_version=$(dotnet --version)
+ log_info "检测到 .NET 版本: $dotnet_version"
+
+ if [[ ! "$dotnet_version" =~ ^6\. ]]; then
+ log_warn "建议使用 .NET 6.0 版本"
+ fi
+}
+
+# 检查配置文件
+check_config() {
+ if [ ! -f "appsettings.json" ]; then
+ log_error "配置文件 appsettings.json 不存在"
+ exit 1
+ fi
+ log_info "配置文件检查通过"
+}
+
+# 创建必要目录
+create_directories() {
+ log_info "创建必要目录..."
+ mkdir -p logs output backup
+ log_info "目录创建完成"
+}
+
+# 还原NuGet包
+restore_packages() {
+ log_info "还原NuGet包..."
+ dotnet restore
+ if [ $? -eq 0 ]; then
+ log_info "NuGet包还原成功"
+ else
+ log_error "NuGet包还原失败"
+ exit 1
+ fi
+}
+
+# 构建项目
+build_project() {
+ log_info "构建项目..."
+ dotnet build -c Release
+ if [ $? -eq 0 ]; then
+ log_info "项目构建成功"
+ else
+ log_error "项目构建失败"
+ exit 1
+ fi
+}
+
+# 运行服务
+run_service() {
+ local month=${1:-$(date +%Y-%m)}
+ log_info "启动工资核算服务,计算月份: $month"
+
+ # 检查参数
+ if [ $# -eq 0 ]; then
+ log_warn "未指定计算月份,使用当前月份: $month"
+ log_info "使用方法: $0 [YYYY-MM]"
+ log_info "示例: $0 2024-09"
+ fi
+
+ # 运行服务
+ dotnet run --configuration Release -- $month
+}
+
+# 显示帮助信息
+show_help() {
+ echo "绿纤美业ERP工资核算服务"
+ echo ""
+ echo "使用方法:"
+ echo " $0 [月份] 运行工资核算服务"
+ echo " $0 --help, -h 显示帮助信息"
+ echo " $0 --version, -v 显示版本信息"
+ echo " $0 --build 仅构建项目"
+ echo " $0 --clean 清理构建文件"
+ echo ""
+ echo "参数:"
+ echo " 月份 计算月份,格式: YYYY-MM (例如: 2024-09)"
+ echo ""
+ echo "示例:"
+ echo " $0 2024-09 计算2024年9月工资"
+ echo " $0 计算当前月份工资"
+ echo ""
+ echo "环境要求:"
+ echo " - .NET 6.0 SDK"
+ echo " - MySQL数据库连接"
+ echo " - 网络连接"
+}
+
+# 显示版本信息
+show_version() {
+ echo "绿纤美业ERP工资核算服务 v1.0.0"
+ echo "构建时间: $(date)"
+ echo ".NET版本: $(dotnet --version)"
+}
+
+# 清理构建文件
+clean_project() {
+ log_info "清理构建文件..."
+ dotnet clean
+ rm -rf bin obj
+ log_info "清理完成"
+}
+
+# 主函数
+main() {
+ case "${1:-}" in
+ --help|-h)
+ show_help
+ exit 0
+ ;;
+ --version|-v)
+ show_version
+ exit 0
+ ;;
+ --build)
+ check_dotnet
+ check_config
+ create_directories
+ restore_packages
+ build_project
+ log_info "构建完成"
+ exit 0
+ ;;
+ --clean)
+ clean_project
+ exit 0
+ ;;
+ *)
+ check_dotnet
+ check_config
+ create_directories
+ restore_packages
+ build_project
+ run_service "$@"
+ ;;
+ esac
+}
+
+# 执行主函数
+main "$@"
--
libgit2 0.21.4