Commit caf75b4a0a499559077f3dbd22483a2ce8b82182

Authored by “wangming”
1 parent 07c554d7

feat: 优化开单记录汇总接口,添加营销活动和付款医院信息;修复新店保护阶段验证;添加科技部归类字段SQL脚本

netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs
... ... @@ -64,7 +64,7 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
64 64 public List<string> selectedPurchaseRecordIds { get; set; }
65 65  
66 66 /// <summary>
67   - /// 节点配置列表(3-5个节点
  67 + /// 节点配置列表(至少1个节点,建议不超过20个
68 68 /// </summary>
69 69 public List<ApprovalNodeConfig> nodes { get; set; }
70 70 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationInfoOutput.cs
... ... @@ -27,6 +27,11 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
27 27 /// 申请门店
28 28 /// </summary>
29 29 public string applicationStoreId { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 申请门店名称
  33 + /// </summary>
  34 + public string applicationStoreName { get; set; }
30 35  
31 36 /// <summary>
32 37 /// 申请时间
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs
... ... @@ -26,6 +26,11 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
26 26 /// 申请门店
27 27 /// </summary>
28 28 public string applicationStoreId { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 申请门店名称
  32 + /// </summary>
  33 + public string applicationStoreName { get; set; }
29 34  
30 35 /// <summary>
31 36 /// 申请时间
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/Dto/LqMdXdbhsj/LqMdXdbhsjCrInput.cs
... ... @@ -43,7 +43,7 @@ namespace NCC.Extend.Entitys.Dto.LqMdXdbhsj
43 43 /// 阶段
44 44 /// </summary>
45 45 [Required(ErrorMessage = "阶段不能为空")]
46   - [Range(0, 1, ErrorMessage = "阶段值必须在1-3之间")]
  46 + [Range(1, 3, ErrorMessage = "阶段值必须在1-3之间")]
47 47 public int stage { get; set; } = 1;
48 48 }
49 49 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/Dto/LqMdXdbhsj/LqMdXdbhsjUpInput.cs
... ... @@ -43,7 +43,7 @@ namespace NCC.Extend.Entitys.Dto.LqMdXdbhsj
43 43 /// 阶段
44 44 /// </summary>
45 45 [Required(ErrorMessage = "阶段不能为空")]
46   - [Range(0, 1, ErrorMessage = "阶段值必须在1-3之间")]
  46 + [Range(1, 3, ErrorMessage = "阶段值必须在1-3之间")]
47 47 public int stage { get; set; } = 1;
48 48 }
49 49 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
... ... @@ -27,7 +27,7 @@ namespace NCC.Extend
27 27 /// <summary>
28 28 /// 店助薪酬服务
29 29 /// </summary>
30   - [ApiDescriptionSettings(Tag = "店助薪酬服务", Name = "LqAssistantSalary", Order = 301)]
  30 + [ApiDescriptionSettings(Tag = "店助、店助主任薪酬服务", Name = "LqAssistantSalary", Order = 301)]
31 31 [Route("api/Extend/[controller]")]
32 32 public class LqAssistantSalaryService : IDynamicApiController, ITransient
33 33 {
... ... @@ -156,14 +156,14 @@ namespace NCC.Extend
156 156 var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
157 157 var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
158 158  
159   - // 1.3 门店目标信息 (lq_md_target) - 包含门店生命线和阶段目标
  159 + // 1.4 门店目标信息 (lq_md_target) - 包含门店生命线和阶段目标
160 160 var storeTargets = await _db.Queryable<LqMdTargetEntity>()
161 161 .Where(x => x.Month == monthStr)
162 162 .ToListAsync();
163 163 var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId))
164 164 .ToDictionary(x => x.StoreId, x => x);
165 165  
166   - // 1.4 门店新店保护信息 (lq_md_xdbhsj)
  166 + // 1.5 门店新店保护信息 (lq_md_xdbhsj)
167 167 var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>()
168 168 .Where(x => x.Sfqy == 1)
169 169 .ToListAsync();
... ... @@ -172,7 +172,7 @@ namespace NCC.Extend
172 172 .GroupBy(x => x.Mdid)
173 173 .ToDictionary(g => g.Key, g => g.First());
174 174  
175   - // 1.5 门店总业绩计算 (开单实付 - 退卡金额)
  175 + // 1.6 门店总业绩计算 (开单实付 - 退卡金额)
176 176 // 开单实付
177 177 var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>()
178 178 .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
... ... @@ -193,7 +193,7 @@ namespace NCC.Extend
193 193 .GroupBy(x => x.Md)
194 194 .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
195 195  
196   - // 1.6 进店消耗人数统计(有消费金额的,按门店按月去重客户数)
  196 + // 1.7 进店消耗人数统计(有消费金额的,按门店按月去重客户数)
197 197 // 使用SQL查询优化性能
198 198 var headcountSql = $@"
199 199 SELECT
... ... @@ -216,7 +216,7 @@ namespace NCC.Extend
216 216 .Where(x => x.StoreId != null)
217 217 .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount));
218 218  
219   - // 1.7 考勤数据 (lq_attendance_summary)
  219 + // 1.8 考勤数据 (lq_attendance_summary)
220 220 var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
221 221 .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
222 222 .ToListAsync();
... ... @@ -312,9 +312,12 @@ namespace NCC.Extend
312 312 }
313 313  
314 314 // 2.5 计算门店总提成(先计算门店级别的提成)
  315 + // 判断岗位类型:店助 或 店助主任
  316 + bool isDirector = salary.Position == "店助主任";
  317 +
315 318 decimal storeTotalCommission = 0;
316 319 decimal commissionRate = 0;
317   -
  320 +
318 321 // 如果门店生命线未设置(<=0),则没有提成
319 322 if (salary.StoreLifeline <= 0)
320 323 {
... ... @@ -323,10 +326,43 @@ namespace NCC.Extend
323 326 }
324 327 else
325 328 {
326   - commissionRate = CalculateCommissionRate(salary.StoreTotalPerformance, salary.StoreLifeline);
327   - storeTotalCommission = salary.StoreTotalPerformance * commissionRate;
  329 + // 根据岗位类型计算提成
  330 + if (isDirector)
  331 + {
  332 + // 店助主任:使用阶梯提成模式
  333 + // 业绩 < 70%:0%
  334 + // 70% ≤ 业绩 < 100%:0.4%(阶梯)
  335 + // 超过生命线部分:1.6%(阶梯)
  336 + storeTotalCommission = CalculateDirectorCommission(salary.StoreTotalPerformance, salary.StoreLifeline);
  337 + // 店助主任的提成比例用于显示,计算平均比例
  338 + if (salary.StoreTotalPerformance > 0)
  339 + {
  340 + commissionRate = storeTotalCommission / salary.StoreTotalPerformance;
  341 + }
  342 + else
  343 + {
  344 + commissionRate = 0;
  345 + }
  346 + }
  347 + else
  348 + {
  349 + // 店助:使用阶梯提成模式
  350 + // 业绩 < 70%:0%
  351 + // 70% ≤ 业绩 < 100%:0.4%(阶梯)
  352 + // 业绩 ≥ 100%:0.6%(阶梯)
  353 + storeTotalCommission = CalculateAssistantCommission(salary.StoreTotalPerformance, salary.StoreLifeline);
  354 + // 店助的提成比例用于显示,计算平均比例
  355 + if (salary.StoreTotalPerformance > 0)
  356 + {
  357 + commissionRate = storeTotalCommission / salary.StoreTotalPerformance;
  358 + }
  359 + else
  360 + {
  361 + commissionRate = 0;
  362 + }
  363 + }
328 364 }
329   -
  365 +
330 366 salary.CommissionRate = commissionRate;
331 367  
332 368 // 2.6 统计进店消耗人数
... ... @@ -336,7 +372,7 @@ namespace NCC.Extend
336 372 decimal storeTotalStageReward = 0;
337 373 bool reachedStage1 = false;
338 374 bool reachedStage2 = false;
339   -
  375 +
340 376 // 如果阶段目标未设置(为0),则没有奖励考核,奖励金额为0
341 377 if (salary.Stage1TargetHeadCount <= 0 && salary.Stage2TargetHeadCount <= 0)
342 378 {
... ... @@ -372,7 +408,7 @@ namespace NCC.Extend
372 408 }
373 409 }
374 410  
375   - // 2.8 计算底薪(根据门店分类
  411 + // 2.8 计算底薪(店助和店助主任底薪规则相同,都根据门店分类确定
376 412 salary.BaseSalary = CalculateBaseSalary(salary.StoreCategory.Value);
377 413  
378 414 // 2.9 固定奖励(手机管理费)
... ... @@ -400,10 +436,10 @@ namespace NCC.Extend
400 436 {
401 437 // 按比例计算提成
402 438 salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays;
403   -
  439 +
404 440 // 按比例计算奖励
405 441 salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays;
406   -
  442 +
407 443 // 计算阶段奖励的明细(按比例分配)
408 444 if (reachedStage2)
409 445 {
... ... @@ -482,12 +518,12 @@ namespace NCC.Extend
482 518 }
483 519  
484 520 /// <summary>
485   - /// 计算提成比例
  521 + /// 计算店助提成(阶梯提成模式)
486 522 /// </summary>
487 523 /// <param name="storePerformance">门店业绩</param>
488 524 /// <param name="storeLifeline">门店生命线</param>
489   - /// <returns>提成比例(0%/0.4%/0.6%)</returns>
490   - private decimal CalculateCommissionRate(decimal storePerformance, decimal storeLifeline)
  525 + /// <returns>提成金额</returns>
  526 + private decimal CalculateAssistantCommission(decimal storePerformance, decimal storeLifeline)
491 527 {
492 528 if (storeLifeline <= 0)
493 529 {
... ... @@ -503,18 +539,34 @@ namespace NCC.Extend
503 539 }
504 540 else if (ratio < 1.0m)
505 541 {
506   - // 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% → 0.4%
507   - return 0.004m;
  542 + // 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% → 0.4%(阶梯)
  543 + // 70%以下部分:0%
  544 + // 70%-100%部分:0.4%
  545 + decimal stage70 = storeLifeline * 0.7m;
  546 + decimal stage100 = storeLifeline;
  547 + decimal performanceInRange = storePerformance - stage70;
  548 + return performanceInRange * 0.004m;
508 549 }
509 550 else
510 551 {
511   - // 门店业绩 ≥ 门店生命线 × 100% → 0.6%
512   - return 0.006m;
  552 + // 门店业绩 ≥ 门店生命线 × 100% → 0.6%(阶梯)
  553 + // 70%以下部分:0%
  554 + // 70%-100%部分:0.4%
  555 + // 100%以上部分:0.6%
  556 + decimal stage70 = storeLifeline * 0.7m;
  557 + decimal stage100 = storeLifeline;
  558 + decimal performance70To100 = stage100 - stage70;
  559 + decimal performanceAbove100 = storePerformance - stage100;
  560 +
  561 + decimal commission70To100 = performance70To100 * 0.004m;
  562 + decimal commissionAbove100 = performanceAbove100 * 0.006m;
  563 +
  564 + return commission70To100 + commissionAbove100;
513 565 }
514 566 }
515 567  
516 568 /// <summary>
517   - /// 计算底薪
  569 + /// 计算底薪(店助)
518 570 /// </summary>
519 571 /// <param name="storeCategory">门店分类(1=A类,2=B类,3=C类)</param>
520 572 /// <returns>底薪金额</returns>
... ... @@ -528,6 +580,53 @@ namespace NCC.Extend
528 580 _ => throw new Exception($"门店分类值无效:{storeCategory},有效值为1(A类)、2(B类)、3(C类)")
529 581 };
530 582 }
  583 +
  584 + /// <summary>
  585 + /// 计算店助主任提成(阶梯提成模式)
  586 + /// </summary>
  587 + /// <param name="storePerformance">门店业绩</param>
  588 + /// <param name="storeLifeline">门店生命线</param>
  589 + /// <returns>提成金额</returns>
  590 + private decimal CalculateDirectorCommission(decimal storePerformance, decimal storeLifeline)
  591 + {
  592 + if (storeLifeline <= 0)
  593 + {
  594 + return 0;
  595 + }
  596 +
  597 + decimal ratio = storePerformance / storeLifeline;
  598 +
  599 + if (ratio < 0.7m)
  600 + {
  601 + // 门店业绩 < 门店生命线 × 70% → 0%
  602 + return 0;
  603 + }
  604 + else if (ratio < 1.0m)
  605 + {
  606 + // 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% → 0.4%(阶梯)
  607 + // 70%以下部分:0%
  608 + // 70%-100%部分:0.4%
  609 + decimal stage70 = storeLifeline * 0.7m;
  610 + decimal performanceInRange = storePerformance - stage70;
  611 + return performanceInRange * 0.004m;
  612 + }
  613 + else
  614 + {
  615 + // 门店业绩 ≥ 门店生命线 × 100% → 阶梯提成
  616 + // 70%以下部分:0%
  617 + // 70%-100%部分:0.4%
  618 + // 超过生命线部分:1.6%
  619 + decimal stage70 = storeLifeline * 0.7m;
  620 + decimal stage100 = storeLifeline;
  621 + decimal performance70To100 = stage100 - stage70;
  622 + decimal performanceAbove100 = storePerformance - stage100;
  623 +
  624 + decimal commission70To100 = performance70To100 * 0.004m;
  625 + decimal commissionAbove100 = performanceAbove100 * 0.016m;
  626 +
  627 + return commission70To100 + commissionAbove100;
  628 + }
  629 + }
531 630 }
532 631 }
533 632  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
1   -using System;
  1 +using System;
2 2 using System.Collections.Generic;
3 3 using System.Linq;
4 4 using System.Net.Http;
... ... @@ -2381,6 +2381,10 @@ namespace NCC.Extend.LqKdKdjlb
2381 2381 kdhyc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc),
2382 2382 kdhysjh = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh),
2383 2383 fkfs = it.Fkfs, // 付款方式
  2384 + fkyy = it.Fkyy, // 付款医院ID
  2385 + fkyyName = SqlFunc.Subqueryable<LqHzfEntity>().Where(x => x.Id == it.Fkyy).Select(x => x.Hzmc), // 付款医院名称
  2386 + activityId = it.ActivityId, // 营销活动ID
  2387 + activityName = SqlFunc.Subqueryable<LqPackageInfoEntity>().Where(x => x.Id == it.ActivityId).Select(x => x.ActivityName), // 营销活动名称
2384 2388 khly = it.Khly, // 客户来源
2385 2389 bz = it.Bz, // 备注
2386 2390 createTime = it.CreateTime
... ... @@ -2564,6 +2568,10 @@ namespace NCC.Extend.LqKdKdjlb
2564 2568 customerType = billing.gjlx,
2565 2569 cooperationInstitution = billing.hgjg,
2566 2570 cooperationInstitutionName = billing.hgjgName,
  2571 + paymentHospital = billing.fkyy, // 付款医院ID
  2572 + paymentHospitalName = billing.fkyyName, // 付款医院名称
  2573 + activityId = billing.activityId, // 营销活动ID
  2574 + activityName = billing.activityName, // 营销活动名称
2567 2575 // 品项分类
2568 2576 purchasedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "购买").Select(x => new
2569 2577 {
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs
... ... @@ -79,7 +79,8 @@ namespace NCC.Extend.LqPurchaseRecords
79 79 List<string> queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject<List<string>>() : null;
80 80 DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null;
81 81 DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null;
82   - var data = await _db.Queryable<LqPurchaseRecordsEntity>()
  82 + // 先查询购买记录数据
  83 + var purchaseRecordsQuery = _db.Queryable<LqPurchaseRecordsEntity>()
83 84 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
84 85 .WhereIF(!string.IsNullOrEmpty(input.reimbursementCategoryId), p => p.ReimbursementCategoryId.Contains(input.reimbursementCategoryId))
85 86 .WhereIF(!string.IsNullOrEmpty(input.reimbursementCategoryName), p => p.ReimbursementCategoryName.Contains(input.reimbursementCategoryName))
... ... @@ -93,30 +94,92 @@ namespace NCC.Extend.LqPurchaseRecords
93 94 .WhereIF(queryCreateTime != null, p => p.CreateTime <= new DateTime(endCreateTime.ToDate().Year, endCreateTime.ToDate().Month, endCreateTime.ToDate().Day, 23, 59, 59))
94 95 .WhereIF(!string.IsNullOrEmpty(input.createUser), p => p.CreateUser.Equals(input.createUser))
95 96 .WhereIF(!string.IsNullOrEmpty(input.createUserStoreId), p => p.CreateUserStoreId.Contains(input.createUserStoreId))
96   - .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => p.ApproveStatus.Contains(input.approveStatus))
97 97 .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser))
98 98 .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0))
99 99 .WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59))
100   - .WhereIF(!string.IsNullOrEmpty(input.applicationId), p => p.ApplicationId.Contains(input.applicationId))
101   - .Select(it => new LqPurchaseRecordsListOutput
  100 + .WhereIF(!string.IsNullOrEmpty(input.applicationId), p => p.ApplicationId.Contains(input.applicationId));
  101 +
  102 + var total = await purchaseRecordsQuery.CountAsync();
  103 + var purchaseRecords = await purchaseRecordsQuery.ToPageListAsync(input.currentPage, input.pageSize);
  104 +
  105 + // 获取所有关联的报销申请ID
  106 + var applicationIds = purchaseRecords.Where(pr => !string.IsNullOrEmpty(pr.ApplicationId)).Select(pr => pr.ApplicationId).Distinct().ToList();
  107 +
  108 + // 查询关联的报销申请状态
  109 + var applications = new Dictionary<string, string>();
  110 + if (applicationIds.Any())
  111 + {
  112 + var appList = await _db.Queryable<LqReimbursementApplicationEntity>()
  113 + .Where(x => applicationIds.Contains(x.Id))
  114 + .Select(x => new { x.Id, ApprovalStatus = x.ApprovalStatus ?? x.ApproveStatus })
  115 + .ToListAsync();
  116 +
  117 + foreach (var app in appList)
102 118 {
103   - id = it.Id,
104   - reimbursementCategoryId = it.ReimbursementCategoryId,
105   - reimbursementCategoryName = it.ReimbursementCategoryName,
106   - unitPrice = it.UnitPrice,
107   - quantity = it.Quantity,
108   - amount = it.Amount,
109   - memo = it.Memo,
110   - purchaseTime = it.PurchaseTime,
111   - createTime = it.CreateTime,
112   - createUser = it.CreateUser,
113   - createUserStoreId = it.CreateUserStoreId,
114   - approveStatus = it.ApproveStatus,
115   - approveUser = it.ApproveUser,
116   - approveTime = it.ApproveTime,
117   - applicationId = it.ApplicationId,
118   - }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
119   - return PageResult<LqPurchaseRecordsListOutput>.SqlSugarPageResult(data);
  119 + // 状态映射:已通过 -> 已审批,其他状态保持原样
  120 + var mappedStatus = app.ApprovalStatus == "已通过" ? "已审批" : app.ApprovalStatus;
  121 + applications[app.Id] = mappedStatus;
  122 + }
  123 + }
  124 +
  125 + // 组装返回数据,如果关联了报销申请,优先使用报销申请的审批状态
  126 + var result = purchaseRecords.Select(pr => new LqPurchaseRecordsListOutput
  127 + {
  128 + id = pr.Id,
  129 + reimbursementCategoryId = pr.ReimbursementCategoryId,
  130 + reimbursementCategoryName = pr.ReimbursementCategoryName,
  131 + unitPrice = pr.UnitPrice,
  132 + quantity = pr.Quantity,
  133 + amount = pr.Amount,
  134 + memo = pr.Memo,
  135 + purchaseTime = pr.PurchaseTime,
  136 + createTime = pr.CreateTime,
  137 + createUser = pr.CreateUser,
  138 + createUserStoreId = pr.CreateUserStoreId,
  139 + // 如果关联了报销申请,优先使用报销申请的审批状态;否则使用购买记录的状态
  140 + approveStatus = !string.IsNullOrEmpty(pr.ApplicationId) && applications.ContainsKey(pr.ApplicationId)
  141 + ? applications[pr.ApplicationId]
  142 + : pr.ApproveStatus,
  143 + approveUser = pr.ApproveUser,
  144 + approveTime = pr.ApproveTime,
  145 + applicationId = pr.ApplicationId,
  146 + }).ToList();
  147 +
  148 + // 处理审批状态筛选(在内存中处理,因为状态可能来自报销申请)
  149 + if (!string.IsNullOrEmpty(input.approveStatus))
  150 + {
  151 + result = result.Where(x => x.approveStatus != null && x.approveStatus.Contains(input.approveStatus)).ToList();
  152 + total = result.Count;
  153 + }
  154 +
  155 + // 处理排序
  156 + if (string.IsNullOrEmpty(input.sidx))
  157 + {
  158 + result = result.OrderByDescending(x => x.createTime).ToList();
  159 + }
  160 + else
  161 + {
  162 + var sortType = input.sort?.ToLower() == "desc" ? OrderByType.Desc : OrderByType.Asc;
  163 + switch (input.sidx.ToLower())
  164 + {
  165 + case "id":
  166 + result = sortType == OrderByType.Desc ? result.OrderByDescending(x => x.id).ToList() : result.OrderBy(x => x.id).ToList();
  167 + break;
  168 + case "createtime":
  169 + result = sortType == OrderByType.Desc ? result.OrderByDescending(x => x.createTime).ToList() : result.OrderBy(x => x.createTime).ToList();
  170 + break;
  171 + default:
  172 + result = result.OrderByDescending(x => x.createTime).ToList();
  173 + break;
  174 + }
  175 + }
  176 +
  177 + return PageResult<LqPurchaseRecordsListOutput>.SqlSugarPageResult(
  178 + new SqlSugarPagedList<LqPurchaseRecordsListOutput>
  179 + {
  180 + list = result,
  181 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total }
  182 + });
120 183 }
121 184  
122 185 /// <summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
... ... @@ -18,6 +18,7 @@ using NCC.Extend.Entitys.Dto.LqReimbursementApplication;
18 18 using NCC.Extend.Entitys.lq_reimbursement_application_node;
19 19 using NCC.Extend.Entitys.lq_reimbursement_application_node_user;
20 20 using NCC.Extend.Entitys.lq_reimbursement_approval_record;
  21 +using NCC.Extend.Entitys.lq_mdxx;
21 22 using Yitter.IdGenerator;
22 23 using NCC.Common.Helper;
23 24 using NCC.JsonSerialization;
... ... @@ -84,6 +85,24 @@ namespace NCC.Extend.LqReimbursementApplication
84 85 .OrderBy(x => x.ApprovalTime)
85 86 .ToListAsync();
86 87  
  88 + // 获取关联的购买记录
  89 + var purchaseRecords = await _db.Queryable<LqPurchaseRecordsEntity>()
  90 + .Where(x => x.ApplicationId == id)
  91 + .OrderBy(x => x.CreateTime)
  92 + .ToListAsync();
  93 +
  94 + // 获取门店名称
  95 + string storeName = null;
  96 + if (!string.IsNullOrEmpty(entity.ApplicationStoreId))
  97 + {
  98 + var store = await _db.Queryable<LqMdxxEntity>()
  99 + .Where(x => x.Id == entity.ApplicationStoreId)
  100 + .Select(x => x.Dm)
  101 + .FirstAsync();
  102 + storeName = store ?? null;
  103 + }
  104 + output.applicationStoreName = storeName;
  105 +
87 106 // 组装节点信息
88 107 var nodeList = nodes.Select(n => new
89 108 {
... ... @@ -122,6 +141,27 @@ namespace NCC.Extend.LqReimbursementApplication
122 141 .ToList();
123 142 }
124 143  
  144 + // 组装购买记录数据
  145 + var purchaseRecordsList = purchaseRecords.Select(pr => new
  146 + {
  147 + id = pr.Id,
  148 + reimbursementCategoryId = pr.ReimbursementCategoryId,
  149 + reimbursementCategoryName = pr.ReimbursementCategoryName,
  150 + unitPrice = pr.UnitPrice,
  151 + quantity = pr.Quantity,
  152 + amount = pr.Amount,
  153 + memo = pr.Memo,
  154 + attachment = pr.Attachment,
  155 + purchaseTime = pr.PurchaseTime,
  156 + createTime = pr.CreateTime,
  157 + createUser = pr.CreateUser,
  158 + createUserStoreId = pr.CreateUserStoreId,
  159 + approveStatus = pr.ApproveStatus,
  160 + approveUser = pr.ApproveUser,
  161 + approveTime = pr.ApproveTime,
  162 + applicationId = pr.ApplicationId
  163 + }).ToList();
  164 +
125 165 return new
126 166 {
127 167 form = output,
... ... @@ -129,7 +169,8 @@ namespace NCC.Extend.LqReimbursementApplication
129 169 currentApprovers = currentApprovers,
130 170 currentNodeOrder = entity.CurrentNodeOrder,
131 171 approvalStatus = entity.ApprovalStatus ?? entity.ApproveStatus,
132   - returnedReason = entity.ReturnedReason
  172 + returnedReason = entity.ReturnedReason,
  173 + purchaseRecords = purchaseRecordsList
133 174 };
134 175 }
135 176  
... ... @@ -141,7 +182,6 @@ namespace NCC.Extend.LqReimbursementApplication
141 182 [HttpGet("")]
142 183 public async Task<dynamic> GetList([FromQuery] LqReimbursementApplicationListQueryInput input)
143 184 {
144   - var sidx = input.sidx == null ? "id" : input.sidx;
145 185 List<string> queryApplicationTime = input.applicationTime != null ? input.applicationTime.Split(',').ToObeject<List<string>>() : null;
146 186 DateTime? startApplicationTime = queryApplicationTime != null ? Ext.GetDateTime(queryApplicationTime.First()) : null;
147 187 DateTime? endApplicationTime = queryApplicationTime != null ? Ext.GetDateTime(queryApplicationTime.Last()) : null;
... ... @@ -160,8 +200,50 @@ namespace NCC.Extend.LqReimbursementApplication
160 200 .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => (p.ApprovalStatus ?? p.ApproveStatus).Contains(input.approveStatus))
161 201 // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0))
162 202 //.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59))
163   - .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId))
164   - .OrderBy(sidx + " " + input.sort);
  203 + .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId));
  204 +
  205 + // 处理排序(兼容前端传入的字段名)
  206 + if (string.IsNullOrEmpty(input.sidx))
  207 + {
  208 + query = query.OrderBy(x => x.ApplicationTime, OrderByType.Desc);
  209 + }
  210 + else
  211 + {
  212 + var sortType = input.sort?.ToLower() == "desc" ? OrderByType.Desc : OrderByType.Asc;
  213 + // 根据字段名映射到实体属性(兼容前端传入的字段名)
  214 + switch (input.sidx.ToLower())
  215 + {
  216 + case "id":
  217 + query = query.OrderBy(x => x.Id, sortType);
  218 + break;
  219 + case "applicationtime":
  220 + case "application_time":
  221 + query = query.OrderBy(x => x.ApplicationTime, sortType);
  222 + break;
  223 + case "amount":
  224 + query = query.OrderBy(x => x.Amount, sortType);
  225 + break;
  226 + case "applicationuserid":
  227 + case "application_user_id":
  228 + query = query.OrderBy(x => x.ApplicationUserId, sortType);
  229 + break;
  230 + case "applicationusername":
  231 + case "application_user_name":
  232 + query = query.OrderBy(x => x.ApplicationUserName, sortType);
  233 + break;
  234 + case "approvestatus":
  235 + case "approve_status":
  236 + query = query.OrderBy(x => x.ApprovalStatus ?? x.ApproveStatus, sortType);
  237 + break;
  238 + case "approvetime":
  239 + case "approve_time":
  240 + query = query.OrderBy(x => x.ApproveTime, sortType);
  241 + break;
  242 + default:
  243 + query = query.OrderBy(x => x.ApplicationTime, OrderByType.Desc);
  244 + break;
  245 + }
  246 + }
165 247  
166 248 var total = await query.CountAsync();
167 249 var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
... ... @@ -187,6 +269,18 @@ namespace NCC.Extend.LqReimbursementApplication
187 269 .GroupBy(x => (string)x.applicationId)
188 270 .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName)));
189 271  
  272 + // 获取门店名称
  273 + var storeIds = entities.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId)).Select(x => x.ApplicationStoreId).Distinct().ToList();
  274 + var storeDict = new Dictionary<string, string>();
  275 + if (storeIds.Any())
  276 + {
  277 + var stores = await _db.Queryable<LqMdxxEntity>()
  278 + .Where(x => storeIds.Contains(x.Id))
  279 + .Select(x => new { x.Id, x.Dm })
  280 + .ToListAsync();
  281 + storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
  282 + }
  283 +
190 284 // 组装返回数据
191 285 var result = entities.Select(item => new LqReimbursementApplicationListOutput
192 286 {
... ... @@ -194,6 +288,9 @@ namespace NCC.Extend.LqReimbursementApplication
194 288 applicationUserId = item.ApplicationUserId,
195 289 applicationUserName = item.ApplicationUserName,
196 290 applicationStoreId = item.ApplicationStoreId,
  291 + applicationStoreName = !string.IsNullOrEmpty(item.ApplicationStoreId) && storeDict.ContainsKey(item.ApplicationStoreId)
  292 + ? storeDict[item.ApplicationStoreId]
  293 + : null,
197 294 applicationTime = item.ApplicationTime,
198 295 amount = item.Amount,
199 296 approveUser = item.ApproveUser,
... ... @@ -217,9 +314,9 @@ namespace NCC.Extend.LqReimbursementApplication
217 314 /// 新建报销申请表
218 315 /// </summary>
219 316 /// <param name="input">参数</param>
220   - /// <returns></returns>
  317 + /// <returns>返回创建的申请ID</returns>
221 318 [HttpPost("")]
222   - public async Task Create([FromBody] LqReimbursementApplicationCrInput input)
  319 + public async Task<dynamic> Create([FromBody] LqReimbursementApplicationCrInput input)
223 320 {
224 321 var userInfo = await _userManager.GetUserInfo();
225 322 var entity = input.Adapt<LqReimbursementApplicationEntity>();
... ... @@ -231,9 +328,15 @@ namespace NCC.Extend.LqReimbursementApplication
231 328 _db.BeginTran();
232 329  
233 330 // 1. 验证节点配置
234   - if (input.nodes == null || input.nodes.Count < 3 || input.nodes.Count > 5)
  331 + if (input.nodes == null || input.nodes.Count == 0)
  332 + {
  333 + throw new Exception("至少需要配置1个审批节点");
  334 + }
  335 +
  336 + // 设置合理的上限,避免节点过多
  337 + if (input.nodes.Count > 20)
235 338 {
236   - throw new Exception("节点数量必须在3-5个之间");
  339 + throw new Exception("节点数量不能超过20个");
237 340 }
238 341  
239 342 // 验证节点顺序是否连续(1, 2, 3, ...)
... ... @@ -340,6 +443,9 @@ namespace NCC.Extend.LqReimbursementApplication
340 443  
341 444 //关闭事务
342 445 _db.CommitTran();
  446 +
  447 + // 返回创建的申请ID
  448 + return new { id = entity.Id };
343 449 }
344 450 catch (Exception)
345 451 {
... ... @@ -484,6 +590,13 @@ namespace NCC.Extend.LqReimbursementApplication
484 590  
485 591 // 获取原有的关联购买记录ID
486 592 var oldEntity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id);
  593 + _ = oldEntity ?? throw NCCException.Oh(ErrorCode.COM1005);
  594 +
  595 + // 检查是否可以修改:只有"待审批"(CurrentNodeOrder=0)或"已退回"状态的申请才能修改
  596 + if (oldEntity.CurrentNodeOrder != null && oldEntity.CurrentNodeOrder != 0 && oldEntity.ApprovalStatus != "已退回")
  597 + {
  598 + throw new Exception($"该申请当前状态为{oldEntity.ApprovalStatus},无法修改。只有待审批或已退回状态的申请才能修改。");
  599 + }
487 600 var oldIds = new List<string>();
488 601 if (oldEntity != null && !string.IsNullOrEmpty(oldEntity.PurchaseRecordsId))
489 602 {
... ... @@ -582,7 +695,7 @@ namespace NCC.Extend.LqReimbursementApplication
582 695 throw new Exception("该申请已经提交审批,不能重复提交");
583 696 }
584 697  
585   - if (entity.NodeCount == null || entity.NodeCount < 3 || entity.NodeCount > 5)
  698 + if (entity.NodeCount == null || entity.NodeCount == 0)
586 699 {
587 700 throw new Exception("节点配置异常,无法提交审批");
588 701 }
... ... @@ -605,30 +718,61 @@ namespace NCC.Extend.LqReimbursementApplication
605 718 entity.CurrentNodeOrder = 1;
606 719 entity.CurrentNodeId = firstNode.Id;
607 720 entity.ApprovalStatus = "审批中";
  721 + // 清除退回原因(重新提交审批)
  722 + entity.ReturnedReason = null;
  723 + entity.ReturnedNodeOrder = null;
608 724 await _db.Updateable(entity).ExecuteCommandAsync();
609 725  
610   - // 为第一个节点的每个审批人创建待审批记录
  726 + // 获取第一个节点的所有审批人
611 727 var firstNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
612 728 .Where(x => x.ApplicationId == id && x.NodeOrder == 1)
613 729 .ToListAsync();
614 730  
  731 + // 获取第一个节点已有的审批记录
  732 + var existingRecords = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  733 + .Where(x => x.ApplicationId == id && x.NodeOrder == 1)
  734 + .ToListAsync();
  735 +
  736 + // 为第一个节点的每个审批人创建待审批记录(如果不存在)
615 737 foreach (var approver in firstNodeApprovers)
616 738 {
617   - var record = new LqReimbursementApprovalRecordEntity
  739 + // 检查是否已存在该审批人的"待审批"记录
  740 + var existingPendingRecord = existingRecords.FirstOrDefault(x =>
  741 + x.ApproverId == approver.UserId && x.ApprovalResult == "待审批");
  742 +
  743 + if (existingPendingRecord == null)
618 744 {
619   - Id = YitIdHelper.NextId().ToString(),
620   - ApplicationId = id,
621   - NodeId = firstNode.Id,
622   - NodeOrder = 1,
623   - ApproverId = approver.UserId,
624   - ApproverName = approver.UserName,
625   - ApprovalResult = "待审批",
626   - IsCurrentNode = 1,
627   - ApprovalTime = null
628   - };
629   - await _db.Insertable(record).ExecuteCommandAsync();
  745 + // 如果不存在"待审批"记录,创建新的待审批记录
  746 + // 注意:即使之前有"退回"记录,也创建新的"待审批"记录,保留历史记录
  747 + var record = new LqReimbursementApprovalRecordEntity
  748 + {
  749 + Id = YitIdHelper.NextId().ToString(),
  750 + ApplicationId = id,
  751 + NodeId = firstNode.Id,
  752 + NodeOrder = 1,
  753 + ApproverId = approver.UserId,
  754 + ApproverName = approver.UserName,
  755 + ApprovalResult = "待审批",
  756 + IsCurrentNode = 1,
  757 + ApprovalTime = null
  758 + };
  759 + await _db.Insertable(record).ExecuteCommandAsync();
  760 + }
  761 + else
  762 + {
  763 + // 如果已存在"待审批"记录,只更新IsCurrentNode状态
  764 + existingPendingRecord.IsCurrentNode = 1;
  765 + await _db.Updateable(existingPendingRecord).ExecuteCommandAsync();
  766 + }
630 767 }
631 768  
  769 + // 更新所有审批记录的IsCurrentNode状态(将第一个节点的"待审批"记录设为1,其他设为0)
  770 + // 注意:保留所有历史记录(包括"退回"记录),只更新IsCurrentNode状态
  771 + await _db.Updateable<LqReimbursementApprovalRecordEntity>()
  772 + .SetColumns(it => it.IsCurrentNode == 0)
  773 + .Where(it => it.ApplicationId == id && (it.NodeOrder != 1 || it.ApprovalResult != "待审批"))
  774 + .ExecuteCommandAsync();
  775 +
632 776 _db.CommitTran();
633 777 }
634 778 catch (Exception)
... ... @@ -678,17 +822,33 @@ namespace NCC.Extend.LqReimbursementApplication
678 822 }
679 823 }
680 824  
681   - // 检查是否已经审批过
682   - var hasApproved = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  825 + // 检查是否已经审批过(排除待审批状态)
  826 + // 注意:如果存在"待审批"记录,说明可以审批
  827 + // 如果不存在"待审批"记录,但存在其他状态的记录(通过、不通过),说明已经审批过
  828 + // 但是,如果之前有"退回"记录,重新提交审批后会创建新的"待审批"记录,所以这里优先检查"待审批"状态
  829 + var existingPendingApprovalRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
683 830 .Where(x => x.ApplicationId == id
684 831 && x.NodeOrder == entity.CurrentNodeOrder
685 832 && x.ApproverId == userInfo.userId
686   - && x.ApprovalResult != "待审批")
687   - .AnyAsync();
  833 + && x.ApprovalResult == "待审批")
  834 + .FirstAsync();
688 835  
689   - if (hasApproved)
  836 + // 如果存在"待审批"记录,说明可以审批,不需要抛出异常
  837 + // 如果不存在"待审批"记录,需要检查是否有"通过"或"不通过"的记录(不包括"退回",因为退回后可以重新审批)
  838 + if (existingPendingApprovalRecord == null)
690 839 {
691   - throw new Exception("您已经审批过该节点");
  840 + // 检查是否有"通过"或"不通过"的记录(不包括"退回")
  841 + var completedRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  842 + .Where(x => x.ApplicationId == id
  843 + && x.NodeOrder == entity.CurrentNodeOrder
  844 + && x.ApproverId == userInfo.userId
  845 + && (x.ApprovalResult == "通过" || x.ApprovalResult == "不通过"))
  846 + .FirstAsync();
  847 +
  848 + if (completedRecord != null)
  849 + {
  850 + throw new Exception("您已经审批过该节点");
  851 + }
692 852 }
693 853  
694 854 try
... ... @@ -705,7 +865,7 @@ namespace NCC.Extend.LqReimbursementApplication
705 865 throw new Exception("未找到当前节点配置");
706 866 }
707 867  
708   - // 更新待审批记录为已审批(如果存在待审批记录)
  868 + // 查找现有的审批记录(优先查找"待审批"记录)
709 869 var existingRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
710 870 .Where(x => x.ApplicationId == id
711 871 && x.NodeOrder == entity.CurrentNodeOrder
... ... @@ -713,12 +873,35 @@ namespace NCC.Extend.LqReimbursementApplication
713 873 && x.ApprovalResult == "待审批")
714 874 .FirstAsync();
715 875  
  876 + // 如果不存在"待审批"记录,再查找其他记录
  877 + if (existingRecord == null)
  878 + {
  879 + existingRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  880 + .Where(x => x.ApplicationId == id
  881 + && x.NodeOrder == entity.CurrentNodeOrder
  882 + && x.ApproverId == userInfo.userId)
  883 + .FirstAsync();
  884 + }
  885 +
716 886 if (existingRecord != null)
717 887 {
718   - // 更新现有记录
  888 + // 如果审批结果是"待审批",允许更新
  889 + // 如果已经有明确的审批结果(通过/不通过),则不允许重复审批
  890 + // 注意:不包括"退回",因为退回后可以重新审批
  891 + if (!string.IsNullOrEmpty(existingRecord.ApprovalResult)
  892 + && existingRecord.ApprovalResult != "待审批"
  893 + && existingRecord.ApprovalResult != ""
  894 + && existingRecord.ApprovalResult != "退回")
  895 + {
  896 + // 已经有明确的审批结果(通过/不通过),不允许重复审批
  897 + throw new Exception("您已经审批过该节点");
  898 + }
  899 +
  900 + // 更新现有记录(包括空字符串、"待审批"、"退回"的情况)
719 901 existingRecord.ApprovalResult = result;
720 902 existingRecord.ApprovalOpinion = opinion;
721 903 existingRecord.ApprovalTime = DateTime.Now;
  904 + existingRecord.IsCurrentNode = 1;
722 905 await _db.Updateable(existingRecord).ExecuteCommandAsync();
723 906 }
724 907 else
... ... @@ -761,6 +944,13 @@ namespace NCC.Extend.LqReimbursementApplication
761 944 else if (result == "退回")
762 945 {
763 946 // 退回:退回到上一节点
  947 + // 先更新当前节点的退回记录的IsCurrentNode状态(设为0,因为不再是当前节点)
  948 + var currentNodeOrder = entity.CurrentNodeOrder.Value;
  949 + await _db.Updateable<LqReimbursementApprovalRecordEntity>()
  950 + .SetColumns(it => it.IsCurrentNode == 0)
  951 + .Where(x => x.ApplicationId == id && x.NodeOrder == currentNodeOrder)
  952 + .ExecuteCommandAsync();
  953 +
764 954 if (entity.CurrentNodeOrder == 1)
765 955 {
766 956 // 退回到申请人
... ... @@ -769,6 +959,7 @@ namespace NCC.Extend.LqReimbursementApplication
769 959 entity.ReturnedNodeOrder = 0;
770 960 entity.ReturnedReason = opinion;
771 961 entity.CurrentNodeId = null;
  962 + // 注意:不退回到发起人时,不删除审批记录,保留所有历史记录(包括退回记录)
772 963 }
773 964 else
774 965 {
... ... @@ -787,31 +978,47 @@ namespace NCC.Extend.LqReimbursementApplication
787 978 entity.CurrentNodeId = prevNode.Id;
788 979 }
789 980  
790   - // 清除上一节点的所有审批记录(重新审批)
791   - await _db.Deleteable<LqReimbursementApprovalRecordEntity>()
  981 + // 获取上一节点已有的审批记录
  982 + var prevNodeExistingRecords = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
792 983 .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
793   - .ExecuteCommandAsync();
  984 + .ToListAsync();
794 985  
795   - // 为上一节点的每个审批人创建新的待审批记录
  986 + // 获取上一节点的所有审批人
796 987 var prevNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
797 988 .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
798 989 .ToListAsync();
799 990  
  991 + // 为上一节点的每个审批人创建待审批记录(如果不存在)
800 992 foreach (var approver in prevNodeApprovers)
801 993 {
802   - var record = new LqReimbursementApprovalRecordEntity
  994 + // 检查是否已存在该审批人的"待审批"记录
  995 + var prevNodeExistingPendingRecord = prevNodeExistingRecords.FirstOrDefault(x =>
  996 + x.ApproverId == approver.UserId && x.ApprovalResult == "待审批");
  997 +
  998 + if (prevNodeExistingPendingRecord == null)
  999 + {
  1000 + // 如果不存在"待审批"记录,创建新的待审批记录
  1001 + // 注意:即使之前有"退回"或其他记录,也创建新的"待审批"记录,保留历史记录
  1002 + var record = new LqReimbursementApprovalRecordEntity
  1003 + {
  1004 + Id = YitIdHelper.NextId().ToString(),
  1005 + ApplicationId = id,
  1006 + NodeId = prevNode.Id,
  1007 + NodeOrder = entity.CurrentNodeOrder.Value,
  1008 + ApproverId = approver.UserId,
  1009 + ApproverName = approver.UserName,
  1010 + ApprovalResult = "待审批",
  1011 + IsCurrentNode = 1,
  1012 + ApprovalTime = null
  1013 + };
  1014 + await _db.Insertable(record).ExecuteCommandAsync();
  1015 + }
  1016 + else
803 1017 {
804   - Id = YitIdHelper.NextId().ToString(),
805   - ApplicationId = id,
806   - NodeId = prevNode.Id,
807   - NodeOrder = entity.CurrentNodeOrder.Value,
808   - ApproverId = approver.UserId,
809   - ApproverName = approver.UserName,
810   - ApprovalResult = "待审批",
811   - IsCurrentNode = 1,
812   - ApprovalTime = null
813   - };
814   - await _db.Insertable(record).ExecuteCommandAsync();
  1018 + // 如果已存在"待审批"记录,只更新IsCurrentNode状态
  1019 + prevNodeExistingPendingRecord.IsCurrentNode = 1;
  1020 + await _db.Updateable(prevNodeExistingPendingRecord).ExecuteCommandAsync();
  1021 + }
815 1022 }
816 1023 }
817 1024  
... ... @@ -848,6 +1055,11 @@ namespace NCC.Extend.LqReimbursementApplication
848 1055 // 所有人都已通过
849 1056 shouldMoveToNext = true;
850 1057 }
  1058 + // 特殊情况:如果只有一个审批人,且当前审批人就是该审批人,且审批结果是"通过",则直接进入下一节点
  1059 + else if (approvers.Count == 1 && approvers.Contains(userInfo.userId) && result == "通过")
  1060 + {
  1061 + shouldMoveToNext = true;
  1062 + }
851 1063 }
852 1064  
853 1065 if (shouldMoveToNext)
... ... @@ -952,13 +1164,36 @@ namespace NCC.Extend.LqReimbursementApplication
952 1164 [HttpGet("Actions/AllPendingApproval")]
953 1165 public async Task<dynamic> GetAllPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input)
954 1166 {
955   - var sidx = input.sidx == null ? "id" : input.sidx;
956   -
957 1167 // 管理员可以查看所有待审批的申请
958 1168 var query = _db.Queryable<LqReimbursementApplicationEntity>()
959 1169 .Where(x => x.ApprovalStatus == "审批中")
960   - .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId))
961   - .OrderBy(sidx + " " + input.sort);
  1170 + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId));
  1171 +
  1172 + // 处理排序
  1173 + if (string.IsNullOrEmpty(input.sidx))
  1174 + {
  1175 + query = query.OrderBy(x => x.ApplicationTime, OrderByType.Desc);
  1176 + }
  1177 + else
  1178 + {
  1179 + var sortType = input.sort?.ToLower() == "desc" ? OrderByType.Desc : OrderByType.Asc;
  1180 + // 根据字段名映射到实体属性
  1181 + switch (input.sidx.ToLower())
  1182 + {
  1183 + case "id":
  1184 + query = query.OrderBy(x => x.Id, sortType);
  1185 + break;
  1186 + case "applicationtime":
  1187 + query = query.OrderBy(x => x.ApplicationTime, sortType);
  1188 + break;
  1189 + case "amount":
  1190 + query = query.OrderBy(x => x.Amount, sortType);
  1191 + break;
  1192 + default:
  1193 + query = query.OrderBy(x => x.ApplicationTime, OrderByType.Desc);
  1194 + break;
  1195 + }
  1196 + }
962 1197  
963 1198 var total = await query.CountAsync();
964 1199 var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
... ... @@ -984,6 +1219,18 @@ namespace NCC.Extend.LqReimbursementApplication
984 1219 .GroupBy(x => (string)x.applicationId)
985 1220 .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName)));
986 1221  
  1222 + // 获取门店名称
  1223 + var storeIds = entities.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId)).Select(x => x.ApplicationStoreId).Distinct().ToList();
  1224 + var storeDict = new Dictionary<string, string>();
  1225 + if (storeIds.Any())
  1226 + {
  1227 + var stores = await _db.Queryable<LqMdxxEntity>()
  1228 + .Where(x => storeIds.Contains(x.Id))
  1229 + .Select(x => new { x.Id, x.Dm })
  1230 + .ToListAsync();
  1231 + storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
  1232 + }
  1233 +
987 1234 // 组装返回数据
988 1235 var result = entities.Select(item => new LqReimbursementApplicationListOutput
989 1236 {
... ... @@ -991,6 +1238,9 @@ namespace NCC.Extend.LqReimbursementApplication
991 1238 applicationUserId = item.ApplicationUserId,
992 1239 applicationUserName = item.ApplicationUserName,
993 1240 applicationStoreId = item.ApplicationStoreId,
  1241 + applicationStoreName = !string.IsNullOrEmpty(item.ApplicationStoreId) && storeDict.ContainsKey(item.ApplicationStoreId)
  1242 + ? storeDict[item.ApplicationStoreId]
  1243 + : null,
994 1244 applicationTime = item.ApplicationTime,
995 1245 amount = item.Amount,
996 1246 approveUser = item.ApproveUser,
... ... @@ -1019,7 +1269,6 @@ namespace NCC.Extend.LqReimbursementApplication
1019 1269 public async Task<dynamic> GetPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input)
1020 1270 {
1021 1271 var userInfo = await _userManager.GetUserInfo();
1022   - var sidx = input.sidx == null ? "id" : input.sidx;
1023 1272  
1024 1273 // 查询当前用户作为审批人的节点
1025 1274 var userNodeOrders = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
... ... @@ -1044,16 +1293,71 @@ namespace NCC.Extend.LqReimbursementApplication
1044 1293  
1045 1294 var applicationIds = userApplications.Keys.ToList();
1046 1295  
1047   - // 查询这些申请中,当前节点是用户有权限的节点,且状态为"审批中"的申请
1048   - var query = _db.Queryable<LqReimbursementApplicationEntity>()
  1296 + // 先查询所有符合条件的申请(状态为"审批中"且在用户有权限的申请列表中)
  1297 + var allApplications = await _db.Queryable<LqReimbursementApplicationEntity>()
1049 1298 .Where(x => applicationIds.Contains(x.Id) && x.ApprovalStatus == "审批中")
  1299 + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId))
  1300 + .ToListAsync();
  1301 +
  1302 + // 在内存中过滤:当前节点必须是用户有权限的节点
  1303 + var filteredApplications = allApplications
1050 1304 .Where(x => userApplications.ContainsKey(x.Id)
1051 1305 && userApplications[x.Id].Contains(x.CurrentNodeOrder ?? 0))
1052   - .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId))
1053   - .OrderBy(sidx + " " + input.sort);
  1306 + .ToList();
1054 1307  
1055   - var total = await query.CountAsync();
1056   - var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
  1308 + // 获取过滤后的申请ID列表
  1309 + var filteredApplicationIds = filteredApplications.Select(x => x.Id).ToList();
  1310 +
  1311 + // 如果没有符合条件的申请,直接返回空列表
  1312 + if (!filteredApplicationIds.Any())
  1313 + {
  1314 + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult(
  1315 + new SqlSugarPagedList<LqReimbursementApplicationListOutput>
  1316 + {
  1317 + list = new List<LqReimbursementApplicationListOutput>(),
  1318 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = 0 }
  1319 + });
  1320 + }
  1321 +
  1322 + // 对过滤后的申请进行排序
  1323 + var sortType = input.sort?.ToLower() == "desc" ? OrderByType.Desc : OrderByType.Asc;
  1324 + IOrderedEnumerable<LqReimbursementApplicationEntity> orderedApplications;
  1325 +
  1326 + if (string.IsNullOrEmpty(input.sidx))
  1327 + {
  1328 + orderedApplications = filteredApplications.OrderByDescending(x => x.ApplicationTime);
  1329 + }
  1330 + else
  1331 + {
  1332 + switch (input.sidx.ToLower())
  1333 + {
  1334 + case "id":
  1335 + orderedApplications = sortType == OrderByType.Desc
  1336 + ? filteredApplications.OrderByDescending(x => x.Id)
  1337 + : filteredApplications.OrderBy(x => x.Id);
  1338 + break;
  1339 + case "applicationtime":
  1340 + orderedApplications = sortType == OrderByType.Desc
  1341 + ? filteredApplications.OrderByDescending(x => x.ApplicationTime)
  1342 + : filteredApplications.OrderBy(x => x.ApplicationTime);
  1343 + break;
  1344 + case "amount":
  1345 + orderedApplications = sortType == OrderByType.Desc
  1346 + ? filteredApplications.OrderByDescending(x => x.Amount)
  1347 + : filteredApplications.OrderBy(x => x.Amount);
  1348 + break;
  1349 + default:
  1350 + orderedApplications = filteredApplications.OrderByDescending(x => x.ApplicationTime);
  1351 + break;
  1352 + }
  1353 + }
  1354 +
  1355 + // 手动分页
  1356 + var total = filteredApplications.Count;
  1357 + var entities = orderedApplications
  1358 + .Skip((input.currentPage - 1) * input.pageSize)
  1359 + .Take(input.pageSize)
  1360 + .ToList();
1057 1361  
1058 1362 // 获取当前审批人信息
1059 1363 var resultApplicationIds = entities.Select(x => x.Id).ToList();
... ... @@ -1076,6 +1380,18 @@ namespace NCC.Extend.LqReimbursementApplication
1076 1380 .GroupBy(x => (string)x.applicationId)
1077 1381 .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName)));
1078 1382  
  1383 + // 获取门店名称
  1384 + var storeIds = entities.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId)).Select(x => x.ApplicationStoreId).Distinct().ToList();
  1385 + var storeDict = new Dictionary<string, string>();
  1386 + if (storeIds.Any())
  1387 + {
  1388 + var stores = await _db.Queryable<LqMdxxEntity>()
  1389 + .Where(x => storeIds.Contains(x.Id))
  1390 + .Select(x => new { x.Id, x.Dm })
  1391 + .ToListAsync();
  1392 + storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
  1393 + }
  1394 +
1079 1395 // 组装返回数据
1080 1396 var result = entities.Select(item => new LqReimbursementApplicationListOutput
1081 1397 {
... ... @@ -1083,6 +1399,9 @@ namespace NCC.Extend.LqReimbursementApplication
1083 1399 applicationUserId = item.ApplicationUserId,
1084 1400 applicationUserName = item.ApplicationUserName,
1085 1401 applicationStoreId = item.ApplicationStoreId,
  1402 + applicationStoreName = !string.IsNullOrEmpty(item.ApplicationStoreId) && storeDict.ContainsKey(item.ApplicationStoreId)
  1403 + ? storeDict[item.ApplicationStoreId]
  1404 + : null,
1086 1405 applicationTime = item.ApplicationTime,
1087 1406 amount = item.Amount,
1088 1407 approveUser = item.ApproveUser,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
... ... @@ -18,6 +18,7 @@ using NCC.Extend.Entitys.lq_xh_jksyj;
18 18 using NCC.Extend.Entitys.lq_ycsd_jsj;
19 19 using NCC.Extend.Entitys.lq_mdxx;
20 20 using NCC.Extend.Entitys.lq_hytk_hytk;
  21 +using NCC.Extend.Entitys.lq_hytk_jksyj;
21 22 using NCC.Extend.Entitys.lq_md_xdbhsj;
22 23 using NCC.Extend.Entitys.lq_salary_extra_calculation;
23 24 using NCC.System.Entitys.Permission;
... ... @@ -224,6 +225,11 @@ namespace NCC.Extend
224 225 .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
225 226 .ToListAsync();
226 227  
  228 + // 1.3.1 退款数据 (lq_hytk_jksyj)
  229 + var refundList = await _db.Queryable<LqHytkJksyjEntity>()
  230 + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
  231 + .ToListAsync();
  232 +
227 233 // 1.4 战队成员及顾问信息 (lq_jinsanjiao_user + lq_ycsd_jsj)
228 234 var teamUserList = await _db.Queryable<LqJinsanjiaoUserEntity>()
229 235 .Where(x => x.Month == monthStr && x.DeleteMark == 0)
... ... @@ -272,12 +278,12 @@ namespace NCC.Extend
272 278 // 退款金额
273 279 var storeRefundList = await _db.Queryable<LqHytkHytkEntity>()
274 280 .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
275   - .Select(x => new { x.Mdbh, x.Tkje })
  281 + .Select(x => new { x.Mdbh, x.Tkje, x.ActualRefundAmount })
276 282 .ToListAsync();
277 283 var storeRefundDict = storeRefundList
278 284 .Where(x => !string.IsNullOrEmpty(x.Mdbh))
279 285 .GroupBy(x => x.Mdbh)
280   - .ToDictionary(g => g.Key, g => g.Sum(x => x.Tkje ?? 0));
  286 + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? 0));
281 287  
282 288 // 1.7 门店信息 (lq_mdxx)
283 289 var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
... ... @@ -412,6 +418,26 @@ namespace NCC.Extend
412 418 salary.CooperationPerformance = myPerf.Where(x => (x.PerformanceType ?? "").Trim() == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0"));
413 419 salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0"));
414 420  
  421 + // 扣除退款
  422 + var myRefunds = refundList.Where(x => x.Jks == empId).ToList();
  423 + if (myRefunds.Any())
  424 + {
  425 + decimal totalRefund = myRefunds.Sum(x => x.Jksyj ?? 0);
  426 + decimal baseRefund = myRefunds.Where(x => (x.PerformanceType ?? "").Trim() == "基础业绩").Sum(x => x.Jksyj ?? 0);
  427 + decimal cooperationRefund = myRefunds.Where(x => (x.PerformanceType ?? "").Trim() == "合作业绩").Sum(x => x.Jksyj ?? 0);
  428 +
  429 + // 如果退款记录未标记类型,且总退款大于0,则可能需要处理(当前暂不处理未分类退款的明细扣除,仅扣除总额)
  430 + // 修正:如果PerformanceType为空,应当从总业绩扣除。若要从Base或Coop扣除,需确认业务规则。
  431 + // 假设:如果未分类,暂时不从Base/Coop扣(或者按比例扣?)。
  432 + // 根据现有数据,有部分是null。
  433 + // 安全起见,TotalPerformance 减去 totalRefund。
  434 + // Base 和 Coop 减去各自明确标识的部分。
  435 +
  436 + salary.TotalPerformance -= totalRefund;
  437 + salary.BasePerformance -= baseRefund;
  438 + salary.CooperationPerformance -= cooperationRefund;
  439 + }
  440 +
415 441 // 新客与升单业绩
416 442 salary.NewCustomerPerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "是")).Sum(x => decimal.Parse(x.Jksyj ?? "0"));
417 443 salary.UpgradePerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "否")).Sum(x => decimal.Parse(x.Jksyj ?? "0"));
... ...
sql/更新历史数据科技部归类字段.sql 0 → 100644
  1 +-- ============================================
  2 +-- 批量更新品项明细表和业绩表的科技部归类字段
  3 +-- ============================================
  4 +-- 说明:此脚本用于批量更新以下表的 F_KjbCategory 字段
  5 +-- 1. lq_kd_pxmx(开单品项明细表)
  6 +-- 2. lq_kd_jksyj(开单健康师业绩表)
  7 +-- 3. lq_kd_kjbsyj(开单科技部老师业绩表)
  8 +-- 4. lq_hytk_mx(退卡品项明细表)
  9 +-- 5. lq_hytk_jksyj(退卡健康师业绩表)
  10 +-- 6. lq_hytk_kjbsyj(退卡科技部老师业绩表)
  11 +--
  12 +-- 数据来源:从关联的项目资料表(lq_xmzl)的 F_BeautyType 字段获取科技部归类
  13 +--
  14 +-- 关联关系:
  15 +-- - lq_kd_pxmx.px = lq_xmzl.F_Id
  16 +-- - lq_kd_jksyj.F_ItemId 或通过 lq_kd_pxmx 关联 = lq_xmzl.F_Id
  17 +-- - lq_kd_kjbsyj.F_ItemId 或通过 lq_kd_pxmx 关联 = lq_xmzl.F_Id
  18 +-- - lq_hytk_mx.px = lq_xmzl.F_Id
  19 +-- - lq_hytk_jksyj.F_ItemId 或通过 lq_hytk_mx 关联 = lq_xmzl.F_Id
  20 +-- - lq_hytk_kjbsyj.F_ItemId 或通过 lq_hytk_mx 关联 = lq_xmzl.F_Id
  21 +--
  22 +-- 更新逻辑:
  23 +-- - 更新所有记录(不判断是否有效)
  24 +-- - 只更新关联的项目资料存在且F_BeautyType字段有值的记录
  25 +-- - 从 lq_xmzl.F_BeautyType 字段获取科技部归类
  26 +
  27 +-- ============================================
  28 +-- 1. 更新开单品项明细表的科技部归类字段
  29 +-- ============================================
  30 +UPDATE lq_kd_pxmx pxmx
  31 +INNER JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id
  32 +SET pxmx.F_KjbCategory = xmzl.F_BeautyType
  33 +WHERE xmzl.F_BeautyType IS NOT NULL
  34 + AND xmzl.F_BeautyType != '';
  35 +
  36 +-- ============================================
  37 +-- 2. 更新开单健康师业绩表的科技部归类字段
  38 +-- ============================================
  39 +-- 方式1:通过品项ID直接关联
  40 +UPDATE lq_kd_jksyj jksyj
  41 +INNER JOIN lq_xmzl xmzl ON jksyj.F_ItemId = xmzl.F_Id
  42 +SET jksyj.F_KjbCategory = xmzl.F_BeautyType
  43 +WHERE xmzl.F_BeautyType IS NOT NULL
  44 + AND xmzl.F_BeautyType != ''
  45 + AND jksyj.F_ItemId IS NOT NULL
  46 + AND jksyj.F_ItemId != '';
  47 +
  48 +-- 方式2:通过开单品项明细表关联(当F_ItemId为空时)
  49 +UPDATE lq_kd_jksyj jksyj
  50 +INNER JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id
  51 +INNER JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id
  52 +SET jksyj.F_KjbCategory = xmzl.F_BeautyType
  53 +WHERE xmzl.F_BeautyType IS NOT NULL
  54 + AND xmzl.F_BeautyType != ''
  55 + AND (jksyj.F_ItemId IS NULL OR jksyj.F_ItemId = '')
  56 + AND jksyj.F_kdpxid IS NOT NULL
  57 + AND jksyj.F_kdpxid != '';
  58 +
  59 +-- ============================================
  60 +-- 3. 更新开单科技部老师业绩表的科技部归类字段
  61 +-- ============================================
  62 +-- 方式1:通过品项ID直接关联
  63 +UPDATE lq_kd_kjbsyj kjbsyj
  64 +INNER JOIN lq_xmzl xmzl ON kjbsyj.F_ItemId = xmzl.F_Id
  65 +SET kjbsyj.F_KjbCategory = xmzl.F_BeautyType
  66 +WHERE xmzl.F_BeautyType IS NOT NULL
  67 + AND xmzl.F_BeautyType != ''
  68 + AND kjbsyj.F_ItemId IS NOT NULL
  69 + AND kjbsyj.F_ItemId != '';
  70 +
  71 +-- 方式2:通过开单品项明细表关联(当F_ItemId为空时)
  72 +UPDATE lq_kd_kjbsyj kjbsyj
  73 +INNER JOIN lq_kd_pxmx pxmx ON kjbsyj.F_kdpxid = pxmx.F_Id
  74 +INNER JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id
  75 +SET kjbsyj.F_KjbCategory = xmzl.F_BeautyType
  76 +WHERE xmzl.F_BeautyType IS NOT NULL
  77 + AND xmzl.F_BeautyType != ''
  78 + AND (kjbsyj.F_ItemId IS NULL OR kjbsyj.F_ItemId = '')
  79 + AND kjbsyj.F_kdpxid IS NOT NULL
  80 + AND kjbsyj.F_kdpxid != '';
  81 +
  82 +-- ============================================
  83 +-- 4. 更新退卡品项明细表的科技部归类字段
  84 +-- ============================================
  85 +UPDATE lq_hytk_mx mx
  86 +INNER JOIN lq_xmzl xmzl ON mx.px = xmzl.F_Id
  87 +SET mx.F_KjbCategory = xmzl.F_BeautyType
  88 +WHERE xmzl.F_BeautyType IS NOT NULL
  89 + AND xmzl.F_BeautyType != '';
  90 +
  91 +-- ============================================
  92 +-- 5. 更新退卡健康师业绩表的科技部归类字段
  93 +-- ============================================
  94 +-- 方式1:通过品项ID直接关联
  95 +UPDATE lq_hytk_jksyj jksyj
  96 +INNER JOIN lq_xmzl xmzl ON jksyj.F_ItemId = xmzl.F_Id
  97 +SET jksyj.F_KjbCategory = xmzl.F_BeautyType
  98 +WHERE xmzl.F_BeautyType IS NOT NULL
  99 + AND xmzl.F_BeautyType != ''
  100 + AND jksyj.F_ItemId IS NOT NULL
  101 + AND jksyj.F_ItemId != '';
  102 +
  103 +-- 方式2:通过退卡品项明细表关联(当F_ItemId为空时)
  104 +UPDATE lq_hytk_jksyj jksyj
  105 +INNER JOIN lq_hytk_mx mx ON jksyj.F_tkpxid = mx.F_Id
  106 +INNER JOIN lq_xmzl xmzl ON mx.px = xmzl.F_Id
  107 +SET jksyj.F_KjbCategory = xmzl.F_BeautyType
  108 +WHERE xmzl.F_BeautyType IS NOT NULL
  109 + AND xmzl.F_BeautyType != ''
  110 + AND (jksyj.F_ItemId IS NULL OR jksyj.F_ItemId = '')
  111 + AND jksyj.F_tkpxid IS NOT NULL
  112 + AND jksyj.F_tkpxid != '';
  113 +
  114 +-- ============================================
  115 +-- 6. 更新退卡科技部老师业绩表的科技部归类字段
  116 +-- ============================================
  117 +-- 方式1:通过品项ID直接关联
  118 +UPDATE lq_hytk_kjbsyj kjbsyj
  119 +INNER JOIN lq_xmzl xmzl ON kjbsyj.F_ItemId = xmzl.F_Id
  120 +SET kjbsyj.F_KjbCategory = xmzl.F_BeautyType
  121 +WHERE xmzl.F_BeautyType IS NOT NULL
  122 + AND xmzl.F_BeautyType != ''
  123 + AND kjbsyj.F_ItemId IS NOT NULL
  124 + AND kjbsyj.F_ItemId != '';
  125 +
  126 +-- 方式2:通过退卡品项明细表关联(当F_ItemId为空时)
  127 +UPDATE lq_hytk_kjbsyj kjbsyj
  128 +INNER JOIN lq_hytk_mx mx ON kjbsyj.F_tkpxid = mx.F_Id
  129 +INNER JOIN lq_xmzl xmzl ON mx.px = xmzl.F_Id
  130 +SET kjbsyj.F_KjbCategory = xmzl.F_BeautyType
  131 +WHERE xmzl.F_BeautyType IS NOT NULL
  132 + AND xmzl.F_BeautyType != ''
  133 + AND (kjbsyj.F_ItemId IS NULL OR kjbsyj.F_ItemId = '')
  134 + AND kjbsyj.F_tkpxid IS NOT NULL
  135 + AND kjbsyj.F_tkpxid != '';
  136 +
  137 +-- ============================================
  138 +-- 7. 验证更新结果
  139 +-- ============================================
  140 +-- 查看各表更新后的统计信息
  141 +--
  142 +-- lq_kd_pxmx 更新后的统计信息
  143 +-- SELECT
  144 +-- '开单品项明细' AS 表名,
  145 +-- F_KjbCategory AS 科技部归类,
  146 +-- COUNT(*) AS 记录数
  147 +-- FROM lq_kd_pxmx
  148 +-- WHERE F_KjbCategory IS NOT NULL
  149 +-- GROUP BY F_KjbCategory
  150 +-- ORDER BY 记录数 DESC;
  151 +
  152 +-- lq_kd_jksyj 更新后的统计信息
  153 +-- SELECT
  154 +-- '开单健康师业绩' AS 表名,
  155 +-- F_KjbCategory AS 科技部归类,
  156 +-- COUNT(*) AS 记录数
  157 +-- FROM lq_kd_jksyj
  158 +-- WHERE F_KjbCategory IS NOT NULL
  159 +-- GROUP BY F_KjbCategory
  160 +-- ORDER BY 记录数 DESC;
  161 +
  162 +-- lq_kd_kjbsyj 更新后的统计信息
  163 +-- SELECT
  164 +-- '开单科技部老师业绩' AS 表名,
  165 +-- F_KjbCategory AS 科技部归类,
  166 +-- COUNT(*) AS 记录数
  167 +-- FROM lq_kd_kjbsyj
  168 +-- WHERE F_KjbCategory IS NOT NULL
  169 +-- GROUP BY F_KjbCategory
  170 +-- ORDER BY 记录数 DESC;
  171 +
  172 +-- lq_hytk_mx 更新后的统计信息
  173 +-- SELECT
  174 +-- '退卡品项明细' AS 表名,
  175 +-- F_KjbCategory AS 科技部归类,
  176 +-- COUNT(*) AS 记录数
  177 +-- FROM lq_hytk_mx
  178 +-- WHERE F_KjbCategory IS NOT NULL
  179 +-- GROUP BY F_KjbCategory
  180 +-- ORDER BY 记录数 DESC;
  181 +
  182 +-- lq_hytk_jksyj 更新后的统计信息
  183 +-- SELECT
  184 +-- '退卡健康师业绩' AS 表名,
  185 +-- F_KjbCategory AS 科技部归类,
  186 +-- COUNT(*) AS 记录数
  187 +-- FROM lq_hytk_jksyj
  188 +-- WHERE F_KjbCategory IS NOT NULL
  189 +-- GROUP BY F_KjbCategory
  190 +-- ORDER BY 记录数 DESC;
  191 +
  192 +-- lq_hytk_kjbsyj 更新后的统计信息
  193 +-- SELECT
  194 +-- '退卡科技部老师业绩' AS 表名,
  195 +-- F_KjbCategory AS 科技部归类,
  196 +-- COUNT(*) AS 记录数
  197 +-- FROM lq_hytk_kjbsyj
  198 +-- WHERE F_KjbCategory IS NOT NULL
  199 +-- GROUP BY F_KjbCategory
  200 +-- ORDER BY 记录数 DESC;
  201 +
  202 +-- ============================================
  203 +-- 8. 查看未更新的记录数(关联的项目资料不存在或fl4为空)
  204 +-- ============================================
  205 +--
  206 +-- lq_kd_pxmx 未更新记录数
  207 +-- SELECT COUNT(*) AS 未更新记录数
  208 +-- FROM lq_kd_pxmx pxmx
  209 +-- LEFT JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id
  210 +-- WHERE xmzl.F_Id IS NULL
  211 +-- OR xmzl.F_BeautyType IS NULL
  212 +-- OR xmzl.F_BeautyType = '';
  213 +
  214 +-- lq_kd_jksyj 未更新记录数
  215 +-- SELECT COUNT(*) AS 未更新记录数
  216 +-- FROM lq_kd_jksyj jksyj
  217 +-- LEFT JOIN lq_xmzl xmzl ON jksyj.F_ItemId = xmzl.F_Id
  218 +-- LEFT JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id
  219 +-- LEFT JOIN lq_xmzl xmzl2 ON pxmx.px = xmzl2.F_Id
  220 +-- WHERE (xmzl.F_Id IS NULL OR xmzl.F_BeautyType IS NULL OR xmzl.F_BeautyType = '')
  221 +-- AND (xmzl2.F_Id IS NULL OR xmzl2.F_BeautyType IS NULL OR xmzl2.F_BeautyType = '');
  222 +
  223 +-- lq_kd_kjbsyj 未更新记录数
  224 +-- SELECT COUNT(*) AS 未更新记录数
  225 +-- FROM lq_kd_kjbsyj kjbsyj
  226 +-- LEFT JOIN lq_xmzl xmzl ON kjbsyj.F_ItemId = xmzl.F_Id
  227 +-- LEFT JOIN lq_kd_pxmx pxmx ON kjbsyj.F_kdpxid = pxmx.F_Id
  228 +-- LEFT JOIN lq_xmzl xmzl2 ON pxmx.px = xmzl2.F_Id
  229 +-- WHERE (xmzl.F_Id IS NULL OR xmzl.F_BeautyType IS NULL OR xmzl.F_BeautyType = '')
  230 +-- AND (xmzl2.F_Id IS NULL OR xmzl2.F_BeautyType IS NULL OR xmzl2.F_BeautyType = '');
  231 +
  232 +-- lq_hytk_mx 未更新记录数
  233 +-- SELECT COUNT(*) AS 未更新记录数
  234 +-- FROM lq_hytk_mx mx
  235 +-- LEFT JOIN lq_xmzl xmzl ON mx.px = xmzl.F_Id
  236 +-- WHERE xmzl.F_Id IS NULL
  237 +-- OR xmzl.F_BeautyType IS NULL
  238 +-- OR xmzl.F_BeautyType = '';
  239 +
  240 +-- lq_hytk_jksyj 未更新记录数
  241 +-- SELECT COUNT(*) AS 未更新记录数
  242 +-- FROM lq_hytk_jksyj jksyj
  243 +-- LEFT JOIN lq_xmzl xmzl ON jksyj.F_ItemId = xmzl.F_Id
  244 +-- LEFT JOIN lq_hytk_mx mx ON jksyj.F_tkpxid = mx.F_Id
  245 +-- LEFT JOIN lq_xmzl xmzl2 ON mx.px = xmzl2.F_Id
  246 +-- WHERE (xmzl.F_Id IS NULL OR xmzl.F_BeautyType IS NULL OR xmzl.F_BeautyType = '')
  247 +-- AND (xmzl2.F_Id IS NULL OR xmzl2.F_BeautyType IS NULL OR xmzl2.F_BeautyType = '');
  248 +
  249 +-- lq_hytk_kjbsyj 未更新记录数
  250 +-- SELECT COUNT(*) AS 未更新记录数
  251 +-- FROM lq_hytk_kjbsyj kjbsyj
  252 +-- LEFT JOIN lq_xmzl xmzl ON kjbsyj.F_ItemId = xmzl.F_Id
  253 +-- LEFT JOIN lq_hytk_mx mx ON kjbsyj.F_tkpxid = mx.F_Id
  254 +-- LEFT JOIN lq_xmzl xmzl2 ON mx.px = xmzl2.F_Id
  255 +-- WHERE (xmzl.F_Id IS NULL OR xmzl.F_BeautyType IS NULL OR xmzl.F_BeautyType = '')
  256 +-- AND (xmzl2.F_Id IS NULL OR xmzl2.F_BeautyType IS NULL OR xmzl2.F_BeautyType = '');
... ...
sql/添加科技部归类字段.sql 0 → 100644
  1 +-- ============================================
  2 +-- 为品项明细表和业绩表添加科技部归类字段
  3 +-- ============================================
  4 +-- 说明:此脚本为品项明细相关表和业绩表添加科技部归类字段,用于存储品项的科技部归类信息
  5 +--
  6 +-- 字段说明:
  7 +-- F_KjbCategory:科技部归类,用于存储科技部对品项的分类信息
  8 +--
  9 +-- 业务含义:
  10 +-- - 科技部归类用于科技部对品项进行分类统计和分析
  11 +-- - 分类值来源于项目资料表(lq_xmzl)的 F_BeautyType 字段(科美类型)
  12 +--
  13 +-- 注意事项:
  14 +-- - 字段类型为VARCHAR(50),可存储各种分类值
  15 +-- - 所有字段允许为NULL,因为历史数据可能没有这些分类信息
  16 +-- - 字段位置:放在品项相关字段(px、pxmc)或业绩相关字段之后
  17 +
  18 +-- ============================================
  19 +-- 1. lq_kd_pxmx(开单品项明细表) - 添加科技部归类字段
  20 +-- ============================================
  21 +ALTER TABLE lq_kd_pxmx
  22 +ADD COLUMN F_KjbCategory VARCHAR(50) NULL COMMENT '科技部归类(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  23 +
  24 +-- ============================================
  25 +-- 2. lq_kd_jksyj(开单健康师业绩表) - 添加科技部归类字段
  26 +-- ============================================
  27 +ALTER TABLE lq_kd_jksyj
  28 +ADD COLUMN F_KjbCategory VARCHAR(50) NULL COMMENT '科技部归类(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  29 +
  30 +-- ============================================
  31 +-- 3. lq_kd_kjbsyj(开单科技部老师业绩表) - 添加科技部归类字段
  32 +-- ============================================
  33 +ALTER TABLE lq_kd_kjbsyj
  34 +ADD COLUMN F_KjbCategory VARCHAR(50) NULL COMMENT '科技部归类(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  35 +
  36 +-- ============================================
  37 +-- 4. lq_hytk_mx(退卡品项明细表) - 添加科技部归类字段
  38 +-- ============================================
  39 +ALTER TABLE lq_hytk_mx
  40 +ADD COLUMN F_KjbCategory VARCHAR(50) NULL COMMENT '科技部归类(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  41 +
  42 +-- ============================================
  43 +-- 5. lq_hytk_jksyj(退卡健康师业绩表) - 添加科技部归类字段
  44 +-- ============================================
  45 +ALTER TABLE lq_hytk_jksyj
  46 +ADD COLUMN F_KjbCategory VARCHAR(50) NULL COMMENT '科技部归类(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  47 +
  48 +-- ============================================
  49 +-- 6. lq_hytk_kjbsyj(退卡科技部老师业绩表) - 添加科技部归类字段
  50 +-- ============================================
  51 +ALTER TABLE lq_hytk_kjbsyj
  52 +ADD COLUMN F_KjbCategory VARCHAR(50) NULL COMMENT '科技部归类(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  53 +
  54 +-- ============================================
  55 +-- 7. 验证字段创建
  56 +-- ============================================
  57 +-- 验证 lq_kd_pxmx 表
  58 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  59 +-- FROM INFORMATION_SCHEMA.COLUMNS
  60 +-- WHERE TABLE_NAME = 'lq_kd_pxmx'
  61 +-- AND COLUMN_NAME = 'F_KjbCategory';
  62 +
  63 +-- 验证 lq_kd_jksyj 表
  64 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  65 +-- FROM INFORMATION_SCHEMA.COLUMNS
  66 +-- WHERE TABLE_NAME = 'lq_kd_jksyj'
  67 +-- AND COLUMN_NAME = 'F_KjbCategory';
  68 +
  69 +-- 验证 lq_kd_kjbsyj 表
  70 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  71 +-- FROM INFORMATION_SCHEMA.COLUMNS
  72 +-- WHERE TABLE_NAME = 'lq_kd_kjbsyj'
  73 +-- AND COLUMN_NAME = 'F_KjbCategory';
  74 +
  75 +-- 验证 lq_hytk_mx 表
  76 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  77 +-- FROM INFORMATION_SCHEMA.COLUMNS
  78 +-- WHERE TABLE_NAME = 'lq_hytk_mx'
  79 +-- AND COLUMN_NAME = 'F_KjbCategory';
  80 +
  81 +-- 验证 lq_hytk_jksyj 表
  82 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  83 +-- FROM INFORMATION_SCHEMA.COLUMNS
  84 +-- WHERE TABLE_NAME = 'lq_hytk_jksyj'
  85 +-- AND COLUMN_NAME = 'F_KjbCategory';
  86 +
  87 +-- 验证 lq_hytk_kjbsyj 表
  88 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  89 +-- FROM INFORMATION_SCHEMA.COLUMNS
  90 +-- WHERE TABLE_NAME = 'lq_hytk_kjbsyj'
  91 +-- AND COLUMN_NAME = 'F_KjbCategory';
... ...