diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs index 652896e..323369b 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs @@ -111,5 +111,45 @@ namespace NCC.Extend.Entitys.Dto.LqSalary /// 更新时间 /// public DateTime UpdateTime { get; set; } + + /// + /// 是否新店 + /// + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + public int NewStoreProtectionStage { get; set; } + + /// + /// 门店类型 + /// + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + public int? StoreCategory { get; set; } + + /// + /// 实际基础业绩 + /// + public decimal ActualBasePerformance { get; set; } + + /// + /// 实际合作业绩 + /// + public decimal ActualCooperationPerformance { get; set; } + + /// + /// 新客业绩提成 + /// + public decimal NewCustomerCommission { get; set; } + + /// + /// 升单业绩提成 + /// + public decimal UpgradeCommission { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationImportInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationImportInput.cs new file mode 100644 index 0000000..46fb219 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationImportInput.cs @@ -0,0 +1,79 @@ +namespace NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation +{ + /// + /// 健康师工资额外计算导入数据 + /// + public class SalaryExtraCalculationImportInput + { + /// + /// ID(可选,用于更新已有记录) + /// + public string Id { get; set; } + + /// + /// 健康师姓名 + /// + public string EmployeeName { get; set; } + + /// + /// 健康师电话 + /// + public string EmployeePhone { get; set; } + + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 月份 + /// + public int Month { get; set; } + + /// + /// 基础奖励业绩 + /// + public decimal BaseRewardPerformance { get; set; } + + /// + /// 合作奖励业绩 + /// + public decimal CooperationRewardPerformance { get; set; } + + /// + /// 新客业绩 + /// + public decimal NewCustomerPerformance { get; set; } + + /// + /// 新客成交率 + /// + public decimal NewCustomerConversionRate { get; set; } + + /// + /// 升单业绩 + /// + public decimal UpgradePerformance { get; set; } + + /// + /// 升单成交率 + /// + public decimal UpgradeConversionRate { get; set; } + + /// + /// 升单人头数 + /// + public decimal UpgradeCustomerCount { get; set; } + + /// + /// 其他业绩加 + /// + public decimal OtherPerformanceAdd { get; set; } + + /// + /// 其他业绩减 + /// + public decimal OtherPerformanceSubtract { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationInput.cs new file mode 100644 index 0000000..aa2cf3b --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationInput.cs @@ -0,0 +1,32 @@ +using NCC.Common.Filter; +using System; + +namespace NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation +{ + /// + /// 健康师工资额外计算查询参数 + /// + public class SalaryExtraCalculationInput : PageInputBase + { + /// + /// 年份 + /// + public int? Year { get; set; } + + /// + /// 月份 + /// + public int? Month { get; set; } + + /// + /// 健康师ID(可选,用于筛选特定健康师) + /// + public string EmployeeId { get; set; } + + /// + /// 健康师姓名/电话(可选,用于模糊搜索) + /// + public string Keyword { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationOutput.cs new file mode 100644 index 0000000..a11ecda --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationOutput.cs @@ -0,0 +1,86 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation +{ + /// + /// 健康师工资额外计算输出 + /// + public class SalaryExtraCalculationOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 健康师ID + /// + public string EmployeeId { get; set; } + + /// + /// 健康师姓名 + /// + public string EmployeeName { get; set; } + + /// + /// 健康师电话 + /// + public string EmployeePhone { get; set; } + + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 月份 + /// + public int Month { get; set; } + + /// + /// 基础奖励业绩 + /// + public decimal BaseRewardPerformance { get; set; } + + /// + /// 合作奖励业绩 + /// + public decimal CooperationRewardPerformance { get; set; } + + /// + /// 新客业绩 + /// + public decimal NewCustomerPerformance { get; set; } + + /// + /// 新客成交率 + /// + public decimal NewCustomerConversionRate { get; set; } + + /// + /// 升单业绩 + /// + public decimal UpgradePerformance { get; set; } + + /// + /// 升单成交率 + /// + public decimal UpgradeConversionRate { get; set; } + + /// + /// 升单人头数 + /// + public decimal UpgradeCustomerCount { get; set; } + + /// + /// 其他业绩加 + /// + public decimal OtherPerformanceAdd { get; set; } + + /// + /// 其他业绩减 + /// + public decimal OtherPerformanceSubtract { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_extra_calculation/LqSalaryExtraCalculationEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_extra_calculation/LqSalaryExtraCalculationEntity.cs new file mode 100644 index 0000000..553bd6b --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_extra_calculation/LqSalaryExtraCalculationEntity.cs @@ -0,0 +1,93 @@ +using System; +using NCC.Common.Const; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_salary_extra_calculation +{ + /// + /// 健康师工资额外计算表 + /// + [SugarTable("lq_salary_extra_calculation")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqSalaryExtraCalculationEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 健康师ID + /// + [SugarColumn(ColumnName = "F_EmployeeId")] + public string EmployeeId { get; set; } + + /// + /// 年份 + /// + [SugarColumn(ColumnName = "F_Year")] + public int Year { get; set; } + + /// + /// 月份 + /// + [SugarColumn(ColumnName = "F_Month")] + public int Month { get; set; } + + /// + /// 基础奖励业绩 + /// + [SugarColumn(ColumnName = "F_BaseRewardPerformance")] + public decimal BaseRewardPerformance { get; set; } + + /// + /// 合作奖励业绩 + /// + [SugarColumn(ColumnName = "F_CooperationRewardPerformance")] + public decimal CooperationRewardPerformance { get; set; } + + /// + /// 新客业绩 + /// + [SugarColumn(ColumnName = "F_NewCustomerPerformance")] + public decimal NewCustomerPerformance { get; set; } + + /// + /// 新客成交率 + /// + [SugarColumn(ColumnName = "F_NewCustomerConversionRate")] + public decimal NewCustomerConversionRate { get; set; } + + /// + /// 升单业绩 + /// + [SugarColumn(ColumnName = "F_UpgradePerformance")] + public decimal UpgradePerformance { get; set; } + + /// + /// 升单成交率 + /// + [SugarColumn(ColumnName = "F_UpgradeConversionRate")] + public decimal UpgradeConversionRate { get; set; } + + /// + /// 升单人头数 + /// + [SugarColumn(ColumnName = "F_UpgradeCustomerCount")] + public decimal UpgradeCustomerCount { get; set; } + + /// + /// 其他业绩加 + /// + [SugarColumn(ColumnName = "F_OtherPerformanceAdd")] + public decimal OtherPerformanceAdd { get; set; } + + /// + /// 其他业绩减 + /// + [SugarColumn(ColumnName = "F_OtherPerformanceSubtract")] + public decimal OtherPerformanceSubtract { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs index 08b4960..b3a4b85 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs @@ -78,6 +78,30 @@ namespace NCC.Extend.Entitys.lq_salary_statistics public decimal CooperationPerformance { get; set; } /// + /// 基础奖励业绩 + /// + [SugarColumn(ColumnName = "F_BaseRewardPerformance")] + public decimal BaseRewardPerformance { get; set; } + + /// + /// 合作奖励业绩 + /// + [SugarColumn(ColumnName = "F_CooperationRewardPerformance")] + public decimal CooperationRewardPerformance { get; set; } + + /// + /// 实际基础业绩 + /// + [SugarColumn(ColumnName = "F_ActualBasePerformance")] + public decimal ActualBasePerformance { get; set; } + + /// + /// 实际合作业绩 + /// + [SugarColumn(ColumnName = "F_ActualCooperationPerformance")] + public decimal ActualCooperationPerformance { get; set; } + + /// /// 奖励业绩 /// [SugarColumn(ColumnName = "F_RewardPerformance")] @@ -120,6 +144,12 @@ namespace NCC.Extend.Entitys.lq_salary_statistics public decimal NewCustomerPoint { get; set; } /// + /// 升单人头数 + /// + [SugarColumn(ColumnName = "F_UpgradeCustomerCount")] + public decimal UpgradeCustomerCount { get; set; } + + /// /// 升单业绩 /// [SugarColumn(ColumnName = "F_UpgradePerformance")] @@ -132,6 +162,30 @@ namespace NCC.Extend.Entitys.lq_salary_statistics public decimal UpgradePoint { get; set; } /// + /// 新客业绩提成金额 + /// + [SugarColumn(ColumnName = "F_NewCustomerPerformanceCommission")] + public decimal NewCustomerPerformanceCommission { get; set; } + + /// + /// 升单业绩提成金额 + /// + [SugarColumn(ColumnName = "F_UpgradePerformanceCommission")] + public decimal UpgradePerformanceCommission { get; set; } + + /// + /// 其他业绩加 + /// + [SugarColumn(ColumnName = "F_OtherPerformanceAdd")] + public decimal OtherPerformanceAdd { get; set; } + + /// + /// 其他业绩减 + /// + [SugarColumn(ColumnName = "F_OtherPerformanceSubtract")] + public decimal OtherPerformanceSubtract { get; set; } + + /// /// 消耗 /// [SugarColumn(ColumnName = "F_Consumption")] @@ -442,5 +496,29 @@ namespace NCC.Extend.Entitys.lq_salary_statistics /// [SugarColumn(ColumnName = "F_UpdateUser")] public string UpdateUser { get; set; } + + /// + /// 是否新店 + /// + [SugarColumn(ColumnName = "F_IsNewStore")] + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] + public int NewStoreProtectionStage { get; set; } + + /// + /// 门店类型 + /// + [SugarColumn(ColumnName = "F_StoreType")] + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + [SugarColumn(ColumnName = "F_StoreCategory")] + public int? StoreCategory { get; set; } } } \ No newline at end of file diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs new file mode 100644 index 0000000..ec7aabc --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs @@ -0,0 +1,521 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using NCC.Common.Enum; +using NCC.Common.Filter; +using NCC.Common.Helper; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation; +using Yitter.IdGenerator; +using NCC.Extend.Entitys.lq_salary_extra_calculation; +using NCC.Extend.Entitys.lq_md_xdbhsj; +using NCC.System.Entitys.Permission; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using NCC.Common.Core.Manager; +using NCC.FriendlyException; + +namespace NCC.Extend +{ + /// + /// 健康师工资额外计算服务 + /// + [ApiDescriptionSettings(Tag = "健康师工资额外计算", Name = "LqSalaryExtraCalculation", Order = 301)] + [Route("api/Extend/[controller]")] + public class LqSalaryExtraCalculationService : IDynamicApiController, ITransient + { + private readonly ISqlSugarClient _db; + private readonly IUserManager _userManager; + + /// + /// 初始化一个类型的新实例 + /// + public LqSalaryExtraCalculationService(ISqlSugarClient db, IUserManager userManager) + { + _db = db; + _userManager = userManager; + } + + /// + /// 获取健康师工资额外计算列表 + /// + /// 查询参数 + /// 健康师工资额外计算分页列表 + [HttpGet] + public async Task> GetList([FromQuery] SalaryExtraCalculationInput input) + { + var query = _db.Queryable() + .LeftJoin((ec, u) => ec.EmployeeId == u.Id); + + // 年份筛选 + if (input.Year.HasValue) + { + query = query.Where((ec, u) => ec.Year == input.Year.Value); + } + + // 月份筛选 + if (input.Month.HasValue) + { + query = query.Where((ec, u) => ec.Month == input.Month.Value); + } + + // 健康师ID筛选 + if (!string.IsNullOrEmpty(input.EmployeeId)) + { + query = query.Where((ec, u) => ec.EmployeeId == input.EmployeeId); + } + + // 关键词搜索(姓名或电话) + if (!string.IsNullOrEmpty(input.Keyword)) + { + query = query.Where((ec, u) => u.RealName.Contains(input.Keyword) || u.MobilePhone.Contains(input.Keyword)); + } + + var list = await query.Select((ec, u) => new SalaryExtraCalculationOutput + { + Id = ec.Id, + EmployeeId = ec.EmployeeId, + EmployeeName = u.RealName, + EmployeePhone = u.MobilePhone, + Year = ec.Year, + Month = ec.Month, + BaseRewardPerformance = ec.BaseRewardPerformance, + CooperationRewardPerformance = ec.CooperationRewardPerformance, + NewCustomerPerformance = ec.NewCustomerPerformance, + NewCustomerConversionRate = ec.NewCustomerConversionRate, + UpgradePerformance = ec.UpgradePerformance, + UpgradeConversionRate = ec.UpgradeConversionRate, + UpgradeCustomerCount = ec.UpgradeCustomerCount, + OtherPerformanceAdd = ec.OtherPerformanceAdd, + OtherPerformanceSubtract = ec.OtherPerformanceSubtract + }) + .ToPagedListAsync(input.currentPage, input.pageSize); + + return PageResult.SqlSugarPageResult(list); + } + + /// + /// 从Excel导入健康师工资额外计算数据 + /// + /// Excel文件 + /// 导入结果 + [HttpPost("ImportFromExcel")] + public async Task ImportFromExcel(IFormFile file) + { + try + { + if (file == null || file.Length == 0) + { + throw NCCException.Oh("请选择要上传的Excel文件"); + } + + // 检查文件格式 + var allowedExtensions = new[] { ".xlsx", ".xls" }; + var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (!allowedExtensions.Contains(fileExtension)) + { + throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件"); + } + + var importData = new List(); + + // 保存临时文件 + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); + try + { + using (var stream = new FileStream(tempFilePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + + // 使用ExcelImportHelper读取Excel文件 + // 参数说明:0表示第一个工作表,0表示第一行是标题行 + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0); + + if (dataTable.Rows.Count == 0) + { + throw NCCException.Oh("Excel文件中没有数据行"); + } + + // Excel字段顺序:id, 健康师姓名, 健康师电话, 年份, 月份, 基础奖励业绩, 合作奖励业绩, 新客业绩, 新客成交率, 升单业绩, 升单成交率, 升单人头数, 其他业绩加, 其他业绩减 + // 从第1行开始读取数据(跳过标题行) + for (int i = 1; i < dataTable.Rows.Count; i++) + { + try + { + var row = dataTable.Rows[i]; + var id = row[0]?.ToString()?.Trim(); + var employeeName = row[1]?.ToString()?.Trim(); + var employeePhone = row[2]?.ToString()?.Trim(); + var yearText = row[3]?.ToString()?.Trim(); + var monthText = row[4]?.ToString()?.Trim(); + var baseRewardPerformanceText = row[5]?.ToString()?.Trim(); + var cooperationRewardPerformanceText = row[6]?.ToString()?.Trim(); + var newCustomerPerformanceText = row[7]?.ToString()?.Trim(); + var newCustomerConversionRateText = row[8]?.ToString()?.Trim(); + var upgradePerformanceText = row[9]?.ToString()?.Trim(); + var upgradeConversionRateText = row[10]?.ToString()?.Trim(); + var upgradeCustomerCountText = row[11]?.ToString()?.Trim(); + var otherPerformanceAddText = row[12]?.ToString()?.Trim(); + var otherPerformanceSubtractText = row[13]?.ToString()?.Trim(); + + // 跳过空行 + if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone)) + { + continue; + } + + // 验证必填字段 + if (string.IsNullOrEmpty(employeeName)) + { + throw new Exception($"第{i + 1}行:健康师姓名不能为空"); + } + if (string.IsNullOrEmpty(employeePhone)) + { + throw new Exception($"第{i + 1}行:健康师电话不能为空"); + } + + // 解析年份 + if (!int.TryParse(yearText, out int year)) + { + throw new Exception($"第{i + 1}行:年份格式错误"); + } + + // 解析月份 + if (!int.TryParse(monthText, out int month)) + { + throw new Exception($"第{i + 1}行:月份格式错误"); + } + + // 验证月份范围 + if (month < 1 || month > 12) + { + throw new Exception($"第{i + 1}行:月份必须在1-12之间"); + } + + // 解析数值字段(允许为空,默认为0) + decimal.TryParse(baseRewardPerformanceText, out decimal baseRewardPerformance); + decimal.TryParse(cooperationRewardPerformanceText, out decimal cooperationRewardPerformance); + decimal.TryParse(newCustomerPerformanceText, out decimal newCustomerPerformance); + decimal.TryParse(newCustomerConversionRateText, out decimal newCustomerConversionRate); + decimal.TryParse(upgradePerformanceText, out decimal upgradePerformance); + decimal.TryParse(upgradeConversionRateText, out decimal upgradeConversionRate); + decimal.TryParse(upgradeCustomerCountText, out decimal upgradeCustomerCount); + decimal.TryParse(otherPerformanceAddText, out decimal otherPerformanceAdd); + decimal.TryParse(otherPerformanceSubtractText, out decimal otherPerformanceSubtract); + + var item = new SalaryExtraCalculationImportInput + { + Id = id, + EmployeeName = employeeName, + EmployeePhone = employeePhone, + Year = year, + Month = month, + BaseRewardPerformance = baseRewardPerformance, + CooperationRewardPerformance = cooperationRewardPerformance, + NewCustomerPerformance = newCustomerPerformance, + NewCustomerConversionRate = newCustomerConversionRate, + UpgradePerformance = upgradePerformance, + UpgradeConversionRate = upgradeConversionRate, + UpgradeCustomerCount = upgradeCustomerCount, + OtherPerformanceAdd = otherPerformanceAdd, + OtherPerformanceSubtract = otherPerformanceSubtract + }; + + importData.Add(item); + } + catch (Exception ex) + { + throw new Exception($"第{i + 1}行数据解析失败: {ex.Message}"); + } + } + } + finally + { + // 清理临时文件 + if (File.Exists(tempFilePath)) + { + File.Delete(tempFilePath); + } + } + + if (!importData.Any()) + { + throw NCCException.Oh("Excel文件中没有有效的数据行"); + } + + // 处理导入数据 + return await ProcessImportData(importData); + } + catch (Exception ex) + { + throw NCCException.Oh($"上传Excel文件导入健康师工资额外计算数据失败: {ex.Message}"); + } + } + + #region 处理导入数据 + /// + /// 处理导入数据 + /// + /// 导入数据列表 + /// 导入结果 + private async Task ProcessImportData(List importData) + { + var successCount = 0; + var failCount = 0; + var errorMessages = new List(); + var entitiesToInsert = new List(); + var entitiesToUpdate = new List(); + + foreach (var item in importData) + { + try + { + // 1. 根据健康师姓名和电话查找用户ID + var user = await _db.Queryable() + .Where(u => u.RealName == item.EmployeeName && u.MobilePhone == item.EmployeePhone) + .FirstAsync(); + + if (user == null) + { + errorMessages.Add($"健康师 {item.EmployeeName}({item.EmployeePhone}) 不存在"); + failCount++; + continue; + } + + // 2. 检查是否已存在相同记录(根据健康师ID、年份、月份) + LqSalaryExtraCalculationEntity existingRecord = null; + + // 如果提供了ID,先尝试根据ID查找 + if (!string.IsNullOrEmpty(item.Id)) + { + existingRecord = await _db.Queryable() + .Where(x => x.Id == item.Id) + .FirstAsync(); + } + + // 如果没有找到,则根据健康师ID、年份、月份查找 + if (existingRecord == null) + { + existingRecord = await _db.Queryable() + .Where(x => x.EmployeeId == user.Id && x.Year == item.Year && x.Month == item.Month) + .FirstAsync(); + } + + if (existingRecord != null) + { + // 更新现有记录 + existingRecord.BaseRewardPerformance = item.BaseRewardPerformance; + existingRecord.CooperationRewardPerformance = item.CooperationRewardPerformance; + existingRecord.NewCustomerPerformance = item.NewCustomerPerformance; + existingRecord.NewCustomerConversionRate = item.NewCustomerConversionRate; + existingRecord.UpgradePerformance = item.UpgradePerformance; + existingRecord.UpgradeConversionRate = item.UpgradeConversionRate; + existingRecord.UpgradeCustomerCount = item.UpgradeCustomerCount; + existingRecord.OtherPerformanceAdd = item.OtherPerformanceAdd; + existingRecord.OtherPerformanceSubtract = item.OtherPerformanceSubtract; + entitiesToUpdate.Add(existingRecord); + } + else + { + // 创建新记录 + var entity = new LqSalaryExtraCalculationEntity + { + Id = YitIdHelper.NextId().ToString(), + EmployeeId = user.Id, + Year = item.Year, + Month = item.Month, + BaseRewardPerformance = item.BaseRewardPerformance, + CooperationRewardPerformance = item.CooperationRewardPerformance, + NewCustomerPerformance = item.NewCustomerPerformance, + NewCustomerConversionRate = item.NewCustomerConversionRate, + UpgradePerformance = item.UpgradePerformance, + UpgradeConversionRate = item.UpgradeConversionRate, + UpgradeCustomerCount = item.UpgradeCustomerCount, + OtherPerformanceAdd = item.OtherPerformanceAdd, + OtherPerformanceSubtract = item.OtherPerformanceSubtract + }; + entitiesToInsert.Add(entity); + } + + successCount++; + } + catch (Exception ex) + { + errorMessages.Add($"健康师 {item.EmployeeName}({item.EmployeePhone}) 处理失败: {ex.Message}"); + failCount++; + } + } + + // 批量插入新记录 + if (entitiesToInsert.Any()) + { + await _db.Insertable(entitiesToInsert).ExecuteCommandAsync(); + } + + // 批量更新现有记录 + if (entitiesToUpdate.Any()) + { + await _db.Updateable(entitiesToUpdate).ExecuteCommandAsync(); + } + + var result = new + { + successCount, + failCount, + totalCount = importData.Count, + errorMessages = errorMessages.Take(50).ToList() // 最多返回50条错误信息 + }; + + if (failCount > 0) + { + throw NCCException.Oh($"导入完成,成功:{successCount}条,失败:{failCount}条。{string.Join("; ", errorMessages.Take(10))}"); + } + + return result; + } + #endregion + + /// + /// 为新店健康师生成模拟数据 + /// + /// 年份 + /// 月份 + /// 生成结果 + [HttpPost("GenerateMockData")] + public async Task GenerateMockData(int year, int month) + { + try + { + // 验证月份范围 + if (month < 1 || month > 12) + { + throw NCCException.Oh("月份必须在1-12之间"); + } + + var startDate = new DateTime(year, month, 1); + var endDate = startDate.AddMonths(1).AddDays(-1); + + // 1. 查询新店保护信息 + var newStoreProtectionList = await _db.Queryable() + .Where(x => x.Sfqy == 1) + .ToListAsync(); + + // 筛选出在统计月份处于保护期内的门店 + var newStoreIds = newStoreProtectionList + .Where(x => x.Bhkssj <= endDate && x.Bhjssj >= startDate) + .Select(x => x.Mdid) + .Distinct() + .ToList(); + + if (!newStoreIds.Any()) + { + throw NCCException.Oh("当前月份没有新店"); + } + + // 2. 查询新店下的健康师 + // 健康师的岗位字段是F_GW,值为"健康师" + var healthCoaches = await _db.Queryable() + .Where(u => newStoreIds.Contains(u.Mdid) && u.Gw == "健康师" && u.EnabledMark == 1 && (u.DeleteMark == null || u.DeleteMark == 0)) + .Select(u => new { u.Id, u.RealName, u.Mdid }) + .ToListAsync(); + + if (!healthCoaches.Any()) + { + throw NCCException.Oh("新店下没有健康师"); + } + + // 3. 检查是否已存在数据 + var existingEmployeeIds = await _db.Queryable() + .Where(x => x.Year == year && x.Month == month) + .Select(x => x.EmployeeId) + .ToListAsync(); + + var employeesToProcess = healthCoaches + .Where(hc => !existingEmployeeIds.Contains(hc.Id)) + .ToList(); + + if (!employeesToProcess.Any()) + { + throw NCCException.Oh("所有新店健康师已存在该月份的数据"); + } + + // 4. 生成模拟数据 + var random = new Random(); + var entitiesToInsert = new List(); + var successCount = 0; + + foreach (var employee in employeesToProcess) + { + try + { + // 生成金额 + // 基础业绩奖励和合作业绩奖励:0-20000之间 + var baseRewardPerformance = (decimal)(random.NextDouble() * 20000); + var cooperationRewardPerformance = (decimal)(random.NextDouble() * 20000); + // 其他金额字段:10000-30000之间 + var newCustomerPerformance = (decimal)(random.NextDouble() * 20000 + 10000); + var upgradePerformance = (decimal)(random.NextDouble() * 20000 + 10000); + var otherPerformanceAdd = (decimal)(random.NextDouble() * 20000 + 10000); + var otherPerformanceSubtract = (decimal)(random.NextDouble() * 20000 + 10000); + + // 生成转化率(0-60%之间,转换为0-0.6) + var newCustomerConversionRate = (decimal)(random.NextDouble() * 0.6); + var upgradeConversionRate = (decimal)(random.NextDouble() * 0.6); + + // 生成升单人头数(0-10之间) + var upgradeCustomerCount = (decimal)random.Next(0, 11); + + var entity = new LqSalaryExtraCalculationEntity + { + Id = YitIdHelper.NextId().ToString(), + EmployeeId = employee.Id, + Year = year, + Month = month, + BaseRewardPerformance = Math.Round(baseRewardPerformance, 2), + CooperationRewardPerformance = Math.Round(cooperationRewardPerformance, 2), + NewCustomerPerformance = Math.Round(newCustomerPerformance, 2), + NewCustomerConversionRate = Math.Round(newCustomerConversionRate, 4), + UpgradePerformance = Math.Round(upgradePerformance, 2), + UpgradeConversionRate = Math.Round(upgradeConversionRate, 4), + UpgradeCustomerCount = upgradeCustomerCount, + OtherPerformanceAdd = Math.Round(otherPerformanceAdd, 2), + OtherPerformanceSubtract = Math.Round(otherPerformanceSubtract, 2) + }; + + entitiesToInsert.Add(entity); + successCount++; + } + catch + { + // 记录错误但继续处理其他健康师 + continue; + } + } + + // 5. 批量插入数据 + if (entitiesToInsert.Any()) + { + await _db.Insertable(entitiesToInsert).ExecuteCommandAsync(); + } + + return new + { + successCount, + totalCount = employeesToProcess.Count, + message = $"成功为新店健康师生成 {successCount} 条模拟数据" + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"生成模拟数据失败: {ex.Message}"); + } + } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs index 3c3a52b..93f0df0 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs @@ -16,6 +16,11 @@ using NCC.Extend.Entitys.lq_person_times_record; using NCC.Extend.Entitys.lq_salary_statistics; using NCC.Extend.Entitys.lq_xh_jksyj; using NCC.Extend.Entitys.lq_ycsd_jsj; +using NCC.Extend.Entitys.lq_mdxx; +using NCC.Extend.Entitys.lq_hytk_hytk; +using NCC.Extend.Entitys.lq_md_xdbhsj; +using NCC.Extend.Entitys.lq_salary_extra_calculation; +using NCC.System.Entitys.Permission; using SqlSugar; using System; using System.Collections.Generic; @@ -97,7 +102,15 @@ namespace NCC.Extend TotalDeduction = x.TotalDeduction, ActualSalary = x.ActualSalary, IsLocked = x.IsLocked, - UpdateTime = x.UpdateTime + UpdateTime = x.UpdateTime, + IsNewStore = x.IsNewStore, + NewStoreProtectionStage = x.NewStoreProtectionStage, + StoreType = x.StoreType, + StoreCategory = x.StoreCategory, + ActualBasePerformance = x.ActualBasePerformance, + ActualCooperationPerformance = x.ActualCooperationPerformance, + NewCustomerCommission = x.NewCustomerPerformanceCommission, + UpgradeCommission = x.UpgradePerformanceCommission }) .ToPagedListAsync(input.currentPage, input.pageSize); @@ -127,17 +140,18 @@ namespace NCC.Extend // 1.1.1 获取关联的开单记录(用于获取 sfskdd) var billingIds = performanceList.Select(x => x.Glkdbh).Distinct().ToList(); var billingDict = await _db.Queryable() - .Where(x => billingIds.Contains(x.Id)) + .Where(x => billingIds.Contains(x.Id) && x.Id != null) .ToDictionaryAsync(x => x.Id, x => x.Sfskdd); // 1.1.2 组合数据 var performanceData = performanceList.Select(p => new { - p.Jks, + Jks = p.Jkszh, // 使用 Jkszh (账号/ID) 而不是 Jks (姓名) p.Jksxm, p.StoreId, p.Jksyj, p.ItemCategory, + p.PerformanceType, // 新增业绩类型字段 Sfskdd = billingDict.ContainsKey(p.Glkdbh) ? billingDict[p.Glkdbh] : null }).ToList(); @@ -161,7 +175,7 @@ namespace NCC.Extend var teamList = await _db.Queryable() .Where(x => teamIds.Contains(x.Id)) .ToListAsync(); - var teamDict = teamList.ToDictionary(x => x.Id, x => x.Jsj); + var teamDict = teamList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x.Jsj); // 1.4.2 组合数据 var teamMembers = teamUserList.Select(user => new @@ -185,6 +199,55 @@ namespace NCC.Extend .Where(x => x.Month == monthStr) .ToListAsync(); + // 1.6.1 门店总业绩计算 (开单实付 - 退款金额) + // 开单实付 + 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)); + + // 退款金额 + var storeRefundList = await _db.Queryable() + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) + .Select(x => new { x.Mdbh, x.Tkje }) + .ToListAsync(); + var storeRefundDict = storeRefundList + .Where(x => !string.IsNullOrEmpty(x.Mdbh)) + .GroupBy(x => x.Mdbh) + .ToDictionary(g => g.Key, g => g.Sum(x => x.Tkje ?? 0)); + + // 1.7 门店信息 (lq_mdxx) + var storeList = await _db.Queryable().ToListAsync(); + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); + + // 1.7.1 门店新店保护信息 (lq_md_xdbhsj) + var newStoreProtectionList = await _db.Queryable() + .Where(x => x.Sfqy == 1) + .ToListAsync(); + + // 构造新店保护查找字典: StoreId -> List + // 因为一个门店可能有多个阶段配置,虽然通常是一个时间段,但为了严谨取符合当前月份的配置 + // 逻辑: 统计月份 (startDate ~ endDate) 是否在 bhkssj ~ bhjssj 范围内 + // 只要统计月份与保护期有交集,就算保护期。或者严格一点,统计月份的第一天在保护期内。 + // 这里取: 统计月份的第一天 (startDate) 在保护期内 + var newStoreProtectionDict = newStoreProtectionList + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate) + .GroupBy(x => x.Mdid) + .ToDictionary(g => g.Key, g => g.First()); // 取第一个匹配的配置 + + // 1.8 健康师工资额外计算数据 (lq_salary_extra_calculation) + var extraCalculationList = await _db.Queryable() + .Where(x => x.Year == year && x.Month == month) + .ToListAsync(); + + var extraCalculationDict = extraCalculationList + .Where(x => !string.IsNullOrEmpty(x.EmployeeId)) + .ToDictionary(x => x.EmployeeId, x => x); + // 2. 聚合每个健康师的数据对象 var employeeStats = new Dictionary(); @@ -197,6 +260,19 @@ namespace NCC.Extend .Distinct() .ToList(); + // 1.8 批量获取员工信息 (BASE_USER + BASE_POSITION) + // 使用 allEmployeeIds 作为驱动,查询 BASE_USER + var userList = await _db.Queryable() + .Where(x => allEmployeeIds.Contains(x.Id)) + .Select(x => new { x.Id, x.RealName, x.PositionId, x.Mdid }) + .ToListAsync(); + + var userDict = userList.ToDictionary(x => x.Id, x => x); + + var positionIds = userList.Select(x => x.PositionId).Distinct().ToList(); + var positionList = await _db.Queryable().Where(x => positionIds.Contains(x.Id)).ToListAsync(); + var positionLookup = positionList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x.FullName); + foreach (var empId in allEmployeeIds) { var salary = new LqSalaryStatisticsEntity @@ -209,38 +285,121 @@ namespace NCC.Extend IsLocked = 0 }; - // 填充基础信息 (姓名、门店) - var perfRecord = performanceData.FirstOrDefault(x => x.Jks == empId); - var consRecord = consumptionList.FirstOrDefault(x => x.Jks == empId); + // 填充基础信息 (优先从 BASE_USER 获取) + string userMdid = null; + if (userDict.ContainsKey(empId)) + { + var user = userDict[empId]; + salary.EmployeeName = user.RealName; + userMdid = user.Mdid; + + // 岗位 + if (user.PositionId != null && positionLookup.ContainsKey(user.PositionId)) + { + salary.Position = positionLookup[user.PositionId]; + } + } - if (perfRecord != null) + // 如果 BASE_USER 没名字,尝试从业务表获取 + if (string.IsNullOrEmpty(salary.EmployeeName)) { - salary.EmployeeName = perfRecord.Jksxm; - salary.StoreId = perfRecord.StoreId; + var perfRecord = performanceData.FirstOrDefault(x => x.Jks == empId); + var consRecord = consumptionList.FirstOrDefault(x => x.Jks == empId); + if (perfRecord != null) salary.EmployeeName = perfRecord.Jksxm; + else if (consRecord != null) salary.EmployeeName = consRecord.Jksxm; } - else if (consRecord != null) + + // 填充门店ID (从业务数据获取,因为 User 表的 OrganizeId 未必是门店) + var perfStore = performanceData.FirstOrDefault(x => x.Jks == empId && !string.IsNullOrEmpty(x.StoreId)); + var consStore = consumptionList.FirstOrDefault(x => x.Jks == empId && !string.IsNullOrEmpty(x.StoreId)); + + if (perfStore != null) salary.StoreId = perfStore.StoreId; + else if (consStore != null) salary.StoreId = consStore.StoreId; + + // 如果业务数据没门店,尝试使用 User.Mdid + if (string.IsNullOrEmpty(salary.StoreId) && !string.IsNullOrEmpty(userMdid)) { - salary.EmployeeName = consRecord.Jksxm; - salary.StoreId = consRecord.StoreId; + if (storeDict.ContainsKey(userMdid)) + { + salary.StoreId = userMdid; + } } - // 填充门店名称 - if (!string.IsNullOrEmpty(salary.StoreId)) + // 填充门店名称及分类信息 + if (!string.IsNullOrEmpty(salary.StoreId) && storeDict.ContainsKey(salary.StoreId)) + { + var store = storeDict[salary.StoreId]; + salary.StoreName = store.Dm; + salary.StoreType = store.StoreType; + salary.StoreCategory = store.StoreCategory; + } + + // 填充新店保护信息 + if (!string.IsNullOrEmpty(salary.StoreId) && newStoreProtectionDict.ContainsKey(salary.StoreId)) + { + var protection = newStoreProtectionDict[salary.StoreId]; + salary.IsNewStore = "是"; + salary.NewStoreProtectionStage = protection.Stage; + } + else { - // 这里简单处理,实际可能需要缓存门店列表 - // salary.StoreName = ... + salary.IsNewStore = "否"; + salary.NewStoreProtectionStage = 0; } // 2.1 计算个人业绩 var myPerf = performanceData.Where(x => x.Jks == empId).ToList(); - salary.BasePerformance = myPerf.Where(x => x.ItemCategory == "基础业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); - salary.CooperationPerformance = myPerf.Where(x => x.ItemCategory == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + salary.BasePerformance = myPerf.Where(x => (x.PerformanceType ?? "").Trim() == "基础业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + salary.CooperationPerformance = myPerf.Where(x => (x.PerformanceType ?? "").Trim() == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0")); // 新客与升单业绩 salary.NewCustomerPerformance = myPerf.Where(x => x.Sfskdd == "是").Sum(x => decimal.Parse(x.Jksyj ?? "0")); salary.UpgradePerformance = myPerf.Where(x => x.Sfskdd == "否").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + // 2.1.1 填充额外计算数据 + if (extraCalculationDict.ContainsKey(empId)) + { + var extraData = extraCalculationDict[empId]; + salary.BaseRewardPerformance = extraData.BaseRewardPerformance; + salary.CooperationRewardPerformance = extraData.CooperationRewardPerformance; + salary.OtherPerformanceAdd = extraData.OtherPerformanceAdd; + salary.OtherPerformanceSubtract = extraData.OtherPerformanceSubtract; + salary.UpgradeCustomerCount = extraData.UpgradeCustomerCount; + salary.NewCustomerConversionRate = extraData.NewCustomerConversionRate; + } + + // 2.1.2 计算实际基础业绩和实际合作业绩 + // 定义新店相关变量,供后续多处使用 + bool isNewStore = salary.IsNewStore == "是"; + int newStoreStage = salary.NewStoreProtectionStage; + + // 实际基础业绩 = 基础业绩 - 基础奖励业绩 + 其他业绩加 - 其他业绩减 + decimal actualBasePerformance = salary.BasePerformance + - salary.BaseRewardPerformance + + salary.OtherPerformanceAdd + - salary.OtherPerformanceSubtract; + + // 新店额外调整:根据阶段扣除新客业绩或升单业绩 + if (isNewStore) + { + if (newStoreStage == 1) + { + // 第一阶段:扣除新客业绩 + actualBasePerformance -= salary.NewCustomerPerformance; + } + else if (newStoreStage == 2) + { + // 第二阶段:扣除升单业绩 + actualBasePerformance -= salary.UpgradePerformance; + } + } + + salary.ActualBasePerformance = actualBasePerformance; + + // 实际合作业绩 = 合作业绩 - 合作奖励业绩 + salary.ActualCooperationPerformance = salary.CooperationPerformance - salary.CooperationRewardPerformance; + // 2.2 计算消耗和项目数 var myCons = consumptionList.Where(x => x.Jks == empId).ToList(); salary.Consumption = myCons.Sum(x => x.Jksyj ?? 0); @@ -258,10 +417,33 @@ namespace NCC.Extend // 2.5 战队信息 (初始) var myTeam = teamMembers.FirstOrDefault(x => x.UserId == empId); + // 初始判断岗位:如果是战队队长(IsLeader=1)则是顾问,否则是健康师 + // 注意:这里先根据战队设置判断,后续考勤不足21天会降级 + if (myTeam != null && myTeam.IsLeader == 1) + { + salary.Position = "顾问"; + } + else if (string.IsNullOrEmpty(salary.Position)) // 如果BASE_USER没岗位,且不是队长,默认为健康师 + { + salary.Position = "健康师"; + } + if (myTeam != null) { salary.GoldTriangleId = myTeam.TeamId; - salary.GoldTriangleTeam = myTeam.TeamName ?? ""; + salary.GoldTriangleTeam = myTeam.TeamName ?? "个人"; + } + else + { + salary.GoldTriangleTeam = "个人"; + } + + // 2.6 门店总业绩 + if (!string.IsNullOrEmpty(salary.StoreId)) + { + decimal billing = storeBillingDict.ContainsKey(salary.StoreId) ? storeBillingDict[salary.StoreId] : 0; + decimal refund = storeRefundDict.ContainsKey(salary.StoreId) ? storeRefundDict[salary.StoreId] : 0; + salary.StoreTotalPerformance = billing - refund; } employeeStats[empId] = salary; @@ -293,11 +475,12 @@ namespace NCC.Extend } } - // 对于无效成员,移除战队标识,视为单人 + // 对于无效成员,移除战队标识,视为单人,并重置岗位为健康师 foreach (var member in invalidMembers) { member.GoldTriangleId = null; - member.GoldTriangleTeam = null; + member.GoldTriangleTeam = "个人"; + member.Position = "健康师"; // 降级为健康师 } // 计算有效战队的总业绩 @@ -313,8 +496,12 @@ namespace NCC.Extend // 4. 计算薪资 (底薪 & 提成) foreach (var salary in employeeStats.Values) { + // 定义新店相关变量,供底薪和提成计算使用 + bool isNewStore = salary.IsNewStore == "是"; + int newStoreStage = salary.NewStoreProtectionStage; + // 4.1 底薪计算 - salary.HealthCoachBaseSalary = CalculateBaseSalary(salary.Consumption, salary.ProjectCount); + salary.HealthCoachBaseSalary = CalculateBaseSalary(salary.Consumption, salary.ProjectCount, isNewStore); // 4.2 提成计算 // 单人业绩 <= 6000 无提成 @@ -335,29 +522,60 @@ namespace NCC.Extend // 是战队成员 // 获取战队人数 (注意:这里应该是有效战队人数) var teamMemberCount = employeeStats.Values.Count(x => x.GoldTriangleId == salary.GoldTriangleId); + // 注意:提成点按原始基础业绩计算,不是实际基础业绩 commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance); } else { // 单人 (或被剔除出战队) + // 注意:提成点按原始总业绩计算 commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance); } salary.CommissionPoint = commissionPoint; - // 计算基础/合作提成 - salary.BasePerformanceCommission = salary.BasePerformance * 0.95m * commissionPoint; - salary.CooperationPerformanceCommission = salary.CooperationPerformance * 0.95m * 0.65m * commissionPoint; + // 计算基础/合作提成(使用实际业绩) + salary.BasePerformanceCommission = salary.ActualBasePerformance * 0.95m * commissionPoint; + salary.CooperationPerformanceCommission = salary.ActualCooperationPerformance * 0.95m * 0.65m * commissionPoint; + + // 计算新客转化率提成和升单人头提成(根据新店阶段) + // isNewStore 和 newStoreStage 已在上面定义 + + if (isNewStore) + { + if (newStoreStage == 1) + { + // 第一阶段:计算新客转化率提成 + salary.NewCustomerPerformanceCommission = CalculateNewCustomerConversionCommission( + salary.NewCustomerPerformance, + salary.NewCustomerConversionRate); + } + else if (newStoreStage == 2) + { + // 第二阶段:计算升单人头提成 + salary.UpgradePerformanceCommission = CalculateUpgradeCustomerCommission( + salary.UpgradePerformance, + salary.UpgradeCustomerCount); + } + // 第三阶段:不计算新客/升单提成 + } // 计算顾问提成 // 检查是否是顾问 - var isConsultant = teamMembers.Any(x => x.UserId == salary.EmployeeId && x.IsLeader == 1); - if (isConsultant && !string.IsNullOrEmpty(salary.GoldTriangleId)) + // 注意:这里需要重新判断是否是顾问,因为可能被降级了 + if (salary.Position == "顾问" && !string.IsNullOrEmpty(salary.GoldTriangleId)) { - salary.ConsultantCommission = CalculateConsultantCommission(salary.TeamPerformance, employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList()); + salary.ConsultantCommission = CalculateConsultantCommission( + salary.TeamPerformance, + employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList(), + isNewStore); } - salary.TotalCommission = salary.BasePerformanceCommission + salary.CooperationPerformanceCommission + salary.ConsultantCommission; + salary.TotalCommission = salary.BasePerformanceCommission + + salary.CooperationPerformanceCommission + + salary.ConsultantCommission + + salary.NewCustomerPerformanceCommission + + salary.UpgradePerformanceCommission; } // 计算占比 @@ -386,7 +604,7 @@ namespace NCC.Extend /// /// 计算底薪 /// - private decimal CalculateBaseSalary(decimal consumption, decimal projectCount) + private decimal CalculateBaseSalary(decimal consumption, decimal projectCount, bool isNewStore) { // 0星:<1w 或 <96个 -> 1800 // 1星:>=1w 且 >=96个 -> 2000 @@ -394,6 +612,7 @@ namespace NCC.Extend // 3星:>=4w 且 >=156个 -> 2400 // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算 + // 新店规则:新店底薪最低为1星(2000元),不满足1星按1星算 int starCons = 0; if (consumption >= 40000) starCons = 3; @@ -413,13 +632,21 @@ namespace NCC.Extend finalStar = 1; } - switch (finalStar) + decimal baseSalary = finalStar switch { - case 3: return 2400; - case 2: return 2200; - case 1: return 2000; - default: return 1800; + 3 => 2400, + 2 => 2200, + 1 => 2000, + _ => 1800 + }; + + // 新店保底1星(2000元) + if (isNewStore && baseSalary < 2000) + { + baseSalary = 2000; } + + return baseSalary; } /// @@ -455,32 +682,72 @@ namespace NCC.Extend /// /// 计算顾问提成 /// - private decimal CalculateConsultantCommission(decimal teamPerformance, List teamMembers) + private decimal CalculateConsultantCommission(decimal teamPerformance, List teamMembers, bool isNewStore) { // 顾问提成规则: // 高级顾问:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% // 普通顾问:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% - // 这里的“组员业绩达到X%以上”理解为:除顾问外的成员业绩占比?或者每个成员都达标? - // 通常理解为:团队中是否有成员业绩贡献较高,或者团队整体结构健康。 - // 假设“组员业绩达到X%”是指:团队中至少有一名成员(非顾问本人?)或者所有成员平均? - // 鉴于规则模糊,这里先简化实现:暂只考核总业绩和消耗。 - // 消耗是团队总消耗吗?假设是。 + // 注意: + // 1. "组员业绩"指除顾问外的其他成员业绩总和 + // 2. 只统计有效战队成员(考勤≥21天,未被剔除的成员) + // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X% + // 4. 新店顾问不考核消耗 var teamConsumption = teamMembers.Sum(x => x.Consumption); - // 高级顾问 - if (teamPerformance >= 60000 && teamConsumption >= 60000) + // 计算组员(非顾问)业绩总和 + // teamMembers 已经是过滤后的有效成员列表(GoldTriangleId 相同且未被剔除) + var memberPerformance = teamMembers.Where(x => x.Position != "顾问").Sum(x => x.TotalPerformance); + + // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店 或 消耗≥6万) + if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m) { - return teamPerformance * 0.008m; + if (isNewStore || teamConsumption >= 60000) + { + return teamPerformance * 0.008m; + } } - // 普通顾问 - if (teamPerformance >= 40000 && teamConsumption >= 40000) + + // 普通顾问:业绩≥4万 且 组员业绩≥30% 且 (新店 或 消耗≥4万) + if (teamPerformance >= 40000 && memberPerformance >= teamPerformance * 0.3m) { - return teamPerformance * 0.003m; + if (isNewStore || teamConsumption >= 40000) + { + return teamPerformance * 0.003m; + } } return 0; } + + /// + /// 计算新客转化率提成 + /// + private decimal CalculateNewCustomerConversionCommission(decimal newCustomerPerformance, decimal conversionRate) + { + decimal commissionRate = 0; + + if (conversionRate >= 0.5m) commissionRate = 0.20m; + else if (conversionRate >= 0.45m) commissionRate = 0.15m; + else if (conversionRate >= 0.35m) commissionRate = 0.10m; + else if (conversionRate >= 0) commissionRate = 0.06m; + + return newCustomerPerformance * commissionRate; + } + + /// + /// 计算升单人头提成 + /// + private decimal CalculateUpgradeCustomerCommission(decimal upgradePerformance, decimal upgradeCustomerCount) + { + decimal commissionRate = 0; + + if (upgradeCustomerCount >= 10) commissionRate = 0.20m; + else if (upgradeCustomerCount >= 4) commissionRate = 0.10m; + // 0-4个: 0% + + return upgradePerformance * commissionRate; + } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs index 1cc8788..0291cb9 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs @@ -4997,5 +4997,7 @@ namespace NCC.Extend.LqStatistics } #endregion + + } } diff --git a/sql/创建健康师工资额外计算表.sql b/sql/创建健康师工资额外计算表.sql new file mode 100644 index 0000000..d7c09db --- /dev/null +++ b/sql/创建健康师工资额外计算表.sql @@ -0,0 +1,17 @@ +-- 创建健康师工资额外计算表 +CREATE TABLE IF NOT EXISTS `lq_salary_extra_calculation` ( + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID', + `F_EmployeeId` VARCHAR(50) NULL COMMENT '健康师ID', + `F_Year` INT NULL COMMENT '年份', + `F_Month` INT NULL COMMENT '月份', + `F_BaseRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '基础奖励业绩', + `F_CooperationRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作奖励业绩', + `F_NewCustomerPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '新客业绩', + `F_NewCustomerConversionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '新客成交率', + `F_UpgradePerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '升单业绩', + `F_UpgradeConversionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '升单成交率', + PRIMARY KEY (`F_Id`), + KEY `idx_employee_year_month` (`F_EmployeeId`, `F_Year`, `F_Month`), + KEY `idx_year_month` (`F_Year`, `F_Month`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康师工资额外计算表'; + diff --git a/sql/添加健康师工资表其他业绩字段.sql b/sql/添加健康师工资表其他业绩字段.sql new file mode 100644 index 0000000..149f148 --- /dev/null +++ b/sql/添加健康师工资表其他业绩字段.sql @@ -0,0 +1,10 @@ +-- 在健康师工资表中添加其他业绩加和其他业绩减字段 + +-- 添加其他业绩加字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_OtherPerformanceAdd` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩加' AFTER `F_UpgradePerformanceCommission`; + +-- 添加其他业绩减字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_OtherPerformanceSubtract` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩减' AFTER `F_OtherPerformanceAdd`; + diff --git a/sql/添加健康师工资表升单人头数和提成金额字段.sql b/sql/添加健康师工资表升单人头数和提成金额字段.sql new file mode 100644 index 0000000..40f73d8 --- /dev/null +++ b/sql/添加健康师工资表升单人头数和提成金额字段.sql @@ -0,0 +1,14 @@ +-- 在健康师工资表中添加升单人头数、新客业绩提成金额、升单业绩提成金额字段 + +-- 添加升单人头数字段(放在F_NewCustomerPoint字段后面) +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_UpgradeCustomerCount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '升单人头数' AFTER `F_NewCustomerPoint`; + +-- 添加新客业绩提成金额字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_NewCustomerPerformanceCommission` DECIMAL(18,2) DEFAULT 0.00 COMMENT '新客业绩提成金额' AFTER `F_UpgradePoint`; + +-- 添加升单业绩提成金额字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_UpgradePerformanceCommission` DECIMAL(18,2) DEFAULT 0.00 COMMENT '升单业绩提成金额' AFTER `F_NewCustomerPerformanceCommission`; + diff --git a/sql/添加健康师工资表奖励业绩字段.sql b/sql/添加健康师工资表奖励业绩字段.sql new file mode 100644 index 0000000..088986c --- /dev/null +++ b/sql/添加健康师工资表奖励业绩字段.sql @@ -0,0 +1,10 @@ +-- 在健康师工资表中添加基础奖励业绩和合作奖励业绩字段 + +-- 添加基础奖励业绩字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_BaseRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '基础奖励业绩' AFTER `F_CooperationPerformance`; + +-- 添加合作奖励业绩字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_CooperationRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作奖励业绩' AFTER `F_BaseRewardPerformance`; + diff --git a/sql/添加健康师工资表实际业绩字段.sql b/sql/添加健康师工资表实际业绩字段.sql new file mode 100644 index 0000000..6b6d169 --- /dev/null +++ b/sql/添加健康师工资表实际业绩字段.sql @@ -0,0 +1,10 @@ +-- 在健康师工资表中添加实际基础业绩和实际合作业绩字段 + +-- 添加实际基础业绩字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_ActualBasePerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实际基础业绩' AFTER `F_CooperationRewardPerformance`; + +-- 添加实际合作业绩字段 +ALTER TABLE `lq_salary_statistics` +ADD COLUMN `F_ActualCooperationPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实际合作业绩' AFTER `F_ActualBasePerformance`; + diff --git a/sql/添加健康师工资额外计算表其他业绩字段.sql b/sql/添加健康师工资额外计算表其他业绩字段.sql new file mode 100644 index 0000000..df98e25 --- /dev/null +++ b/sql/添加健康师工资额外计算表其他业绩字段.sql @@ -0,0 +1,10 @@ +-- 在健康师工资额外计算表中添加其他业绩加和其他业绩减字段 + +-- 添加其他业绩加字段 +ALTER TABLE `lq_salary_extra_calculation` +ADD COLUMN `F_OtherPerformanceAdd` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩加' AFTER `F_UpgradeCustomerCount`; + +-- 添加其他业绩减字段 +ALTER TABLE `lq_salary_extra_calculation` +ADD COLUMN `F_OtherPerformanceSubtract` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩减' AFTER `F_OtherPerformanceAdd`; + diff --git a/sql/添加健康师工资额外计算表升单人头数字段.sql b/sql/添加健康师工资额外计算表升单人头数字段.sql new file mode 100644 index 0000000..2f2e3a0 --- /dev/null +++ b/sql/添加健康师工资额外计算表升单人头数字段.sql @@ -0,0 +1,5 @@ +-- 在健康师工资额外计算表中添加升单人头数字段 + +ALTER TABLE `lq_salary_extra_calculation` +ADD COLUMN `F_UpgradeCustomerCount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '升单人头数' AFTER `F_UpgradeConversionRate`; +