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((ec, u) => new JoinQueryInfos( JoinType.Left, u.Id == SqlFunc.ToString(ec.EmployeeId))) .Where((ec, u) => u.Id != null && (u.DeleteMark == null || u.DeleteMark == 0)); // 年份筛选 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 data = 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 }) .MergeTable() .OrderByIF(!string.IsNullOrEmpty(input.sidx), input.sidx + " " + input.sort) .ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(data); } /// /// 从Excel导入健康师工资额外计算数据 /// /// Excel文件 /// 是否需要清理导入月份数据(默认:true,清理) /// 导入结果 [HttpPost("ImportFromExcel")] public async Task ImportFromExcel(IFormFile file, bool clearBeforeImport = true) { 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 GetColumnValue = new Func((colIndex) => { if (colIndex < row.ItemArray.Length) { return row[colIndex]?.ToString()?.Trim() ?? ""; } return ""; }); var id = GetColumnValue(0); var employeeName = GetColumnValue(1); var employeePhone = GetColumnValue(2); var yearText = GetColumnValue(3); var monthText = GetColumnValue(4); var baseRewardPerformanceText = GetColumnValue(5); var cooperationRewardPerformanceText = GetColumnValue(6); var newCustomerPerformanceText = GetColumnValue(7); var newCustomerConversionRateText = GetColumnValue(8); var upgradePerformanceText = GetColumnValue(9); var upgradeConversionRateText = GetColumnValue(10); var upgradeCustomerCountText = GetColumnValue(11); var otherPerformanceAddText = GetColumnValue(12); var otherPerformanceSubtractText = GetColumnValue(13); // 跳过空行 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之间"); } // 辅助方法:清理数值字符串(去除千分位分隔符、货币符号等) Func CleanNumericString = (str) => { if (string.IsNullOrWhiteSpace(str)) return "0"; // 去除常见的非数字字符(保留小数点和负号) return str.Trim() .Replace(",", "") // 去除千分位分隔符 .Replace(",", "") // 去除中文逗号 .Replace("¥", "") // 去除人民币符号 .Replace("$", "") // 去除美元符号 .Replace("元", "") // 去除"元"字 .Replace(" ", ""); // 去除空格 }; // 解析数值字段(允许为空,默认为0) decimal.TryParse(CleanNumericString(baseRewardPerformanceText), out decimal baseRewardPerformance); decimal.TryParse(CleanNumericString(cooperationRewardPerformanceText), out decimal cooperationRewardPerformance); decimal.TryParse(CleanNumericString(newCustomerPerformanceText), out decimal newCustomerPerformance); decimal.TryParse(CleanNumericString(newCustomerConversionRateText), out decimal newCustomerConversionRate); decimal.TryParse(CleanNumericString(upgradePerformanceText), out decimal upgradePerformance); decimal.TryParse(CleanNumericString(upgradeConversionRateText), out decimal upgradeConversionRate); decimal.TryParse(CleanNumericString(upgradeCustomerCountText), out decimal upgradeCustomerCount); decimal.TryParse(CleanNumericString(otherPerformanceAddText), out decimal otherPerformanceAdd); decimal.TryParse(CleanNumericString(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文件中没有有效的数据行"); } // 如果需要清理导入月份数据,先删除导入数据中所有涉及的年份+月份组合的数据 if (clearBeforeImport) { // 获取导入数据中所有唯一的年份+月份组合 var yearMonthPairs = importData .Select(x => new { x.Year, x.Month }) .Distinct() .ToList(); foreach (var pair in yearMonthPairs) { await _db.Deleteable() .Where(x => x.Year == pair.Year && x.Month == pair.Month) .ExecuteCommandAsync(); } } // 处理导入数据 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(); // 用于跟踪已处理的员工记录,避免重复(Key: EmployeeId_Year_Month) var processedEmployeeRecords = new Dictionary(); foreach (var item in importData) { try { // 1. 查找用户ID(优先使用Excel中的id作为员工ID,否则通过姓名和电话查找) UserEntity user = null; LqSalaryExtraCalculationEntity existingRecord = null; // 优先使用Excel中的id作为员工ID(EmployeeId)直接查找用户 if (!string.IsNullOrEmpty(item.Id)) { // 先尝试将id作为员工ID查找用户 user = await _db.Queryable() .Where(u => u.Id == item.Id) .FirstAsync(); // 如果找不到,尝试将id作为记录ID查找现有记录,获取EmployeeId if (user == null) { existingRecord = await _db.Queryable() .Where(x => x.Id == item.Id) .FirstAsync(); if (existingRecord != null) { // 通过EmployeeId查找用户 user = await _db.Queryable() .Where(u => u.Id == existingRecord.EmployeeId) .FirstAsync(); } } } // 如果还没有找到用户,通过姓名和电话查找 if (user == null) { // 处理姓名:去除"A"前缀 var cleanName = item.EmployeeName; if (cleanName.StartsWith("A")) { cleanName = cleanName.Substring(1).Trim(); } // 处理电话:如果电话是"无"或空,只按姓名查找 var phoneIsEmpty = string.IsNullOrWhiteSpace(item.EmployeePhone) || item.EmployeePhone.Trim() == "无" || item.EmployeePhone.Trim() == "00000000" || item.EmployeePhone.Trim().Length < 8; // 排除明显无效的电话 if (phoneIsEmpty) { // 只按姓名查找(支持原姓名和去除前缀后的姓名) user = await _db.Queryable() .Where(u => u.RealName == item.EmployeeName || u.RealName == cleanName) .FirstAsync(); } else { // 同时匹配姓名和电话(支持原姓名和去除前缀后的姓名) user = await _db.Queryable() .Where(u => (u.RealName == item.EmployeeName || u.RealName == cleanName) && u.MobilePhone == item.EmployeePhone) .FirstAsync(); } } if (user == null) { errorMessages.Add($"健康师 {item.EmployeeName}({item.EmployeePhone}) 不存在"); failCount++; continue; } // 2. 检查是否已经在本次导入中处理过该员工(避免重复,使用员工ID而不是姓名) var recordKey = $"{user.Id}_{item.Year}_{item.Month}"; if (processedEmployeeRecords.ContainsKey(recordKey)) { // 如果已经处理过,更新记录而不是创建新记录 existingRecord = processedEmployeeRecords[recordKey]; 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,因为已经在processedEmployeeRecords中,会在最后统一处理 successCount++; continue; } // 3. 如果还没有找到现有记录,则根据健康师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); // 记录到已处理字典中 processedEmployeeRecords[recordKey] = 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); // 记录到已处理字典中(注意:这里需要创建一个临时对象,因为entity还没有ID) processedEmployeeRecords[recordKey] = 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()) { // 根据主键ID去重,确保每个记录只更新一次 var uniqueEntitiesToUpdate = entitiesToUpdate .GroupBy(x => x.Id) .Select(g => g.First()) .ToList(); // 明确指定要更新的字段,确保所有字段都被更新(包括升单业绩) // 注意:Updateable接收实体列表时,会自动根据主键更新,不需要Where条件 await _db.Updateable(uniqueEntitiesToUpdate) .UpdateColumns(it => new { it.BaseRewardPerformance, it.CooperationRewardPerformance, it.NewCustomerPerformance, it.NewCustomerConversionRate, it.UpgradePerformance, it.UpgradeConversionRate, it.UpgradeCustomerCount, it.OtherPerformanceAdd, it.OtherPerformanceSubtract }) .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() * 10000); var cooperationRewardPerformance = (decimal)(random.NextDouble() * 10000); // 其他金额字段:10000-30000之间 var newCustomerPerformance = (decimal)(random.NextDouble() * 10000); var upgradePerformance = (decimal)(random.NextDouble() * 10000); var otherPerformanceAdd = (decimal)(random.NextDouble() * 10000); var otherPerformanceSubtract = (decimal)(random.NextDouble() * 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}"); } } } }