Commit 3fcf01d821cafc54c0fecde78eb3bd045ce9793d
1 parent
2ce7c3cb
hxd202509112049
Showing
20 changed files
with
1838 additions
and
3 deletions
.DS_Store
No preview for this file type
antis-ncc-admin/.env.development
netcore/netcore/.DS_Store
No preview for this file type
netcore/netcore/src/.DS_Store
No preview for this file type
netcore/netcore/src/Modularity/.DS_Store
No preview for this file type
netcore/src/.DS_Store
No preview for this file type
netcore/src/Application/.DS_Store
No preview for this file type
netcore/src/Application/NCC.API/appsettings.json
| ... | ... | @@ -77,7 +77,7 @@ |
| 77 | 77 | }, |
| 78 | 78 | "CorsAccessorSettings": { |
| 79 | 79 | "PolicyName": "NCCCorsAccessor", |
| 80 | - "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" ], | |
| 80 | + "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" ], | |
| 81 | 81 | "WithExposedHeaders": [ "access-token", "x-access-token", "Content-Disposition" ] |
| 82 | 82 | }, |
| 83 | 83 | "PaymentSettings": { | ... | ... |
service/LqSalaryCalculationService/Dockerfile
0 → 100644
| 1 | +# 使用官方.NET 6运行时作为基础镜像 | |
| 2 | +FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base | |
| 3 | +WORKDIR /app | |
| 4 | + | |
| 5 | +# 安装必要的系统依赖 | |
| 6 | +RUN apt-get update && apt-get install -y \ | |
| 7 | + curl \ | |
| 8 | + && rm -rf /var/lib/apt/lists/* | |
| 9 | + | |
| 10 | +# 创建必要的目录 | |
| 11 | +RUN mkdir -p /app/logs /app/output /app/backup | |
| 12 | + | |
| 13 | +# 使用官方.NET 6 SDK作为构建镜像 | |
| 14 | +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build | |
| 15 | +WORKDIR /src | |
| 16 | + | |
| 17 | +# 复制项目文件 | |
| 18 | +COPY ["LqSalaryCalculationService.csproj", "./"] | |
| 19 | +RUN dotnet restore "LqSalaryCalculationService.csproj" | |
| 20 | + | |
| 21 | +# 复制所有源代码 | |
| 22 | +COPY . . | |
| 23 | + | |
| 24 | +# 构建应用程序 | |
| 25 | +RUN dotnet build "LqSalaryCalculationService.csproj" -c Release -o /app/build | |
| 26 | + | |
| 27 | +# 发布应用程序 | |
| 28 | +FROM build AS publish | |
| 29 | +RUN dotnet publish "LqSalaryCalculationService.csproj" -c Release -o /app/publish \ | |
| 30 | + --self-contained true \ | |
| 31 | + --runtime linux-x64 | |
| 32 | + | |
| 33 | +# 最终运行镜像 | |
| 34 | +FROM base AS final | |
| 35 | +WORKDIR /app | |
| 36 | + | |
| 37 | +# 复制发布的应用程序 | |
| 38 | +COPY --from=publish /app/publish . | |
| 39 | + | |
| 40 | +# 设置环境变量 | |
| 41 | +ENV ASPNETCORE_ENVIRONMENT=Production | |
| 42 | +ENV DOTNET_RUNNING_IN_CONTAINER=true | |
| 43 | + | |
| 44 | +# 创建非root用户 | |
| 45 | +RUN groupadd -r appuser && useradd -r -g appuser appuser | |
| 46 | +RUN chown -R appuser:appuser /app | |
| 47 | +USER appuser | |
| 48 | + | |
| 49 | +# 设置入口点 | |
| 50 | +ENTRYPOINT ["dotnet", "LqSalaryCalculationService.dll"] | |
| 51 | + | |
| 52 | +# 健康检查 | |
| 53 | +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ | |
| 54 | + CMD curl -f http://localhost:8080/health || exit 1 | ... | ... |
service/LqSalaryCalculationService/LqSalaryCalculationService.csproj
0 → 100644
| 1 | +<Project Sdk="Microsoft.NET.Sdk"> | |
| 2 | + | |
| 3 | + <PropertyGroup> | |
| 4 | + <OutputType>Exe</OutputType> | |
| 5 | + <TargetFramework>net6.0</TargetFramework> | |
| 6 | + <ImplicitUsings>enable</ImplicitUsings> | |
| 7 | + <Nullable>enable</Nullable> | |
| 8 | + <PublishSingleFile>true</PublishSingleFile> | |
| 9 | + <SelfContained>true</SelfContained> | |
| 10 | + </PropertyGroup> | |
| 11 | + | |
| 12 | + <ItemGroup> | |
| 13 | + <PackageReference Include="SqlSugar" Version="5.1.4.156" /> | |
| 14 | + <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" /> | |
| 15 | + <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> | |
| 16 | + <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" /> | |
| 17 | + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> | |
| 18 | + <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" /> | |
| 19 | + <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> | |
| 20 | + <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> | |
| 21 | + <PackageReference Include="Serilog" Version="2.12.0" /> | |
| 22 | + <PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" /> | |
| 23 | + <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> | |
| 24 | + <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> | |
| 25 | + <PackageReference Include="Yitter.IdGenerator" Version="1.0.6" /> | |
| 26 | + <PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" /> | |
| 27 | + <PackageReference Include="System.Text.Json" Version="7.0.1" /> | |
| 28 | + <PackageReference Include="MySql.Data" Version="8.0.33" /> | |
| 29 | + </ItemGroup> | |
| 30 | + | |
| 31 | +</Project> | ... | ... |
service/LqSalaryCalculationService/Models/EmployeeSalary.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.ComponentModel.DataAnnotations; | |
| 3 | + | |
| 4 | +namespace LqSalaryCalculationService.Models | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 员工工资计算结果模型 | |
| 8 | + /// </summary> | |
| 9 | + public class EmployeeSalary | |
| 10 | + { | |
| 11 | + /// <summary> | |
| 12 | + /// 主键ID | |
| 13 | + /// </summary> | |
| 14 | + public string Id { get; set; } = string.Empty; | |
| 15 | + | |
| 16 | + /// <summary> | |
| 17 | + /// 员工姓名 | |
| 18 | + /// </summary> | |
| 19 | + public string EmployeeName { get; set; } = string.Empty; | |
| 20 | + | |
| 21 | + /// <summary> | |
| 22 | + /// 员工ID | |
| 23 | + /// </summary> | |
| 24 | + public string EmployeeId { get; set; } = string.Empty; | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 门店名称 | |
| 28 | + /// </summary> | |
| 29 | + public string StoreName { get; set; } = string.Empty; | |
| 30 | + | |
| 31 | + /// <summary> | |
| 32 | + /// 门店ID | |
| 33 | + /// </summary> | |
| 34 | + public string StoreId { get; set; } = string.Empty; | |
| 35 | + | |
| 36 | + /// <summary> | |
| 37 | + /// 职位 | |
| 38 | + /// </summary> | |
| 39 | + public string Position { get; set; } = string.Empty; | |
| 40 | + | |
| 41 | + /// <summary> | |
| 42 | + /// 岗位分类 | |
| 43 | + /// </summary> | |
| 44 | + public string PositionCategory { get; set; } = string.Empty; | |
| 45 | + | |
| 46 | + /// <summary> | |
| 47 | + /// 计算月份 | |
| 48 | + /// </summary> | |
| 49 | + public string CalculationMonth { get; set; } = string.Empty; | |
| 50 | + | |
| 51 | + /// <summary> | |
| 52 | + /// 底薪 | |
| 53 | + /// </summary> | |
| 54 | + public decimal BaseSalary { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 总业绩 | |
| 58 | + /// </summary> | |
| 59 | + public decimal TotalPerformance { get; set; } | |
| 60 | + | |
| 61 | + /// <summary> | |
| 62 | + /// 消耗业绩 | |
| 63 | + /// </summary> | |
| 64 | + public decimal ConsumptionPerformance { get; set; } | |
| 65 | + | |
| 66 | + /// <summary> | |
| 67 | + /// 项目数 | |
| 68 | + /// </summary> | |
| 69 | + public int ProjectCount { get; set; } | |
| 70 | + | |
| 71 | + /// <summary> | |
| 72 | + /// 人头数 | |
| 73 | + /// </summary> | |
| 74 | + public int CustomerCount { get; set; } | |
| 75 | + | |
| 76 | + /// <summary> | |
| 77 | + /// 提成金额 | |
| 78 | + /// </summary> | |
| 79 | + public decimal CommissionAmount { get; set; } | |
| 80 | + | |
| 81 | + /// <summary> | |
| 82 | + /// 奖金金额 | |
| 83 | + /// </summary> | |
| 84 | + public decimal BonusAmount { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 扣款金额 | |
| 88 | + /// </summary> | |
| 89 | + public decimal DeductionAmount { get; set; } | |
| 90 | + | |
| 91 | + /// <summary> | |
| 92 | + /// 应发工资 | |
| 93 | + /// </summary> | |
| 94 | + public decimal GrossSalary { get; set; } | |
| 95 | + | |
| 96 | + /// <summary> | |
| 97 | + /// 实发工资 | |
| 98 | + /// </summary> | |
| 99 | + public decimal NetSalary { get; set; } | |
| 100 | + | |
| 101 | + /// <summary> | |
| 102 | + /// 金三角业绩 | |
| 103 | + /// </summary> | |
| 104 | + public decimal TeamPerformance { get; set; } | |
| 105 | + | |
| 106 | + /// <summary> | |
| 107 | + /// 金三角提成 | |
| 108 | + /// </summary> | |
| 109 | + public decimal TeamCommission { get; set; } | |
| 110 | + | |
| 111 | + /// <summary> | |
| 112 | + /// 是否新店 | |
| 113 | + /// </summary> | |
| 114 | + public bool IsNewStore { get; set; } | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 门店生命线 | |
| 118 | + /// </summary> | |
| 119 | + public decimal StoreLifeLine { get; set; } | |
| 120 | + | |
| 121 | + /// <summary> | |
| 122 | + /// 是否达标 | |
| 123 | + /// </summary> | |
| 124 | + public bool IsTargetAchieved { get; set; } | |
| 125 | + | |
| 126 | + /// <summary> | |
| 127 | + /// 创建时间 | |
| 128 | + /// </summary> | |
| 129 | + public DateTime CreatedTime { get; set; } = DateTime.Now; | |
| 130 | + | |
| 131 | + /// <summary> | |
| 132 | + /// 备注 | |
| 133 | + /// </summary> | |
| 134 | + public string Remarks { get; set; } = string.Empty; | |
| 135 | + } | |
| 136 | +} | ... | ... |
service/LqSalaryCalculationService/Models/SalaryCalculationConfig.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.Collections.Generic; | |
| 3 | + | |
| 4 | +namespace LqSalaryCalculationService.Models | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 工资核算配置模型 | |
| 8 | + /// </summary> | |
| 9 | + public class SalaryCalculationConfig | |
| 10 | + { | |
| 11 | + /// <summary> | |
| 12 | + /// 计算月份 | |
| 13 | + /// </summary> | |
| 14 | + public string CalculationMonth { get; set; } = string.Empty; | |
| 15 | + | |
| 16 | + /// <summary> | |
| 17 | + /// 输出路径 | |
| 18 | + /// </summary> | |
| 19 | + public string OutputPath { get; set; } = "./output"; | |
| 20 | + | |
| 21 | + /// <summary> | |
| 22 | + /// 备份路径 | |
| 23 | + /// </summary> | |
| 24 | + public string BackupPath { get; set; } = "./backup"; | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 日志路径 | |
| 28 | + /// </summary> | |
| 29 | + public string LogPath { get; set; } = "./logs"; | |
| 30 | + | |
| 31 | + /// <summary> | |
| 32 | + /// 是否启用邮件通知 | |
| 33 | + /// </summary> | |
| 34 | + public bool EnableEmailNotification { get; set; } = true; | |
| 35 | + | |
| 36 | + /// <summary> | |
| 37 | + /// 邮件设置 | |
| 38 | + /// </summary> | |
| 39 | + public EmailSettings EmailSettings { get; set; } = new EmailSettings(); | |
| 40 | + } | |
| 41 | + | |
| 42 | + /// <summary> | |
| 43 | + /// 邮件设置 | |
| 44 | + /// </summary> | |
| 45 | + public class EmailSettings | |
| 46 | + { | |
| 47 | + /// <summary> | |
| 48 | + /// SMTP服务器 | |
| 49 | + /// </summary> | |
| 50 | + public string SmtpServer { get; set; } = string.Empty; | |
| 51 | + | |
| 52 | + /// <summary> | |
| 53 | + /// SMTP端口 | |
| 54 | + /// </summary> | |
| 55 | + public int SmtpPort { get; set; } = 587; | |
| 56 | + | |
| 57 | + /// <summary> | |
| 58 | + /// 用户名 | |
| 59 | + /// </summary> | |
| 60 | + public string Username { get; set; } = string.Empty; | |
| 61 | + | |
| 62 | + /// <summary> | |
| 63 | + /// 密码 | |
| 64 | + /// </summary> | |
| 65 | + public string Password { get; set; } = string.Empty; | |
| 66 | + | |
| 67 | + /// <summary> | |
| 68 | + /// 发件人邮箱 | |
| 69 | + /// </summary> | |
| 70 | + public string FromEmail { get; set; } = string.Empty; | |
| 71 | + | |
| 72 | + /// <summary> | |
| 73 | + /// 收件人邮箱列表 | |
| 74 | + /// </summary> | |
| 75 | + public List<string> ToEmails { get; set; } = new List<string>(); | |
| 76 | + } | |
| 77 | +} | ... | ... |
service/LqSalaryCalculationService/Program.cs
0 → 100644
| 1 | +using Microsoft.Extensions.Configuration; | |
| 2 | +using Microsoft.Extensions.DependencyInjection; | |
| 3 | +using Microsoft.Extensions.Hosting; | |
| 4 | +using Microsoft.Extensions.Logging; | |
| 5 | +using Serilog; | |
| 6 | +using SqlSugar; | |
| 7 | +using LqSalaryCalculationService.Services; | |
| 8 | +using LqSalaryCalculationService.Models; | |
| 9 | + | |
| 10 | +namespace LqSalaryCalculationService | |
| 11 | +{ | |
| 12 | + class Program | |
| 13 | + { | |
| 14 | + static async Task Main(string[] args) | |
| 15 | + { | |
| 16 | + // 配置Serilog | |
| 17 | + var configuration = GetConfiguration(); | |
| 18 | + Log.Logger = new LoggerConfiguration() | |
| 19 | + .ReadFrom.Configuration(configuration) | |
| 20 | + .CreateLogger(); | |
| 21 | + | |
| 22 | + try | |
| 23 | + { | |
| 24 | + Log.Information("绿纤美业ERP工资核算服务启动中..."); | |
| 25 | + | |
| 26 | + var host = CreateHostBuilder(args).Build(); | |
| 27 | + | |
| 28 | + // 获取服务 | |
| 29 | + var salaryService = host.Services.GetRequiredService<ISalaryCalculationService>(); | |
| 30 | + var logger = host.Services.GetRequiredService<ILogger<Program>>(); | |
| 31 | + | |
| 32 | + // 获取计算月份参数 | |
| 33 | + var calculationMonth = GetCalculationMonth(args); | |
| 34 | + if (string.IsNullOrEmpty(calculationMonth)) | |
| 35 | + { | |
| 36 | + logger.LogError("请提供计算月份参数,格式: yyyy-MM"); | |
| 37 | + Console.WriteLine("使用方法: dotnet run -- 2024-09"); | |
| 38 | + return; | |
| 39 | + } | |
| 40 | + | |
| 41 | + logger.LogInformation($"开始执行工资核算,计算月份: {calculationMonth}"); | |
| 42 | + | |
| 43 | + // 获取用户基础信息并插入到工资表 | |
| 44 | + logger.LogInformation("开始获取用户基础信息并插入到工资表..."); | |
| 45 | + var userInfoResult = await salaryService.InitializeUserInfoAsync(calculationMonth); | |
| 46 | + | |
| 47 | + if (userInfoResult.Success) | |
| 48 | + { | |
| 49 | + logger.LogInformation($"✅ 成功初始化 {userInfoResult.UserCount} 名用户信息"); | |
| 50 | + Console.WriteLine($"✅ 用户信息初始化完成!"); | |
| 51 | + Console.WriteLine($"📊 共处理 {userInfoResult.UserCount} 名用户"); | |
| 52 | + Console.WriteLine($"📅 计算月份: {calculationMonth}"); | |
| 53 | + } | |
| 54 | + else | |
| 55 | + { | |
| 56 | + logger.LogError($"初始化用户信息失败: {userInfoResult.Message}"); | |
| 57 | + Console.WriteLine($"❌ 初始化用户信息失败: {userInfoResult.Message}"); | |
| 58 | + } | |
| 59 | + } | |
| 60 | + catch (Exception ex) | |
| 61 | + { | |
| 62 | + Log.Fatal(ex, "服务启动失败"); | |
| 63 | + Console.WriteLine($"❌ 服务启动失败: {ex.Message}"); | |
| 64 | + } | |
| 65 | + finally | |
| 66 | + { | |
| 67 | + Log.CloseAndFlush(); | |
| 68 | + } | |
| 69 | + } | |
| 70 | + | |
| 71 | + static IHostBuilder CreateHostBuilder(string[] args) => | |
| 72 | + Host.CreateDefaultBuilder(args) | |
| 73 | + .UseSerilog() | |
| 74 | + .ConfigureServices((context, services) => | |
| 75 | + { | |
| 76 | + // 配置SqlSugar | |
| 77 | + services.AddSingleton<SqlSugarClient>(provider => | |
| 78 | + { | |
| 79 | + var configuration = provider.GetRequiredService<IConfiguration>(); | |
| 80 | + var connectionString = configuration.GetConnectionString("DefaultConnection"); | |
| 81 | + | |
| 82 | + return new SqlSugarClient(new ConnectionConfig() | |
| 83 | + { | |
| 84 | + ConnectionString = connectionString, | |
| 85 | + DbType = DbType.MySql, | |
| 86 | + IsAutoCloseConnection = true, | |
| 87 | + InitKeyType = InitKeyType.Attribute | |
| 88 | + }); | |
| 89 | + }); | |
| 90 | + | |
| 91 | + // 注册服务 | |
| 92 | + services.AddScoped<ISalaryCalculationService, SalaryCalculationService>(); | |
| 93 | + }); | |
| 94 | + | |
| 95 | + static IConfiguration GetConfiguration() | |
| 96 | + { | |
| 97 | + return new ConfigurationBuilder() | |
| 98 | + .SetBasePath(Directory.GetCurrentDirectory()) | |
| 99 | + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) | |
| 100 | + .AddEnvironmentVariables() | |
| 101 | + .Build(); | |
| 102 | + } | |
| 103 | + | |
| 104 | + static string GetCalculationMonth(string[] args) | |
| 105 | + { | |
| 106 | + if (args.Length > 0) | |
| 107 | + { | |
| 108 | + return args[0]; | |
| 109 | + } | |
| 110 | + | |
| 111 | + // 如果没有提供参数,使用当前月份 | |
| 112 | + return DateTime.Now.ToString("yyyy-MM"); | |
| 113 | + } | |
| 114 | + } | |
| 115 | +} | ... | ... |
service/LqSalaryCalculationService/README.md
0 → 100644
| 1 | +# 绿纤美业ERP工资核算服务 | |
| 2 | + | |
| 3 | +## 项目简介 | |
| 4 | + | |
| 5 | +这是一个基于.NET 6开发的跨平台控制台服务,专门用于绿纤美业ERP系统的月末财务工资核算。该服务支持Windows、Linux、CentOS等操作系统,能够自动计算员工工资、生成工资报表并发送邮件通知。 | |
| 6 | + | |
| 7 | +## 功能特性 | |
| 8 | + | |
| 9 | +- ✅ **跨平台支持**: 支持Windows、Linux、CentOS等操作系统 | |
| 10 | +- ✅ **用户信息初始化**: 自动从系统用户表获取用户信息并插入到工资表 | |
| 11 | +- ✅ **自动工资核算**: 根据绿纤美业薪酬规则自动计算员工工资 | |
| 12 | +- ✅ **多角色支持**: 支持健康师、店长、主任、店助等不同岗位的工资计算 | |
| 13 | +- ✅ **金三角提成**: 支持金三角(战队)提成计算 | |
| 14 | +- ✅ **门店生命线**: 基于门店生命线的考核机制 | |
| 15 | +- ✅ **新店保护**: 支持新店特殊薪酬规则 | |
| 16 | +- ✅ **报表导出**: 自动生成Excel/CSV格式的工资报表 | |
| 17 | +- ✅ **邮件通知**: 支持邮件通知功能 | |
| 18 | +- ✅ **日志记录**: 完整的操作日志记录 | |
| 19 | +- ✅ **Docker支持**: 支持Docker容器化部署 | |
| 20 | + | |
| 21 | +## 技术栈 | |
| 22 | + | |
| 23 | +- **.NET 6.0**: 跨平台运行时 | |
| 24 | +- **SqlSugar**: ORM框架,支持MySQL数据库 | |
| 25 | +- **Serilog**: 结构化日志记录 | |
| 26 | +- **Microsoft.Extensions**: 依赖注入和配置管理 | |
| 27 | +- **Yitter.IdGenerator**: 分布式ID生成 | |
| 28 | + | |
| 29 | +## 系统要求 | |
| 30 | + | |
| 31 | +### 开发环境 | |
| 32 | +- .NET 6.0 SDK | |
| 33 | +- Visual Studio 2022 或 VS Code | |
| 34 | +- MySQL 5.7+ 或 8.0+ | |
| 35 | + | |
| 36 | +### 运行环境 | |
| 37 | +- .NET 6.0 Runtime | |
| 38 | +- MySQL数据库连接 | |
| 39 | +- 网络连接(用于数据库访问) | |
| 40 | + | |
| 41 | +## 快速开始 | |
| 42 | + | |
| 43 | +### 1. 克隆项目 | |
| 44 | +```bash | |
| 45 | +git clone [项目地址] | |
| 46 | +cd service/LqSalaryCalculationService | |
| 47 | +``` | |
| 48 | + | |
| 49 | +### 2. 配置数据库 | |
| 50 | +编辑 `appsettings.json` 文件,配置数据库连接字符串: | |
| 51 | + | |
| 52 | +```json | |
| 53 | +{ | |
| 54 | + "ConnectionStrings": { | |
| 55 | + "DefaultConnection": "Database=lqerp;Data Source=your-server;Port=3306;User Id=your-username;Password=your-password;Charset=utf8;" | |
| 56 | + } | |
| 57 | +} | |
| 58 | +``` | |
| 59 | + | |
| 60 | +### 3. 运行服务 | |
| 61 | + | |
| 62 | +#### Windows | |
| 63 | +```cmd | |
| 64 | +# 计算当前月份工资 | |
| 65 | +start.bat | |
| 66 | + | |
| 67 | +# 计算指定月份工资 | |
| 68 | +start.bat 2024-09 | |
| 69 | + | |
| 70 | +# 显示帮助 | |
| 71 | +start.bat --help | |
| 72 | +``` | |
| 73 | + | |
| 74 | +#### Linux/CentOS | |
| 75 | +```bash | |
| 76 | +# 计算当前月份工资 | |
| 77 | +./start.sh | |
| 78 | + | |
| 79 | +# 计算指定月份工资 | |
| 80 | +./start.sh 2024-09 | |
| 81 | + | |
| 82 | +# 显示帮助 | |
| 83 | +./start.sh --help | |
| 84 | +``` | |
| 85 | + | |
| 86 | +#### 直接使用dotnet命令 | |
| 87 | +```bash | |
| 88 | +# 还原包 | |
| 89 | +dotnet restore | |
| 90 | + | |
| 91 | +# 构建项目 | |
| 92 | +dotnet build -c Release | |
| 93 | + | |
| 94 | +# 运行服务 | |
| 95 | +dotnet run --configuration Release -- 2024-09 | |
| 96 | +``` | |
| 97 | + | |
| 98 | +## Docker部署 | |
| 99 | + | |
| 100 | +### 1. 构建Docker镜像 | |
| 101 | +```bash | |
| 102 | +docker build -t lq-salary-calculation-service . | |
| 103 | +``` | |
| 104 | + | |
| 105 | +### 2. 运行容器 | |
| 106 | +```bash | |
| 107 | +# 运行容器 | |
| 108 | +docker run -d \ | |
| 109 | + --name salary-service \ | |
| 110 | + -v $(pwd)/logs:/app/logs \ | |
| 111 | + -v $(pwd)/output:/app/output \ | |
| 112 | + -v $(pwd)/backup:/app/backup \ | |
| 113 | + lq-salary-calculation-service 2024-09 | |
| 114 | + | |
| 115 | +# 查看日志 | |
| 116 | +docker logs salary-service | |
| 117 | +``` | |
| 118 | + | |
| 119 | +### 3. 使用Docker Compose | |
| 120 | +```bash | |
| 121 | +# 启动服务 | |
| 122 | +docker-compose up -d | |
| 123 | + | |
| 124 | +# 查看日志 | |
| 125 | +docker-compose logs -f | |
| 126 | + | |
| 127 | +# 停止服务 | |
| 128 | +docker-compose down | |
| 129 | +``` | |
| 130 | + | |
| 131 | +## 配置说明 | |
| 132 | + | |
| 133 | +### appsettings.json配置项 | |
| 134 | + | |
| 135 | +```json | |
| 136 | +{ | |
| 137 | + "ConnectionStrings": { | |
| 138 | + "DefaultConnection": "数据库连接字符串" | |
| 139 | + }, | |
| 140 | + "SalaryCalculation": { | |
| 141 | + "CalculationMonth": "计算月份", | |
| 142 | + "OutputPath": "./output", | |
| 143 | + "BackupPath": "./backup", | |
| 144 | + "LogPath": "./logs", | |
| 145 | + "EnableEmailNotification": true, | |
| 146 | + "EmailSettings": { | |
| 147 | + "SmtpServer": "smtp.163.com", | |
| 148 | + "SmtpPort": 587, | |
| 149 | + "Username": "邮箱用户名", | |
| 150 | + "Password": "邮箱密码", | |
| 151 | + "FromEmail": "发件人邮箱", | |
| 152 | + "ToEmails": ["收件人邮箱列表"] | |
| 153 | + } | |
| 154 | + } | |
| 155 | +} | |
| 156 | +``` | |
| 157 | + | |
| 158 | +## 工作流程 | |
| 159 | + | |
| 160 | +### 1. 用户信息初始化 | |
| 161 | +服务启动时首先执行以下步骤: | |
| 162 | + | |
| 163 | +1. **获取用户基础信息**: | |
| 164 | + - 从 `BASE_USER` 表获取 `F_Id`(用户编号)、`F_REALNAME`(姓名)、`F_GW`(岗位) | |
| 165 | + - 通过 `F_MDID`(门店编号)关联 `lq_mdxx` 表获取 `dm`(门店名称) | |
| 166 | + | |
| 167 | +2. **获取金三角信息**: | |
| 168 | + - 通过用户ID在 `lq_jinsanjiao_user` 表中查找对应月份的金三角ID | |
| 169 | + - 通过金三角ID在 `lq_ycsd_jsj` 表中获取 `jsj`(金三角名称) | |
| 170 | + | |
| 171 | +3. **插入工资表**: | |
| 172 | + - 将用户信息插入到 `lq_gz`(工资表)的对应字段: | |
| 173 | + - `userid` ← `F_Id`(用户编号) | |
| 174 | + - `xm` ← `F_REALNAME`(姓名) | |
| 175 | + - `hsgw` ← `F_GW`(岗位) | |
| 176 | + - `md` ← `dm`(门店名称) | |
| 177 | + - `jsjzd` ← `jsj`(金三角名称) | |
| 178 | + | |
| 179 | +### 2. 工资核算 | |
| 180 | +完成用户信息初始化后,执行工资核算: | |
| 181 | + | |
| 182 | +## 薪酬规则 | |
| 183 | + | |
| 184 | +### 健康师薪酬规则 | |
| 185 | +- **一星**: 月消耗≥10000元 且 项目数≥96个 → 底薪2000元 | |
| 186 | +- **二星**: 月消耗≥20000元 且 项目数≥126个 → 底薪2200元 | |
| 187 | +- **三星**: 月消耗≥40000元 且 项目数≥156个 → 底薪2400元 | |
| 188 | +- **0星**: 未达到最低标准 → 底薪1800元 | |
| 189 | + | |
| 190 | +### 金三角提成规则 | |
| 191 | +- **3人战队**: 业绩30000-150000元,提成比例3%-7% | |
| 192 | +- **2人战队**: 业绩20000-80000元,提成比例3%-6% | |
| 193 | +- **1人战队**: 业绩10000-60000元,提成比例3%-6% | |
| 194 | + | |
| 195 | +### 管理岗位薪酬规则 | |
| 196 | +- **店长**: 底薪4000元,基于毛利的提成计算 | |
| 197 | +- **主任**: 底薪3500元,基于毛利的提成计算 | |
| 198 | +- **店助**: 底薪3000元,基于毛利的提成计算 | |
| 199 | + | |
| 200 | +## 输出文件 | |
| 201 | + | |
| 202 | +服务运行后会在以下目录生成文件: | |
| 203 | + | |
| 204 | +- `./output/`: 工资报表文件(Excel/CSV格式) | |
| 205 | +- `./logs/`: 日志文件 | |
| 206 | +- `./backup/`: 备份文件 | |
| 207 | + | |
| 208 | +## 日志说明 | |
| 209 | + | |
| 210 | +日志文件位置:`./logs/salary-calculation-YYYY-MM-DD.log` | |
| 211 | + | |
| 212 | +日志级别: | |
| 213 | +- **Information**: 一般信息 | |
| 214 | +- **Warning**: 警告信息 | |
| 215 | +- **Error**: 错误信息 | |
| 216 | +- **Fatal**: 致命错误 | |
| 217 | + | |
| 218 | +## 故障排除 | |
| 219 | + | |
| 220 | +### 常见问题 | |
| 221 | + | |
| 222 | +1. **数据库连接失败** | |
| 223 | + - 检查数据库连接字符串是否正确 | |
| 224 | + - 确认数据库服务是否运行 | |
| 225 | + - 检查网络连接 | |
| 226 | + | |
| 227 | +2. **权限不足** | |
| 228 | + - 确保有足够的文件系统权限 | |
| 229 | + - 检查输出目录是否可写 | |
| 230 | + | |
| 231 | +3. **依赖包问题** | |
| 232 | + - 运行 `dotnet restore` 还原包 | |
| 233 | + - 检查网络连接 | |
| 234 | + | |
| 235 | +### 调试模式 | |
| 236 | + | |
| 237 | +```bash | |
| 238 | +# 启用详细日志 | |
| 239 | +export ASPNETCORE_ENVIRONMENT=Development | |
| 240 | +dotnet run --configuration Debug -- 2024-09 | |
| 241 | +``` | |
| 242 | + | |
| 243 | +## 开发指南 | |
| 244 | + | |
| 245 | +### 项目结构 | |
| 246 | +``` | |
| 247 | +LqSalaryCalculationService/ | |
| 248 | +├── Models/ # 数据模型 | |
| 249 | +│ ├── EmployeeSalary.cs # 员工工资模型 | |
| 250 | +│ └── SalaryCalculationConfig.cs # 配置模型 | |
| 251 | +├── Services/ # 服务层 | |
| 252 | +│ ├── ISalaryCalculationService.cs # 服务接口 | |
| 253 | +│ └── SalaryCalculationService.cs # 服务实现 | |
| 254 | +├── Program.cs # 程序入口 | |
| 255 | +├── appsettings.json # 配置文件 | |
| 256 | +├── Dockerfile # Docker配置 | |
| 257 | +├── docker-compose.yml # Docker Compose配置 | |
| 258 | +├── start.sh # Linux启动脚本 | |
| 259 | +├── start.bat # Windows启动脚本 | |
| 260 | +└── README.md # 说明文档 | |
| 261 | +``` | |
| 262 | + | |
| 263 | +### 添加新的薪酬规则 | |
| 264 | + | |
| 265 | +1. 在 `SalaryCalculationService.cs` 中添加新的计算方法 | |
| 266 | +2. 在 `CalculateSalaryAsync` 方法中添加新的岗位判断 | |
| 267 | +3. 更新配置文件以支持新的参数 | |
| 268 | + | |
| 269 | +## 版本历史 | |
| 270 | + | |
| 271 | +### v1.0.0 (2024-09-11) | |
| 272 | +- 初始版本发布 | |
| 273 | +- 支持基础工资核算功能 | |
| 274 | +- 支持跨平台部署 | |
| 275 | +- 支持Docker容器化 | |
| 276 | + | |
| 277 | +## 许可证 | |
| 278 | + | |
| 279 | +本项目仅供内部使用,请勿用于商业用途。 | |
| 280 | + | |
| 281 | +## 技术支持 | |
| 282 | + | |
| 283 | +如有技术问题,请联系开发团队或查看项目文档。 | |
| 284 | + | |
| 285 | +--- | |
| 286 | + | |
| 287 | +**注意**: 请确保在生产环境中正确配置数据库连接和邮件设置。 | ... | ... |
service/LqSalaryCalculationService/Services/ISalaryCalculationService.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.Collections.Generic; | |
| 3 | +using System.Threading.Tasks; | |
| 4 | +using LqSalaryCalculationService.Models; | |
| 5 | + | |
| 6 | +namespace LqSalaryCalculationService.Services | |
| 7 | +{ | |
| 8 | + /// <summary> | |
| 9 | + /// 工资核算服务接口 | |
| 10 | + /// </summary> | |
| 11 | + public interface ISalaryCalculationService | |
| 12 | + { | |
| 13 | + /// <summary> | |
| 14 | + /// 初始化用户信息到工资表 | |
| 15 | + /// </summary> | |
| 16 | + /// <param name="calculationMonth">计算月份 (格式: yyyy-MM)</param> | |
| 17 | + /// <returns>初始化结果</returns> | |
| 18 | + Task<UserInfoInitResult> InitializeUserInfoAsync(string calculationMonth); | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 执行工资核算 | |
| 22 | + /// </summary> | |
| 23 | + /// <param name="calculationMonth">计算月份 (格式: yyyy-MM)</param> | |
| 24 | + /// <returns>核算结果</returns> | |
| 25 | + Task<SalaryCalculationResult> CalculateSalaryAsync(string calculationMonth); | |
| 26 | + | |
| 27 | + /// <summary> | |
| 28 | + /// 获取员工业绩数据 | |
| 29 | + /// </summary> | |
| 30 | + /// <param name="calculationMonth">计算月份</param> | |
| 31 | + /// <returns>员工业绩列表</returns> | |
| 32 | + Task<List<EmployeePerformance>> GetEmployeePerformanceAsync(string calculationMonth); | |
| 33 | + | |
| 34 | + /// <summary> | |
| 35 | + /// 计算健康师工资 | |
| 36 | + /// </summary> | |
| 37 | + /// <param name="performance">业绩数据</param> | |
| 38 | + /// <returns>工资计算结果</returns> | |
| 39 | + Task<EmployeeSalary> CalculateHealthWorkerSalaryAsync(EmployeePerformance performance); | |
| 40 | + | |
| 41 | + /// <summary> | |
| 42 | + /// 计算金三角提成 | |
| 43 | + /// </summary> | |
| 44 | + /// <param name="teamPerformance">团队业绩</param> | |
| 45 | + /// <param name="memberCount">团队成员数</param> | |
| 46 | + /// <returns>提成比例</returns> | |
| 47 | + decimal CalculateTeamCommissionRate(decimal teamPerformance, int memberCount); | |
| 48 | + | |
| 49 | + /// <summary> | |
| 50 | + /// 计算门店管理员工资 | |
| 51 | + /// </summary> | |
| 52 | + /// <param name="performance">业绩数据</param> | |
| 53 | + /// <returns>工资计算结果</returns> | |
| 54 | + Task<EmployeeSalary> CalculateManagerSalaryAsync(EmployeePerformance performance); | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 导出工资报表 | |
| 58 | + /// </summary> | |
| 59 | + /// <param name="salaries">工资列表</param> | |
| 60 | + /// <param name="outputPath">输出路径</param> | |
| 61 | + /// <returns>导出文件路径</returns> | |
| 62 | + Task<string> ExportSalaryReportAsync(List<EmployeeSalary> salaries, string outputPath); | |
| 63 | + | |
| 64 | + /// <summary> | |
| 65 | + /// 发送邮件通知 | |
| 66 | + /// </summary> | |
| 67 | + /// <param name="result">核算结果</param> | |
| 68 | + /// <returns>发送结果</returns> | |
| 69 | + Task<bool> SendEmailNotificationAsync(SalaryCalculationResult result); | |
| 70 | + } | |
| 71 | + | |
| 72 | + /// <summary> | |
| 73 | + /// 员工业绩数据模型 | |
| 74 | + /// </summary> | |
| 75 | + public class EmployeePerformance | |
| 76 | + { | |
| 77 | + public string EmployeeId { get; set; } = string.Empty; | |
| 78 | + public string EmployeeName { get; set; } = string.Empty; | |
| 79 | + public string StoreId { get; set; } = string.Empty; | |
| 80 | + public string StoreName { get; set; } = string.Empty; | |
| 81 | + public string Position { get; set; } = string.Empty; | |
| 82 | + public string PositionCategory { get; set; } = string.Empty; | |
| 83 | + public decimal TotalPerformance { get; set; } | |
| 84 | + public decimal ConsumptionPerformance { get; set; } | |
| 85 | + public int ProjectCount { get; set; } | |
| 86 | + public int CustomerCount { get; set; } | |
| 87 | + public decimal TeamPerformance { get; set; } | |
| 88 | + public bool IsNewStore { get; set; } | |
| 89 | + public decimal StoreLifeLine { get; set; } | |
| 90 | + public string TeamId { get; set; } = string.Empty; | |
| 91 | + public int TeamMemberCount { get; set; } | |
| 92 | + } | |
| 93 | + | |
| 94 | + /// <summary> | |
| 95 | + /// 工资核算结果 | |
| 96 | + /// </summary> | |
| 97 | + public class SalaryCalculationResult | |
| 98 | + { | |
| 99 | + public bool Success { get; set; } | |
| 100 | + public string Message { get; set; } = string.Empty; | |
| 101 | + public List<EmployeeSalary> Salaries { get; set; } = new List<EmployeeSalary>(); | |
| 102 | + public string OutputFilePath { get; set; } = string.Empty; | |
| 103 | + public DateTime CalculationTime { get; set; } = DateTime.Now; | |
| 104 | + public string CalculationMonth { get; set; } = string.Empty; | |
| 105 | + } | |
| 106 | + | |
| 107 | + /// <summary> | |
| 108 | + /// 用户信息初始化结果 | |
| 109 | + /// </summary> | |
| 110 | + public class UserInfoInitResult | |
| 111 | + { | |
| 112 | + public bool Success { get; set; } | |
| 113 | + public string Message { get; set; } = string.Empty; | |
| 114 | + public int UserCount { get; set; } | |
| 115 | + public DateTime InitTime { get; set; } = DateTime.Now; | |
| 116 | + public string CalculationMonth { get; set; } = string.Empty; | |
| 117 | + } | |
| 118 | +} | ... | ... |
service/LqSalaryCalculationService/Services/SalaryCalculationService.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.Collections.Generic; | |
| 3 | +using System.Data; | |
| 4 | +using System.IO; | |
| 5 | +using System.Linq; | |
| 6 | +using System.Threading.Tasks; | |
| 7 | +using Microsoft.Extensions.Configuration; | |
| 8 | +using Microsoft.Extensions.Logging; | |
| 9 | +using SqlSugar; | |
| 10 | +using LqSalaryCalculationService.Models; | |
| 11 | +using Yitter.IdGenerator; | |
| 12 | + | |
| 13 | +namespace LqSalaryCalculationService.Services | |
| 14 | +{ | |
| 15 | + /// <summary> | |
| 16 | + /// 工资核算服务实现 | |
| 17 | + /// </summary> | |
| 18 | + public class SalaryCalculationService : ISalaryCalculationService | |
| 19 | + { | |
| 20 | + private readonly SqlSugarClient _db; | |
| 21 | + private readonly ILogger<SalaryCalculationService> _logger; | |
| 22 | + private readonly IConfiguration _configuration; | |
| 23 | + private readonly SalaryCalculationConfig _config; | |
| 24 | + | |
| 25 | + public SalaryCalculationService( | |
| 26 | + SqlSugarClient db, | |
| 27 | + ILogger<SalaryCalculationService> logger, | |
| 28 | + IConfiguration configuration) | |
| 29 | + { | |
| 30 | + _db = db; | |
| 31 | + _logger = logger; | |
| 32 | + _configuration = configuration; | |
| 33 | + _config = _configuration.GetSection("SalaryCalculation").Get<SalaryCalculationConfig>() ?? new SalaryCalculationConfig(); | |
| 34 | + } | |
| 35 | + | |
| 36 | + /// <summary> | |
| 37 | + /// 初始化用户信息到工资表 | |
| 38 | + /// </summary> | |
| 39 | + public async Task<UserInfoInitResult> InitializeUserInfoAsync(string calculationMonth) | |
| 40 | + { | |
| 41 | + try | |
| 42 | + { | |
| 43 | + _logger.LogInformation($"开始初始化用户信息,计算月份: {calculationMonth}"); | |
| 44 | + | |
| 45 | + // 1. 从base_user表获取所有用户基础信息(包括没有门店信息的) | |
| 46 | + var userInfoSql = @" | |
| 47 | + SELECT | |
| 48 | + u.F_Id as UserId, | |
| 49 | + u.F_REALNAME as UserName, | |
| 50 | + u.F_GW as Position, | |
| 51 | + u.F_MDID as StoreId, | |
| 52 | + COALESCE(m.dm, '无门店') as StoreName | |
| 53 | + FROM BASE_USER u | |
| 54 | + LEFT JOIN lq_mdxx m ON u.F_MDID = m.F_Id | |
| 55 | + WHERE u.F_ENABLEDMARK = 1 | |
| 56 | + AND u.F_DELETEMARK IS NULL"; | |
| 57 | + | |
| 58 | + var userInfos = await _db.Ado.SqlQueryAsync<UserInfoDto>(userInfoSql); | |
| 59 | + _logger.LogInformation($"获取到 {userInfos.Count()} 名用户基础信息"); | |
| 60 | + | |
| 61 | + // 2. 清理工资表数据 | |
| 62 | + _logger.LogInformation("开始清理工资表数据..."); | |
| 63 | + var clearSql = "DELETE FROM lq_gz"; | |
| 64 | + await _db.Ado.ExecuteCommandAsync(clearSql); | |
| 65 | + _logger.LogInformation("工资表数据清理完成"); | |
| 66 | + | |
| 67 | + // 3. 先插入所有用户到工资表 | |
| 68 | + var insertCount = 0; | |
| 69 | + foreach (var userInfo in userInfos) | |
| 70 | + { | |
| 71 | + try | |
| 72 | + { | |
| 73 | + // 插入到lq_gz工资表 - 先插入基本字段 | |
| 74 | + var insertSql = @" | |
| 75 | + INSERT INTO lq_gz (F_Id, userid, xm, hsgw, md, jsjzd) | |
| 76 | + VALUES (@fid, @userid, @xm, @hsgw, @md, @jsjzd) | |
| 77 | + ON DUPLICATE KEY UPDATE | |
| 78 | + xm = VALUES(xm), | |
| 79 | + hsgw = VALUES(hsgw), | |
| 80 | + md = VALUES(md)"; | |
| 81 | + | |
| 82 | + await _db.Ado.ExecuteCommandAsync(insertSql, new | |
| 83 | + { | |
| 84 | + fid = Guid.NewGuid().ToString(), | |
| 85 | + userid = userInfo.UserId, | |
| 86 | + xm = userInfo.UserName, | |
| 87 | + hsgw = userInfo.Position, | |
| 88 | + md = userInfo.StoreName, | |
| 89 | + jsjzd = "" // 先插入空值 | |
| 90 | + }); | |
| 91 | + | |
| 92 | + insertCount++; | |
| 93 | + _logger.LogDebug($"已插入用户: {userInfo.UserName} - {userInfo.StoreName}"); | |
| 94 | + } | |
| 95 | + catch (Exception ex) | |
| 96 | + { | |
| 97 | + _logger.LogWarning(ex, $"插入用户 {userInfo.UserName} 时出错: {ex.Message}"); | |
| 98 | + } | |
| 99 | + } | |
| 100 | + | |
| 101 | + _logger.LogInformation($"第一阶段完成:已插入 {insertCount} 名用户到工资表"); | |
| 102 | + | |
| 103 | + // 4. 更新门店信息(重新查询有门店信息的用户) | |
| 104 | + var updateStoreCount = 0; | |
| 105 | + var storeUpdateSql = @" | |
| 106 | + SELECT | |
| 107 | + u.F_Id as UserId, | |
| 108 | + u.F_REALNAME as UserName, | |
| 109 | + m.dm as StoreName | |
| 110 | + FROM BASE_USER u | |
| 111 | + LEFT JOIN lq_mdxx m ON u.F_MDID = m.F_Id | |
| 112 | + WHERE u.F_ENABLEDMARK = 1 | |
| 113 | + AND u.F_DELETEMARK IS NULL | |
| 114 | + AND u.F_MDID IS NOT NULL | |
| 115 | + AND m.dm IS NOT NULL"; | |
| 116 | + | |
| 117 | + var storeInfos = await _db.Ado.SqlQueryAsync<UserInfoDto>(storeUpdateSql); | |
| 118 | + | |
| 119 | + foreach (var storeInfo in storeInfos) | |
| 120 | + { | |
| 121 | + try | |
| 122 | + { | |
| 123 | + var updateStoreSql = @" | |
| 124 | + UPDATE lq_gz | |
| 125 | + SET md = @storeName | |
| 126 | + WHERE userid = @userId"; | |
| 127 | + | |
| 128 | + await _db.Ado.ExecuteCommandAsync(updateStoreSql, new | |
| 129 | + { | |
| 130 | + storeName = storeInfo.StoreName, | |
| 131 | + userId = storeInfo.UserId | |
| 132 | + }); | |
| 133 | + | |
| 134 | + updateStoreCount++; | |
| 135 | + _logger.LogDebug($"已更新门店信息: {storeInfo.UserName} - {storeInfo.StoreName}"); | |
| 136 | + } | |
| 137 | + catch (Exception ex) | |
| 138 | + { | |
| 139 | + _logger.LogWarning(ex, $"更新门店信息 {storeInfo.UserName} 时出错: {ex.Message}"); | |
| 140 | + } | |
| 141 | + } | |
| 142 | + | |
| 143 | + _logger.LogInformation($"第二阶段完成:已更新 {updateStoreCount} 名用户的门店信息"); | |
| 144 | + | |
| 145 | + // 5. 更新金三角信息 | |
| 146 | + var updateTeamCount = 0; | |
| 147 | + foreach (var userInfo in userInfos) | |
| 148 | + { | |
| 149 | + try | |
| 150 | + { | |
| 151 | + // 通过用户ID在lq_jinsanjiao_user中获取对应月份的金三角ID | |
| 152 | + var monthFormat = calculationMonth.Replace("-", ""); | |
| 153 | + var teamSql = @" | |
| 154 | + SELECT jsj_user.jsj_id, jsj.jsj as TeamName | |
| 155 | + FROM lq_jinsanjiao_user jsj_user | |
| 156 | + LEFT JOIN lq_ycsd_jsj jsj ON jsj_user.jsj_id = jsj.F_Id | |
| 157 | + WHERE jsj_user.user_id = @userId | |
| 158 | + AND jsj_user.status = 'ACTIVE' | |
| 159 | + AND jsj_user.F_Month = @monthFormat | |
| 160 | + LIMIT 1"; | |
| 161 | + | |
| 162 | + var teamInfo = await _db.Ado.SqlQueryAsync<TeamInfoDto>(teamSql, new { userId = userInfo.UserId, monthFormat = monthFormat }); | |
| 163 | + var teamName = teamInfo.FirstOrDefault()?.TeamName ?? ""; | |
| 164 | + | |
| 165 | + if (!string.IsNullOrEmpty(teamName)) | |
| 166 | + { | |
| 167 | + var updateTeamSql = @" | |
| 168 | + UPDATE lq_gz | |
| 169 | + SET jsjzd = @teamName | |
| 170 | + WHERE userid = @userId"; | |
| 171 | + | |
| 172 | + await _db.Ado.ExecuteCommandAsync(updateTeamSql, new | |
| 173 | + { | |
| 174 | + teamName = teamName, | |
| 175 | + userId = userInfo.UserId | |
| 176 | + }); | |
| 177 | + | |
| 178 | + updateTeamCount++; | |
| 179 | + _logger.LogInformation($"已更新金三角信息: {userInfo.UserName} - {teamName}"); | |
| 180 | + } | |
| 181 | + else | |
| 182 | + { | |
| 183 | + _logger.LogDebug($"用户 {userInfo.UserName} 没有找到金三角信息"); | |
| 184 | + } | |
| 185 | + } | |
| 186 | + catch (Exception ex) | |
| 187 | + { | |
| 188 | + _logger.LogWarning(ex, $"更新金三角信息 {userInfo.UserName} 时出错: {ex.Message}"); | |
| 189 | + } | |
| 190 | + } | |
| 191 | + | |
| 192 | + _logger.LogInformation($"第三阶段完成:已更新 {updateTeamCount} 名用户的金三角信息"); | |
| 193 | + _logger.LogInformation($"用户信息初始化完成,共处理 {insertCount} 名用户"); | |
| 194 | + | |
| 195 | + return new UserInfoInitResult | |
| 196 | + { | |
| 197 | + Success = true, | |
| 198 | + Message = "用户信息初始化完成", | |
| 199 | + UserCount = insertCount, | |
| 200 | + CalculationMonth = calculationMonth | |
| 201 | + }; | |
| 202 | + } | |
| 203 | + catch (Exception ex) | |
| 204 | + { | |
| 205 | + _logger.LogError(ex, $"初始化用户信息失败: {ex.Message}"); | |
| 206 | + return new UserInfoInitResult | |
| 207 | + { | |
| 208 | + Success = false, | |
| 209 | + Message = $"初始化用户信息失败: {ex.Message}", | |
| 210 | + CalculationMonth = calculationMonth | |
| 211 | + }; | |
| 212 | + } | |
| 213 | + } | |
| 214 | + | |
| 215 | + /// <summary> | |
| 216 | + /// 执行工资核算 | |
| 217 | + /// </summary> | |
| 218 | + public async Task<SalaryCalculationResult> CalculateSalaryAsync(string calculationMonth) | |
| 219 | + { | |
| 220 | + try | |
| 221 | + { | |
| 222 | + _logger.LogInformation($"开始执行工资核算,计算月份: {calculationMonth}"); | |
| 223 | + | |
| 224 | + // 1. 获取员工业绩数据 | |
| 225 | + var performances = await GetEmployeePerformanceAsync(calculationMonth); | |
| 226 | + _logger.LogInformation($"获取到 {performances.Count} 条员工业绩数据"); | |
| 227 | + | |
| 228 | + // 2. 计算每个员工的工资 | |
| 229 | + var salaries = new List<EmployeeSalary>(); | |
| 230 | + foreach (var performance in performances) | |
| 231 | + { | |
| 232 | + EmployeeSalary salary; | |
| 233 | + switch (performance.PositionCategory) | |
| 234 | + { | |
| 235 | + case "健康师": | |
| 236 | + salary = await CalculateHealthWorkerSalaryAsync(performance); | |
| 237 | + break; | |
| 238 | + case "店长": | |
| 239 | + case "主任": | |
| 240 | + case "店助": | |
| 241 | + salary = await CalculateManagerSalaryAsync(performance); | |
| 242 | + break; | |
| 243 | + default: | |
| 244 | + salary = await CalculateDefaultSalaryAsync(performance); | |
| 245 | + break; | |
| 246 | + } | |
| 247 | + salaries.Add(salary); | |
| 248 | + } | |
| 249 | + | |
| 250 | + // 3. 导出工资报表 | |
| 251 | + var outputPath = Path.Combine(_config.OutputPath, $"工资核算_{calculationMonth}.xlsx"); | |
| 252 | + var exportPath = await ExportSalaryReportAsync(salaries, outputPath); | |
| 253 | + | |
| 254 | + // 4. 发送邮件通知 | |
| 255 | + if (_config.EnableEmailNotification) | |
| 256 | + { | |
| 257 | + await SendEmailNotificationAsync(new SalaryCalculationResult | |
| 258 | + { | |
| 259 | + Success = true, | |
| 260 | + Message = "工资核算完成", | |
| 261 | + Salaries = salaries, | |
| 262 | + OutputFilePath = exportPath, | |
| 263 | + CalculationMonth = calculationMonth | |
| 264 | + }); | |
| 265 | + } | |
| 266 | + | |
| 267 | + _logger.LogInformation($"工资核算完成,共计算 {salaries.Count} 名员工工资"); | |
| 268 | + | |
| 269 | + return new SalaryCalculationResult | |
| 270 | + { | |
| 271 | + Success = true, | |
| 272 | + Message = "工资核算完成", | |
| 273 | + Salaries = salaries, | |
| 274 | + OutputFilePath = exportPath, | |
| 275 | + CalculationMonth = calculationMonth | |
| 276 | + }; | |
| 277 | + } | |
| 278 | + catch (Exception ex) | |
| 279 | + { | |
| 280 | + _logger.LogError(ex, $"工资核算失败: {ex.Message}"); | |
| 281 | + return new SalaryCalculationResult | |
| 282 | + { | |
| 283 | + Success = false, | |
| 284 | + Message = $"工资核算失败: {ex.Message}", | |
| 285 | + CalculationMonth = calculationMonth | |
| 286 | + }; | |
| 287 | + } | |
| 288 | + } | |
| 289 | + | |
| 290 | + /// <summary> | |
| 291 | + /// 获取员工业绩数据 | |
| 292 | + /// </summary> | |
| 293 | + public async Task<List<EmployeePerformance>> GetEmployeePerformanceAsync(string calculationMonth) | |
| 294 | + { | |
| 295 | + var startDate = DateTime.Parse($"{calculationMonth}-01"); | |
| 296 | + var endDate = startDate.AddMonths(1).AddDays(-1); | |
| 297 | + | |
| 298 | + var sql = @" | |
| 299 | + SELECT | |
| 300 | + u.F_Id as EmployeeId, | |
| 301 | + u.F_REALNAME as EmployeeName, | |
| 302 | + u.F_MDID as StoreId, | |
| 303 | + m.dm as StoreName, | |
| 304 | + u.F_GW as Position, | |
| 305 | + u.F_GWFL as PositionCategory, | |
| 306 | + COALESCE(SUM(CAST(yj.ssyj as DECIMAL(18,2))), 0) as TotalPerformance, | |
| 307 | + COALESCE(SUM(CAST(xh.ssyj as DECIMAL(18,2))), 0) as ConsumptionPerformance, | |
| 308 | + COUNT(DISTINCT yj.xmbh) as ProjectCount, | |
| 309 | + COUNT(DISTINCT yj.khbh) as CustomerCount, | |
| 310 | + COALESCE(SUM(CAST(jsj.team_performance as DECIMAL(18,2))), 0) as TeamPerformance, | |
| 311 | + CASE WHEN m.kysj >= DATE_SUB(NOW(), INTERVAL 6 MONTH) THEN 1 ELSE 0 END as IsNewStore, | |
| 312 | + COALESCE(m.xsyj, 0) as StoreLifeLine, | |
| 313 | + COALESCE(jsj.team_id, '') as TeamId, | |
| 314 | + COALESCE(jsj.team_member_count, 1) as TeamMemberCount | |
| 315 | + FROM BASE_USER u | |
| 316 | + LEFT JOIN lq_mdxx m ON u.F_MDID = m.F_Id | |
| 317 | + LEFT JOIN lq_yjmxb yj ON u.F_REALNAME = yj.jks | |
| 318 | + AND DATE_FORMAT(yj.fssj, '%Y-%m') = @month | |
| 319 | + LEFT JOIN lq_xhmxb xh ON u.F_REALNAME = xh.jks | |
| 320 | + AND DATE_FORMAT(xh.fssj, '%Y-%m') = @month | |
| 321 | + LEFT JOIN ( | |
| 322 | + SELECT | |
| 323 | + jsj.F_Id as team_id, | |
| 324 | + jsj.jsj, | |
| 325 | + COUNT(jsj_user.user_id) as team_member_count, | |
| 326 | + SUM(CAST(yj2.ssyj as DECIMAL(18,2))) as team_performance | |
| 327 | + FROM lq_ycsd_jsj jsj | |
| 328 | + LEFT JOIN lq_jinsanjiao_user jsj_user ON jsj.F_Id = jsj_user.jsj_id | |
| 329 | + LEFT JOIN lq_yjmxb yj2 ON jsj_user.user_name = yj2.jks | |
| 330 | + AND DATE_FORMAT(yj2.fssj, '%Y-%m') = @month | |
| 331 | + WHERE jsj.yf = @month | |
| 332 | + GROUP BY jsj.F_Id, jsj.jsj | |
| 333 | + ) jsj ON u.F_REALNAME = jsj.jsj | |
| 334 | + WHERE u.F_ENABLEDMARK = 1 | |
| 335 | + AND u.F_DELETEMARK IS NULL | |
| 336 | + GROUP BY u.F_Id, u.F_REALNAME, u.F_MDID, m.dm, u.F_GW, u.F_GWFL, | |
| 337 | + m.kysj, m.xsyj, jsj.team_id, jsj.team_member_count"; | |
| 338 | + | |
| 339 | + var performances = await _db.Ado.SqlQueryAsync<EmployeePerformance>(sql, new { month = calculationMonth }); | |
| 340 | + return performances.ToList(); | |
| 341 | + } | |
| 342 | + | |
| 343 | + /// <summary> | |
| 344 | + /// 计算健康师工资 | |
| 345 | + /// </summary> | |
| 346 | + public Task<EmployeeSalary> CalculateHealthWorkerSalaryAsync(EmployeePerformance performance) | |
| 347 | + { | |
| 348 | + var salary = new EmployeeSalary | |
| 349 | + { | |
| 350 | + Id = Guid.NewGuid().ToString(), | |
| 351 | + EmployeeId = performance.EmployeeId, | |
| 352 | + EmployeeName = performance.EmployeeName, | |
| 353 | + StoreId = performance.StoreId, | |
| 354 | + StoreName = performance.StoreName, | |
| 355 | + Position = performance.Position, | |
| 356 | + PositionCategory = performance.PositionCategory, | |
| 357 | + CalculationMonth = performance.StoreId, // 这里应该传计算月份 | |
| 358 | + TotalPerformance = performance.TotalPerformance, | |
| 359 | + ConsumptionPerformance = performance.ConsumptionPerformance, | |
| 360 | + ProjectCount = performance.ProjectCount, | |
| 361 | + CustomerCount = performance.CustomerCount, | |
| 362 | + TeamPerformance = performance.TeamPerformance, | |
| 363 | + IsNewStore = performance.IsNewStore, | |
| 364 | + StoreLifeLine = performance.StoreLifeLine, | |
| 365 | + CreatedTime = DateTime.Now | |
| 366 | + }; | |
| 367 | + | |
| 368 | + // 根据健康师薪酬规则计算底薪 | |
| 369 | + if (performance.ConsumptionPerformance >= 40000 && performance.ProjectCount >= 156) | |
| 370 | + { | |
| 371 | + salary.BaseSalary = 2400; // 三星 | |
| 372 | + } | |
| 373 | + else if (performance.ConsumptionPerformance >= 20000 && performance.ProjectCount >= 126) | |
| 374 | + { | |
| 375 | + salary.BaseSalary = 2200; // 二星 | |
| 376 | + } | |
| 377 | + else if (performance.ConsumptionPerformance >= 10000 && performance.ProjectCount >= 96) | |
| 378 | + { | |
| 379 | + salary.BaseSalary = 2000; // 一星 | |
| 380 | + } | |
| 381 | + else | |
| 382 | + { | |
| 383 | + salary.BaseSalary = 1800; // 0星 | |
| 384 | + } | |
| 385 | + | |
| 386 | + // 计算金三角提成 | |
| 387 | + if (performance.TeamMemberCount >= 1) | |
| 388 | + { | |
| 389 | + var commissionRate = CalculateTeamCommissionRate(performance.TeamPerformance, performance.TeamMemberCount); | |
| 390 | + salary.TeamCommission = performance.TeamPerformance * commissionRate / 100; | |
| 391 | + } | |
| 392 | + | |
| 393 | + // 计算个人提成(基础业绩提成) | |
| 394 | + if (performance.TotalPerformance > 6000) | |
| 395 | + { | |
| 396 | + salary.CommissionAmount = performance.TotalPerformance * 0.95m * 0.03m; // 3%提成点 | |
| 397 | + } | |
| 398 | + | |
| 399 | + salary.GrossSalary = salary.BaseSalary + salary.CommissionAmount + salary.TeamCommission + salary.BonusAmount - salary.DeductionAmount; | |
| 400 | + salary.NetSalary = salary.GrossSalary; | |
| 401 | + | |
| 402 | + return Task.FromResult(salary); | |
| 403 | + } | |
| 404 | + | |
| 405 | + /// <summary> | |
| 406 | + /// 计算金三角提成比例 | |
| 407 | + /// </summary> | |
| 408 | + public decimal CalculateTeamCommissionRate(decimal teamPerformance, int memberCount) | |
| 409 | + { | |
| 410 | + switch (memberCount) | |
| 411 | + { | |
| 412 | + case 3: // 3人战队 | |
| 413 | + if (teamPerformance >= 150000) return 7m; | |
| 414 | + if (teamPerformance >= 120000) return 6m; | |
| 415 | + if (teamPerformance >= 90000) return 5m; | |
| 416 | + if (teamPerformance >= 60000) return 4m; | |
| 417 | + if (teamPerformance >= 30000) return 3m; | |
| 418 | + break; | |
| 419 | + case 2: // 2人战队 | |
| 420 | + if (teamPerformance >= 80000) return 6m; | |
| 421 | + if (teamPerformance >= 60000) return 5m; | |
| 422 | + if (teamPerformance >= 40000) return 4m; | |
| 423 | + if (teamPerformance >= 20000) return 3m; | |
| 424 | + break; | |
| 425 | + case 1: // 1人战队 | |
| 426 | + if (teamPerformance >= 60000) return 6m; | |
| 427 | + if (teamPerformance >= 40000) return 5m; | |
| 428 | + if (teamPerformance >= 20000) return 4m; | |
| 429 | + if (teamPerformance >= 10000) return 3m; | |
| 430 | + break; | |
| 431 | + } | |
| 432 | + return 0m; | |
| 433 | + } | |
| 434 | + | |
| 435 | + /// <summary> | |
| 436 | + /// 计算门店管理员工资 | |
| 437 | + /// </summary> | |
| 438 | + public Task<EmployeeSalary> CalculateManagerSalaryAsync(EmployeePerformance performance) | |
| 439 | + { | |
| 440 | + var salary = new EmployeeSalary | |
| 441 | + { | |
| 442 | + Id = Guid.NewGuid().ToString(), | |
| 443 | + EmployeeId = performance.EmployeeId, | |
| 444 | + EmployeeName = performance.EmployeeName, | |
| 445 | + StoreId = performance.StoreId, | |
| 446 | + StoreName = performance.StoreName, | |
| 447 | + Position = performance.Position, | |
| 448 | + PositionCategory = performance.PositionCategory, | |
| 449 | + CalculationMonth = performance.StoreId, // 这里应该传计算月份 | |
| 450 | + TotalPerformance = performance.TotalPerformance, | |
| 451 | + ConsumptionPerformance = performance.ConsumptionPerformance, | |
| 452 | + ProjectCount = performance.ProjectCount, | |
| 453 | + CustomerCount = performance.CustomerCount, | |
| 454 | + TeamPerformance = performance.TeamPerformance, | |
| 455 | + IsNewStore = performance.IsNewStore, | |
| 456 | + StoreLifeLine = performance.StoreLifeLine, | |
| 457 | + CreatedTime = DateTime.Now | |
| 458 | + }; | |
| 459 | + | |
| 460 | + // 根据职位设置底薪 | |
| 461 | + switch (performance.Position) | |
| 462 | + { | |
| 463 | + case "店长": | |
| 464 | + salary.BaseSalary = 4000; | |
| 465 | + break; | |
| 466 | + case "主任": | |
| 467 | + salary.BaseSalary = 3500; | |
| 468 | + break; | |
| 469 | + case "店助": | |
| 470 | + salary.BaseSalary = 3000; | |
| 471 | + break; | |
| 472 | + default: | |
| 473 | + salary.BaseSalary = 3000; | |
| 474 | + break; | |
| 475 | + } | |
| 476 | + | |
| 477 | + // 计算提成(基于毛利) | |
| 478 | + var grossProfit = performance.TotalPerformance * 0.6m; // 假设毛利率60% | |
| 479 | + var commissionRate = 0.03m; // 3%提成率 | |
| 480 | + salary.CommissionAmount = grossProfit * commissionRate; | |
| 481 | + | |
| 482 | + // 检查是否达标 | |
| 483 | + salary.IsTargetAchieved = performance.TotalPerformance >= performance.StoreLifeLine; | |
| 484 | + | |
| 485 | + salary.GrossSalary = salary.BaseSalary + salary.CommissionAmount + salary.BonusAmount - salary.DeductionAmount; | |
| 486 | + salary.NetSalary = salary.GrossSalary; | |
| 487 | + | |
| 488 | + return Task.FromResult(salary); | |
| 489 | + } | |
| 490 | + | |
| 491 | + /// <summary> | |
| 492 | + /// 计算默认工资 | |
| 493 | + /// </summary> | |
| 494 | + private Task<EmployeeSalary> CalculateDefaultSalaryAsync(EmployeePerformance performance) | |
| 495 | + { | |
| 496 | + var salary = new EmployeeSalary | |
| 497 | + { | |
| 498 | + Id = Guid.NewGuid().ToString(), | |
| 499 | + EmployeeId = performance.EmployeeId, | |
| 500 | + EmployeeName = performance.EmployeeName, | |
| 501 | + StoreId = performance.StoreId, | |
| 502 | + StoreName = performance.StoreName, | |
| 503 | + Position = performance.Position, | |
| 504 | + PositionCategory = performance.PositionCategory, | |
| 505 | + CalculationMonth = performance.StoreId, | |
| 506 | + TotalPerformance = performance.TotalPerformance, | |
| 507 | + ConsumptionPerformance = performance.ConsumptionPerformance, | |
| 508 | + ProjectCount = performance.ProjectCount, | |
| 509 | + CustomerCount = performance.CustomerCount, | |
| 510 | + TeamPerformance = performance.TeamPerformance, | |
| 511 | + IsNewStore = performance.IsNewStore, | |
| 512 | + StoreLifeLine = performance.StoreLifeLine, | |
| 513 | + CreatedTime = DateTime.Now, | |
| 514 | + BaseSalary = 2000, // 默认底薪 | |
| 515 | + GrossSalary = 2000, | |
| 516 | + NetSalary = 2000 | |
| 517 | + }; | |
| 518 | + | |
| 519 | + return Task.FromResult(salary); | |
| 520 | + } | |
| 521 | + | |
| 522 | + /// <summary> | |
| 523 | + /// 导出工资报表 | |
| 524 | + /// </summary> | |
| 525 | + public async Task<string> ExportSalaryReportAsync(List<EmployeeSalary> salaries, string outputPath) | |
| 526 | + { | |
| 527 | + try | |
| 528 | + { | |
| 529 | + // 确保输出目录存在 | |
| 530 | + var directory = Path.GetDirectoryName(outputPath); | |
| 531 | + if (!Directory.Exists(directory)) | |
| 532 | + { | |
| 533 | + Directory.CreateDirectory(directory!); | |
| 534 | + } | |
| 535 | + | |
| 536 | + // 这里可以使用EPPlus或其他Excel库来生成Excel文件 | |
| 537 | + // 为了简化,这里生成CSV文件 | |
| 538 | + var csvPath = outputPath.Replace(".xlsx", ".csv"); | |
| 539 | + | |
| 540 | + using var writer = new StreamWriter(csvPath); | |
| 541 | + await writer.WriteLineAsync("员工姓名,门店名称,职位,底薪,总业绩,消耗业绩,提成金额,应发工资,实发工资,计算月份"); | |
| 542 | + | |
| 543 | + foreach (var salary in salaries) | |
| 544 | + { | |
| 545 | + await writer.WriteLineAsync($"{salary.EmployeeName},{salary.StoreName},{salary.Position}," + | |
| 546 | + $"{salary.BaseSalary},{salary.TotalPerformance},{salary.ConsumptionPerformance}," + | |
| 547 | + $"{salary.CommissionAmount},{salary.GrossSalary},{salary.NetSalary},{salary.CalculationMonth}"); | |
| 548 | + } | |
| 549 | + | |
| 550 | + _logger.LogInformation($"工资报表已导出到: {csvPath}"); | |
| 551 | + return csvPath; | |
| 552 | + } | |
| 553 | + catch (Exception ex) | |
| 554 | + { | |
| 555 | + _logger.LogError(ex, $"导出工资报表失败: {ex.Message}"); | |
| 556 | + throw; | |
| 557 | + } | |
| 558 | + } | |
| 559 | + | |
| 560 | + /// <summary> | |
| 561 | + /// 发送邮件通知 | |
| 562 | + /// </summary> | |
| 563 | + public Task<bool> SendEmailNotificationAsync(SalaryCalculationResult result) | |
| 564 | + { | |
| 565 | + try | |
| 566 | + { | |
| 567 | + // 这里可以实现邮件发送逻辑 | |
| 568 | + // 可以使用MailKit或其他邮件库 | |
| 569 | + _logger.LogInformation($"邮件通知功能待实现,核算结果: {result.Message}"); | |
| 570 | + return Task.FromResult(true); | |
| 571 | + } | |
| 572 | + catch (Exception ex) | |
| 573 | + { | |
| 574 | + _logger.LogError(ex, $"发送邮件通知失败: {ex.Message}"); | |
| 575 | + return Task.FromResult(false); | |
| 576 | + } | |
| 577 | + } | |
| 578 | + } | |
| 579 | + | |
| 580 | + /// <summary> | |
| 581 | + /// 用户信息DTO | |
| 582 | + /// </summary> | |
| 583 | + public class UserInfoDto | |
| 584 | + { | |
| 585 | + public string UserId { get; set; } = string.Empty; | |
| 586 | + public string UserName { get; set; } = string.Empty; | |
| 587 | + public string Position { get; set; } = string.Empty; | |
| 588 | + public string StoreId { get; set; } = string.Empty; | |
| 589 | + public string StoreName { get; set; } = string.Empty; | |
| 590 | + } | |
| 591 | + | |
| 592 | + /// <summary> | |
| 593 | + /// 团队信息DTO | |
| 594 | + /// </summary> | |
| 595 | + public class TeamInfoDto | |
| 596 | + { | |
| 597 | + public string JsjId { get; set; } = string.Empty; | |
| 598 | + public string TeamName { get; set; } = string.Empty; | |
| 599 | + } | |
| 600 | +} | ... | ... |
service/LqSalaryCalculationService/appsettings.json
0 → 100644
| 1 | +{ | |
| 2 | + "ConnectionStrings": { | |
| 3 | + "DefaultConnection": "Database=lqerp;Data Source=rm-bp19ohrgc6111ynzh1o.mysql.rds.aliyuncs.com;Port=3306;User Id=netteam;Password=netteam;Charset=utf8;TreatTinyAsBoolean=true;" | |
| 4 | + }, | |
| 5 | + "Logging": { | |
| 6 | + "LogLevel": { | |
| 7 | + "Default": "Information", | |
| 8 | + "Microsoft": "Warning", | |
| 9 | + "Microsoft.Hosting.Lifetime": "Information" | |
| 10 | + } | |
| 11 | + }, | |
| 12 | + "SalaryCalculation": { | |
| 13 | + "CalculationMonth": "", | |
| 14 | + "OutputPath": "./output", | |
| 15 | + "BackupPath": "./backup", | |
| 16 | + "LogPath": "./logs", | |
| 17 | + "EnableEmailNotification": true, | |
| 18 | + "EmailSettings": { | |
| 19 | + "SmtpServer": "smtp.163.com", | |
| 20 | + "SmtpPort": 587, | |
| 21 | + "Username": "", | |
| 22 | + "Password": "", | |
| 23 | + "FromEmail": "", | |
| 24 | + "ToEmails": [] | |
| 25 | + } | |
| 26 | + }, | |
| 27 | + "Serilog": { | |
| 28 | + "MinimumLevel": { | |
| 29 | + "Default": "Information", | |
| 30 | + "Override": { | |
| 31 | + "System": "Warning", | |
| 32 | + "Microsoft": "Warning" | |
| 33 | + } | |
| 34 | + }, | |
| 35 | + "WriteTo": [ | |
| 36 | + { | |
| 37 | + "Name": "Console", | |
| 38 | + "Args": { | |
| 39 | + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}" | |
| 40 | + } | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + "Name": "File", | |
| 44 | + "Args": { | |
| 45 | + "path": "./logs/salary-calculation-.log", | |
| 46 | + "rollingInterval": "Day", | |
| 47 | + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}" | |
| 48 | + } | |
| 49 | + } | |
| 50 | + ] | |
| 51 | + } | |
| 52 | +} | ... | ... |
service/LqSalaryCalculationService/docker-compose.yml
0 → 100644
| 1 | +version: '3.8' | |
| 2 | + | |
| 3 | +services: | |
| 4 | + salary-calculation-service: | |
| 5 | + build: | |
| 6 | + context: . | |
| 7 | + dockerfile: Dockerfile | |
| 8 | + container_name: lq-salary-calculation-service | |
| 9 | + restart: unless-stopped | |
| 10 | + environment: | |
| 11 | + - ASPNETCORE_ENVIRONMENT=Production | |
| 12 | + - ConnectionStrings__DefaultConnection=Database=lqerp;Data Source=rm-bp19ohrgc6111ynzh1o.mysql.rds.aliyuncs.com;Port=3306;User Id=netteam;Password=netteam;Charset=utf8;TreatTinyAsBoolean=true; | |
| 13 | + volumes: | |
| 14 | + - ./logs:/app/logs | |
| 15 | + - ./output:/app/output | |
| 16 | + - ./backup:/app/backup | |
| 17 | + networks: | |
| 18 | + - salary-network | |
| 19 | + command: ["2024-09"] # 默认计算2024年9月工资 | |
| 20 | + | |
| 21 | +networks: | |
| 22 | + salary-network: | |
| 23 | + driver: bridge | ... | ... |
service/LqSalaryCalculationService/start.bat
0 → 100644
| 1 | +@echo off | |
| 2 | +chcp 65001 >nul | |
| 3 | +setlocal enabledelayedexpansion | |
| 4 | + | |
| 5 | +REM 绿纤美业ERP工资核算服务启动脚本 (Windows) | |
| 6 | +REM 支持Windows 10/11, Windows Server 2016+ | |
| 7 | + | |
| 8 | +echo. | |
| 9 | +echo ======================================== | |
| 10 | +echo 绿纤美业ERP工资核算服务 | |
| 11 | +echo ======================================== | |
| 12 | +echo. | |
| 13 | + | |
| 14 | +REM 检查参数 | |
| 15 | +if "%1"=="--help" goto :help | |
| 16 | +if "%1"=="-h" goto :help | |
| 17 | +if "%1"=="--version" goto :version | |
| 18 | +if "%1"=="-v" goto :version | |
| 19 | +if "%1"=="--build" goto :build | |
| 20 | +if "%1"=="--clean" goto :clean | |
| 21 | + | |
| 22 | +REM 检查.NET 6是否安装 | |
| 23 | +echo [INFO] 检查.NET 6环境... | |
| 24 | +dotnet --version >nul 2>&1 | |
| 25 | +if errorlevel 1 ( | |
| 26 | + echo [ERROR] .NET 6 未安装,请先安装 .NET 6 SDK | |
| 27 | + echo [INFO] 下载地址: https://dotnet.microsoft.com/download/dotnet/6.0 | |
| 28 | + pause | |
| 29 | + exit /b 1 | |
| 30 | +) | |
| 31 | + | |
| 32 | +for /f "tokens=*" %%i in ('dotnet --version') do set DOTNET_VERSION=%%i | |
| 33 | +echo [INFO] 检测到 .NET 版本: %DOTNET_VERSION% | |
| 34 | + | |
| 35 | +REM 检查配置文件 | |
| 36 | +if not exist "appsettings.json" ( | |
| 37 | + echo [ERROR] 配置文件 appsettings.json 不存在 | |
| 38 | + pause | |
| 39 | + exit /b 1 | |
| 40 | +) | |
| 41 | +echo [INFO] 配置文件检查通过 | |
| 42 | + | |
| 43 | +REM 创建必要目录 | |
| 44 | +echo [INFO] 创建必要目录... | |
| 45 | +if not exist "logs" mkdir logs | |
| 46 | +if not exist "output" mkdir output | |
| 47 | +if not exist "backup" mkdir backup | |
| 48 | +echo [INFO] 目录创建完成 | |
| 49 | + | |
| 50 | +REM 还原NuGet包 | |
| 51 | +echo [INFO] 还原NuGet包... | |
| 52 | +dotnet restore | |
| 53 | +if errorlevel 1 ( | |
| 54 | + echo [ERROR] NuGet包还原失败 | |
| 55 | + pause | |
| 56 | + exit /b 1 | |
| 57 | +) | |
| 58 | +echo [INFO] NuGet包还原成功 | |
| 59 | + | |
| 60 | +REM 构建项目 | |
| 61 | +echo [INFO] 构建项目... | |
| 62 | +dotnet build -c Release | |
| 63 | +if errorlevel 1 ( | |
| 64 | + echo [ERROR] 项目构建失败 | |
| 65 | + pause | |
| 66 | + exit /b 1 | |
| 67 | +) | |
| 68 | +echo [INFO] 项目构建成功 | |
| 69 | + | |
| 70 | +REM 运行服务 | |
| 71 | +set MONTH=%1 | |
| 72 | +if "%MONTH%"=="" ( | |
| 73 | + for /f "tokens=1,2 delims=-" %%a in ('date /t') do set MONTH=%%a-%%b | |
| 74 | + echo [WARN] 未指定计算月份,使用当前月份: %MONTH% | |
| 75 | + echo [INFO] 使用方法: %0 [YYYY-MM] | |
| 76 | + echo [INFO] 示例: %0 2024-09 | |
| 77 | +) | |
| 78 | + | |
| 79 | +echo [INFO] 启动工资核算服务,计算月份: %MONTH% | |
| 80 | +dotnet run --configuration Release -- %MONTH% | |
| 81 | + | |
| 82 | +goto :end | |
| 83 | + | |
| 84 | +:help | |
| 85 | +echo. | |
| 86 | +echo 绿纤美业ERP工资核算服务 | |
| 87 | +echo. | |
| 88 | +echo 使用方法: | |
| 89 | +echo %0 [月份] 运行工资核算服务 | |
| 90 | +echo %0 --help, -h 显示帮助信息 | |
| 91 | +echo %0 --version, -v 显示版本信息 | |
| 92 | +echo %0 --build 仅构建项目 | |
| 93 | +echo %0 --clean 清理构建文件 | |
| 94 | +echo. | |
| 95 | +echo 参数: | |
| 96 | +echo 月份 计算月份,格式: YYYY-MM (例如: 2024-09) | |
| 97 | +echo. | |
| 98 | +echo 示例: | |
| 99 | +echo %0 2024-09 计算2024年9月工资 | |
| 100 | +echo %0 计算当前月份工资 | |
| 101 | +echo. | |
| 102 | +echo 环境要求: | |
| 103 | +echo - .NET 6.0 SDK | |
| 104 | +echo - MySQL数据库连接 | |
| 105 | +echo - 网络连接 | |
| 106 | +goto :end | |
| 107 | + | |
| 108 | +:version | |
| 109 | +echo 绿纤美业ERP工资核算服务 v1.0.0 | |
| 110 | +echo 构建时间: %date% %time% | |
| 111 | +for /f "tokens=*" %%i in ('dotnet --version') do echo .NET版本: %%i | |
| 112 | +goto :end | |
| 113 | + | |
| 114 | +:build | |
| 115 | +echo [INFO] 检查.NET 6环境... | |
| 116 | +dotnet --version >nul 2>&1 | |
| 117 | +if errorlevel 1 ( | |
| 118 | + echo [ERROR] .NET 6 未安装 | |
| 119 | + pause | |
| 120 | + exit /b 1 | |
| 121 | +) | |
| 122 | + | |
| 123 | +echo [INFO] 检查配置文件... | |
| 124 | +if not exist "appsettings.json" ( | |
| 125 | + echo [ERROR] 配置文件不存在 | |
| 126 | + pause | |
| 127 | + exit /b 1 | |
| 128 | +) | |
| 129 | + | |
| 130 | +echo [INFO] 创建目录... | |
| 131 | +if not exist "logs" mkdir logs | |
| 132 | +if not exist "output" mkdir output | |
| 133 | +if not exist "backup" mkdir backup | |
| 134 | + | |
| 135 | +echo [INFO] 还原包... | |
| 136 | +dotnet restore | |
| 137 | +if errorlevel 1 ( | |
| 138 | + echo [ERROR] 还原失败 | |
| 139 | + pause | |
| 140 | + exit /b 1 | |
| 141 | +) | |
| 142 | + | |
| 143 | +echo [INFO] 构建项目... | |
| 144 | +dotnet build -c Release | |
| 145 | +if errorlevel 1 ( | |
| 146 | + echo [ERROR] 构建失败 | |
| 147 | + pause | |
| 148 | + exit /b 1 | |
| 149 | +) | |
| 150 | + | |
| 151 | +echo [INFO] 构建完成 | |
| 152 | +goto :end | |
| 153 | + | |
| 154 | +:clean | |
| 155 | +echo [INFO] 清理构建文件... | |
| 156 | +dotnet clean | |
| 157 | +if exist "bin" rmdir /s /q "bin" | |
| 158 | +if exist "obj" rmdir /s /q "obj" | |
| 159 | +echo [INFO] 清理完成 | |
| 160 | +goto :end | |
| 161 | + | |
| 162 | +:end | |
| 163 | +pause | ... | ... |
service/LqSalaryCalculationService/start.sh
0 → 100755
| 1 | +#!/bin/bash | |
| 2 | + | |
| 3 | +# 绿纤美业ERP工资核算服务启动脚本 | |
| 4 | +# 支持Windows、Linux、CentOS等操作系统 | |
| 5 | + | |
| 6 | +set -e | |
| 7 | + | |
| 8 | +# 颜色定义 | |
| 9 | +RED='\033[0;31m' | |
| 10 | +GREEN='\033[0;32m' | |
| 11 | +YELLOW='\033[1;33m' | |
| 12 | +BLUE='\033[0;34m' | |
| 13 | +NC='\033[0m' # No Color | |
| 14 | + | |
| 15 | +# 日志函数 | |
| 16 | +log_info() { | |
| 17 | + echo -e "${GREEN}[INFO]${NC} $1" | |
| 18 | +} | |
| 19 | + | |
| 20 | +log_warn() { | |
| 21 | + echo -e "${YELLOW}[WARN]${NC} $1" | |
| 22 | +} | |
| 23 | + | |
| 24 | +log_error() { | |
| 25 | + echo -e "${RED}[ERROR]${NC} $1" | |
| 26 | +} | |
| 27 | + | |
| 28 | +log_debug() { | |
| 29 | + echo -e "${BLUE}[DEBUG]${NC} $1" | |
| 30 | +} | |
| 31 | + | |
| 32 | +# 检查.NET 6是否安装 | |
| 33 | +check_dotnet() { | |
| 34 | + if ! command -v dotnet &> /dev/null; then | |
| 35 | + log_error ".NET 6 未安装,请先安装 .NET 6 SDK" | |
| 36 | + log_info "下载地址: https://dotnet.microsoft.com/download/dotnet/6.0" | |
| 37 | + exit 1 | |
| 38 | + fi | |
| 39 | + | |
| 40 | + local dotnet_version=$(dotnet --version) | |
| 41 | + log_info "检测到 .NET 版本: $dotnet_version" | |
| 42 | + | |
| 43 | + if [[ ! "$dotnet_version" =~ ^6\. ]]; then | |
| 44 | + log_warn "建议使用 .NET 6.0 版本" | |
| 45 | + fi | |
| 46 | +} | |
| 47 | + | |
| 48 | +# 检查配置文件 | |
| 49 | +check_config() { | |
| 50 | + if [ ! -f "appsettings.json" ]; then | |
| 51 | + log_error "配置文件 appsettings.json 不存在" | |
| 52 | + exit 1 | |
| 53 | + fi | |
| 54 | + log_info "配置文件检查通过" | |
| 55 | +} | |
| 56 | + | |
| 57 | +# 创建必要目录 | |
| 58 | +create_directories() { | |
| 59 | + log_info "创建必要目录..." | |
| 60 | + mkdir -p logs output backup | |
| 61 | + log_info "目录创建完成" | |
| 62 | +} | |
| 63 | + | |
| 64 | +# 还原NuGet包 | |
| 65 | +restore_packages() { | |
| 66 | + log_info "还原NuGet包..." | |
| 67 | + dotnet restore | |
| 68 | + if [ $? -eq 0 ]; then | |
| 69 | + log_info "NuGet包还原成功" | |
| 70 | + else | |
| 71 | + log_error "NuGet包还原失败" | |
| 72 | + exit 1 | |
| 73 | + fi | |
| 74 | +} | |
| 75 | + | |
| 76 | +# 构建项目 | |
| 77 | +build_project() { | |
| 78 | + log_info "构建项目..." | |
| 79 | + dotnet build -c Release | |
| 80 | + if [ $? -eq 0 ]; then | |
| 81 | + log_info "项目构建成功" | |
| 82 | + else | |
| 83 | + log_error "项目构建失败" | |
| 84 | + exit 1 | |
| 85 | + fi | |
| 86 | +} | |
| 87 | + | |
| 88 | +# 运行服务 | |
| 89 | +run_service() { | |
| 90 | + local month=${1:-$(date +%Y-%m)} | |
| 91 | + log_info "启动工资核算服务,计算月份: $month" | |
| 92 | + | |
| 93 | + # 检查参数 | |
| 94 | + if [ $# -eq 0 ]; then | |
| 95 | + log_warn "未指定计算月份,使用当前月份: $month" | |
| 96 | + log_info "使用方法: $0 [YYYY-MM]" | |
| 97 | + log_info "示例: $0 2024-09" | |
| 98 | + fi | |
| 99 | + | |
| 100 | + # 运行服务 | |
| 101 | + dotnet run --configuration Release -- $month | |
| 102 | +} | |
| 103 | + | |
| 104 | +# 显示帮助信息 | |
| 105 | +show_help() { | |
| 106 | + echo "绿纤美业ERP工资核算服务" | |
| 107 | + echo "" | |
| 108 | + echo "使用方法:" | |
| 109 | + echo " $0 [月份] 运行工资核算服务" | |
| 110 | + echo " $0 --help, -h 显示帮助信息" | |
| 111 | + echo " $0 --version, -v 显示版本信息" | |
| 112 | + echo " $0 --build 仅构建项目" | |
| 113 | + echo " $0 --clean 清理构建文件" | |
| 114 | + echo "" | |
| 115 | + echo "参数:" | |
| 116 | + echo " 月份 计算月份,格式: YYYY-MM (例如: 2024-09)" | |
| 117 | + echo "" | |
| 118 | + echo "示例:" | |
| 119 | + echo " $0 2024-09 计算2024年9月工资" | |
| 120 | + echo " $0 计算当前月份工资" | |
| 121 | + echo "" | |
| 122 | + echo "环境要求:" | |
| 123 | + echo " - .NET 6.0 SDK" | |
| 124 | + echo " - MySQL数据库连接" | |
| 125 | + echo " - 网络连接" | |
| 126 | +} | |
| 127 | + | |
| 128 | +# 显示版本信息 | |
| 129 | +show_version() { | |
| 130 | + echo "绿纤美业ERP工资核算服务 v1.0.0" | |
| 131 | + echo "构建时间: $(date)" | |
| 132 | + echo ".NET版本: $(dotnet --version)" | |
| 133 | +} | |
| 134 | + | |
| 135 | +# 清理构建文件 | |
| 136 | +clean_project() { | |
| 137 | + log_info "清理构建文件..." | |
| 138 | + dotnet clean | |
| 139 | + rm -rf bin obj | |
| 140 | + log_info "清理完成" | |
| 141 | +} | |
| 142 | + | |
| 143 | +# 主函数 | |
| 144 | +main() { | |
| 145 | + case "${1:-}" in | |
| 146 | + --help|-h) | |
| 147 | + show_help | |
| 148 | + exit 0 | |
| 149 | + ;; | |
| 150 | + --version|-v) | |
| 151 | + show_version | |
| 152 | + exit 0 | |
| 153 | + ;; | |
| 154 | + --build) | |
| 155 | + check_dotnet | |
| 156 | + check_config | |
| 157 | + create_directories | |
| 158 | + restore_packages | |
| 159 | + build_project | |
| 160 | + log_info "构建完成" | |
| 161 | + exit 0 | |
| 162 | + ;; | |
| 163 | + --clean) | |
| 164 | + clean_project | |
| 165 | + exit 0 | |
| 166 | + ;; | |
| 167 | + *) | |
| 168 | + check_dotnet | |
| 169 | + check_config | |
| 170 | + create_directories | |
| 171 | + restore_packages | |
| 172 | + build_project | |
| 173 | + run_service "$@" | |
| 174 | + ;; | |
| 175 | + esac | |
| 176 | +} | |
| 177 | + | |
| 178 | +# 执行主函数 | |
| 179 | +main "$@" | ... | ... |