using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using NCC.Common.Filter; using NCC.Common.Helper; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.LqBusinessUnitManagerSalary; using NCC.Extend.Entitys.lq_attendance_summary; using NCC.Extend.Entitys.lq_hytk_hytk; using NCC.Extend.Entitys.lq_kd_kdjlb; using NCC.Extend.Entitys.lq_md_general_manager_lifeline; using NCC.Extend.Entitys.lq_md_target; using NCC.Extend.Entitys.lq_mdxx; using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics; using NCC.System.Entitys.Permission; using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Yitter.IdGenerator; namespace NCC.Extend { /// /// 事业部总经理/经理薪酬服务 /// [ApiDescriptionSettings(Tag = "事业部总经理/经理薪酬服务", Name = "LqBusinessUnitManagerSalary", Order = 304)] [Route("api/Extend/[controller]")] public class LqBusinessUnitManagerSalaryService : IDynamicApiController, ITransient { private readonly ISqlSugarClient _db; /// /// 初始化一个类型的新实例 /// public LqBusinessUnitManagerSalaryService(ISqlSugarClient db) { _db = db; } /// /// 获取事业部总经理/经理工资列表 /// /// 查询参数 /// 事业部总经理/经理工资分页列表 [HttpGet("business-unit-manager")] public async Task GetBusinessUnitManagerSalaryList([FromQuery] BusinessUnitManagerSalaryInput input) { var monthStr = $"{input.Year}{input.Month:D2}"; // 1. 检查当月是否已生成工资数据 var exists = await _db.Queryable() .AnyAsync(x => x.StatisticsMonth == monthStr); // 2. 如果没有数据,则进行计算 if (!exists) { await CalculateBusinessUnitManagerSalary(input.Year, input.Month); } // 3. 查询数据 var query = _db.Queryable() .Where(x => x.StatisticsMonth == monthStr); if (input.ManagerType.HasValue) { query = query.Where(x => x.ManagerType == input.ManagerType.Value); } if (!string.IsNullOrEmpty(input.Keyword)) { query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeAccount.Contains(input.Keyword)); } var list = await query.Select(x => new BusinessUnitManagerSalaryOutput { Id = x.Id, StatisticsMonth = x.StatisticsMonth, Position = x.Position, EmployeeName = x.EmployeeName, EmployeeId = x.EmployeeId, EmployeeAccount = x.EmployeeAccount, ManagerType = x.ManagerType, IsTerminated = x.IsTerminated, StorePerformanceDetail = x.StorePerformanceDetail, BaseSalary = x.BaseSalary, TotalCommission = x.TotalCommission, WorkingDays = x.WorkingDays, LeaveDays = x.LeaveDays, CalculatedGrossSalary = x.CalculatedGrossSalary, FinalGrossSalary = x.FinalGrossSalary, MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy, MonthlyTransportSubsidy = x.MonthlyTransportSubsidy, LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy, LastMonthTransportSubsidy = x.LastMonthTransportSubsidy, TotalSubsidy = x.TotalSubsidy, MissingCard = x.MissingCard, LateArrival = x.LateArrival, LeaveDeduction = x.LeaveDeduction, SocialInsuranceDeduction = x.SocialInsuranceDeduction, RewardDeduction = x.RewardDeduction, AccommodationDeduction = x.AccommodationDeduction, StudyPeriodDeduction = x.StudyPeriodDeduction, WorkClothesDeduction = x.WorkClothesDeduction, TotalDeduction = x.TotalDeduction, Bonus = x.Bonus, ReturnPhoneDeposit = x.ReturnPhoneDeposit, ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, ActualSalary = x.ActualSalary, MonthlyPaymentStatus = x.MonthlyPaymentStatus, PaidAmount = x.PaidAmount, PendingAmount = x.PendingAmount, LastMonthSupplement = x.LastMonthSupplement, MonthlyTotalPayment = x.MonthlyTotalPayment, IsLocked = x.IsLocked, UpdateTime = x.UpdateTime }) .ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(list); } /// /// 计算事业部总经理/经理工资 /// /// 年份 /// 月份 /// [HttpPost("calculate/business-unit-manager")] public async Task CalculateBusinessUnitManagerSalary(int year, int month) { var startDate = new DateTime(year, month, 1); var endDate = startDate.AddMonths(1).AddDays(-1); var monthStr = $"{year}{month:D2}"; // 1. 获取基础数据 // 1.1 获取总经理/经理归属信息(从lq_md_general_manager_lifeline表) var lifelineList = await _db.Queryable() .Where(x => x.Month == monthStr) .ToListAsync(); if (!lifelineList.Any()) { // 如果没有归属信息,直接返回 return; } // 1.2 获取所有不重复的总经理/经理ID(确保所有总经理/经理都被计算) var allManagerIds = lifelineList .Where(x => !string.IsNullOrEmpty(x.GeneralManagerId)) .Select(x => x.GeneralManagerId) .Distinct() .ToList(); // 1.3 按总经理/经理ID分组,获取每个总经理/经理管理的门店 var managerStoreDict = lifelineList .Where(x => !string.IsNullOrEmpty(x.GeneralManagerId) && !string.IsNullOrEmpty(x.StoreId)) .GroupBy(x => x.GeneralManagerId) .ToDictionary(g => g.Key, g => g.Select(x => x.StoreId).Distinct().ToList()); // 1.4 门店信息 (lq_mdxx) var storeList = await _db.Queryable().ToListAsync(); var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); // 1.5 门店生命线信息 (lq_md_target) var targetList = await _db.Queryable() .Where(x => x.Month == monthStr) .ToListAsync(); var storeLifelineDict = targetList .Where(x => !string.IsNullOrEmpty(x.StoreId)) .ToDictionary(x => x.StoreId, x => x.StoreLifeline); // 1.6 门店总业绩计算 (开单实付 - 退卡金额) // 开单实付(从lq_kd_kdjlb表统计sfyj字段) var storeBillingList = await _db.Queryable() .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) .Select(x => new { x.Djmd, x.Sfyj }) .ToListAsync(); var storeBillingDict = storeBillingList .Where(x => !string.IsNullOrEmpty(x.Djmd)) .GroupBy(x => x.Djmd) .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj)); // 退卡金额(从lq_hytk_hytk表统计,使用F_ActualRefundAmount,如果没有则使用tkje) var storeRefundList = await _db.Queryable() .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje }) .ToListAsync(); var storeRefundDict = storeRefundList .Where(x => !string.IsNullOrEmpty(x.Md)) .GroupBy(x => x.Md) .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); // 1.7 考勤数据 (lq_attendance_summary) var attendanceList = await _db.Queryable() .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) .ToListAsync(); var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); // 1.8 获取员工信息 (BASE_USER) var userList = await _db.Queryable() .Where(x => allManagerIds.Contains(x.Id)) .Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob }) .ToListAsync(); var userDict = userList.ToDictionary(x => x.Id, x => x); // 2. 按总经理/经理聚合数据 var managerStats = new Dictionary(); foreach (var managerId in allManagerIds) { if (string.IsNullOrEmpty(managerId)) { continue; } // 获取该总经理/经理的信息 var managerLifeline = lifelineList.FirstOrDefault(x => x.GeneralManagerId == managerId); if (managerLifeline == null) { continue; } // 2.1 创建工资统计对象 var salary = new LqBusinessUnitManagerSalaryStatisticsEntity { Id = YitIdHelper.NextId().ToString(), StatisticsMonth = monthStr, EmployeeId = managerId, ManagerType = managerLifeline.ManagerType, Position = managerLifeline.ManagerType == 1 ? "总经理" : "经理", IsTerminated = 0, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsLocked = 0 }; // 2.2 填充员工信息 if (userDict.ContainsKey(managerId)) { var user = userDict[managerId]; salary.EmployeeName = user.RealName ?? ""; salary.EmployeeAccount = user.Account ?? ""; salary.IsTerminated = user.IsOnJob == 0 ? 1 : 0; } // 2.3 考勤数据 var attendance = attendanceDict.ContainsKey(managerId) ? attendanceDict[managerId] : null; salary.WorkingDays = attendance?.WorkDays ?? 0; salary.LeaveDays = attendance?.LeaveDays ?? 0; // 2.4 计算底薪(固定4000元) salary.BaseSalary = 4000m; // 2.5 遍历该总经理/经理管理的每个门店,计算提成 var storePerformanceDetails = new List(); decimal totalCommission = 0m; // 获取该总经理/经理管理的门店列表(如果没有管理的门店,则为空列表) var managedStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List(); foreach (var storeId in managedStores) { if (string.IsNullOrEmpty(storeId)) { continue; } // 获取该门店的提成阶梯设置 var storeLifelineSetting = lifelineList.FirstOrDefault(x => x.StoreId == storeId && x.GeneralManagerId == managerId); if (storeLifelineSetting == null) { continue; } // 获取门店信息 var storeName = storeDict.ContainsKey(storeId) ? storeDict[storeId].Dm ?? "" : ""; // 获取门店生命线(提成门槛) if (!storeLifelineDict.ContainsKey(storeId)) { // 门店生命线未设置,跳过该门店 storePerformanceDetails.Add(new StorePerformanceDetail { StoreId = storeId, StoreName = storeName, StoreLifeline = 0, BillingPerformance = 0, RefundPerformance = 0, StorePerformance = 0, ReachedLifeline = false, CommissionAmount = 0, CalculationDetail = "门店生命线未设置,无法计算提成" }); continue; } var storeLifeline = storeLifelineDict[storeId]; if (storeLifeline <= 0) { // 门店生命线未设置或为0,跳过该门店 storePerformanceDetails.Add(new StorePerformanceDetail { StoreId = storeId, StoreName = storeName, StoreLifeline = 0, BillingPerformance = 0, RefundPerformance = 0, StorePerformance = 0, ReachedLifeline = false, CommissionAmount = 0, CalculationDetail = "门店生命线未设置或为0,无法计算提成" }); continue; } // 获取门店业绩 var billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; var refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0; var storePerformance = billing - refund; // 判断是否达到门店生命线 var reachedLifeline = storePerformance >= storeLifeline; // 计算提成 decimal commissionAmount = 0m; string calculationDetail = ""; if (reachedLifeline) { // 达到门店生命线,使用提成阶梯计算提成(分段累进) var commissionResult = CalculateStoreCommission(storePerformance, storeLifelineSetting); commissionAmount = commissionResult.Amount; calculationDetail = commissionResult.Detail; totalCommission += commissionAmount; } else { calculationDetail = $"业绩{storePerformance:N2}元,未达到门店生命线{storeLifeline:N2}元,无提成"; } // 添加到门店业绩明细 storePerformanceDetails.Add(new StorePerformanceDetail { StoreId = storeId, StoreName = storeName, StoreLifeline = storeLifeline, BillingPerformance = billing, RefundPerformance = refund, StorePerformance = storePerformance, ReachedLifeline = reachedLifeline, Lifeline1 = storeLifelineSetting.Lifeline1, CommissionRate1 = storeLifelineSetting.CommissionRate1, Lifeline2 = storeLifelineSetting.Lifeline2, CommissionRate2 = storeLifelineSetting.CommissionRate2, Lifeline3 = storeLifelineSetting.Lifeline3, CommissionRate3 = storeLifelineSetting.CommissionRate3, CommissionAmount = commissionAmount, CalculationDetail = calculationDetail }); } // 2.6 保存门店业绩明细(JSON格式) salary.StorePerformanceDetail = storePerformanceDetails.ToJson(); // 2.7 提成合计 salary.TotalCommission = totalCommission; // 2.8 计算应发工资 salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission; salary.FinalGrossSalary = salary.CalculatedGrossSalary; // 2.9 初始化其他字段(默认值为0) salary.MonthlyTrainingSubsidy = 0; salary.MonthlyTransportSubsidy = 0; salary.LastMonthTrainingSubsidy = 0; salary.LastMonthTransportSubsidy = 0; salary.TotalSubsidy = 0; salary.MissingCard = 0; salary.LateArrival = 0; salary.LeaveDeduction = 0; salary.SocialInsuranceDeduction = 0; salary.RewardDeduction = 0; salary.AccommodationDeduction = 0; salary.StudyPeriodDeduction = 0; salary.WorkClothesDeduction = 0; salary.TotalDeduction = 0; salary.Bonus = 0; salary.ReturnPhoneDeposit = 0; salary.ReturnAccommodationDeposit = 0; salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; salary.MonthlyPaymentStatus = "未发放"; salary.PaidAmount = 0; salary.PendingAmount = salary.ActualSalary; salary.LastMonthSupplement = 0; salary.MonthlyTotalPayment = 0; managerStats[managerId] = salary; } // 3. 保存数据 if (managerStats.Any()) { // 先删除当月旧数据 (防止重复) await _db.Deleteable() .Where(x => x.StatisticsMonth == monthStr) .ExecuteCommandAsync(); await _db.Insertable(managerStats.Values.ToList()).ExecuteCommandAsync(); } } /// /// 计算门店提成(分段累进) /// /// 门店业绩 /// 提成阶梯设置 /// 提成金额和计算说明 private (decimal Amount, string Detail) CalculateStoreCommission(decimal storePerformance, LqMdGeneralManagerLifelineEntity lifelineSetting) { // 验证提成阶梯1和提成比例1必须设置 if (lifelineSetting.Lifeline1 <= 0 || lifelineSetting.CommissionRate1 <= 0) { return (0m, "提成阶梯1或提成比例1未设置,无法计算提成"); } decimal commissionAmount = 0m; string detail = ""; var lifeline1 = lifelineSetting.Lifeline1; var rate1 = lifelineSetting.CommissionRate1; var lifeline2 = lifelineSetting.Lifeline2 ?? 0; var rate2 = lifelineSetting.CommissionRate2 ?? 0; var lifeline3 = lifelineSetting.Lifeline3 ?? 0; var rate3 = lifelineSetting.CommissionRate3 ?? 0; // 分段累进计算 if (storePerformance <= lifeline1) { // 业绩 ≤ 提成阶梯1 commissionAmount = storePerformance * (rate1 / 100m); detail = $"业绩{storePerformance:N2}元,≤ 提成阶梯1({lifeline1:N2}元),提成 = {storePerformance:N2} × {rate1}% = {commissionAmount:N2}元"; } else if (lifeline2 > 0 && storePerformance <= lifeline2) { // 提成阶梯1 < 业绩 ≤ 提成阶梯2 var part1 = lifeline1 * (rate1 / 100m); var part2 = (storePerformance - lifeline1) * (rate2 / 100m); commissionAmount = part1 + part2; detail = $"业绩{storePerformance:N2}元,> 提成阶梯1({lifeline1:N2}元) 且 ≤ 提成阶梯2({lifeline2:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({storePerformance:N2} - {lifeline1:N2}) × {rate2}% = {part1:N2} + {part2:N2} = {commissionAmount:N2}元"; } else if (lifeline3 > 0 && storePerformance <= lifeline3) { // 提成阶梯2 < 业绩 ≤ 提成阶梯3 var part1 = lifeline1 * (rate1 / 100m); var part2 = (lifeline2 - lifeline1) * (rate2 / 100m); var part3 = (storePerformance - lifeline2) * (rate3 / 100m); commissionAmount = part1 + part2 + part3; detail = $"业绩{storePerformance:N2}元,> 提成阶梯2({lifeline2:N2}元) 且 ≤ 提成阶梯3({lifeline3:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({lifeline2:N2} - {lifeline1:N2}) × {rate2}% + ({storePerformance:N2} - {lifeline2:N2}) × {rate3}% = {part1:N2} + {part2:N2} + {part3:N2} = {commissionAmount:N2}元"; } else if (lifeline3 > 0) { // 业绩 > 提成阶梯3 var part1 = lifeline1 * (rate1 / 100m); var part2 = (lifeline2 - lifeline1) * (rate2 / 100m); var part3 = (lifeline3 - lifeline2) * (rate3 / 100m); var part4 = (storePerformance - lifeline3) * (rate3 / 100m); commissionAmount = part1 + part2 + part3 + part4; detail = $"业绩{storePerformance:N2}元,> 提成阶梯3({lifeline3:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({lifeline2:N2} - {lifeline1:N2}) × {rate2}% + ({lifeline3:N2} - {lifeline2:N2}) × {rate3}% + ({storePerformance:N2} - {lifeline3:N2}) × {rate3}% = {part1:N2} + {part2:N2} + {part3:N2} + {part4:N2} = {commissionAmount:N2}元"; } else if (lifeline2 > 0) { // 提成阶梯3未设置,业绩 > 提成阶梯2,按提成比例2计算超出部分 var part1 = lifeline1 * (rate1 / 100m); var part2 = (storePerformance - lifeline1) * (rate2 / 100m); commissionAmount = part1 + part2; detail = $"业绩{storePerformance:N2}元,> 提成阶梯2({lifeline2:N2}元),提成阶梯3未设置,提成 = {lifeline1:N2} × {rate1}% + ({storePerformance:N2} - {lifeline1:N2}) × {rate2}% = {part1:N2} + {part2:N2} = {commissionAmount:N2}元"; } else { // 只有提成阶梯1,业绩 > 提成阶梯1,按提成比例1计算 commissionAmount = storePerformance * (rate1 / 100m); detail = $"业绩{storePerformance:N2}元,> 提成阶梯1({lifeline1:N2}元),提成阶梯2未设置,提成 = {storePerformance:N2} × {rate1}% = {commissionAmount:N2}元"; } return (commissionAmount, detail); } /// /// 门店业绩明细(用于JSON序列化) /// private class StorePerformanceDetail { public string StoreId { get; set; } public string StoreName { get; set; } public decimal StoreLifeline { get; set; } public decimal BillingPerformance { get; set; } public decimal RefundPerformance { get; set; } public decimal StorePerformance { get; set; } public bool ReachedLifeline { get; set; } public decimal Lifeline1 { get; set; } public decimal CommissionRate1 { get; set; } public decimal? Lifeline2 { get; set; } public decimal? CommissionRate2 { get; set; } public decimal? Lifeline3 { get; set; } public decimal? CommissionRate3 { get; set; } public decimal CommissionAmount { get; set; } public string CalculationDetail { get; set; } } } }