Commit 96e0ccc76c117a041e498d29d5253064a1cff663

Authored by “wangming”
1 parent 19d2e6e6

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

antis-ncc-admin/package-lock.json
@@ -21699,7 +21699,7 @@ @@ -21699,7 +21699,7 @@
21699 }, 21699 },
21700 "node_modules/xlsx": { 21700 "node_modules/xlsx": {
21701 "version": "0.18.5", 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 "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", 21703 "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
21704 "dependencies": { 21704 "dependencies": {
21705 "adler-32": "~1.3.0", 21705 "adler-32": "~1.3.0",
@@ -39759,7 +39759,7 @@ @@ -39759,7 +39759,7 @@
39759 }, 39759 },
39760 "xlsx": { 39760 "xlsx": {
39761 "version": "0.18.5", 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 "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", 39763 "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
39764 "requires": { 39764 "requires": {
39765 "adler-32": "~1.3.0", 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,5 +27,29 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
27 /// </summary> 27 /// </summary>
28 [Display(Name = "结束时间", Description = "查询开单记录的结束时间")] 28 [Display(Name = "结束时间", Description = "查询开单记录的结束时间")]
29 public DateTime? EndTime { get; set; } 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,5 +94,20 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
94 /// 退卡金额 - 统计该健康师在指定时间周期内的退卡业绩总金额 94 /// 退卡金额 - 统计该健康师在指定时间周期内的退卡业绩总金额
95 /// </summary> 95 /// </summary>
96 public decimal refundAmount { get; set; } 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,60 +299,6 @@ namespace NCC.Extend.LqHytkHytk
299 /// 创建退卡信息及其关联的品项明细、健康师业绩、科技部老师业绩信息 299 /// 创建退卡信息及其关联的品项明细、健康师业绩、科技部老师业绩信息
300 /// </summary> 300 /// </summary>
301 /// <remarks> 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 /// - md: 门店ID 303 /// - md: 门店ID
358 /// - hy: 会员ID 304 /// - hy: 会员ID
@@ -378,15 +324,12 @@ namespace NCC.Extend.LqHytkHytk @@ -378,15 +324,12 @@ namespace NCC.Extend.LqHytkHytk
378 { 324 {
379 // 开启事务 325 // 开启事务
380 _db.BeginTran(); 326 _db.BeginTran();
381 -  
382 // 新增退卡主表记录 327 // 新增退卡主表记录
383 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync(); 328 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
384 -  
385 // 收集所有需要插入的实体,然后批量插入 329 // 收集所有需要插入的实体,然后批量插入
386 var allMxEntities = new List<LqHytkMxEntity>(); 330 var allMxEntities = new List<LqHytkMxEntity>();
387 var allJksyjEntities = new List<LqHytkJksyjEntity>(); 331 var allJksyjEntities = new List<LqHytkJksyjEntity>();
388 var allKjbsyjEntities = new List<LqHytkKjbsyjEntity>(); 332 var allKjbsyjEntities = new List<LqHytkKjbsyjEntity>();
389 -  
390 // 处理品项明细列表 333 // 处理品项明细列表
391 if (input.lqHytkMxList != null && input.lqHytkMxList.Any()) 334 if (input.lqHytkMxList != null && input.lqHytkMxList.Any())
392 { 335 {
@@ -405,6 +348,7 @@ namespace NCC.Extend.LqHytkHytk @@ -405,6 +348,7 @@ namespace NCC.Extend.LqHytkHytk
405 Pxmc = item.pxmc, 348 Pxmc = item.pxmc,
406 Pxjg = item.pxjg, 349 Pxjg = item.pxjg,
407 Tkje = item.tkje, 350 Tkje = item.tkje,
  351 + Tksj = input.tksj,
408 ProjectNumber = item.F_ProjectNumber ?? 1, 352 ProjectNumber = item.F_ProjectNumber ?? 1,
409 SourceType = item.F_SourceType, 353 SourceType = item.F_SourceType,
410 TotalPrice = item.F_TotalPrice ?? (item.pxjg * (item.F_ProjectNumber ?? 1)), 354 TotalPrice = item.F_TotalPrice ?? (item.pxjg * (item.F_ProjectNumber ?? 1)),
@@ -426,7 +370,7 @@ namespace NCC.Extend.LqHytkHytk @@ -426,7 +370,7 @@ namespace NCC.Extend.LqHytkHytk
426 Jksxm = ijks_tem.jksxm, 370 Jksxm = ijks_tem.jksxm,
427 Jkszh = ijks_tem.jkszh, 371 Jkszh = ijks_tem.jkszh,
428 Jksyj = ijks_tem.jksyj, 372 Jksyj = ijks_tem.jksyj,
429 - Tksj = DateTime.Now, 373 + Tksj = input.tksj,
430 F_jsjid = ijks_tem.F_jsjid, 374 F_jsjid = ijks_tem.F_jsjid,
431 F_tkpxid = ijks_tem.F_tkpxid, 375 F_tkpxid = ijks_tem.F_tkpxid,
432 F_LaborCost = ijks_tem.F_LaborCost, 376 F_LaborCost = ijks_tem.F_LaborCost,
@@ -458,7 +402,7 @@ namespace NCC.Extend.LqHytkHytk @@ -458,7 +402,7 @@ namespace NCC.Extend.LqHytkHytk
458 Kjblsxm = ikjbs_tem.kjblsxm, 402 Kjblsxm = ikjbs_tem.kjblsxm,
459 Kjblszh = ikjbs_tem.kjblszh, 403 Kjblszh = ikjbs_tem.kjblszh,
460 Kjblsyj = ikjbs_tem.kjblsyj, 404 Kjblsyj = ikjbs_tem.kjblsyj,
461 - Tksj = DateTime.Now, 405 + Tksj = input.tksj,
462 F_tkpxid = ikjbs_tem.F_tkpxid, 406 F_tkpxid = ikjbs_tem.F_tkpxid,
463 F_LaborCost = ikjbs_tem.F_LaborCost, 407 F_LaborCost = ikjbs_tem.F_LaborCost,
464 F_tkpxNumber = ikjbs_tem.F_tkpxNumber, 408 F_tkpxNumber = ikjbs_tem.F_tkpxNumber,
@@ -476,7 +420,6 @@ namespace NCC.Extend.LqHytkHytk @@ -476,7 +420,6 @@ namespace NCC.Extend.LqHytkHytk
476 } 420 }
477 } 421 }
478 } 422 }
479 -  
480 // 批量插入品项明细 423 // 批量插入品项明细
481 if (allMxEntities.Any()) 424 if (allMxEntities.Any())
482 { 425 {
@@ -587,7 +530,7 @@ namespace NCC.Extend.LqHytkHytk @@ -587,7 +530,7 @@ namespace NCC.Extend.LqHytkHytk
587 Jksxm = ijks_tem.jksxm, 530 Jksxm = ijks_tem.jksxm,
588 Jkszh = ijks_tem.jkszh, 531 Jkszh = ijks_tem.jkszh,
589 Jksyj = ijks_tem.jksyj, 532 Jksyj = ijks_tem.jksyj,
590 - Tksj = DateTime.Now, 533 + Tksj = input.tksj,
591 F_jsjid = ijks_tem.F_jsjid, 534 F_jsjid = ijks_tem.F_jsjid,
592 F_tkpxid = ijks_tem.F_tkpxid, 535 F_tkpxid = ijks_tem.F_tkpxid,
593 F_LaborCost = ijks_tem.F_LaborCost, 536 F_LaborCost = ijks_tem.F_LaborCost,
@@ -617,7 +560,7 @@ namespace NCC.Extend.LqHytkHytk @@ -617,7 +560,7 @@ namespace NCC.Extend.LqHytkHytk
617 Kjblsxm = ikjbs_tem.kjblsxm, 560 Kjblsxm = ikjbs_tem.kjblsxm,
618 Kjblszh = ikjbs_tem.kjblszh, 561 Kjblszh = ikjbs_tem.kjblszh,
619 Kjblsyj = ikjbs_tem.kjblsyj, 562 Kjblsyj = ikjbs_tem.kjblsyj,
620 - Tksj = DateTime.Now, 563 + Tksj = input.tksj,
621 F_tkpxid = ikjbs_tem.F_tkpxid, 564 F_tkpxid = ikjbs_tem.F_tkpxid,
622 F_LaborCost = ikjbs_tem.F_LaborCost, 565 F_LaborCost = ikjbs_tem.F_LaborCost,
623 F_tkpxNumber = ikjbs_tem.F_tkpxNumber, 566 F_tkpxNumber = ikjbs_tem.F_tkpxNumber,
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -1117,6 +1117,263 @@ namespace NCC.Extend.LqKdKdjlb @@ -1117,6 +1117,263 @@ namespace NCC.Extend.LqKdKdjlb
1117 } 1117 }
1118 #endregion 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 #region 获取开单记录表无分页列表 1377 #region 获取开单记录表无分页列表
1121 /// <summary> 1378 /// <summary>
1122 /// 获取开单记录表无分页列表 1379 /// 获取开单记录表无分页列表
@@ -1293,225 +1550,9 @@ namespace NCC.Extend.LqKdKdjlb @@ -1293,225 +1550,9 @@ namespace NCC.Extend.LqKdKdjlb
1293 } 1550 }
1294 #endregion 1551 #endregion
1295 1552
1296 - #region 更新开单记录表 1553 + #region 删除开单记录表
1297 /// <summary> 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 /// </summary> 1556 /// </summary>
1516 /// <returns></returns> 1557 /// <returns></returns>
1517 [HttpDelete("{id}")] 1558 [HttpDelete("{id}")]
@@ -1609,317 +1650,83 @@ namespace NCC.Extend.LqKdKdjlb @@ -1609,317 +1650,83 @@ namespace NCC.Extend.LqKdKdjlb
1609 } 1650 }
1610 #endregion 1651 #endregion
1611 1652
1612 - #region 修改开单记录 1653 + #region 作废开单记录
1613 /// <summary> 1654 /// <summary>
1614 - /// 修改开单记录 1655 + /// 作废开单记录
1615 /// </summary> 1656 /// </summary>
  1657 + /// <param name="input">作废开单记录输入</param>
  1658 + /// <returns>无返回值</returns>
1616 /// <remarks> 1659 /// <remarks>
1617 - /// 更新开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息  
1618 - /// 1660 + /// 作废指定的开单记录,包括相关的品项明细、业绩记录等
  1661 + ///
1619 /// 示例请求: 1662 /// 示例请求:
1620 /// ```json 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 /// </remarks> 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 /// <response code="500">服务器内部错误</response> 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 try 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 if (!canCancel) 1691 if (!canCancel)
1665 { 1692 {
1666 throw NCCException.Oh(errorMessage); 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 _db.CommitTran(); 1732 _db.CommitTran();
@@ -2325,7 +2132,7 @@ namespace NCC.Extend.LqKdKdjlb @@ -2325,7 +2132,7 @@ namespace NCC.Extend.LqKdKdjlb
2325 } 2132 }
2326 #endregion 2133 #endregion
2327 2134
2328 - #region 根据会员id获取会员的开单品项列表 2135 + #region 根据会员id获取会员的开单品项列表
2329 /// <summary> 2136 /// <summary>
2330 /// 根据会员id获取会员的开单品项列表 2137 /// 根据会员id获取会员的开单品项列表
2331 /// </summary> 2138 /// </summary>
@@ -2539,9 +2346,18 @@ namespace NCC.Extend.LqKdKdjlb @@ -2539,9 +2346,18 @@ namespace NCC.Extend.LqKdKdjlb
2539 input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month)); 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 .Select(it => new 2361 .Select(it => new
2546 { 2362 {
2547 id = it.Id, 2363 id = it.Id,
@@ -2594,9 +2410,24 @@ namespace NCC.Extend.LqKdKdjlb @@ -2594,9 +2410,24 @@ namespace NCC.Extend.LqKdKdjlb
2594 2410
2595 var billingIds = billingRecords.Select(x => x.id).ToList(); 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 // 查询品项明细,按F_SourceType分类 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 .Select(it => new 2431 .Select(it => new
2601 { 2432 {
2602 id = it.Id, 2433 id = it.Id,
@@ -2618,9 +2449,30 @@ namespace NCC.Extend.LqKdKdjlb @@ -2618,9 +2449,30 @@ namespace NCC.Extend.LqKdKdjlb
2618 var giftedItems = itemDetails.Where(x => x.sourceType == "赠送").ToList(); 2449 var giftedItems = itemDetails.Where(x => x.sourceType == "赠送").ToList();
2619 var experienceItems = itemDetails.Where(x => x.sourceType == "体验").ToList(); 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 .Select(it => new 2476 .Select(it => new
2625 { 2477 {
2626 id = it.Id, 2478 id = it.Id,
@@ -2750,31 +2602,266 @@ namespace NCC.Extend.LqKdKdjlb @@ -2750,31 +2602,266 @@ namespace NCC.Extend.LqKdKdjlb
2750 } 2602 }
2751 #endregion 2603 #endregion
2752 2604
2753 - #region 私有方法 2605 + #region 获取门店某个时间段的开单记录汇总信息(备份)
2754 /// <summary> 2606 /// <summary>
2755 - /// 检查开单记录是否可以操作 2607 + /// 获取门店某个时间段的开单记录汇总信息(备份)
2756 /// </summary> 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 try 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 return (false, "该开单记录有对应的补缴记录,不能进行操作"); 2865 return (false, "该开单记录有对应的补缴记录,不能进行操作");
2779 } 2866 }
2780 // 查询开单记录下的品项明细ID列表 2867 // 查询开单记录下的品项明细ID列表
@@ -3258,6 +3345,9 @@ namespace NCC.Extend.LqKdKdjlb @@ -3258,6 +3345,9 @@ namespace NCC.Extend.LqKdKdjlb
3258 /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次) 3345 /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次)
3259 /// - ProjectCount: 消耗项目数(项目总次数) 3346 /// - ProjectCount: 消耗项目数(项目总次数)
3260 /// - RefundAmount: 退卡金额(健康师退卡业绩总金额) 3347 /// - RefundAmount: 退卡金额(健康师退卡业绩总金额)
  3348 + /// - LaborCost: 手工费(消耗时的手工费总金额)
  3349 + /// - OriginalLaborCost: 原始手工费(消耗时的原始手工费总金额)
  3350 + /// - OvertimeLaborCost: 加班手工费(消耗时的加班手工费总金额)
3261 /// </remarks> 3351 /// </remarks>
3262 /// <param name="input">查询参数</param> 3352 /// <param name="input">查询参数</param>
3263 /// <returns>健康师统计数据列表</returns> 3353 /// <returns>健康师统计数据列表</returns>
@@ -3305,7 +3395,357 @@ namespace NCC.Extend.LqKdKdjlb @@ -3305,7 +3395,357 @@ namespace NCC.Extend.LqKdKdjlb
3305 CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount, 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 FROM BASE_USER u 3750 FROM BASE_USER u
3311 LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id 3751 LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
@@ -3365,17 +3805,20 @@ namespace NCC.Extend.LqKdKdjlb @@ -3365,17 +3805,20 @@ namespace NCC.Extend.LqKdKdjlb
3365 -- 消耗统计子查询 3805 -- 消耗统计子查询
3366 LEFT JOIN ( 3806 LEFT JOIN (
3367 SELECT 3807 SELECT
3368 - jksyj.jkszh as EmployeeId, 3808 + jksyj.jks as EmployeeId,
3369 SUM(jksyj.jksyj) as ConsumeAmount, 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 FROM lq_xh_jksyj jksyj 3814 FROM lq_xh_jksyj jksyj
3372 INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id 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 AND jksyj.F_IsEffective = 1 3817 AND jksyj.F_IsEffective = 1
3375 AND hyhk.F_IsEffective = 1 3818 AND hyhk.F_IsEffective = 1
3376 AND hyhk.hksj >= @startTime 3819 AND hyhk.hksj >= @startTime
3377 AND hyhk.hksj <= @endTime 3820 AND hyhk.hksj <= @endTime
3378 - GROUP BY jksyj.jkszh 3821 + GROUP BY jksyj.jks
3379 ) consume_stats ON u.F_Id = consume_stats.EmployeeId 3822 ) consume_stats ON u.F_Id = consume_stats.EmployeeId
3380 3823
3381 -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1) 3824 -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1)
netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs
@@ -254,6 +254,7 @@ namespace NCC.Extend @@ -254,6 +254,7 @@ namespace NCC.Extend
254 var productIds = data.list.Select(x => x.id).ToList(); 254 var productIds = data.list.Select(x => x.id).ToList();
255 if (productIds.Any()) 255 if (productIds.Any())
256 { 256 {
  257 + // 查询总库存数量
257 var inventoryDict = await _db.Queryable<LqInventoryEntity>() 258 var inventoryDict = await _db.Queryable<LqInventoryEntity>()
258 .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) 259 .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
259 .GroupBy(x => x.ProductId) 260 .GroupBy(x => x.ProductId)
@@ -262,10 +263,21 @@ namespace NCC.Extend @@ -262,10 +263,21 @@ namespace NCC.Extend
262 263
263 var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0); 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 foreach (var item in data.list) 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,6 +439,7 @@ namespace NCC.Extend
427 var productIds = products.Select(x => x.id).ToList(); 439 var productIds = products.Select(x => x.id).ToList();
428 if (productIds.Any()) 440 if (productIds.Any())
429 { 441 {
  442 + // 查询总库存数量
430 var inventoryDict = await _db.Queryable<LqInventoryEntity>() 443 var inventoryDict = await _db.Queryable<LqInventoryEntity>()
431 .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) 444 .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
432 .GroupBy(x => x.ProductId) 445 .GroupBy(x => x.ProductId)
@@ -435,10 +448,21 @@ namespace NCC.Extend @@ -435,10 +448,21 @@ namespace NCC.Extend
435 448
436 var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0); 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 foreach (var item in products) 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,7 +4997,5 @@ namespace NCC.Extend.LqStatistics
4997 } 4997 }
4998 #endregion 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,50 +775,50 @@ namespace NCC.Extend.LqXhHyhk
775 .MergeTable() 775 .MergeTable()
776 .Distinct() // 去重,因为一个耗卡可能对应多个科技部老师业绩记录 776 .Distinct() // 去重,因为一个耗卡可能对应多个科技部老师业绩记录
777 .OrderBy($"{sidx} {sort}") 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 catch (Exception ex) 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 +