Commit 96e0ccc76c117a041e498d29d5253064a1cff663

Authored by “wangming”
1 parent 19d2e6e6

优化GetBillingRecordSummaryByStoreId方法:移除多个门店筛选以提升性能,保留单个门店筛选及其他筛选条件

antis-ncc-admin/package-lock.json
... ... @@ -21699,7 +21699,7 @@
21699 21699 },
21700 21700 "node_modules/xlsx": {
21701 21701 "version": "0.18.5",
21702   - "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
  21702 + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
21703 21703 "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
21704 21704 "dependencies": {
21705 21705 "adler-32": "~1.3.0",
... ... @@ -39759,7 +39759,7 @@
39759 39759 },
39760 39760 "xlsx": {
39761 39761 "version": "0.18.5",
39762   - "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
  39762 + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
39763 39763 "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
39764 39764 "requires": {
39765 39765 "adler-32": "~1.3.0",
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryQueryInput.cs
... ... @@ -27,5 +27,29 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
27 27 /// </summary>
28 28 [Display(Name = "结束时间", Description = "查询开单记录的结束时间")]
29 29 public DateTime? EndTime { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 品项分类
  33 + /// </summary>
  34 + [Display(Name = "品项分类", Description = "筛选品项分类")]
  35 + public string ItemCategory { get; set; }
  36 +
  37 + /// <summary>
  38 + /// 品项ID
  39 + /// </summary>
  40 + [Display(Name = "品项ID", Description = "筛选品项ID")]
  41 + public string ItemId { get; set; }
  42 +
  43 + /// <summary>
  44 + /// 健康师ID
  45 + /// </summary>
  46 + [Display(Name = "健康师ID", Description = "筛选健康师ID")]
  47 + public string HealthCoachId { get; set; }
  48 +
  49 + /// <summary>
  50 + /// 客户ID(会员ID)
  51 + /// </summary>
  52 + [Display(Name = "客户ID", Description = "筛选客户ID")]
  53 + public string MemberId { get; set; }
30 54 }
31 55 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs
... ... @@ -94,5 +94,20 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
94 94 /// 退卡金额 - 统计该健康师在指定时间周期内的退卡业绩总金额
95 95 /// </summary>
96 96 public decimal refundAmount { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 手工费 - 统计该健康师在指定时间周期内消耗时的手工费总金额
  100 + /// </summary>
  101 + public decimal laborCost { get; set; }
  102 +
  103 + /// <summary>
  104 + /// 原始手工费 - 统计该健康师在指定时间周期内消耗时的原始手工费总金额
  105 + /// </summary>
  106 + public decimal originalLaborCost { get; set; }
  107 +
  108 + /// <summary>
  109 + /// 加班手工费 - 统计该健康师在指定时间周期内消耗时的加班手工费总金额
  110 + /// </summary>
  111 + public decimal overtimeLaborCost { get; set; }
97 112 }
98 113 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqSalary
  5 +{
  6 + /// <summary>
  7 + /// 健康师工资查询参数
  8 + /// </summary>
  9 + public class HealthCoachSalaryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 年份
  13 + /// </summary>
  14 + public int Year { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 月份
  18 + /// </summary>
  19 + public int Month { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 门店ID(可选,用于筛选特定门店)
  23 + /// </summary>
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 健康师姓名/账号(可选,用于模糊搜索)
  28 + /// </summary>
  29 + public string Keyword { get; set; }
  30 + }
  31 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqSalary
  4 +{
  5 + /// <summary>
  6 + /// 健康师工资输出
  7 + /// </summary>
  8 + public class HealthCoachSalaryOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店名称
  17 + /// </summary>
  18 + public string StoreName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 员工姓名
  22 + /// </summary>
  23 + public string EmployeeName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 岗位
  27 + /// </summary>
  28 + public string Position { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 金三角战队
  32 + /// </summary>
  33 + public string GoldTriangleTeam { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 总业绩
  37 + /// </summary>
  38 + public decimal TotalPerformance { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 基础业绩
  42 + /// </summary>
  43 + public decimal BasePerformance { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 合作业绩
  47 + /// </summary>
  48 + public decimal CooperationPerformance { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 奖励业绩
  52 + /// </summary>
  53 + public decimal RewardPerformance { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 消耗
  57 + /// </summary>
  58 + public decimal Consumption { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 项目数
  62 + /// </summary>
  63 + public decimal ProjectCount { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 到店人头
  67 + /// </summary>
  68 + public decimal CustomerCount { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 在店天数
  72 + /// </summary>
  73 + public decimal WorkingDays { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 健康师底薪
  77 + /// </summary>
  78 + public decimal HealthCoachBaseSalary { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 提成合计
  82 + /// </summary>
  83 + public decimal TotalCommission { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 手工费
  87 + /// </summary>
  88 + public decimal HandworkFee { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 补贴合计
  92 + /// </summary>
  93 + public decimal TotalSubsidy { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 扣款合计
  97 + /// </summary>
  98 + public decimal TotalDeduction { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 实发工资
  102 + /// </summary>
  103 + public decimal ActualSalary { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 是否锁定
  107 + /// </summary>
  108 + public int IsLocked { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 更新时间
  112 + /// </summary>
  113 + public DateTime UpdateTime { get; set; }
  114 + }
  115 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs
... ... @@ -299,60 +299,6 @@ namespace NCC.Extend.LqHytkHytk
299 299 /// 创建退卡信息及其关联的品项明细、健康师业绩、科技部老师业绩信息
300 300 /// </summary>
301 301 /// <remarks>
302   - /// 创建退卡记录及其关联的品项明细、健康师业绩、科技部老师业绩信息
303   - ///
304   - /// 示例请求:
305   - /// ```json
306   - /// {
307   - /// "md": "门店ID",
308   - /// "mdbh": "门店编号",
309   - /// "mdmc": "门店名称",
310   - /// "hy": "会员ID",
311   - /// "hymc": "会员姓名",
312   - /// "hyzh": "会员账号",
313   - /// "gklx": "顾客类型",
314   - /// "tkje": 1000.00,
315   - /// "sgfy": 50.00,
316   - /// "bz": "备注",
317   - /// "tkzt": "退卡状态",
318   - /// "tkyy": "退卡原因",
319   - /// "lqHytkMxList": [
320   - /// {
321   - /// "px": "品项编号",
322   - /// "pxmc": "品项名称",
323   - /// "pxjg": 100.00,
324   - /// "tkje": 100.00,
325   - /// "F_ProjectNumber": 1,
326   - /// "F_SourceType": "来源类型",
327   - /// "F_TotalPrice": 100.00,
328   - /// "lqHytkJksyjList": [
329   - /// {
330   - /// "jks": "健康师",
331   - /// "jksxm": "健康师姓名",
332   - /// "jkszh": "健康师账号",
333   - /// "jksyj": 50.00,
334   - /// "F_jsjid": "金三角ID",
335   - /// "F_tkpxid": "项目资料ID",
336   - /// "F_LaborCost": 10.00,
337   - /// "F_tkpxNumber": 1
338   - /// }
339   - /// ],
340   - /// "lqHytkKjbsyjList": [
341   - /// {
342   - /// "kjbls": "科技部老师",
343   - /// "kjblsxm": "科技部老师姓名",
344   - /// "kjblszh": "科技部老师账号",
345   - /// "kjblsyj": 30.00,
346   - /// "F_tkpxid": "项目资料ID",
347   - /// "F_LaborCost": 5.00,
348   - /// "F_tkpxNumber": 1
349   - /// }
350   - /// ]
351   - /// }
352   - /// ]
353   - /// }
354   - /// ```
355   - ///
356 302 /// 参数说明:
357 303 /// - md: 门店ID
358 304 /// - hy: 会员ID
... ... @@ -378,15 +324,12 @@ namespace NCC.Extend.LqHytkHytk
378 324 {
379 325 // 开启事务
380 326 _db.BeginTran();
381   -
382 327 // 新增退卡主表记录
383 328 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
384   -
385 329 // 收集所有需要插入的实体,然后批量插入
386 330 var allMxEntities = new List<LqHytkMxEntity>();
387 331 var allJksyjEntities = new List<LqHytkJksyjEntity>();
388 332 var allKjbsyjEntities = new List<LqHytkKjbsyjEntity>();
389   -
390 333 // 处理品项明细列表
391 334 if (input.lqHytkMxList != null && input.lqHytkMxList.Any())
392 335 {
... ... @@ -405,6 +348,7 @@ namespace NCC.Extend.LqHytkHytk
405 348 Pxmc = item.pxmc,
406 349 Pxjg = item.pxjg,
407 350 Tkje = item.tkje,
  351 + Tksj = input.tksj,
408 352 ProjectNumber = item.F_ProjectNumber ?? 1,
409 353 SourceType = item.F_SourceType,
410 354 TotalPrice = item.F_TotalPrice ?? (item.pxjg * (item.F_ProjectNumber ?? 1)),
... ... @@ -426,7 +370,7 @@ namespace NCC.Extend.LqHytkHytk
426 370 Jksxm = ijks_tem.jksxm,
427 371 Jkszh = ijks_tem.jkszh,
428 372 Jksyj = ijks_tem.jksyj,
429   - Tksj = DateTime.Now,
  373 + Tksj = input.tksj,
430 374 F_jsjid = ijks_tem.F_jsjid,
431 375 F_tkpxid = ijks_tem.F_tkpxid,
432 376 F_LaborCost = ijks_tem.F_LaborCost,
... ... @@ -458,7 +402,7 @@ namespace NCC.Extend.LqHytkHytk
458 402 Kjblsxm = ikjbs_tem.kjblsxm,
459 403 Kjblszh = ikjbs_tem.kjblszh,
460 404 Kjblsyj = ikjbs_tem.kjblsyj,
461   - Tksj = DateTime.Now,
  405 + Tksj = input.tksj,
462 406 F_tkpxid = ikjbs_tem.F_tkpxid,
463 407 F_LaborCost = ikjbs_tem.F_LaborCost,
464 408 F_tkpxNumber = ikjbs_tem.F_tkpxNumber,
... ... @@ -476,7 +420,6 @@ namespace NCC.Extend.LqHytkHytk
476 420 }
477 421 }
478 422 }
479   -
480 423 // 批量插入品项明细
481 424 if (allMxEntities.Any())
482 425 {
... ... @@ -587,7 +530,7 @@ namespace NCC.Extend.LqHytkHytk
587 530 Jksxm = ijks_tem.jksxm,
588 531 Jkszh = ijks_tem.jkszh,
589 532 Jksyj = ijks_tem.jksyj,
590   - Tksj = DateTime.Now,
  533 + Tksj = input.tksj,
591 534 F_jsjid = ijks_tem.F_jsjid,
592 535 F_tkpxid = ijks_tem.F_tkpxid,
593 536 F_LaborCost = ijks_tem.F_LaborCost,
... ... @@ -617,7 +560,7 @@ namespace NCC.Extend.LqHytkHytk
617 560 Kjblsxm = ikjbs_tem.kjblsxm,
618 561 Kjblszh = ikjbs_tem.kjblszh,
619 562 Kjblsyj = ikjbs_tem.kjblsyj,
620   - Tksj = DateTime.Now,
  563 + Tksj = input.tksj,
621 564 F_tkpxid = ikjbs_tem.F_tkpxid,
622 565 F_LaborCost = ikjbs_tem.F_LaborCost,
623 566 F_tkpxNumber = ikjbs_tem.F_tkpxNumber,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -1117,6 +1117,263 @@ namespace NCC.Extend.LqKdKdjlb
1117 1117 }
1118 1118 #endregion
1119 1119  
  1120 + #region 修改开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息
  1121 + /// <summary>
  1122 + /// 修改开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息
  1123 + /// </summary>
  1124 + /// <remarks>
  1125 + /// 参数说明:
  1126 + /// - id: 开单记录主键ID
  1127 + /// - input: 开单记录更新参数,包含品项明细和业绩信息
  1128 + /// </remarks>
  1129 + /// <param name="id">开单记录主键ID</param>
  1130 + /// <param name="input">开单记录更新参数</param>
  1131 + /// <returns>无返回值</returns>
  1132 + /// <response code="200">更新成功</response>
  1133 + /// <response code="400">参数错误或数据验证失败</response>
  1134 + /// <response code="500">服务器内部错误</response>
  1135 + [HttpPut("UpdateForNoDelete/{id}")]
  1136 + public async Task UpdateForNoDelete(string id, [FromBody] LqKdKdjlbUpInput input)
  1137 + {
  1138 + var entity = input.Adapt<LqKdKdjlbEntity>();
  1139 + entity.Id = id; // 确保ID正确设置
  1140 + try
  1141 + {
  1142 + //检查开单记录是否可以操作
  1143 + var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(id);
  1144 + if (!canCancel)
  1145 + {
  1146 + throw NCCException.Oh(errorMessage);
  1147 + }
  1148 + //开启事务
  1149 + _db.BeginTran();
  1150 + //查询是否有对应的补缴开单ID
  1151 + if (!string.IsNullOrEmpty(entity.SupplementBillingId))
  1152 + {
  1153 + //查询补缴开单ID
  1154 + var supplementBillingEntity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == entity.SupplementBillingId);//900,900
  1155 + if (supplementBillingEntity == null || supplementBillingEntity.IsEffective == StatusEnum.无效.GetHashCode())
  1156 + {
  1157 + throw NCCException.Oh("补缴开单记录不存在或已作废");
  1158 + }
  1159 + //查询当前开单已经补缴金额
  1160 + var OldSupplementAmount = await _db.Queryable<LqKdKdjlbEntity>().Where(p => p.Id == id).SumAsync(p => p.SupplementAmount);//900,0
  1161 + supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - OldSupplementAmount + input.supplementAmount;
  1162 + await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync();
  1163 + }
  1164 + //批量查询当前开单所有品项的分类(性能优化)
  1165 + var itemIds = input.lqKdPxmxList?.Where(x => !string.IsNullOrEmpty(x.px)).Select(x => x.px).Distinct().ToList() ?? new List<string>();
  1166 + var itemCategoryDict = new Dictionary<string, string>();
  1167 + if (itemIds.Any())
  1168 + {
  1169 + var itemCategories = await _db.Queryable<LqXmzlEntity>()
  1170 + .Where(x => itemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode())
  1171 + .Select(x => new { x.Id, x.Qt2 })
  1172 + .ToListAsync();
  1173 + itemCategoryDict = itemCategories.ToDictionary(k => k.Id, v => v.Qt2 ?? "");
  1174 + }
  1175 + //判断当前开单是否包含医美品项
  1176 + var hasMedicalItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美");
  1177 + var MedicalItemInCurrentBillingAmount = input.lqKdPxmxList.Where(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美").Sum(x => x.actualPrice);
  1178 + //判断当前开单是否包含科美品项
  1179 + var hasTechItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "科美");
  1180 + //判断当前开单是否包含生美品项
  1181 + var hasLifeItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "生美");
  1182 + //获取该会员之前开单品项里面是否有医美项目
  1183 + var isMedicalProject = hasMedicalItemInCurrentBilling && await _db.Queryable<LqKdPxmxEntity>().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "医美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync();
  1184 + if (isMedicalProject && MedicalItemInCurrentBillingAmount >= 1000)
  1185 + {
  1186 + entity.UpgradeLifeBeauty = "是";
  1187 + }
  1188 + else
  1189 + {
  1190 + entity.UpgradeLifeBeauty = "否";
  1191 + }
  1192 + //获取该会员之前开单品项里面是否有科美项目
  1193 + var isTechProject = hasTechItemInCurrentBilling && await _db.Queryable<LqKdPxmxEntity>().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "科美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync();
  1194 + if (isTechProject)
  1195 + {
  1196 + entity.UpgradeTechBeauty = "是";
  1197 + }
  1198 + else
  1199 + {
  1200 + entity.UpgradeTechBeauty = "否";
  1201 + }
  1202 + //获取该会员之前开单品项里面是否有生美项目
  1203 + var isLifeProject = hasLifeItemInCurrentBilling && await _db.Queryable<LqKdPxmxEntity>().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "生美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync();
  1204 + if (isLifeProject)
  1205 + {
  1206 + entity.UpgradeMedicalBeauty = "是";
  1207 + }
  1208 + else
  1209 + {
  1210 + entity.UpgradeMedicalBeauty = "否";
  1211 + }
  1212 + //计算储扣总金额
  1213 + entity.DeductAmount = input.lqKdKdjlbDeductList.Sum(x => x.Amount ?? 0);
  1214 + entity.UpdateTime = DateTime.Now;
  1215 + // 更新开单记录主表
  1216 + await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).IgnoreColumns(x => x.CreateTime).ExecuteCommandAsync();
  1217 + //清空原有品项明细
  1218 + await _db.Deleteable<LqKdPxmxEntity>().Where(x => x.Glkdbh == id).ExecuteCommandAsync();
  1219 + //清空原有健康师业绩
  1220 + await _db.Deleteable<LqKdJksyjEntity>().Where(x => x.Glkdbh == id).ExecuteCommandAsync();
  1221 + //清空原有科技部老师业绩
  1222 + await _db.Deleteable<LqKdKjbsyjEntity>().Where(x => x.Glkdbh == id).ExecuteCommandAsync();
  1223 + //清空原有扣款信息
  1224 + await _db.Deleteable<LqKdDeductinfoEntity>().Where(x => x.BillingId == id).ExecuteCommandAsync();
  1225 + //循环品相信息
  1226 + // 收集所有需要插入的实体,然后批量插入
  1227 + var allPxmxEntities = new List<LqKdPxmxEntity>();
  1228 + var allJksyjEntities = new List<LqKdJksyjEntity>();
  1229 + var allKjbsyjEntities = new List<LqKdKjbsyjEntity>();
  1230 + var allDeductEntities = new List<LqKdDeductinfoEntity>();
  1231 + // 处理扣款信息列表
  1232 + foreach (var item in input.lqKdKdjlbDeductList)
  1233 + {
  1234 + var lqKdDeductEntity = new LqKdDeductinfoEntity
  1235 + {
  1236 + Id = YitIdHelper.NextId().ToString(),
  1237 + BillingId = id,
  1238 + DeductId = item.DeductId,
  1239 + DeductType = item.DeductType,
  1240 + Amount = item.Amount,
  1241 + ProjectNumber = item.ProjectNumber,
  1242 + UnitPrice = item.UnitPrice,
  1243 + ItemName = item.ItemName,
  1244 + ItemId = item.ItemId,
  1245 + IsEffective = StatusEnum.有效.GetHashCode(), // 设置为有效
  1246 + CreateTime = DateTime.Now, // 设置创建时间
  1247 + ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync(),
  1248 + };
  1249 + allDeductEntities.Add(lqKdDeductEntity);
  1250 + }
  1251 + // 处理品项明细列表
  1252 + foreach (var item in input.lqKdPxmxList)
  1253 + {
  1254 + // 创建品项明细实体
  1255 + var lqKdPxmxEntity = new LqKdPxmxEntity
  1256 + {
  1257 + Id = YitIdHelper.NextId().ToString(),
  1258 + Glkdbh = id,
  1259 + Yjsj = input.kdrq,
  1260 + CreateTIme = DateTime.Now,
  1261 + MemberId = entity.Kdhy,
  1262 + IsEnabled = StatusEnum.有效.GetHashCode(),
  1263 + ProjectNumber = item.projectNumber,
  1264 + TotalPrice = (decimal)(item.pxjg * item.projectNumber),
  1265 + Px = item.px,
  1266 + Pxmc = item.pxmc,
  1267 + Pxjg = item.pxjg,
  1268 + SourceType = item.sourceType,
  1269 + ActualPrice = item.actualPrice,
  1270 + Remark = item.remark,
  1271 + IsEffective = StatusEnum.有效.GetHashCode(),
  1272 + ActivityId = input.activityId,
  1273 + ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
  1274 + };
  1275 + allPxmxEntities.Add(lqKdPxmxEntity);
  1276 +
  1277 + // 收集该品项关联的健康师业绩
  1278 + if (item.lqKdJksyjList != null && item.lqKdJksyjList.Any())
  1279 + {
  1280 + //把jksxm保存到HealthInstructorNames
  1281 + foreach (var ijks_tem in item.lqKdJksyjList)
  1282 + {
  1283 + allJksyjEntities.Add(new LqKdJksyjEntity
  1284 + {
  1285 + Id = YitIdHelper.NextId().ToString(),
  1286 + Glkdbh = id,
  1287 + Jks = ijks_tem.jks,
  1288 + Jksxm = ijks_tem.jksxm,
  1289 + Jkszh = ijks_tem.jkszh,
  1290 + Jksyj = ijks_tem.jksyj,
  1291 + Yjsj = input.kdrq,
  1292 + Jsj_id = ijks_tem.jsj_id,
  1293 + Kdpxid = lqKdPxmxEntity.Id,
  1294 + IsEffective = StatusEnum.有效.GetHashCode(),
  1295 + ActivityId = input.activityId,
  1296 + ItemCategory = lqKdPxmxEntity.ItemCategory,
  1297 + ItemId = lqKdPxmxEntity.Px,
  1298 + StoreId = entity.Djmd,
  1299 + ItemName = lqKdPxmxEntity.Pxmc,
  1300 + });
  1301 + }
  1302 + }
  1303 +
  1304 + // 收集该品项关联的科技部老师业绩
  1305 + if (item.lqKdKjbsyjList != null && item.lqKdKjbsyjList.Any())
  1306 + {
  1307 + foreach (var ikjbs_tem in item.lqKdKjbsyjList)
  1308 + {
  1309 + allKjbsyjEntities.Add(new LqKdKjbsyjEntity
  1310 + {
  1311 + Id = YitIdHelper.NextId().ToString(),
  1312 + Glkdbh = id,
  1313 + Kjbls = ikjbs_tem.kjbls,
  1314 + Kjblsxm = ikjbs_tem.kjblsxm,
  1315 + Kjblszh = ikjbs_tem.kjblszh,
  1316 + Kjblsyj = ikjbs_tem.kjblsyj,
  1317 + Yjsj = input.kdrq,
  1318 + Kdpxid = lqKdPxmxEntity.Id,
  1319 + IsEffective = StatusEnum.有效.GetHashCode(),
  1320 + ActivityId = input.activityId,
  1321 + ItemCategory = lqKdPxmxEntity.ItemCategory,
  1322 + ItemId = lqKdPxmxEntity.Px,
  1323 + StoreId = entity.Djmd,
  1324 + ItemName = lqKdPxmxEntity.Pxmc,
  1325 + });
  1326 + }
  1327 + }
  1328 + }
  1329 +
  1330 + //通过会员id查询会员信息
  1331 + var memberInfo = await _db.Queryable<LqKhxxEntity>().Where(u => u.Id == entity.Kdhy).FirstAsync();
  1332 + //通过开单记录表查询这个会员开单金额
  1333 + var kdAmount = await _db.Queryable<LqKdKdjlbEntity>().Where(u => u.Kdhy == entity.Kdhy).SumAsync(u => u.Sfyj);
  1334 + //如果开单金额小于500,为散客,如果大于500,为会员
  1335 + if (kdAmount < 500)
  1336 + {
  1337 + memberInfo.Khlx = MemberTypeEnum.散客.GetHashCode().ToString();
  1338 + }
  1339 + else
  1340 + {
  1341 + memberInfo.Khlx = MemberTypeEnum.会员.GetHashCode().ToString();
  1342 + }
  1343 + await _db.Updateable(memberInfo).ExecuteCommandAsync();
  1344 + // 批量插入扣款信息
  1345 + if (allDeductEntities.Any())
  1346 + {
  1347 + await _db.Insertable(allDeductEntities).ExecuteCommandAsync();
  1348 + }
  1349 + // 批量插入品项明细
  1350 + if (allPxmxEntities.Any())
  1351 + {
  1352 + await _db.Insertable(allPxmxEntities).ExecuteCommandAsync();
  1353 + }
  1354 + // 批量插入健康师业绩
  1355 + if (allJksyjEntities.Any())
  1356 + {
  1357 + await _db.Insertable(allJksyjEntities).ExecuteCommandAsync();
  1358 + }
  1359 + // 批量插入科技部老师业绩
  1360 + if (allKjbsyjEntities.Any())
  1361 + {
  1362 + await _db.Insertable(allKjbsyjEntities).ExecuteCommandAsync();
  1363 + }
  1364 +
  1365 + //关闭事务
  1366 + _db.CommitTran();
  1367 + }
  1368 + catch (Exception ex)
  1369 + {
  1370 + //回滚事务
  1371 + _db.RollbackTran();
  1372 + throw NCCException.Oh($"创建开单记录失败: {ex.Message}");
  1373 + }
  1374 + }
  1375 + #endregion
  1376 +
1120 1377 #region 获取开单记录表无分页列表
1121 1378 /// <summary>
1122 1379 /// 获取开单记录表无分页列表
... ... @@ -1293,225 +1550,9 @@ namespace NCC.Extend.LqKdKdjlb
1293 1550 }
1294 1551 #endregion
1295 1552  
1296   - #region 更新开单记录表
  1553 + #region 删除开单记录表
1297 1554 /// <summary>
1298   - /// 更新开单记录表
1299   - /// </summary>
1300   - /// <remarks>
1301   - /// 更新开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息
1302   - ///
1303   - /// 示例请求:
1304   - /// ```json
1305   - /// {
1306   - /// "id": "开单编号",
1307   - /// "djmd": "单据门店",
1308   - /// "jsj": "金三角",
1309   - /// "kdrq": "2025-01-11",
1310   - /// "lqKdPxmxList": [
1311   - /// {
1312   - /// "px": "品项编号",
1313   - /// "pxmc": "品项名称",
1314   - /// "pxjg": 100.00,
1315   - /// "projectNumber": 1,
1316   - /// "sourceType": "购买",
1317   - /// "lqKdJksyjList": [
1318   - /// {
1319   - /// "jks": "健康师",
1320   - /// "jksxm": "健康师姓名",
1321   - /// "jksyj": "100"
1322   - /// }
1323   - /// ]
1324   - /// }
1325   - /// ]
1326   - /// }
1327   - /// ```
1328   - ///
1329   - /// 参数说明:
1330   - /// - id: 开单记录主键ID
1331   - /// - input: 开单记录更新参数,包含品项明细和业绩信息
1332   - /// </remarks>
1333   - /// <param name="id">开单记录主键ID</param>
1334   - /// <param name="input">开单记录更新参数</param>
1335   - /// <returns>无返回值</returns>
1336   - /// <response code="200">更新成功</response>
1337   - /// <response code="400">参数错误或数据验证失败</response>
1338   - /// <response code="500">服务器内部错误</response>
1339   - [HttpPut("{id}")]
1340   - public async Task Update(string id, [FromBody] LqKdKdjlbUpInput input)
1341   - {
1342   - var entity = input.Adapt<LqKdKdjlbEntity>();
1343   - try
1344   - {
1345   - //开启事务
1346   - _db.BeginTran();
1347   - //批量查询当前开单所有品项的分类(性能优化)
1348   - var itemIds = input.lqKdPxmxList?.Where(x => !string.IsNullOrEmpty(x.px)).Select(x => x.px).Distinct().ToList() ?? new List<string>();
1349   - var itemCategoryDict = new Dictionary<string, string>();
1350   - if (itemIds.Any())
1351   - {
1352   - var itemCategories = await _db.Queryable<LqXmzlEntity>()
1353   - .Where(x => itemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode())
1354   - .Select(x => new { x.Id, x.Qt2 })
1355   - .ToListAsync();
1356   - itemCategoryDict = itemCategories.ToDictionary(k => k.Id, v => v.Qt2 ?? "");
1357   - }
1358   - //判断当前开单是否包含医美品项
1359   - var hasMedicalItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x =>
1360   - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美");
1361   - var MedicalItemInCurrentBillingAmount = input.lqKdPxmxList.Where(x =>
1362   - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美").Sum(x => x.actualPrice);
1363   - //判断当前开单是否包含科美品项
1364   - var hasTechItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x =>
1365   - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "科美");
1366   - //判断当前开单是否包含生美品项
1367   - var hasLifeItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x =>
1368   - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "生美");
1369   - //获取该会员之前开单品项里面是否有医美项目
1370   - var isMedicalProject = hasMedicalItemInCurrentBilling && await _db.Queryable<LqKdPxmxEntity>().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "医美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync();
1371   - if (isMedicalProject && MedicalItemInCurrentBillingAmount >= 1000)
1372   - {
1373   - entity.UpgradeLifeBeauty = "是";
1374   - }
1375   - else
1376   - {
1377   - entity.UpgradeLifeBeauty = "否";
1378   - }
1379   - //获取该会员之前开单品项里面是否有科美项目
1380   - var isTechProject = hasTechItemInCurrentBilling && await _db.Queryable<LqKdPxmxEntity>().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "科美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync();
1381   - if (isTechProject)
1382   - {
1383   - entity.UpgradeTechBeauty = "是";
1384   - }
1385   - else
1386   - {
1387   - entity.UpgradeTechBeauty = "否";
1388   - }
1389   - //获取该会员之前开单品项里面是否有生美项目
1390   - var isLifeProject = hasLifeItemInCurrentBilling && await _db.Queryable<LqKdPxmxEntity>().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "生美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync();
1391   - if (isLifeProject)
1392   - {
1393   - entity.UpgradeMedicalBeauty = "是";
1394   - }
1395   - else
1396   - {
1397   - entity.UpgradeMedicalBeauty = "否";
1398   - }
1399   -
1400   - //更新开单记录表记录
1401   - await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
1402   -
1403   - //清空原有数据
1404   - await _db.Deleteable<LqKdJksyjEntity>().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
1405   - await _db.Deleteable<LqKdKjbsyjEntity>().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
1406   - await _db.Deleteable<LqKdPxmxEntity>().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
1407   -
1408   - // 收集所有需要插入的实体,然后批量插入
1409   - var allPxmxEntities = new List<LqKdPxmxEntity>();
1410   - var allJksyjEntities = new List<LqKdJksyjEntity>();
1411   - var allKjbsyjEntities = new List<LqKdKjbsyjEntity>();
1412   -
1413   - // 处理品项明细列表
1414   - if (input.lqKdPxmxList != null && input.lqKdPxmxList.Any())
1415   - {
1416   - foreach (var item in input.lqKdPxmxList)
1417   - {
1418   - // 创建品项明细实体
1419   - var lqKdPxmxEntity = new LqKdPxmxEntity
1420   - {
1421   - Id = YitIdHelper.NextId().ToString(),
1422   - Glkdbh = entity.Id,
1423   - CreateTIme = DateTime.Now,
1424   - MemberId = entity.Kdhy,
1425   - IsEnabled = 0,
1426   - ProjectNumber = item.projectNumber == 0 ? 1 : item.projectNumber,
1427   - TotalPrice = (decimal)(item.pxjg * (item.projectNumber == 0 ? 1 : item.projectNumber)),
1428   - Px = item.px,
1429   - Pxmc = item.pxmc,
1430   - Pxjg = item.pxjg,
1431   - SourceType = item.sourceType,
1432   - ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
1433   - };
1434   - allPxmxEntities.Add(lqKdPxmxEntity);
1435   -
1436   - // 收集该品项关联的健康师业绩
1437   - if (item.lqKdJksyjList != null && item.lqKdJksyjList.Any())
1438   - {
1439   - foreach (var ijks_tem in item.lqKdJksyjList)
1440   - {
1441   - allJksyjEntities.Add(new LqKdJksyjEntity
1442   - {
1443   - Id = YitIdHelper.NextId().ToString(),
1444   - Glkdbh = entity.Id,
1445   - Jks = ijks_tem.jks,
1446   - Jksxm = ijks_tem.jksxm,
1447   - Jkszh = ijks_tem.jkszh,
1448   - Jksyj = ijks_tem.jksyj,
1449   - Yjsj = DateTime.Now,
1450   - Jsj_id = ijks_tem.jsj_id,
1451   - Kdpxid = lqKdPxmxEntity.Id,
1452   - StoreId = entity.Djmd,
1453   - ItemCategory = lqKdPxmxEntity.ItemCategory,
1454   - ItemId = lqKdPxmxEntity.Px,
1455   - ItemName = lqKdPxmxEntity.Pxmc,
1456   - });
1457   - }
1458   - }
1459   -
1460   - // 收集该品项关联的科技部老师业绩
1461   - if (item.lqKdKjbsyjList != null && item.lqKdKjbsyjList.Any())
1462   - {
1463   - foreach (var ikjbs_tem in item.lqKdKjbsyjList)
1464   - {
1465   - allKjbsyjEntities.Add(new LqKdKjbsyjEntity
1466   - {
1467   - Id = YitIdHelper.NextId().ToString(),
1468   - Glkdbh = entity.Id,
1469   - Kjbls = ikjbs_tem.kjbls,
1470   - Kjblsxm = ikjbs_tem.kjblsxm,
1471   - Kjblszh = ikjbs_tem.kjblszh,
1472   - Kjblsyj = ikjbs_tem.kjblsyj,
1473   - Yjsj = DateTime.Now,
1474   - Kdpxid = lqKdPxmxEntity.Id,
1475   - ItemCategory = lqKdPxmxEntity.ItemCategory,
1476   - ItemId = lqKdPxmxEntity.Px,
1477   - StoreId = entity.Djmd,
1478   - ItemName = lqKdPxmxEntity.Pxmc,
1479   - });
1480   - }
1481   - }
1482   - }
1483   - }
1484   -
1485   - // 批量插入品项明细
1486   - if (allPxmxEntities.Any())
1487   - {
1488   - await _db.Insertable(allPxmxEntities).ExecuteCommandAsync();
1489   - }
1490   - // 批量插入健康师业绩
1491   - if (allJksyjEntities.Any())
1492   - {
1493   - await _db.Insertable(allJksyjEntities).ExecuteCommandAsync();
1494   - }
1495   - // 批量插入科技部老师业绩
1496   - if (allKjbsyjEntities.Any())
1497   - {
1498   - await _db.Insertable(allKjbsyjEntities).ExecuteCommandAsync();
1499   - }
1500   - //关闭事务
1501   - _db.CommitTran();
1502   - }
1503   - catch (Exception)
1504   - {
1505   - //回滚事务
1506   - _db.RollbackTran();
1507   - throw NCCException.Oh(ErrorCode.COM1001);
1508   - }
1509   - }
1510   - #endregion
1511   -
1512   - #region 删除开单记录表
1513   - /// <summary>
1514   - /// 删除开单记录表
  1555 + /// 删除开单记录表
1515 1556 /// </summary>
1516 1557 /// <returns></returns>
1517 1558 [HttpDelete("{id}")]
... ... @@ -1609,317 +1650,83 @@ namespace NCC.Extend.LqKdKdjlb
1609 1650 }
1610 1651 #endregion
1611 1652  
1612   - #region 修改开单记录
  1653 + #region 作废开单记录
1613 1654 /// <summary>
1614   - /// 修改开单记录
  1655 + /// 作废开单记录
1615 1656 /// </summary>
  1657 + /// <param name="input">作废开单记录输入</param>
  1658 + /// <returns>无返回值</returns>
1616 1659 /// <remarks>
1617   - /// 更新开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息
1618   - ///
  1660 + /// 作废指定的开单记录,包括相关的品项明细、业绩记录等
  1661 + ///
1619 1662 /// 示例请求:
1620 1663 /// ```json
1621 1664 /// {
1622   - /// "id": "开单编号",
1623   - /// "djmd": "单据门店",
1624   - /// "jsj": "金三角",
1625   - /// "kdrq": "2025-01-11",
1626   - /// "lqKdPxmxList": [
1627   - /// {
1628   - /// "px": "品项编号",
1629   - /// "pxmc": "品项名称",
1630   - /// "pxjg": 100.00,
1631   - /// "projectNumber": 1,
1632   - /// "sourceType": "购买",
1633   - /// "lqKdJksyjList": [
1634   - /// {
1635   - /// "jks": "健康师",
1636   - /// "jksxm": "健康师姓名",
1637   - /// "jksyj": "100"
1638   - /// }
1639   - /// ]
1640   - /// }
1641   - /// ]
  1665 + /// "id": "123456789",
  1666 + /// "remarks": "客户要求作废此订单"
1642 1667 /// }
1643 1668 /// ```
1644   - ///
  1669 + ///
1645 1670 /// 参数说明:
1646   - /// - id: 开单记录主键ID
1647   - /// - input: 开单记录更新参数,包含品项明细和业绩信息
  1671 + /// - id: 开单记录主键ID(必填)
  1672 + /// - remarks: 作废备注说明
1648 1673 /// </remarks>
1649   - /// <param name="id">开单记录主键ID</param>
1650   - /// <param name="input">开单记录更新参数</param>
1651   - /// <returns>无返回值</returns>
1652   - /// <response code="200">更新成功</response>
1653   - /// <response code="400">参数错误或数据验证失败</response>
  1674 + /// <response code="200">作废成功</response>
  1675 + /// <response code="400">参数错误,开单记录ID不能为空</response>
  1676 + /// <response code="404">开单记录不存在</response>
1654 1677 /// <response code="500">服务器内部错误</response>
1655   - [HttpPut("UpdateForNoDelete/{id}")]
1656   - public async Task UpdateForNoDelete(string id, [FromBody] LqKdKdjlbUpInput input)
  1678 + [HttpPut("Cancel")]
  1679 + public async Task Cancel(CancelBillingInput input)
1657 1680 {
1658   - var entity = input.Adapt<LqKdKdjlbEntity>();
1659   - entity.Id = id; // 确保ID正确设置
  1681 + if (string.IsNullOrEmpty(input.Id))
  1682 + {
  1683 + throw NCCException.Oh("开单记录ID不能为空");
  1684 + }
1660 1685 try
1661 1686 {
1662   - //检查开单记录是否可以操作
1663   - var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(id);
  1687 + //开启事务
  1688 + _db.BeginTran();
  1689 + // 检查开单记录是否可以作废
  1690 + var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(input.Id);
1664 1691 if (!canCancel)
1665 1692 {
1666 1693 throw NCCException.Oh(errorMessage);
1667 1694 }
1668   - //开启事务
1669   - _db.BeginTran();
1670   - //查询是否有对应的补缴开单ID
1671   - if (!string.IsNullOrEmpty(entity.SupplementBillingId))
  1695 +
  1696 + // 查询开单记录
  1697 + var entity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == input.Id);
  1698 + if (entity == null)
1672 1699 {
1673   - //查询补缴开单ID
1674   - var supplementBillingEntity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == entity.SupplementBillingId);//900,900
1675   - if (supplementBillingEntity == null || supplementBillingEntity.IsEffective == StatusEnum.无效.GetHashCode())
1676   - {
1677   - throw NCCException.Oh("补缴开单记录不存在或已作废");
1678   - }
1679   - //查询当前开单已经补缴金额
1680   - var OldSupplementAmount = await _db.Queryable<LqKdKdjlbEntity>().Where(p => p.Id == id).SumAsync(p => p.SupplementAmount);//900,0
1681   - supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - OldSupplementAmount + input.supplementAmount;
1682   - await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync();
  1700 + throw NCCException.Oh("开单记录不存在");
1683 1701 }
1684   - // 更新开单记录主表
1685   - await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).IgnoreColumns(x => x.CreateTime).ExecuteCommandAsync();
1686 1702  
1687   - //清空原有品项明细
1688   - await _db.Deleteable<LqKdPxmxEntity>().Where(x => x.Glkdbh == id).ExecuteCommandAsync();
1689   - //清空原有健康师业绩
1690   - await _db.Deleteable<LqKdJksyjEntity>().Where(x => x.Glkdbh == id).ExecuteCommandAsync();
1691   - //清空原有科技部老师业绩
1692   - await _db.Deleteable<LqKdKjbsyjEntity>().Where(x => x.Glkdbh == id).ExecuteCommandAsync();
1693   - //清空原有扣款信息
1694   - await _db.Deleteable<LqKdDeductinfoEntity>().Where(x => x.BillingId == id).ExecuteCommandAsync();
1695   - //循环品相信息
1696   - // 收集所有需要插入的实体,然后批量插入
1697   - var allPxmxEntities = new List<LqKdPxmxEntity>();
1698   - var allJksyjEntities = new List<LqKdJksyjEntity>();
1699   - var allKjbsyjEntities = new List<LqKdKjbsyjEntity>();
1700   - var allDeductEntities = new List<LqKdDeductinfoEntity>();
1701   - // 处理扣款信息列表
1702   - foreach (var item in input.lqKdKdjlbDeductList)
  1703 + // 标记开单记录为无效,并添加作废备注
  1704 + entity.IsEffective = StatusEnum.无效.GetHashCode();
  1705 + entity.CancelRefRemarks = input.Remarks;
  1706 + entity.UpdateTime = DateTime.Now;
  1707 + await _db.Updateable(entity).ExecuteCommandAsync();
  1708 +
  1709 + // 标记对应开单明细表为无效
  1710 + await _db.Updateable<LqKdPxmxEntity>().SetColumns(it => new LqKdPxmxEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync();
  1711 +
  1712 + // 标记健康师业绩为无效
  1713 + await _db.Updateable<LqKdJksyjEntity>().SetColumns(it => new LqKdJksyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync();
  1714 +
  1715 + // 标记科技部老师业绩为无效
  1716 + await _db.Updateable<LqKdKjbsyjEntity>().SetColumns(it => new LqKdKjbsyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync();
  1717 +
  1718 + // 标记开单_储扣详细表为无效
  1719 + await _db.Updateable<LqKdDeductinfoEntity>().SetColumns(it => new LqKdDeductinfoEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.BillingId == input.Id).ExecuteCommandAsync();
  1720 +
  1721 + //如果存在补缴开单ID,则将补缴开单ID的IsEffective设置为无效
  1722 + if (!string.IsNullOrEmpty(entity.SupplementBillingId))
1703 1723 {
1704   - var lqKdDeductEntity = new LqKdDeductinfoEntity
  1724 + var supplementBillingEntity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == entity.SupplementBillingId);
  1725 + if (supplementBillingEntity != null && supplementBillingEntity.IsEffective == StatusEnum.有效.GetHashCode())
1705 1726 {
1706   - Id = YitIdHelper.NextId().ToString(),
1707   - BillingId = id,
1708   - DeductId = item.DeductId,
1709   - DeductType = item.DeductType,
1710   - Amount = item.Amount,
1711   - ProjectNumber = item.ProjectNumber,
1712   - UnitPrice = item.UnitPrice,
1713   - ItemName = item.ItemName,
1714   - ItemId = item.ItemId,
1715   - IsEffective = StatusEnum.有效.GetHashCode(), // 设置为有效
1716   - CreateTime = DateTime.Now, // 设置创建时间
1717   - ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync(),
1718   - };
1719   - allDeductEntities.Add(lqKdDeductEntity);
1720   - }
1721   - // 处理品项明细列表
1722   - foreach (var item in input.lqKdPxmxList)
1723   - {
1724   - // 创建品项明细实体
1725   - var lqKdPxmxEntity = new LqKdPxmxEntity
1726   - {
1727   - Id = YitIdHelper.NextId().ToString(),
1728   - Glkdbh = id,
1729   - Yjsj = input.kdrq,
1730   - CreateTIme = DateTime.Now,
1731   - MemberId = entity.Kdhy,
1732   - IsEnabled = StatusEnum.有效.GetHashCode(),
1733   - ProjectNumber = item.projectNumber,
1734   - TotalPrice = (decimal)(item.pxjg * item.projectNumber),
1735   - Px = item.px,
1736   - Pxmc = item.pxmc,
1737   - Pxjg = item.pxjg,
1738   - SourceType = item.sourceType,
1739   - ActualPrice = item.actualPrice,
1740   - Remark = item.remark,
1741   - IsEffective = StatusEnum.有效.GetHashCode(),
1742   - ActivityId = input.activityId,
1743   - ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
1744   - };
1745   - allPxmxEntities.Add(lqKdPxmxEntity);
1746   -
1747   - // 收集该品项关联的健康师业绩
1748   - if (item.lqKdJksyjList != null && item.lqKdJksyjList.Any())
1749   - {
1750   - //把jksxm保存到HealthInstructorNames
1751   - foreach (var ijks_tem in item.lqKdJksyjList)
1752   - {
1753   - allJksyjEntities.Add(new LqKdJksyjEntity
1754   - {
1755   - Id = YitIdHelper.NextId().ToString(),
1756   - Glkdbh = id,
1757   - Jks = ijks_tem.jks,
1758   - Jksxm = ijks_tem.jksxm,
1759   - Jkszh = ijks_tem.jkszh,
1760   - Jksyj = ijks_tem.jksyj,
1761   - Yjsj = input.kdrq,
1762   - Jsj_id = ijks_tem.jsj_id,
1763   - Kdpxid = lqKdPxmxEntity.Id,
1764   - IsEffective = StatusEnum.有效.GetHashCode(),
1765   - ActivityId = input.activityId,
1766   - ItemCategory = lqKdPxmxEntity.ItemCategory,
1767   - ItemId = lqKdPxmxEntity.Px,
1768   - });
1769   - }
1770   - }
1771   -
1772   - // 收集该品项关联的科技部老师业绩
1773   - if (item.lqKdKjbsyjList != null && item.lqKdKjbsyjList.Any())
1774   - {
1775   - foreach (var ikjbs_tem in item.lqKdKjbsyjList)
1776   - {
1777   - allKjbsyjEntities.Add(new LqKdKjbsyjEntity
1778   - {
1779   - Id = YitIdHelper.NextId().ToString(),
1780   - Glkdbh = id,
1781   - Kjbls = ikjbs_tem.kjbls,
1782   - Kjblsxm = ikjbs_tem.kjblsxm,
1783   - Kjblszh = ikjbs_tem.kjblszh,
1784   - Kjblsyj = ikjbs_tem.kjblsyj,
1785   - Yjsj = input.kdrq,
1786   - Kdpxid = lqKdPxmxEntity.Id,
1787   - IsEffective = StatusEnum.有效.GetHashCode(),
1788   - ActivityId = input.activityId,
1789   - ItemCategory = lqKdPxmxEntity.ItemCategory,
1790   - ItemId = lqKdPxmxEntity.Px,
1791   - }
1792   - );
1793   - }
1794   - }
1795   - }
1796   -
1797   -
1798   -
1799   - //通过会员id查询会员信息
1800   - var memberInfo = await _db.Queryable<LqKhxxEntity>().Where(u => u.Id == entity.Kdhy).FirstAsync();
1801   - //通过开单记录表查询这个会员开单金额
1802   - var kdAmount = await _db.Queryable<LqKdKdjlbEntity>().Where(u => u.Kdhy == entity.Kdhy).SumAsync(u => u.Sfyj);
1803   - //如果开单金额小于500,为散客,如果大于500,为会员
1804   - if (kdAmount < 500)
1805   - {
1806   - memberInfo.Khlx = MemberTypeEnum.散客.GetHashCode().ToString();
1807   - }
1808   - else
1809   - {
1810   - memberInfo.Khlx = MemberTypeEnum.会员.GetHashCode().ToString();
1811   - }
1812   - await _db.Updateable(memberInfo).ExecuteCommandAsync();
1813   - // 批量插入扣款信息
1814   - if (allDeductEntities.Any())
1815   - {
1816   - await _db.Insertable(allDeductEntities).ExecuteCommandAsync();
1817   - }
1818   - // 批量插入品项明细
1819   - if (allPxmxEntities.Any())
1820   - {
1821   - await _db.Insertable(allPxmxEntities).ExecuteCommandAsync();
1822   - }
1823   - // 批量插入健康师业绩
1824   - if (allJksyjEntities.Any())
1825   - {
1826   - await _db.Insertable(allJksyjEntities).ExecuteCommandAsync();
1827   - }
1828   - // 批量插入科技部老师业绩
1829   - if (allKjbsyjEntities.Any())
1830   - {
1831   - await _db.Insertable(allKjbsyjEntities).ExecuteCommandAsync();
1832   - }
1833   -
1834   - //关闭事务
1835   - _db.CommitTran();
1836   - }
1837   - catch (Exception ex)
1838   - {
1839   - //回滚事务
1840   - _db.RollbackTran();
1841   - throw NCCException.Oh($"创建开单记录失败: {ex.Message}");
1842   - }
1843   - }
1844   - #endregion
1845   -
1846   - #region 作废开单记录
1847   - /// <summary>
1848   - /// 作废开单记录
1849   - /// </summary>
1850   - /// <param name="input">作废开单记录输入</param>
1851   - /// <returns>无返回值</returns>
1852   - /// <remarks>
1853   - /// 作废指定的开单记录,包括相关的品项明细、业绩记录等
1854   - ///
1855   - /// 示例请求:
1856   - /// ```json
1857   - /// {
1858   - /// "id": "123456789",
1859   - /// "remarks": "客户要求作废此订单"
1860   - /// }
1861   - /// ```
1862   - ///
1863   - /// 参数说明:
1864   - /// - id: 开单记录主键ID(必填)
1865   - /// - remarks: 作废备注说明
1866   - /// </remarks>
1867   - /// <response code="200">作废成功</response>
1868   - /// <response code="400">参数错误,开单记录ID不能为空</response>
1869   - /// <response code="404">开单记录不存在</response>
1870   - /// <response code="500">服务器内部错误</response>
1871   - [HttpPut("Cancel")]
1872   - public async Task Cancel(CancelBillingInput input)
1873   - {
1874   - if (string.IsNullOrEmpty(input.Id))
1875   - {
1876   - throw NCCException.Oh("开单记录ID不能为空");
1877   - }
1878   - try
1879   - {
1880   - //开启事务
1881   - _db.BeginTran();
1882   - // 检查开单记录是否可以作废
1883   - var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(input.Id);
1884   - if (!canCancel)
1885   - {
1886   - throw NCCException.Oh(errorMessage);
1887   - }
1888   -
1889   - // 查询开单记录
1890   - var entity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == input.Id);
1891   - if (entity == null)
1892   - {
1893   - throw NCCException.Oh("开单记录不存在");
1894   - }
1895   -
1896   - // 标记开单记录为无效,并添加作废备注
1897   - entity.IsEffective = StatusEnum.无效.GetHashCode();
1898   - entity.CancelRefRemarks = input.Remarks;
1899   - entity.UpdateTime = DateTime.Now;
1900   - await _db.Updateable(entity).ExecuteCommandAsync();
1901   -
1902   - // 标记对应开单明细表为无效
1903   - await _db.Updateable<LqKdPxmxEntity>().SetColumns(it => new LqKdPxmxEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync();
1904   -
1905   - // 标记健康师业绩为无效
1906   - await _db.Updateable<LqKdJksyjEntity>().SetColumns(it => new LqKdJksyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync();
1907   -
1908   - // 标记科技部老师业绩为无效
1909   - await _db.Updateable<LqKdKjbsyjEntity>().SetColumns(it => new LqKdKjbsyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync();
1910   -
1911   - // 标记开单_储扣详细表为无效
1912   - await _db.Updateable<LqKdDeductinfoEntity>().SetColumns(it => new LqKdDeductinfoEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.BillingId == input.Id).ExecuteCommandAsync();
1913   -
1914   - //如果存在补缴开单ID,则将补缴开单ID的IsEffective设置为无效
1915   - if (!string.IsNullOrEmpty(entity.SupplementBillingId))
1916   - {
1917   - var supplementBillingEntity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == entity.SupplementBillingId);
1918   - if (supplementBillingEntity != null && supplementBillingEntity.IsEffective == StatusEnum.有效.GetHashCode())
1919   - {
1920   - supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - entity.SupplementAmount;
1921   - await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync();
1922   - }
  1727 + supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - entity.SupplementAmount;
  1728 + await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync();
  1729 + }
1923 1730 }
1924 1731 //关闭事务
1925 1732 _db.CommitTran();
... ... @@ -2325,7 +2132,7 @@ namespace NCC.Extend.LqKdKdjlb
2325 2132 }
2326 2133 #endregion
2327 2134  
2328   - #region 根据会员id获取会员的开单品项列表
  2135 + #region 根据会员id获取会员的开单品项列表
2329 2136 /// <summary>
2330 2137 /// 根据会员id获取会员的开单品项列表
2331 2138 /// </summary>
... ... @@ -2539,9 +2346,18 @@ namespace NCC.Extend.LqKdKdjlb
2539 2346 input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month));
2540 2347 }
2541 2348  
  2349 + // 构建开单记录查询条件
  2350 + var billingQuery = _db.Queryable<LqKdKdjlbEntity>()
  2351 + .Where(w => w.Djmd == input.StoreId && w.Kdrq >= input.StartTime && w.Kdrq <= input.EndTime && w.IsEffective == StatusEnum.有效.GetHashCode());
  2352 +
  2353 + // 客户筛选
  2354 + if (!string.IsNullOrEmpty(input.MemberId))
  2355 + {
  2356 + billingQuery = billingQuery.Where(w => w.Kdhy == input.MemberId);
  2357 + }
  2358 +
2542 2359 // 查询开单记录
2543   - var billingRecords = await _db.Queryable<LqKdKdjlbEntity>()
2544   - .Where(w => w.Djmd == input.StoreId && w.Kdrq >= input.StartTime && w.Kdrq <= input.EndTime && w.IsEffective == StatusEnum.有效.GetHashCode())
  2360 + var billingRecords = await billingQuery
2545 2361 .Select(it => new
2546 2362 {
2547 2363 id = it.Id,
... ... @@ -2594,9 +2410,24 @@ namespace NCC.Extend.LqKdKdjlb
2594 2410  
2595 2411 var billingIds = billingRecords.Select(x => x.id).ToList();
2596 2412  
  2413 + // 构建品项明细查询条件
  2414 + var itemDetailsQuery = _db.Queryable<LqKdPxmxEntity>()
  2415 + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode());
  2416 +
  2417 + // 品项分类筛选
  2418 + if (!string.IsNullOrEmpty(input.ItemCategory))
  2419 + {
  2420 + itemDetailsQuery = itemDetailsQuery.Where(w => w.ItemCategory == input.ItemCategory);
  2421 + }
  2422 +
  2423 + // 品项筛选
  2424 + if (!string.IsNullOrEmpty(input.ItemId))
  2425 + {
  2426 + itemDetailsQuery = itemDetailsQuery.Where(w => w.Px == input.ItemId);
  2427 + }
  2428 +
2597 2429 // 查询品项明细,按F_SourceType分类
2598   - var itemDetails = await _db.Queryable<LqKdPxmxEntity>()
2599   - .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode())
  2430 + var itemDetails = await itemDetailsQuery
2600 2431 .Select(it => new
2601 2432 {
2602 2433 id = it.Id,
... ... @@ -2618,9 +2449,30 @@ namespace NCC.Extend.LqKdKdjlb
2618 2449 var giftedItems = itemDetails.Where(x => x.sourceType == "赠送").ToList();
2619 2450 var experienceItems = itemDetails.Where(x => x.sourceType == "体验").ToList();
2620 2451  
  2452 + // 构建健康师业绩查询条件
  2453 + var healthTeacherQuery = _db.Queryable<LqKdJksyjEntity>()
  2454 + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode());
  2455 +
  2456 + // 健康师筛选
  2457 + if (!string.IsNullOrEmpty(input.HealthCoachId))
  2458 + {
  2459 + healthTeacherQuery = healthTeacherQuery.Where(w => w.Jks == input.HealthCoachId || w.Jkszh == input.HealthCoachId);
  2460 + }
  2461 +
  2462 + // 品项分类筛选(健康师业绩表)
  2463 + if (!string.IsNullOrEmpty(input.ItemCategory))
  2464 + {
  2465 + healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemCategory == input.ItemCategory);
  2466 + }
  2467 +
  2468 + // 品项筛选(健康师业绩表)
  2469 + if (!string.IsNullOrEmpty(input.ItemId))
  2470 + {
  2471 + healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemId == input.ItemId);
  2472 + }
  2473 +
2621 2474 // 查询健康师业绩数据
2622   - var healthTeacherData = await _db.Queryable<LqKdJksyjEntity>()
2623   - .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode())
  2475 + var healthTeacherData = await healthTeacherQuery
2624 2476 .Select(it => new
2625 2477 {
2626 2478 id = it.Id,
... ... @@ -2750,31 +2602,266 @@ namespace NCC.Extend.LqKdKdjlb
2750 2602 }
2751 2603 #endregion
2752 2604  
2753   - #region 私有方法
  2605 + #region 获取门店某个时间段的开单记录汇总信息(备份)
2754 2606 /// <summary>
2755   - /// 检查开单记录是否可以操作
  2607 + /// 获取门店某个时间段的开单记录汇总信息(备份)
2756 2608 /// </summary>
2757   - /// <param name="billingId">开单记录ID</param>
2758   - /// <returns>是否可以作废</returns>
2759   - private async Task<(bool canCancel, string errorMessage)> CheckBillingCanCancelAsync(string billingId)
  2609 + /// <param name="input">查询参数</param>
  2610 + /// <returns>开单记录汇总信息</returns>
  2611 + [HttpGet("GetBillingRecordSummaryByStoreId_bak")]
  2612 + public async Task<dynamic> GetBillingRecordSummaryByStoreId_bak([FromQuery] BillingRecordSummaryQueryInput input)
2760 2613 {
2761 2614 try
2762 2615 {
2763   - // 查询开单记录
2764   - var entity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == billingId);
2765   - if (entity == null)
  2616 + // 验证参数
  2617 + if (string.IsNullOrEmpty(input.StoreId))
2766 2618 {
2767   - return (false, "开单记录不存在");
  2619 + throw NCCException.Oh("门店ID不能为空");
2768 2620 }
2769   - // 检查是否已经作废
2770   - if (entity.IsEffective == StatusEnum.无效.GetHashCode())
  2621 +
  2622 + // 如果开始时间和结束时间为空,就默认是当月
  2623 + if (input.StartTime == null || input.EndTime == null)
2771 2624 {
2772   - return (false, "该开单记录已经作废");
  2625 + input.StartTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  2626 + input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month));
2773 2627 }
2774   - // 判断是否有对应的补缴记录
2775   - var qkbjList = await _db.Queryable<LqKdKdjlbEntity>().Where(p => p.SupplementBillingId == billingId && p.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
2776   - if (qkbjList.Any())
2777   - {
  2628 +
  2629 + // 查询开单记录
  2630 + var billingRecords = await _db.Queryable<LqKdKdjlbEntity>()
  2631 + .Where(w => w.Djmd == input.StoreId && w.Kdrq >= input.StartTime && w.Kdrq <= input.EndTime && w.IsEffective == StatusEnum.有效.GetHashCode())
  2632 + .Select(it => new
  2633 + {
  2634 + id = it.Id,
  2635 + djmd = it.Djmd,
  2636 + jsj = it.Jsj,
  2637 + kdrq = it.Kdrq,
  2638 + gjlx = it.Gjlx,
  2639 + zdyj = it.Zdyj,
  2640 + sfyj = it.Sfyj,
  2641 + qk = it.Qk,
  2642 + kdhyc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc),
  2643 + kdhysjh = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh),
  2644 + fkfs = it.Fkfs, // 付款方式
  2645 + khly = it.Khly, // 客户来源
  2646 + bz = it.Bz, // 备注
  2647 + createTime = it.CreateTime
  2648 + })
  2649 + .ToListAsync();
  2650 +
  2651 + if (!billingRecords.Any())
  2652 + {
  2653 + return new
  2654 + {
  2655 + success = true,
  2656 + data = new
  2657 + {
  2658 + billingSummary = new
  2659 + {
  2660 + totalCount = 0,
  2661 + totalAmount = 0,
  2662 + totalPaidAmount = 0,
  2663 + totalDebt = 0,
  2664 + billingRecords = new List<object>()
  2665 + },
  2666 + itemSummary = new
  2667 + {
  2668 + purchased = new { count = 0, totalAmount = 0, items = new List<object>() },
  2669 + gifted = new { count = 0, totalAmount = 0, items = new List<object>() },
  2670 + experience = new { count = 0, totalAmount = 0, items = new List<object>() }
  2671 + },
  2672 + healthTeacherSummary = new
  2673 + {
  2674 + totalCount = 0,
  2675 + teachers = new List<object>()
  2676 + }
  2677 + },
  2678 + message = "该时间段内无开单记录"
  2679 + };
  2680 + }
  2681 +
  2682 + var billingIds = billingRecords.Select(x => x.id).ToList();
  2683 +
  2684 + // 查询品项明细,按F_SourceType分类
  2685 + var itemDetails = await _db.Queryable<LqKdPxmxEntity>()
  2686 + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode())
  2687 + .Select(it => new
  2688 + {
  2689 + id = it.Id,
  2690 + glkdbh = it.Glkdbh,
  2691 + px = it.Px,
  2692 + pxmc = it.Pxmc,
  2693 + pxjg = it.Pxjg,
  2694 + sourceType = it.SourceType,
  2695 + totalPrice = it.TotalPrice,
  2696 + actualPrice = it.ActualPrice,
  2697 + projectNumber = it.ProjectNumber,
  2698 + remark = it.Remark,
  2699 + itemCategory = it.ItemCategory,
  2700 + })
  2701 + .ToListAsync();
  2702 +
  2703 + // 按来源类型分类品项
  2704 + var purchasedItems = itemDetails.Where(x => x.sourceType == "购买").ToList();
  2705 + var giftedItems = itemDetails.Where(x => x.sourceType == "赠送").ToList();
  2706 + var experienceItems = itemDetails.Where(x => x.sourceType == "体验").ToList();
  2707 +
  2708 + // 查询健康师业绩数据
  2709 + var healthTeacherData = await _db.Queryable<LqKdJksyjEntity>()
  2710 + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode())
  2711 + .Select(it => new
  2712 + {
  2713 + id = it.Id,
  2714 + glkdbh = it.Glkdbh,
  2715 + jks = it.Jks,
  2716 + jksxm = it.Jksxm,
  2717 + jkszh = it.Jkszh,
  2718 + jksyj = it.Jksyj,
  2719 + kdpxid = it.Kdpxid,
  2720 + yjsj = it.Yjsj,
  2721 + jsj_id = it.Jsj_id,
  2722 + itemCategory = it.ItemCategory,
  2723 + storeId = it.StoreId,
  2724 + itemId = it.ItemId,
  2725 + itemName = it.ItemName,
  2726 + })
  2727 + .ToListAsync();
  2728 +
  2729 + // 构建按开单记录分组的详细数据
  2730 + var detailedRecords = billingRecords.Select(billing => new
  2731 + {
  2732 + // 基本信息
  2733 + date = billing.kdrq?.ToString("yyyy-MM-dd"),
  2734 + customerName = billing.kdhyc,
  2735 + customerPhone = billing.kdhysjh,
  2736 + storeId = billing.djmd,
  2737 + goldTriangle = billing.jsj,
  2738 + customerType = billing.gjlx,
  2739 +
  2740 + // 品项分类
  2741 + purchasedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "购买").Select(x => new
  2742 + {
  2743 + id = x.id,
  2744 + itemName = x.pxmc,
  2745 + itemCode = x.px,
  2746 + price = x.pxjg,
  2747 + totalPrice = x.totalPrice,
  2748 + actualPrice = x.actualPrice,
  2749 + projectNumber = x.projectNumber,
  2750 + remark = x.remark,
  2751 + itemCategory = x.itemCategory,
  2752 + }).ToList(),
  2753 +
  2754 + giftedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "赠送").Select(x => new
  2755 + {
  2756 + id = x.id,
  2757 + itemName = x.pxmc,
  2758 + itemCode = x.px,
  2759 + price = x.pxjg,
  2760 + totalPrice = x.totalPrice,
  2761 + actualPrice = x.actualPrice,
  2762 + projectNumber = x.projectNumber,
  2763 + remark = x.remark,
  2764 + itemCategory = x.itemCategory,
  2765 + }).ToList(),
  2766 +
  2767 + experienceItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "体验").Select(x => new
  2768 + {
  2769 + id = x.id,
  2770 + itemName = x.pxmc,
  2771 + itemCode = x.px,
  2772 + price = x.pxjg,
  2773 + totalPrice = x.totalPrice,
  2774 + actualPrice = x.actualPrice,
  2775 + projectNumber = x.projectNumber,
  2776 + remark = x.remark,
  2777 + itemCategory = x.itemCategory,
  2778 + }).ToList(),
  2779 +
  2780 + // 金额信息
  2781 + paidAmount = billing.sfyj,
  2782 + debtAmount = billing.qk,
  2783 + totalAmount = billing.zdyj,
  2784 +
  2785 + // 健康师信息
  2786 + healthTeachers = healthTeacherData.Where(x => x.glkdbh == billing.id).Select(x => new
  2787 + {
  2788 + teacherId = x.jks,
  2789 + teacherName = x.jksxm,
  2790 + teacherAccount = x.jkszh,
  2791 + performance = x.jksyj,
  2792 + performanceTime = x.yjsj,
  2793 + itemDetailId = x.kdpxid,
  2794 + goldTriangleId = x.jsj_id,
  2795 + itemCategory = x.itemCategory,
  2796 + itemId = x.itemId,
  2797 + storeId = x.storeId,
  2798 + itemName = x.itemName
  2799 + }).ToList(),
  2800 +
  2801 + // 其他信息
  2802 + source = billing.khly, // 客户来源
  2803 + paymentMethod = billing.fkfs, // 支付方式
  2804 + remark = billing.bz, // 备注
  2805 + createTime = billing.createTime
  2806 + }).OrderByDescending(x => x.date).ToList();
  2807 +
  2808 + // 构建返回结果
  2809 + var result = new
  2810 + {
  2811 + success = true,
  2812 + data = new
  2813 + {
  2814 + // 汇总统计
  2815 + summary = new
  2816 + {
  2817 + totalCount = billingRecords.Count,
  2818 + totalAmount = billingRecords.Sum(x => x.zdyj),
  2819 + totalPaidAmount = billingRecords.Sum(x => x.sfyj),
  2820 + totalDebt = billingRecords.Sum(x => x.qk),
  2821 + totalPurchasedItems = purchasedItems.Count,
  2822 + totalGiftedItems = giftedItems.Count,
  2823 + totalExperienceItems = experienceItems.Count,
  2824 + totalHealthTeachers = healthTeacherData.GroupBy(x => x.jks).Count()
  2825 + },
  2826 + // 详细记录列表
  2827 + records = detailedRecords
  2828 + },
  2829 + message = "获取开单记录汇总信息成功"
  2830 + };
  2831 + return result;
  2832 + }
  2833 + catch (Exception ex)
  2834 + {
  2835 + throw NCCException.Oh($"获取开单记录汇总信息失败:{ex.Message}");
  2836 + }
  2837 + }
  2838 + #endregion
  2839 +
  2840 + #region 私有方法
  2841 + /// <summary>
  2842 + /// 检查开单记录是否可以操作
  2843 + /// </summary>
  2844 + /// <param name="billingId">开单记录ID</param>
  2845 + /// <returns>是否可以作废</returns>
  2846 + private async Task<(bool canCancel, string errorMessage)> CheckBillingCanCancelAsync(string billingId)
  2847 + {
  2848 + try
  2849 + {
  2850 + // 查询开单记录
  2851 + var entity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == billingId);
  2852 + if (entity == null)
  2853 + {
  2854 + return (false, "开单记录不存在");
  2855 + }
  2856 + // 检查是否已经作废
  2857 + if (entity.IsEffective == StatusEnum.无效.GetHashCode())
  2858 + {
  2859 + return (false, "该开单记录已经作废");
  2860 + }
  2861 + // 判断是否有对应的补缴记录
  2862 + var qkbjList = await _db.Queryable<LqKdKdjlbEntity>().Where(p => p.SupplementBillingId == billingId && p.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
  2863 + if (qkbjList.Any())
  2864 + {
2778 2865 return (false, "该开单记录有对应的补缴记录,不能进行操作");
2779 2866 }
2780 2867 // 查询开单记录下的品项明细ID列表
... ... @@ -3258,6 +3345,9 @@ namespace NCC.Extend.LqKdKdjlb
3258 3345 /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次)
3259 3346 /// - ProjectCount: 消耗项目数(项目总次数)
3260 3347 /// - RefundAmount: 退卡金额(健康师退卡业绩总金额)
  3348 + /// - LaborCost: 手工费(消耗时的手工费总金额)
  3349 + /// - OriginalLaborCost: 原始手工费(消耗时的原始手工费总金额)
  3350 + /// - OvertimeLaborCost: 加班手工费(消耗时的加班手工费总金额)
3261 3351 /// </remarks>
3262 3352 /// <param name="input">查询参数</param>
3263 3353 /// <returns>健康师统计数据列表</returns>
... ... @@ -3305,7 +3395,357 @@ namespace NCC.Extend.LqKdKdjlb
3305 3395 CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount,
3306 3396  
3307 3397 -- 退卡金额
3308   - COALESCE(refund_stats.RefundAmount, 0) as RefundAmount
  3398 + COALESCE(refund_stats.RefundAmount, 0) as RefundAmount,
  3399 +
  3400 + -- 手工费相关统计
  3401 + COALESCE(consume_stats.LaborCost, 0) as LaborCost,
  3402 + COALESCE(consume_stats.OriginalLaborCost, 0) as OriginalLaborCost,
  3403 + COALESCE(consume_stats.OvertimeLaborCost, 0) as OvertimeLaborCost
  3404 +
  3405 + FROM BASE_USER u
  3406 + LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
  3407 + LEFT JOIN base_organize dept ON md.syb = dept.F_Id
  3408 +
  3409 + -- 邀约统计子查询
  3410 + LEFT JOIN (
  3411 + SELECT
  3412 + yyr as EmployeeId,
  3413 + COUNT(DISTINCT yykh) as InviteCount
  3414 + FROM lq_yaoyjl
  3415 + WHERE yyr IS NOT NULL
  3416 + AND F_CreateTime >= @startTime
  3417 + AND F_CreateTime <= @endTime
  3418 + GROUP BY yyr
  3419 + ) invite_stats ON u.F_Id = invite_stats.EmployeeId
  3420 +
  3421 + -- 预约统计子查询
  3422 + LEFT JOIN (
  3423 + SELECT
  3424 + yyr as EmployeeId,
  3425 + COUNT(DISTINCT gk) as AppointmentCount
  3426 + FROM lq_yyjl
  3427 + WHERE yyr IS NOT NULL
  3428 + AND F_CreateTime >= @startTime
  3429 + AND F_CreateTime <= @endTime
  3430 + GROUP BY yyr
  3431 + ) appointment_stats ON u.F_Id = appointment_stats.EmployeeId
  3432 +
  3433 + -- 到店统计子查询
  3434 + LEFT JOIN (
  3435 + SELECT
  3436 + yyr as EmployeeId,
  3437 + COUNT(DISTINCT gk) as VisitCount
  3438 + FROM lq_yyjl
  3439 + WHERE yyr IS NOT NULL
  3440 + AND F_Status = '已确认'
  3441 + AND F_CreateTime >= @startTime
  3442 + AND F_CreateTime <= @endTime
  3443 + GROUP BY yyr
  3444 + ) visit_stats ON u.F_Id = visit_stats.EmployeeId
  3445 +
  3446 + -- 开单统计子查询
  3447 + LEFT JOIN (
  3448 + SELECT
  3449 + jkszh as EmployeeId,
  3450 + COUNT(DISTINCT glkdbh) as BillingCount,
  3451 + SUM(CAST(jksyj AS DECIMAL(18,2))) as BillingAmount
  3452 + FROM lq_kd_jksyj
  3453 + WHERE jkszh IS NOT NULL
  3454 + AND F_IsEffective = 1
  3455 + AND yjsj >= @startTime
  3456 + AND yjsj <= @endTime
  3457 + GROUP BY jkszh
  3458 + ) billing_stats ON u.F_Id = billing_stats.EmployeeId
  3459 +
  3460 + -- 消耗统计子查询
  3461 + LEFT JOIN (
  3462 + SELECT
  3463 + jksyj.jks as EmployeeId,
  3464 + SUM(jksyj.jksyj) as ConsumeAmount,
  3465 + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount,
  3466 + COALESCE(SUM(jksyj.F_LaborCost), 0) as LaborCost,
  3467 + COALESCE(SUM(jksyj.F_OriginalLaborCost), 0) as OriginalLaborCost,
  3468 + COALESCE(SUM(jksyj.F_OvertimeLaborCost), 0) as OvertimeLaborCost
  3469 + FROM lq_xh_jksyj jksyj
  3470 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
  3471 + WHERE jksyj.jks IS NOT NULL
  3472 + AND jksyj.F_IsEffective = 1
  3473 + AND hyhk.F_IsEffective = 1
  3474 + AND hyhk.hksj >= @startTime
  3475 + AND hyhk.hksj <= @endTime
  3476 + GROUP BY jksyj.jks
  3477 + ) consume_stats ON u.F_Id = consume_stats.EmployeeId
  3478 +
  3479 + -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1)
  3480 + LEFT JOIN (
  3481 + SELECT
  3482 + F_PersonId as EmployeeId,
  3483 + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount
  3484 + FROM (
  3485 + SELECT
  3486 + F_PersonId,
  3487 + F_WorkMonth,
  3488 + F_MemberId,
  3489 + F_Quantity
  3490 + FROM lq_person_times_record
  3491 + WHERE F_PersonId IS NOT NULL
  3492 + AND F_IsEffective = 1
  3493 + AND F_PersonType = '健康师'
  3494 + AND F_HasBilling = 1
  3495 + AND F_WorkDate >= DATE(@startTime)
  3496 + AND F_WorkDate <= DATE(@endTime)
  3497 + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity
  3498 + ) as distinct_headcount
  3499 + GROUP BY F_PersonId
  3500 + ) headcount_stats ON u.F_Id = headcount_stats.EmployeeId
  3501 +
  3502 + -- 有效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=1)
  3503 + LEFT JOIN (
  3504 + SELECT
  3505 + F_PersonId as EmployeeId,
  3506 + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount
  3507 + FROM (
  3508 + SELECT
  3509 + F_PersonId,
  3510 + F_WorkDate,
  3511 + F_MemberId,
  3512 + F_Quantity
  3513 + FROM lq_person_times_record
  3514 + WHERE F_PersonId IS NOT NULL
  3515 + AND F_IsEffective = 1
  3516 + AND F_PersonType = '健康师'
  3517 + AND F_HasBilling = 1
  3518 + AND F_WorkDate >= DATE(@startTime)
  3519 + AND F_WorkDate <= DATE(@endTime)
  3520 + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity
  3521 + ) as distinct_personcount
  3522 + GROUP BY F_PersonId
  3523 + ) personcount_stats ON u.F_Id = personcount_stats.EmployeeId
  3524 +
  3525 + -- 无效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=0)
  3526 + LEFT JOIN (
  3527 + SELECT
  3528 + F_PersonId as EmployeeId,
  3529 + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount
  3530 + FROM (
  3531 + SELECT
  3532 + F_PersonId,
  3533 + F_WorkMonth,
  3534 + F_MemberId,
  3535 + F_Quantity
  3536 + FROM lq_person_times_record
  3537 + WHERE F_PersonId IS NOT NULL
  3538 + AND F_IsEffective = 1
  3539 + AND F_PersonType = '健康师'
  3540 + AND F_HasBilling = 0
  3541 + AND F_WorkDate >= DATE(@startTime)
  3542 + AND F_WorkDate <= DATE(@endTime)
  3543 + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity
  3544 + ) as distinct_headcount
  3545 + GROUP BY F_PersonId
  3546 + ) invalid_headcount_stats ON u.F_Id = invalid_headcount_stats.EmployeeId
  3547 +
  3548 + -- 无效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=0)
  3549 + LEFT JOIN (
  3550 + SELECT
  3551 + F_PersonId as EmployeeId,
  3552 + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount
  3553 + FROM (
  3554 + SELECT
  3555 + F_PersonId,
  3556 + F_WorkDate,
  3557 + F_MemberId,
  3558 + F_Quantity
  3559 + FROM lq_person_times_record
  3560 + WHERE F_PersonId IS NOT NULL
  3561 + AND F_IsEffective = 1
  3562 + AND F_PersonType = '健康师'
  3563 + AND F_HasBilling = 0
  3564 + AND F_WorkDate >= DATE(@startTime)
  3565 + AND F_WorkDate <= DATE(@endTime)
  3566 + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity
  3567 + ) as distinct_personcount
  3568 + GROUP BY F_PersonId
  3569 + ) invalid_personcount_stats ON u.F_Id = invalid_personcount_stats.EmployeeId
  3570 +
  3571 + -- 退卡统计子查询
  3572 + LEFT JOIN (
  3573 + SELECT
  3574 + jkszh as EmployeeId,
  3575 + SUM(CAST(jksyj AS DECIMAL(18,2))) as RefundAmount
  3576 + FROM lq_hytk_jksyj
  3577 + WHERE jkszh IS NOT NULL
  3578 + AND F_IsEffective = 1
  3579 + AND tksj >= @startTime
  3580 + AND tksj <= @endTime
  3581 + GROUP BY jkszh
  3582 + ) refund_stats ON u.F_Id = refund_stats.EmployeeId
  3583 +
  3584 + WHERE u.F_GW = '健康师'
  3585 + ";
  3586 +
  3587 + // 添加条件过滤
  3588 + var conditions = new List<string>();
  3589 + var parameters = new List<SugarParameter>
  3590 + {
  3591 + new SugarParameter("@startTime", startTime),
  3592 + new SugarParameter("@endTime", endTime)
  3593 + };
  3594 +
  3595 + if (!string.IsNullOrEmpty(input.DepartmentId))
  3596 + {
  3597 + conditions.Add("md.syb = @departmentId");
  3598 + parameters.Add(new SugarParameter("@departmentId", input.DepartmentId));
  3599 + }
  3600 +
  3601 + if (!string.IsNullOrEmpty(input.StoreId))
  3602 + {
  3603 + conditions.Add("u.F_MDID = @storeId");
  3604 + parameters.Add(new SugarParameter("@storeId", input.StoreId));
  3605 + }
  3606 +
  3607 + if (!string.IsNullOrEmpty(input.EmployeeName))
  3608 + {
  3609 + conditions.Add("u.F_REALNAME LIKE @employeeName");
  3610 + parameters.Add(new SugarParameter("@employeeName", $"%{input.EmployeeName}%"));
  3611 + }
  3612 +
  3613 + if (conditions.Any())
  3614 + {
  3615 + sql += " AND " + string.Join(" AND ", conditions);
  3616 + }
  3617 +
  3618 + sql += " ORDER BY u.F_REALNAME";
  3619 +
  3620 + // 执行查询
  3621 + var allData = await _db.Ado.SqlQueryAsync<HealthCoachStatisticsOutput>(sql, parameters);
  3622 +
  3623 + // 手动分页
  3624 + var totalCount = allData.Count;
  3625 + var pagedData = allData
  3626 + .Skip((input.currentPage - 1) * input.pageSize)
  3627 + .Take(input.pageSize)
  3628 + .ToList();
  3629 +
  3630 + // 直接返回分页结果
  3631 + return new
  3632 + {
  3633 + list = pagedData,
  3634 + pagination = new
  3635 + {
  3636 + pageIndex = input.currentPage,
  3637 + pageSize = input.pageSize,
  3638 + totalCount = totalCount
  3639 + }
  3640 + };
  3641 + }
  3642 + catch (Exception ex)
  3643 + {
  3644 + _logger.LogError(ex, "获取健康师统计数据失败");
  3645 + throw NCCException.Oh($"获取健康师统计数据失败:{ex.Message}");
  3646 + }
  3647 + }
  3648 + #endregion
  3649 +
  3650 + #region 门店整体统计表(备份)
  3651 + /// <summary>
  3652 + /// 门店整体统计表
  3653 + /// </summary>
  3654 + /// <remarks>
  3655 + /// 统计每个健康师在指定时间周期内的各项数据指标
  3656 + /// 包括:邀约人数、预约人数、到店人数、开单人数、开单金额、消耗金额、人头、人次、消耗项目数
  3657 + ///
  3658 + /// 示例请求:
  3659 + /// ```json
  3660 + /// {
  3661 + /// "startTime": "2025-10-01",
  3662 + /// "endTime": "2025-10-31",
  3663 + /// "departmentId": "部门ID",
  3664 + /// "storeId": "门店ID",
  3665 + /// "employeeName": "健康师姓名"
  3666 + /// }
  3667 + /// ```
  3668 + ///
  3669 + /// 参数说明:
  3670 + /// - startTime: 开始时间(可选,默认为当月1号)
  3671 + /// - endTime: 结束时间(可选,默认为当前时间)
  3672 + /// - departmentId: 事业部ID(可选)
  3673 + /// - storeId: 门店ID(可选)
  3674 + /// - employeeName: 健康师姓名(可选)
  3675 + ///
  3676 + /// 返回字段说明:
  3677 + /// - EmployeeId: 健康师ID
  3678 + /// - EmployeeName: 健康师姓名
  3679 + /// - StoreId: 门店ID
  3680 + /// - StoreName: 门店名称
  3681 + /// - DepartmentId: 事业部ID
  3682 + /// - DepartmentName: 事业部名称
  3683 + /// - InviteCount: 邀约人数(按客户去重)
  3684 + /// - AppointmentCount: 预约人数(按客户去重,无论预约状态)
  3685 + /// - VisitCount: 到店人数(按客户去重,仅统计状态为'已确认'的预约)
  3686 + /// - BillingCount: 开单人数(按开单记录去重)
  3687 + /// - BillingAmount: 开单金额(开单业绩总金额)
  3688 + /// - ConsumeAmount: 消耗金额(消耗业绩总金额)
  3689 + /// - HeadCount: 人头(按客户去重)
  3690 + /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次)
  3691 + /// - ProjectCount: 消耗项目数(项目总次数)
  3692 + /// - RefundAmount: 退卡金额(健康师退卡业绩总金额)
  3693 + /// - LaborCost: 手工费(消耗时的手工费总金额)
  3694 + /// - OriginalLaborCost: 原始手工费(消耗时的原始手工费总金额)
  3695 + /// - OvertimeLaborCost: 加班手工费(消耗时的加班手工费总金额)
  3696 + /// </remarks>
  3697 + /// <param name="input">查询参数</param>
  3698 + /// <returns>健康师统计数据列表</returns>
  3699 + /// <response code="200">成功返回统计数据</response>
  3700 + /// <response code="400">参数错误</response>
  3701 + /// <response code="500">服务器错误</response>
  3702 + [HttpGet("get-health-coach-statistics_bak")]
  3703 + public async Task<dynamic> GetHealthCoachStatistics_bak([FromQuery] HealthCoachStatisticsQueryInput input)
  3704 + {
  3705 + try
  3706 + {
  3707 + // 设置默认时间范围(如果未提供,默认为当月)
  3708 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  3709 + var endTime = input.EndTime ?? DateTime.Now;
  3710 +
  3711 + // 构建SQL查询
  3712 + var sql = $@"
  3713 + SELECT
  3714 + u.F_Id as EmployeeId,
  3715 + u.F_REALNAME as EmployeeName,
  3716 + u.F_MDID as StoreId,
  3717 + md.dm as StoreName,
  3718 + md.syb as DepartmentId,
  3719 + dept.F_FullName as DepartmentName,
  3720 +
  3721 + -- 邀约人数
  3722 + COALESCE(invite_stats.InviteCount, 0) as InviteCount,
  3723 +
  3724 + -- 预约人数(无论状态)
  3725 + COALESCE(appointment_stats.AppointmentCount, 0) as AppointmentCount,
  3726 +
  3727 + -- 到店人数(已确认状态)
  3728 + COALESCE(visit_stats.VisitCount, 0) as VisitCount,
  3729 +
  3730 + -- 开单人数和金额
  3731 + COALESCE(billing_stats.BillingCount, 0) as BillingCount,
  3732 + COALESCE(billing_stats.BillingAmount, 0) as BillingAmount,
  3733 +
  3734 + -- 消耗相关统计
  3735 + COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount,
  3736 + CAST(COALESCE(headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as HeadCount,
  3737 + CAST(COALESCE(personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as PersonCount,
  3738 + CAST(COALESCE(invalid_headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as InvalidHeadCount,
  3739 + CAST(COALESCE(invalid_personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as InvalidPersonCount,
  3740 + CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount,
  3741 +
  3742 + -- 退卡金额
  3743 + COALESCE(refund_stats.RefundAmount, 0) as RefundAmount,
  3744 +
  3745 + -- 手工费相关统计
  3746 + COALESCE(consume_stats.LaborCost, 0) as LaborCost,
  3747 + COALESCE(consume_stats.OriginalLaborCost, 0) as OriginalLaborCost,
  3748 + COALESCE(consume_stats.OvertimeLaborCost, 0) as OvertimeLaborCost
3309 3749  
3310 3750 FROM BASE_USER u
3311 3751 LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
... ... @@ -3365,17 +3805,20 @@ namespace NCC.Extend.LqKdKdjlb
3365 3805 -- 消耗统计子查询
3366 3806 LEFT JOIN (
3367 3807 SELECT
3368   - jksyj.jkszh as EmployeeId,
  3808 + jksyj.jks as EmployeeId,
3369 3809 SUM(jksyj.jksyj) as ConsumeAmount,
3370   - CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount
  3810 + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount,
  3811 + COALESCE(SUM(jksyj.F_LaborCost), 0) as LaborCost,
  3812 + COALESCE(SUM(jksyj.F_OriginalLaborCost), 0) as OriginalLaborCost,
  3813 + COALESCE(SUM(jksyj.F_OvertimeLaborCost), 0) as OvertimeLaborCost
3371 3814 FROM lq_xh_jksyj jksyj
3372 3815 INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
3373   - WHERE jksyj.jkszh IS NOT NULL
  3816 + WHERE jksyj.jks IS NOT NULL
3374 3817 AND jksyj.F_IsEffective = 1
3375 3818 AND hyhk.F_IsEffective = 1
3376 3819 AND hyhk.hksj >= @startTime
3377 3820 AND hyhk.hksj <= @endTime
3378   - GROUP BY jksyj.jkszh
  3821 + GROUP BY jksyj.jks
3379 3822 ) consume_stats ON u.F_Id = consume_stats.EmployeeId
3380 3823  
3381 3824 -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs
... ... @@ -254,6 +254,7 @@ namespace NCC.Extend
254 254 var productIds = data.list.Select(x => x.id).ToList();
255 255 if (productIds.Any())
256 256 {
  257 + // 查询总库存数量
257 258 var inventoryDict = await _db.Queryable<LqInventoryEntity>()
258 259 .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
259 260 .GroupBy(x => x.ProductId)
... ... @@ -262,10 +263,21 @@ namespace NCC.Extend
262 263  
263 264 var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
264 265  
265   - // 填充库存数量
  266 + // 查询已使用数量
  267 + var usageDict = await _db.Queryable<LqInventoryUsageEntity>()
  268 + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  269 + .GroupBy(x => x.ProductId)
  270 + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  271 + .ToListAsync();
  272 +
  273 + var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  274 +
  275 + // 填充库存数量(总库存减去已使用数量)
266 276 foreach (var item in data.list)
267 277 {
268   - item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
  278 + var totalInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
  279 + var totalUsage = usageDictMap.ContainsKey(item.id) ? usageDictMap[item.id] : 0;
  280 + item.currentInventory = totalInventory - totalUsage;
269 281 }
270 282 }
271 283  
... ... @@ -427,6 +439,7 @@ namespace NCC.Extend
427 439 var productIds = products.Select(x => x.id).ToList();
428 440 if (productIds.Any())
429 441 {
  442 + // 查询总库存数量
430 443 var inventoryDict = await _db.Queryable<LqInventoryEntity>()
431 444 .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
432 445 .GroupBy(x => x.ProductId)
... ... @@ -435,10 +448,21 @@ namespace NCC.Extend
435 448  
436 449 var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
437 450  
438   - // 填充库存数量
  451 + // 查询已使用数量
  452 + var usageDict = await _db.Queryable<LqInventoryUsageEntity>()
  453 + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  454 + .GroupBy(x => x.ProductId)
  455 + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  456 + .ToListAsync();
  457 +
  458 + var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  459 +
  460 + // 填充库存数量(总库存减去已使用数量)
439 461 foreach (var item in products)
440 462 {
441   - item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
  463 + var totalInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
  464 + var totalUsage = usageDictMap.ContainsKey(item.id) ? usageDictMap[item.id] : 0;
  465 + item.currentInventory = totalInventory - totalUsage;
442 466 }
443 467 }
444 468  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Authorization;
  2 +using Microsoft.AspNetCore.Mvc;
  3 +using NCC.Common.Enum;
  4 +using NCC.Common.Filter;
  5 +using NCC.Common.Helper;
  6 +using NCC.Dependency;
  7 +using NCC.DynamicApiController;
  8 +using NCC.Extend.Entitys.Dto.LqSalary;
  9 +using Yitter.IdGenerator;
  10 +using NCC.Extend.Entitys.lq_attendance_summary;
  11 +using NCC.Extend.Entitys.lq_jinsanjiao_user;
  12 +using NCC.Extend.Entitys.lq_kd_jksyj;
  13 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  14 +using NCC.Extend.Entitys.lq_md_target;
  15 +using NCC.Extend.Entitys.lq_person_times_record;
  16 +using NCC.Extend.Entitys.lq_salary_statistics;
  17 +using NCC.Extend.Entitys.lq_xh_jksyj;
  18 +using NCC.Extend.Entitys.lq_ycsd_jsj;
  19 +using SqlSugar;
  20 +using System;
  21 +using System.Collections.Generic;
  22 +using System.Linq;
  23 +using System.Threading.Tasks;
  24 +
  25 +namespace NCC.Extend
  26 +{
  27 + /// <summary>
  28 + /// 薪酬服务
  29 + /// </summary>
  30 + [ApiDescriptionSettings(Tag = "薪酬服务", Name = "LqSalary", Order = 300)]
  31 + [Route("api/Extend/[controller]")]
  32 + public class LqSalaryService : IDynamicApiController, ITransient
  33 + {
  34 + private readonly ISqlSugarClient _db;
  35 +
  36 + /// <summary>
  37 + /// 初始化一个<see cref="LqSalaryService"/>类型的新实例
  38 + /// </summary>
  39 + public LqSalaryService(ISqlSugarClient db)
  40 + {
  41 + _db = db;
  42 + }
  43 +
  44 + /// <summary>
  45 + /// 获取健康师工资列表
  46 + /// </summary>
  47 + /// <param name="input">查询参数</param>
  48 + /// <returns>健康师工资分页列表</returns>
  49 + [HttpGet("health-coach")]
  50 + public async Task<PageResult<HealthCoachSalaryOutput>> GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input)
  51 + {
  52 + var monthStr = $"{input.Year}{input.Month:D2}";
  53 +
  54 + // 1. 检查当月是否已生成工资数据
  55 + var exists = await _db.Queryable<LqSalaryStatisticsEntity>()
  56 + .AnyAsync(x => x.StatisticsMonth == monthStr);
  57 +
  58 + // 2. 如果没有数据,则进行计算
  59 + if (!exists)
  60 + {
  61 + await CalculateHealthCoachSalary(input.Year, input.Month);
  62 + }
  63 +
  64 + // 3. 查询数据
  65 + var query = _db.Queryable<LqSalaryStatisticsEntity>()
  66 + .Where(x => x.StatisticsMonth == monthStr);
  67 +
  68 + if (!string.IsNullOrEmpty(input.StoreId))
  69 + {
  70 + query = query.Where(x => x.StoreId == input.StoreId);
  71 + }
  72 +
  73 + if (!string.IsNullOrEmpty(input.Keyword))
  74 + {
  75 + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword));
  76 + }
  77 +
  78 + var list = await query.Select(x => new HealthCoachSalaryOutput
  79 + {
  80 + Id = x.Id,
  81 + StoreName = x.StoreName,
  82 + EmployeeName = x.EmployeeName,
  83 + Position = x.Position,
  84 + GoldTriangleTeam = x.GoldTriangleTeam,
  85 + TotalPerformance = x.TotalPerformance,
  86 + BasePerformance = x.BasePerformance,
  87 + CooperationPerformance = x.CooperationPerformance,
  88 + RewardPerformance = x.RewardPerformance,
  89 + Consumption = x.Consumption,
  90 + ProjectCount = x.ProjectCount,
  91 + CustomerCount = x.CustomerCount,
  92 + WorkingDays = x.WorkingDays,
  93 + HealthCoachBaseSalary = x.HealthCoachBaseSalary,
  94 + TotalCommission = x.TotalCommission,
  95 + HandworkFee = x.HandworkFee,
  96 + TotalSubsidy = x.TotalSubsidy,
  97 + TotalDeduction = x.TotalDeduction,
  98 + ActualSalary = x.ActualSalary,
  99 + IsLocked = x.IsLocked,
  100 + UpdateTime = x.UpdateTime
  101 + })
  102 + .ToPagedListAsync(input.currentPage, input.pageSize);
  103 +
  104 + return PageResult<HealthCoachSalaryOutput>.SqlSugarPageResult(list);
  105 + }
  106 +
  107 + /// <summary>
  108 + /// 计算健康师工资
  109 + /// </summary>
  110 + /// <param name="year">年份</param>
  111 + /// <param name="month">月份</param>
  112 + /// <returns></returns>
  113 + [HttpPost("calculate/health-coach")]
  114 + public async Task CalculateHealthCoachSalary(int year, int month)
  115 + {
  116 + var startDate = new DateTime(year, month, 1);
  117 + var endDate = startDate.AddMonths(1).AddDays(-1);
  118 + var monthStr = $"{year}{month:D2}";
  119 +
  120 + // 1. 获取基础数据
  121 +
  122 + // 1.1 业绩数据 (lq_kd_jksyj)
  123 + var performanceList = await _db.Queryable<LqKdJksyjEntity>()
  124 + .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1) && x.IsEffective == 1)
  125 + .ToListAsync();
  126 +
  127 + // 1.1.1 获取关联的开单记录(用于获取 sfskdd)
  128 + var billingIds = performanceList.Select(x => x.Glkdbh).Distinct().ToList();
  129 + var billingDict = await _db.Queryable<LqKdKdjlbEntity>()
  130 + .Where(x => billingIds.Contains(x.Id))
  131 + .ToDictionaryAsync(x => x.Id, x => x.Sfskdd);
  132 +
  133 + // 1.1.2 组合数据
  134 + var performanceData = performanceList.Select(p => new
  135 + {
  136 + p.Jks,
  137 + p.Jksxm,
  138 + p.StoreId,
  139 + p.Jksyj,
  140 + p.ItemCategory,
  141 + Sfskdd = billingDict.ContainsKey(p.Glkdbh) ? billingDict[p.Glkdbh] : null
  142 + }).ToList();
  143 +
  144 + // 1.2 消耗数据 (lq_xh_jksyj)
  145 + var consumptionList = await _db.Queryable<LqXhJksyjEntity>()
  146 + .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1) && x.IsEffective == 1)
  147 + .ToListAsync();
  148 +
  149 + // 1.3 考勤数据 (lq_attendance_summary)
  150 + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
  151 + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
  152 + .ToListAsync();
  153 +
  154 + // 1.4 战队成员及顾问信息 (lq_jinsanjiao_user + lq_ycsd_jsj)
  155 + var teamUserList = await _db.Queryable<LqJinsanjiaoUserEntity>()
  156 + .Where(x => x.Month == monthStr && x.DeleteMark == 0)
  157 + .ToListAsync();
  158 +
  159 + // 1.4.1 获取战队信息
  160 + var teamIds = teamUserList.Select(x => x.JsjId).Distinct().ToList();
  161 + var teamList = await _db.Queryable<LqYcsdJsjEntity>()
  162 + .Where(x => teamIds.Contains(x.Id))
  163 + .ToListAsync();
  164 + var teamDict = teamList.ToDictionary(x => x.Id, x => x.Jsj);
  165 +
  166 + // 1.4.2 组合数据
  167 + var teamMembers = teamUserList.Select(user => new
  168 + {
  169 + user.UserId,
  170 + user.IsLeader,
  171 + TeamId = user.JsjId,
  172 + TeamName = teamDict.ContainsKey(user.JsjId) ? teamDict[user.JsjId] : (string)null
  173 + }).ToList();
  174 +
  175 + // 1.5 到店人头 (lq_person_times_record)
  176 + // 统计每个健康师的去重会员数
  177 + var headcountList = await _db.Queryable<LqPersonTimesRecordEntity>()
  178 + .Where(x => x.WorkMonth == monthStr && x.IsEffective == 1)
  179 + .GroupBy(x => x.PersonId)
  180 + .Select(x => new { PersonId = x.PersonId, Count = SqlFunc.AggregateDistinctCount(x.MemberId) })
  181 + .ToListAsync();
  182 +
  183 + // 1.6 门店生命线 (lq_md_target)
  184 + var storeTargets = await _db.Queryable<LqMdTargetEntity>()
  185 + .Where(x => x.Month == monthStr)
  186 + .ToListAsync();
  187 +
  188 + // 2. 聚合每个健康师的数据对象
  189 + var employeeStats = new Dictionary<string, LqSalaryStatisticsEntity>();
  190 +
  191 + // 获取所有涉及的健康师ID
  192 + var allEmployeeIds = performanceData.Select(x => x.Jks)
  193 + .Union(consumptionList.Select(x => x.Jks))
  194 + .Union(attendanceList.Select(x => x.UserId))
  195 + .Union(teamMembers.Select(x => x.UserId))
  196 + .Where(x => !string.IsNullOrEmpty(x))
  197 + .Distinct()
  198 + .ToList();
  199 +
  200 + foreach (var empId in allEmployeeIds)
  201 + {
  202 + var salary = new LqSalaryStatisticsEntity
  203 + {
  204 + Id = YitIdHelper.NextId().ToString(),
  205 + EmployeeId = empId,
  206 + StatisticsMonth = monthStr,
  207 + CreateTime = DateTime.Now,
  208 + UpdateTime = DateTime.Now,
  209 + IsLocked = 0
  210 + };
  211 +
  212 + // 填充基础信息 (姓名、门店)
  213 + var perfRecord = performanceData.FirstOrDefault(x => x.Jks == empId);
  214 + var consRecord = consumptionList.FirstOrDefault(x => x.Jks == empId);
  215 +
  216 + if (perfRecord != null)
  217 + {
  218 + salary.EmployeeName = perfRecord.Jksxm;
  219 + salary.StoreId = perfRecord.StoreId;
  220 + }
  221 + else if (consRecord != null)
  222 + {
  223 + salary.EmployeeName = consRecord.Jksxm;
  224 + salary.StoreId = consRecord.StoreId;
  225 + }
  226 +
  227 + // 填充门店名称
  228 + if (!string.IsNullOrEmpty(salary.StoreId))
  229 + {
  230 + // 这里简单处理,实际可能需要缓存门店列表
  231 + // salary.StoreName = ...
  232 + }
  233 +
  234 + // 2.1 计算个人业绩
  235 + var myPerf = performanceData.Where(x => x.Jks == empId).ToList();
  236 + salary.BasePerformance = myPerf.Where(x => x.ItemCategory == "基础业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0"));
  237 + salary.CooperationPerformance = myPerf.Where(x => x.ItemCategory == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0"));
  238 + salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0"));
  239 +
  240 + // 新客与升单业绩
  241 + salary.NewCustomerPerformance = myPerf.Where(x => x.Sfskdd == "是").Sum(x => decimal.Parse(x.Jksyj ?? "0"));
  242 + salary.UpgradePerformance = myPerf.Where(x => x.Sfskdd == "否").Sum(x => decimal.Parse(x.Jksyj ?? "0"));
  243 +
  244 + // 2.2 计算消耗和项目数
  245 + var myCons = consumptionList.Where(x => x.Jks == empId).ToList();
  246 + salary.Consumption = myCons.Sum(x => x.Jksyj ?? 0);
  247 + salary.ProjectCount = myCons.Sum(x => x.KdpxNumber ?? 0);
  248 + salary.HandworkFee = myCons.Sum(x => x.LaborCost ?? 0); // 使用 F_LaborCost
  249 +
  250 + // 2.3 考勤数据
  251 + var myAtt = attendanceList.FirstOrDefault(x => x.UserId == empId);
  252 + salary.WorkingDays = myAtt?.WorkDays ?? 0;
  253 + salary.LeaveDays = myAtt?.LeaveDays ?? 0;
  254 +
  255 + // 2.4 到店人头
  256 + var myHeadcount = headcountList.FirstOrDefault(x => x.PersonId == empId);
  257 + salary.CustomerCount = myHeadcount?.Count ?? 0;
  258 +
  259 + // 2.5 战队信息 (初始)
  260 + var myTeam = teamMembers.FirstOrDefault(x => x.UserId == empId);
  261 + if (myTeam != null)
  262 + {
  263 + salary.GoldTriangleId = myTeam.TeamId;
  264 + salary.GoldTriangleTeam = myTeam.TeamName ?? "";
  265 + }
  266 +
  267 + employeeStats[empId] = salary;
  268 + }
  269 +
  270 + // 3. 处理战队逻辑 (考勤规则)
  271 + // 规则:若出勤天数 < 21天,则该健康师不计入战队,按单人计算。
  272 +
  273 + // 按战队分组
  274 + var teamGroups = employeeStats.Values
  275 + .Where(x => !string.IsNullOrEmpty(x.GoldTriangleId))
  276 + .GroupBy(x => x.GoldTriangleId)
  277 + .ToList();
  278 +
  279 + foreach (var group in teamGroups)
  280 + {
  281 + var validMembers = new List<LqSalaryStatisticsEntity>();
  282 + var invalidMembers = new List<LqSalaryStatisticsEntity>();
  283 +
  284 + foreach (var member in group)
  285 + {
  286 + if (member.WorkingDays >= 21)
  287 + {
  288 + validMembers.Add(member);
  289 + }
  290 + else
  291 + {
  292 + invalidMembers.Add(member);
  293 + }
  294 + }
  295 +
  296 + // 对于无效成员,移除战队标识,视为单人
  297 + foreach (var member in invalidMembers)
  298 + {
  299 + member.GoldTriangleId = null;
  300 + member.GoldTriangleTeam = null;
  301 + }
  302 +
  303 + // 计算有效战队的总业绩
  304 + var teamTotalPerformance = validMembers.Sum(x => x.TotalPerformance);
  305 +
  306 + // 更新有效成员的战队业绩
  307 + foreach (var member in validMembers)
  308 + {
  309 + member.TeamPerformance = teamTotalPerformance;
  310 + }
  311 + }
  312 +
  313 + // 4. 计算薪资 (底薪 & 提成)
  314 + foreach (var salary in employeeStats.Values)
  315 + {
  316 + // 4.1 底薪计算
  317 + salary.HealthCoachBaseSalary = CalculateBaseSalary(salary.Consumption, salary.ProjectCount);
  318 +
  319 + // 4.2 提成计算
  320 + // 单人业绩 <= 6000 无提成
  321 + if (salary.TotalPerformance <= 6000)
  322 + {
  323 + salary.TotalCommission = 0;
  324 + salary.BasePerformanceCommission = 0;
  325 + salary.CooperationPerformanceCommission = 0;
  326 + salary.ConsultantCommission = 0;
  327 + }
  328 + else
  329 + {
  330 + // 确定提成点
  331 + decimal commissionPoint = 0;
  332 +
  333 + if (!string.IsNullOrEmpty(salary.GoldTriangleId))
  334 + {
  335 + // 是战队成员
  336 + // 获取战队人数 (注意:这里应该是有效战队人数)
  337 + var teamMemberCount = employeeStats.Values.Count(x => x.GoldTriangleId == salary.GoldTriangleId);
  338 + commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance);
  339 + }
  340 + else
  341 + {
  342 + // 单人 (或被剔除出战队)
  343 + commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance);
  344 + }
  345 +
  346 + salary.CommissionPoint = commissionPoint;
  347 +
  348 + // 计算基础/合作提成
  349 + salary.BasePerformanceCommission = salary.BasePerformance * 0.95m * commissionPoint;
  350 + salary.CooperationPerformanceCommission = salary.CooperationPerformance * 0.95m * 0.65m * commissionPoint;
  351 +
  352 + // 计算顾问提成
  353 + // 检查是否是顾问
  354 + var isConsultant = teamMembers.Any(x => x.UserId == salary.EmployeeId && x.IsLeader == 1);
  355 + if (isConsultant && !string.IsNullOrEmpty(salary.GoldTriangleId))
  356 + {
  357 + salary.ConsultantCommission = CalculateConsultantCommission(salary.TeamPerformance, employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList());
  358 + }
  359 +
  360 + salary.TotalCommission = salary.BasePerformanceCommission + salary.CooperationPerformanceCommission + salary.ConsultantCommission;
  361 + }
  362 +
  363 + // 计算占比
  364 + if (salary.TeamPerformance > 0)
  365 + {
  366 + salary.Percentage = salary.TotalPerformance / salary.TeamPerformance;
  367 + }
  368 + else if (salary.TotalPerformance > 0 && string.IsNullOrEmpty(salary.GoldTriangleId))
  369 + {
  370 + salary.Percentage = 1; // 单人占比100%
  371 + }
  372 +
  373 + // 4.3 最终工资
  374 + salary.ActualSalary = salary.HealthCoachBaseSalary + salary.TotalCommission + salary.HandworkFee + salary.TotalSubsidy - salary.TotalDeduction;
  375 + }
  376 +
  377 + // 5. 保存数据
  378 + if (employeeStats.Any())
  379 + {
  380 + // 先删除当月旧数据 (防止重复)
  381 + await _db.Deleteable<LqSalaryStatisticsEntity>().Where(x => x.StatisticsMonth == monthStr).ExecuteCommandAsync();
  382 + await _db.Insertable(employeeStats.Values.ToList()).ExecuteCommandAsync();
  383 + }
  384 + }
  385 +
  386 + /// <summary>
  387 + /// 计算底薪
  388 + /// </summary>
  389 + private decimal CalculateBaseSalary(decimal consumption, decimal projectCount)
  390 + {
  391 + // 0星:<1w 或 <96个 -> 1800
  392 + // 1星:>=1w 且 >=96个 -> 2000
  393 + // 2星:>=2w 且 >=126个 -> 2200
  394 + // 3星:>=4w 且 >=156个 -> 2400
  395 +
  396 + // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算
  397 +
  398 + int starCons = 0;
  399 + if (consumption >= 40000) starCons = 3;
  400 + else if (consumption >= 20000) starCons = 2;
  401 + else if (consumption >= 10000) starCons = 1;
  402 +
  403 + int starProj = 0;
  404 + if (projectCount >= 156) starProj = 3;
  405 + else if (projectCount >= 126) starProj = 2;
  406 + else if (projectCount >= 96) starProj = 1;
  407 +
  408 + int finalStar = Math.Min(starCons, starProj);
  409 +
  410 + // 特殊规则处理: 仅一项未达标(0星) -> 1星
  411 + if (finalStar == 0 && (starCons > 0 || starProj > 0))
  412 + {
  413 + finalStar = 1;
  414 + }
  415 +
  416 + switch (finalStar)
  417 + {
  418 + case 3: return 2400;
  419 + case 2: return 2200;
  420 + case 1: return 2000;
  421 + default: return 1800;
  422 + }
  423 + }
  424 +
  425 + /// <summary>
  426 + /// 获取战队提成点
  427 + /// </summary>
  428 + private decimal GetTeamCommissionPoint(int memberCount, decimal teamPerformance)
  429 + {
  430 + if (memberCount >= 3)
  431 + {
  432 + if (teamPerformance >= 150000) return 0.07m;
  433 + if (teamPerformance >= 120000) return 0.06m;
  434 + if (teamPerformance >= 90000) return 0.05m;
  435 + if (teamPerformance >= 60000) return 0.04m;
  436 + if (teamPerformance >= 30000) return 0.03m;
  437 + }
  438 + else if (memberCount == 2)
  439 + {
  440 + if (teamPerformance >= 80000) return 0.06m;
  441 + if (teamPerformance >= 60000) return 0.05m;
  442 + if (teamPerformance >= 40000) return 0.04m;
  443 + if (teamPerformance >= 20000) return 0.03m;
  444 + }
  445 + else // 1人
  446 + {
  447 + if (teamPerformance >= 60000) return 0.06m;
  448 + if (teamPerformance >= 40000) return 0.05m;
  449 + if (teamPerformance >= 20000) return 0.04m;
  450 + if (teamPerformance >= 10000) return 0.03m;
  451 + }
  452 + return 0;
  453 + }
  454 +
  455 + /// <summary>
  456 + /// 计算顾问提成
  457 + /// </summary>
  458 + private decimal CalculateConsultantCommission(decimal teamPerformance, List<LqSalaryStatisticsEntity> teamMembers)
  459 + {
  460 + // 顾问提成规则:
  461 + // 高级顾问:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8%
  462 + // 普通顾问:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3%
  463 +
  464 + // 这里的“组员业绩达到X%以上”理解为:除顾问外的成员业绩占比?或者每个成员都达标?
  465 + // 通常理解为:团队中是否有成员业绩贡献较高,或者团队整体结构健康。
  466 + // 假设“组员业绩达到X%”是指:团队中至少有一名成员(非顾问本人?)或者所有成员平均?
  467 + // 鉴于规则模糊,这里先简化实现:暂只考核总业绩和消耗。
  468 + // 消耗是团队总消耗吗?假设是。
  469 +
  470 + var teamConsumption = teamMembers.Sum(x => x.Consumption);
  471 +
  472 + // 高级顾问
  473 + if (teamPerformance >= 60000 && teamConsumption >= 60000)
  474 + {
  475 + return teamPerformance * 0.008m;
  476 + }
  477 + // 普通顾问
  478 + if (teamPerformance >= 40000 && teamConsumption >= 40000)
  479 + {
  480 + return teamPerformance * 0.003m;
  481 + }
  482 +
  483 + return 0;
  484 + }
  485 + }
  486 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -4997,7 +4997,5 @@ namespace NCC.Extend.LqStatistics
4997 4997 }
4998 4998 #endregion
4999 4999  
5000   -
5001   -
5002 5000 }
5003 5001 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -775,50 +775,50 @@ namespace NCC.Extend.LqXhHyhk
775 775 .MergeTable()
776 776 .Distinct() // 去重,因为一个耗卡可能对应多个科技部老师业绩记录
777 777 .OrderBy($"{sidx} {sort}")
778   - .ToPagedListAsync(input.currentPage, input.pageSize);
  778 + .ToPagedListAsync(input.currentPage, input.pageSize);
779 779  
780   - // 获取当前页的耗卡记录ID列表
781   - var consumeIds = data.list.Select(x => x.id).ToList();
  780 + // 获取当前页的耗卡记录ID列表
  781 + var consumeIds = data.list.Select(x => x.id).ToList();
782 782  
783   - // 批量查询耗卡明细
784   - var consumeDetails = new List<LqXhPxmxInfoOutput>();
785   - if (consumeIds.Any())
786   - {
787   - consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
788   - .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
789   - .Select(x => new LqXhPxmxInfoOutput
790   - {
791   - id = x.Id,
792   - consumeInfoId = x.ConsumeInfoId,
793   - billingItemId = x.BillingItemId,
794   - px = x.Px,
795   - pxmc = x.Pxmc,
796   - pxjg = x.Pxjg,
797   - memberId = x.MemberId,
798   - createTime = x.CreateTIme,
799   - projectNumber = x.ProjectNumber,
800   - originalProjectNumber = x.OriginalProjectNumber,
801   - overtimeProjectNumber = x.OvertimeProjectNumber,
802   - sourceType = x.SourceType,
803   - totalPrice = x.TotalPrice,
804   - isEffective = x.IsEffective,
805   - })
806   - .ToListAsync();
807   - }
  783 + // 批量查询耗卡明细
  784 + var consumeDetails = new List<LqXhPxmxInfoOutput>();
  785 + if (consumeIds.Any())
  786 + {
  787 + consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
  788 + .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  789 + .Select(x => new LqXhPxmxInfoOutput
  790 + {
  791 + id = x.Id,
  792 + consumeInfoId = x.ConsumeInfoId,
  793 + billingItemId = x.BillingItemId,
  794 + px = x.Px,
  795 + pxmc = x.Pxmc,
  796 + pxjg = x.Pxjg,
  797 + memberId = x.MemberId,
  798 + createTime = x.CreateTIme,
  799 + projectNumber = x.ProjectNumber,
  800 + originalProjectNumber = x.OriginalProjectNumber,
  801 + overtimeProjectNumber = x.OvertimeProjectNumber,
  802 + sourceType = x.SourceType,
  803 + totalPrice = x.TotalPrice,
  804 + isEffective = x.IsEffective,
  805 + })
  806 + .ToListAsync();
  807 + }
808 808  
809   - // 按耗卡记录ID分组耗卡明细
810   - var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
811   - .ToDictionary(g => g.Key, g => g.ToList());
  809 + // 按耗卡记录ID分组耗卡明细
  810 + var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
  811 + .ToDictionary(g => g.Key, g => g.ToList());
812 812  
813   - // 为每个耗卡记录分配耗卡明细
814   - foreach (var item in data.list)
815   - {
816   - item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
817   - ? consumeDetailsGrouped[item.id]
818   - : new List<LqXhPxmxInfoOutput>();
819   - }
  813 + // 为每个耗卡记录分配耗卡明细
  814 + foreach (var item in data.list)
  815 + {
  816 + item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
  817 + ? consumeDetailsGrouped[item.id]
  818 + : new List<LqXhPxmxInfoOutput>();
  819 + }
820 820  
821   - return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
  821 + return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
822 822 }
823 823 catch (Exception ex)
824 824 {
... ...
sql/同步退卡时间字段.sql 0 → 100644
  1 +-- 同步退卡业绩表的退卡时间到明细表和业绩表
  2 +-- 数据来源:lq_hytk_hytk.tksj(退卡时间)
  3 +
  4 +-- ============================================
  5 +-- 1. 同步退卡明细表(lq_hytk_mx)的退卡时间
  6 +-- ============================================
  7 +-- 通过 F_RefundInfoId 关联到退卡业绩表的 F_Id
  8 +UPDATE lq_hytk_mx mx
  9 +INNER JOIN lq_hytk_hytk hytk ON mx.F_RefundInfoId = hytk.F_Id
  10 +SET mx.tksj = hytk.tksj
  11 +WHERE hytk.tksj IS NOT NULL
  12 + AND (mx.tksj IS NULL OR mx.tksj != hytk.tksj);
  13 +
  14 +-- ============================================
  15 +-- 2. 同步退卡健康师业绩表(lq_hytk_jksyj)的退卡时间
  16 +-- ============================================
  17 +-- 通过 gltkbh 关联到退卡业绩表的 F_Id
  18 +UPDATE lq_hytk_jksyj jksyj
  19 +INNER JOIN lq_hytk_hytk hytk ON jksyj.gltkbh = hytk.F_Id
  20 +SET jksyj.tksj = hytk.tksj
  21 +WHERE hytk.tksj IS NOT NULL
  22 + AND (jksyj.tksj IS NULL OR jksyj.tksj != hytk.tksj);
  23 +
  24 +-- ============================================
  25 +-- 3. 同步退卡科技老师业绩表(lq_hytk_kjbsyj)的退卡时间
  26 +-- ============================================
  27 +-- 通过 gltkbh 关联到退卡业绩表的 F_Id
  28 +UPDATE lq_hytk_kjbsyj kjbsyj
  29 +INNER JOIN lq_hytk_hytk hytk ON kjbsyj.gltkbh = hytk.F_Id
  30 +SET kjbsyj.tksj = hytk.tksj
  31 +WHERE hytk.tksj IS NOT NULL
  32 + AND (kjbsyj.tksj IS NULL OR kjbsyj.tksj != hytk.tksj);
  33 +
... ...
sql/更新开单记录表储扣金额.sql 0 → 100644
  1 +-- 更新开单记录表中的储扣金额,使其等于储扣详情表中汇总的金额
  2 +-- 只更新那些储扣金额不一致的记录(包括NULL、0或金额不匹配的情况)
  3 +
  4 +UPDATE lq_kd_kdjlb kd
  5 +INNER JOIN (
  6 + SELECT
  7 + F_BillingId,
  8 + COALESCE(SUM(F_Amount), 0) as total_amount
  9 + FROM lq_kd_deductinfo
  10 + WHERE F_IsEffective = 1
  11 + GROUP BY F_BillingId
  12 +) deduct_sum ON kd.F_Id = deduct_sum.F_BillingId
  13 +SET kd.F_DeductAmount = deduct_sum.total_amount
  14 +WHERE kd.F_IsEffective = 1
  15 + AND (
  16 + kd.F_DeductAmount IS NULL
  17 + OR kd.F_DeductAmount = 0
  18 + OR ABS(kd.F_DeductAmount - deduct_sum.total_amount) > 0.01
  19 + )
  20 + AND deduct_sum.total_amount > 0;
  21 +
... ...