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; } }