diff --git a/.DS_Store b/.DS_Store index dcd23dd..5f85f05 100644 --- a/.DS_Store +++ b/.DS_Store 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 --- a/netcore/netcore/.DS_Store +++ b/netcore/netcore/.DS_Store diff --git a/netcore/netcore/src/.DS_Store b/netcore/netcore/src/.DS_Store index c66fa50..b82cfb6 100644 --- a/netcore/netcore/src/.DS_Store +++ b/netcore/netcore/src/.DS_Store diff --git a/netcore/netcore/src/Modularity/.DS_Store b/netcore/netcore/src/Modularity/.DS_Store index 3a7fe57..77bc87c 100644 --- a/netcore/netcore/src/Modularity/.DS_Store +++ b/netcore/netcore/src/Modularity/.DS_Store diff --git a/netcore/src/.DS_Store b/netcore/src/.DS_Store index 804c075..35396ae 100644 --- a/netcore/src/.DS_Store +++ b/netcore/src/.DS_Store diff --git a/netcore/src/Application/.DS_Store b/netcore/src/Application/.DS_Store index 7413767..5f772a8 100644 --- a/netcore/src/Application/.DS_Store +++ b/netcore/src/Application/.DS_Store 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 "$@"