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,7 +64,7 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
64 public List<string> selectedPurchaseRecordIds { get; set; } 64 public List<string> selectedPurchaseRecordIds { get; set; }
65 65
66 /// <summary> 66 /// <summary>
67 - /// 节点配置列表(3-5个节点 67 + /// 节点配置列表(至少1个节点,建议不超过20个
68 /// </summary> 68 /// </summary>
69 public List<ApprovalNodeConfig> nodes { get; set; } 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,6 +27,11 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
27 /// 申请门店 27 /// 申请门店
28 /// </summary> 28 /// </summary>
29 public string applicationStoreId { get; set; } 29 public string applicationStoreId { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 申请门店名称
  33 + /// </summary>
  34 + public string applicationStoreName { get; set; }
30 35
31 /// <summary> 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,6 +26,11 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
26 /// 申请门店 26 /// 申请门店
27 /// </summary> 27 /// </summary>
28 public string applicationStoreId { get; set; } 28 public string applicationStoreId { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 申请门店名称
  32 + /// </summary>
  33 + public string applicationStoreName { get; set; }
29 34
30 /// <summary> 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,7 +43,7 @@ namespace NCC.Extend.Entitys.Dto.LqMdXdbhsj
43 /// 阶段 43 /// 阶段
44 /// </summary> 44 /// </summary>
45 [Required(ErrorMessage = "阶段不能为空")] 45 [Required(ErrorMessage = "阶段不能为空")]
46 - [Range(0, 1, ErrorMessage = "阶段值必须在1-3之间")] 46 + [Range(1, 3, ErrorMessage = "阶段值必须在1-3之间")]
47 public int stage { get; set; } = 1; 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,7 +43,7 @@ namespace NCC.Extend.Entitys.Dto.LqMdXdbhsj
43 /// 阶段 43 /// 阶段
44 /// </summary> 44 /// </summary>
45 [Required(ErrorMessage = "阶段不能为空")] 45 [Required(ErrorMessage = "阶段不能为空")]
46 - [Range(0, 1, ErrorMessage = "阶段值必须在1-3之间")] 46 + [Range(1, 3, ErrorMessage = "阶段值必须在1-3之间")]
47 public int stage { get; set; } = 1; 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,7 +27,7 @@ namespace NCC.Extend
27 /// <summary> 27 /// <summary>
28 /// 店助薪酬服务 28 /// 店助薪酬服务
29 /// </summary> 29 /// </summary>
30 - [ApiDescriptionSettings(Tag = "店助薪酬服务", Name = "LqAssistantSalary", Order = 301)] 30 + [ApiDescriptionSettings(Tag = "店助、店助主任薪酬服务", Name = "LqAssistantSalary", Order = 301)]
31 [Route("api/Extend/[controller]")] 31 [Route("api/Extend/[controller]")]
32 public class LqAssistantSalaryService : IDynamicApiController, ITransient 32 public class LqAssistantSalaryService : IDynamicApiController, ITransient
33 { 33 {
@@ -156,14 +156,14 @@ namespace NCC.Extend @@ -156,14 +156,14 @@ namespace NCC.Extend
156 var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync(); 156 var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
157 var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); 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 var storeTargets = await _db.Queryable<LqMdTargetEntity>() 160 var storeTargets = await _db.Queryable<LqMdTargetEntity>()
161 .Where(x => x.Month == monthStr) 161 .Where(x => x.Month == monthStr)
162 .ToListAsync(); 162 .ToListAsync();
163 var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId)) 163 var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId))
164 .ToDictionary(x => x.StoreId, x => x); 164 .ToDictionary(x => x.StoreId, x => x);
165 165
166 - // 1.4 门店新店保护信息 (lq_md_xdbhsj) 166 + // 1.5 门店新店保护信息 (lq_md_xdbhsj)
167 var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>() 167 var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>()
168 .Where(x => x.Sfqy == 1) 168 .Where(x => x.Sfqy == 1)
169 .ToListAsync(); 169 .ToListAsync();
@@ -172,7 +172,7 @@ namespace NCC.Extend @@ -172,7 +172,7 @@ namespace NCC.Extend
172 .GroupBy(x => x.Mdid) 172 .GroupBy(x => x.Mdid)
173 .ToDictionary(g => g.Key, g => g.First()); 173 .ToDictionary(g => g.Key, g => g.First());
174 174
175 - // 1.5 门店总业绩计算 (开单实付 - 退卡金额) 175 + // 1.6 门店总业绩计算 (开单实付 - 退卡金额)
176 // 开单实付 176 // 开单实付
177 var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>() 177 var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>()
178 .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) 178 .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
@@ -193,7 +193,7 @@ namespace NCC.Extend @@ -193,7 +193,7 @@ namespace NCC.Extend
193 .GroupBy(x => x.Md) 193 .GroupBy(x => x.Md)
194 .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); 194 .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
195 195
196 - // 1.6 进店消耗人数统计(有消费金额的,按门店按月去重客户数) 196 + // 1.7 进店消耗人数统计(有消费金额的,按门店按月去重客户数)
197 // 使用SQL查询优化性能 197 // 使用SQL查询优化性能
198 var headcountSql = $@" 198 var headcountSql = $@"
199 SELECT 199 SELECT
@@ -216,7 +216,7 @@ namespace NCC.Extend @@ -216,7 +216,7 @@ namespace NCC.Extend
216 .Where(x => x.StoreId != null) 216 .Where(x => x.StoreId != null)
217 .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount)); 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 var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>() 220 var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
221 .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) 221 .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
222 .ToListAsync(); 222 .ToListAsync();
@@ -312,9 +312,12 @@ namespace NCC.Extend @@ -312,9 +312,12 @@ namespace NCC.Extend
312 } 312 }
313 313
314 // 2.5 计算门店总提成(先计算门店级别的提成) 314 // 2.5 计算门店总提成(先计算门店级别的提成)
  315 + // 判断岗位类型:店助 或 店助主任
  316 + bool isDirector = salary.Position == "店助主任";
  317 +
315 decimal storeTotalCommission = 0; 318 decimal storeTotalCommission = 0;
316 decimal commissionRate = 0; 319 decimal commissionRate = 0;
317 - 320 +
318 // 如果门店生命线未设置(<=0),则没有提成 321 // 如果门店生命线未设置(<=0),则没有提成
319 if (salary.StoreLifeline <= 0) 322 if (salary.StoreLifeline <= 0)
320 { 323 {
@@ -323,10 +326,43 @@ namespace NCC.Extend @@ -323,10 +326,43 @@ namespace NCC.Extend
323 } 326 }
324 else 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 salary.CommissionRate = commissionRate; 366 salary.CommissionRate = commissionRate;
331 367
332 // 2.6 统计进店消耗人数 368 // 2.6 统计进店消耗人数
@@ -336,7 +372,7 @@ namespace NCC.Extend @@ -336,7 +372,7 @@ namespace NCC.Extend
336 decimal storeTotalStageReward = 0; 372 decimal storeTotalStageReward = 0;
337 bool reachedStage1 = false; 373 bool reachedStage1 = false;
338 bool reachedStage2 = false; 374 bool reachedStage2 = false;
339 - 375 +
340 // 如果阶段目标未设置(为0),则没有奖励考核,奖励金额为0 376 // 如果阶段目标未设置(为0),则没有奖励考核,奖励金额为0
341 if (salary.Stage1TargetHeadCount <= 0 && salary.Stage2TargetHeadCount <= 0) 377 if (salary.Stage1TargetHeadCount <= 0 && salary.Stage2TargetHeadCount <= 0)
342 { 378 {
@@ -372,7 +408,7 @@ namespace NCC.Extend @@ -372,7 +408,7 @@ namespace NCC.Extend
372 } 408 }
373 } 409 }
374 410
375 - // 2.8 计算底薪(根据门店分类 411 + // 2.8 计算底薪(店助和店助主任底薪规则相同,都根据门店分类确定
376 salary.BaseSalary = CalculateBaseSalary(salary.StoreCategory.Value); 412 salary.BaseSalary = CalculateBaseSalary(salary.StoreCategory.Value);
377 413
378 // 2.9 固定奖励(手机管理费) 414 // 2.9 固定奖励(手机管理费)
@@ -400,10 +436,10 @@ namespace NCC.Extend @@ -400,10 +436,10 @@ namespace NCC.Extend
400 { 436 {
401 // 按比例计算提成 437 // 按比例计算提成
402 salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays; 438 salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays;
403 - 439 +
404 // 按比例计算奖励 440 // 按比例计算奖励
405 salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays; 441 salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays;
406 - 442 +
407 // 计算阶段奖励的明细(按比例分配) 443 // 计算阶段奖励的明细(按比例分配)
408 if (reachedStage2) 444 if (reachedStage2)
409 { 445 {
@@ -482,12 +518,12 @@ namespace NCC.Extend @@ -482,12 +518,12 @@ namespace NCC.Extend
482 } 518 }
483 519
484 /// <summary> 520 /// <summary>
485 - /// 计算提成比例 521 + /// 计算店助提成(阶梯提成模式)
486 /// </summary> 522 /// </summary>
487 /// <param name="storePerformance">门店业绩</param> 523 /// <param name="storePerformance">门店业绩</param>
488 /// <param name="storeLifeline">门店生命线</param> 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 if (storeLifeline <= 0) 528 if (storeLifeline <= 0)
493 { 529 {
@@ -503,18 +539,34 @@ namespace NCC.Extend @@ -503,18 +539,34 @@ namespace NCC.Extend
503 } 539 }
504 else if (ratio < 1.0m) 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 else 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 /// <summary> 568 /// <summary>
517 - /// 计算底薪 569 + /// 计算底薪(店助)
518 /// </summary> 570 /// </summary>
519 /// <param name="storeCategory">门店分类(1=A类,2=B类,3=C类)</param> 571 /// <param name="storeCategory">门店分类(1=A类,2=B类,3=C类)</param>
520 /// <returns>底薪金额</returns> 572 /// <returns>底薪金额</returns>
@@ -528,6 +580,53 @@ namespace NCC.Extend @@ -528,6 +580,53 @@ namespace NCC.Extend
528 _ => throw new Exception($"门店分类值无效:{storeCategory},有效值为1(A类)、2(B类)、3(C类)") 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 using System.Collections.Generic; 2 using System.Collections.Generic;
3 using System.Linq; 3 using System.Linq;
4 using System.Net.Http; 4 using System.Net.Http;
@@ -2381,6 +2381,10 @@ namespace NCC.Extend.LqKdKdjlb @@ -2381,6 +2381,10 @@ namespace NCC.Extend.LqKdKdjlb
2381 kdhyc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc), 2381 kdhyc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc),
2382 kdhysjh = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh), 2382 kdhysjh = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh),
2383 fkfs = it.Fkfs, // 付款方式 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 khly = it.Khly, // 客户来源 2388 khly = it.Khly, // 客户来源
2385 bz = it.Bz, // 备注 2389 bz = it.Bz, // 备注
2386 createTime = it.CreateTime 2390 createTime = it.CreateTime
@@ -2564,6 +2568,10 @@ namespace NCC.Extend.LqKdKdjlb @@ -2564,6 +2568,10 @@ namespace NCC.Extend.LqKdKdjlb
2564 customerType = billing.gjlx, 2568 customerType = billing.gjlx,
2565 cooperationInstitution = billing.hgjg, 2569 cooperationInstitution = billing.hgjg,
2566 cooperationInstitutionName = billing.hgjgName, 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 purchasedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "购买").Select(x => new 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,7 +79,8 @@ namespace NCC.Extend.LqPurchaseRecords
79 List<string> queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject<List<string>>() : null; 79 List<string> queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject<List<string>>() : null;
80 DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null; 80 DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null;
81 DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null; 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 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) 84 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
84 .WhereIF(!string.IsNullOrEmpty(input.reimbursementCategoryId), p => p.ReimbursementCategoryId.Contains(input.reimbursementCategoryId)) 85 .WhereIF(!string.IsNullOrEmpty(input.reimbursementCategoryId), p => p.ReimbursementCategoryId.Contains(input.reimbursementCategoryId))
85 .WhereIF(!string.IsNullOrEmpty(input.reimbursementCategoryName), p => p.ReimbursementCategoryName.Contains(input.reimbursementCategoryName)) 86 .WhereIF(!string.IsNullOrEmpty(input.reimbursementCategoryName), p => p.ReimbursementCategoryName.Contains(input.reimbursementCategoryName))
@@ -93,30 +94,92 @@ namespace NCC.Extend.LqPurchaseRecords @@ -93,30 +94,92 @@ namespace NCC.Extend.LqPurchaseRecords
93 .WhereIF(queryCreateTime != null, p => p.CreateTime <= new DateTime(endCreateTime.ToDate().Year, endCreateTime.ToDate().Month, endCreateTime.ToDate().Day, 23, 59, 59)) 94 .WhereIF(queryCreateTime != null, p => p.CreateTime <= new DateTime(endCreateTime.ToDate().Year, endCreateTime.ToDate().Month, endCreateTime.ToDate().Day, 23, 59, 59))
94 .WhereIF(!string.IsNullOrEmpty(input.createUser), p => p.CreateUser.Equals(input.createUser)) 95 .WhereIF(!string.IsNullOrEmpty(input.createUser), p => p.CreateUser.Equals(input.createUser))
95 .WhereIF(!string.IsNullOrEmpty(input.createUserStoreId), p => p.CreateUserStoreId.Contains(input.createUserStoreId)) 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 .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser)) 97 .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser))
98 .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) 98 .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0))
99 .WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) 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 /// <summary> 185 /// <summary>
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
@@ -18,6 +18,7 @@ using NCC.Extend.Entitys.Dto.LqReimbursementApplication; @@ -18,6 +18,7 @@ using NCC.Extend.Entitys.Dto.LqReimbursementApplication;
18 using NCC.Extend.Entitys.lq_reimbursement_application_node; 18 using NCC.Extend.Entitys.lq_reimbursement_application_node;
19 using NCC.Extend.Entitys.lq_reimbursement_application_node_user; 19 using NCC.Extend.Entitys.lq_reimbursement_application_node_user;
20 using NCC.Extend.Entitys.lq_reimbursement_approval_record; 20 using NCC.Extend.Entitys.lq_reimbursement_approval_record;
  21 +using NCC.Extend.Entitys.lq_mdxx;
21 using Yitter.IdGenerator; 22 using Yitter.IdGenerator;
22 using NCC.Common.Helper; 23 using NCC.Common.Helper;
23 using NCC.JsonSerialization; 24 using NCC.JsonSerialization;
@@ -84,6 +85,24 @@ namespace NCC.Extend.LqReimbursementApplication @@ -84,6 +85,24 @@ namespace NCC.Extend.LqReimbursementApplication
84 .OrderBy(x => x.ApprovalTime) 85 .OrderBy(x => x.ApprovalTime)
85 .ToListAsync(); 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 var nodeList = nodes.Select(n => new 107 var nodeList = nodes.Select(n => new
89 { 108 {
@@ -122,6 +141,27 @@ namespace NCC.Extend.LqReimbursementApplication @@ -122,6 +141,27 @@ namespace NCC.Extend.LqReimbursementApplication
122 .ToList(); 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 return new 165 return new
126 { 166 {
127 form = output, 167 form = output,
@@ -129,7 +169,8 @@ namespace NCC.Extend.LqReimbursementApplication @@ -129,7 +169,8 @@ namespace NCC.Extend.LqReimbursementApplication
129 currentApprovers = currentApprovers, 169 currentApprovers = currentApprovers,
130 currentNodeOrder = entity.CurrentNodeOrder, 170 currentNodeOrder = entity.CurrentNodeOrder,
131 approvalStatus = entity.ApprovalStatus ?? entity.ApproveStatus, 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,7 +182,6 @@ namespace NCC.Extend.LqReimbursementApplication
141 [HttpGet("")] 182 [HttpGet("")]
142 public async Task<dynamic> GetList([FromQuery] LqReimbursementApplicationListQueryInput input) 183 public async Task<dynamic> GetList([FromQuery] LqReimbursementApplicationListQueryInput input)
143 { 184 {
144 - var sidx = input.sidx == null ? "id" : input.sidx;  
145 List<string> queryApplicationTime = input.applicationTime != null ? input.applicationTime.Split(',').ToObeject<List<string>>() : null; 185 List<string> queryApplicationTime = input.applicationTime != null ? input.applicationTime.Split(',').ToObeject<List<string>>() : null;
146 DateTime? startApplicationTime = queryApplicationTime != null ? Ext.GetDateTime(queryApplicationTime.First()) : null; 186 DateTime? startApplicationTime = queryApplicationTime != null ? Ext.GetDateTime(queryApplicationTime.First()) : null;
147 DateTime? endApplicationTime = queryApplicationTime != null ? Ext.GetDateTime(queryApplicationTime.Last()) : null; 187 DateTime? endApplicationTime = queryApplicationTime != null ? Ext.GetDateTime(queryApplicationTime.Last()) : null;
@@ -160,8 +200,50 @@ namespace NCC.Extend.LqReimbursementApplication @@ -160,8 +200,50 @@ namespace NCC.Extend.LqReimbursementApplication
160 .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => (p.ApprovalStatus ?? p.ApproveStatus).Contains(input.approveStatus)) 200 .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => (p.ApprovalStatus ?? p.ApproveStatus).Contains(input.approveStatus))
161 // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) 201 // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0))
162 //.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) 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 var total = await query.CountAsync(); 248 var total = await query.CountAsync();
167 var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); 249 var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
@@ -187,6 +269,18 @@ namespace NCC.Extend.LqReimbursementApplication @@ -187,6 +269,18 @@ namespace NCC.Extend.LqReimbursementApplication
187 .GroupBy(x => (string)x.applicationId) 269 .GroupBy(x => (string)x.applicationId)
188 .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); 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 var result = entities.Select(item => new LqReimbursementApplicationListOutput 285 var result = entities.Select(item => new LqReimbursementApplicationListOutput
192 { 286 {
@@ -194,6 +288,9 @@ namespace NCC.Extend.LqReimbursementApplication @@ -194,6 +288,9 @@ namespace NCC.Extend.LqReimbursementApplication
194 applicationUserId = item.ApplicationUserId, 288 applicationUserId = item.ApplicationUserId,
195 applicationUserName = item.ApplicationUserName, 289 applicationUserName = item.ApplicationUserName,
196 applicationStoreId = item.ApplicationStoreId, 290 applicationStoreId = item.ApplicationStoreId,
  291 + applicationStoreName = !string.IsNullOrEmpty(item.ApplicationStoreId) && storeDict.ContainsKey(item.ApplicationStoreId)
  292 + ? storeDict[item.ApplicationStoreId]
  293 + : null,
197 applicationTime = item.ApplicationTime, 294 applicationTime = item.ApplicationTime,
198 amount = item.Amount, 295 amount = item.Amount,
199 approveUser = item.ApproveUser, 296 approveUser = item.ApproveUser,
@@ -217,9 +314,9 @@ namespace NCC.Extend.LqReimbursementApplication @@ -217,9 +314,9 @@ namespace NCC.Extend.LqReimbursementApplication
217 /// 新建报销申请表 314 /// 新建报销申请表
218 /// </summary> 315 /// </summary>
219 /// <param name="input">参数</param> 316 /// <param name="input">参数</param>
220 - /// <returns></returns> 317 + /// <returns>返回创建的申请ID</returns>
221 [HttpPost("")] 318 [HttpPost("")]
222 - public async Task Create([FromBody] LqReimbursementApplicationCrInput input) 319 + public async Task<dynamic> Create([FromBody] LqReimbursementApplicationCrInput input)
223 { 320 {
224 var userInfo = await _userManager.GetUserInfo(); 321 var userInfo = await _userManager.GetUserInfo();
225 var entity = input.Adapt<LqReimbursementApplicationEntity>(); 322 var entity = input.Adapt<LqReimbursementApplicationEntity>();
@@ -231,9 +328,15 @@ namespace NCC.Extend.LqReimbursementApplication @@ -231,9 +328,15 @@ namespace NCC.Extend.LqReimbursementApplication
231 _db.BeginTran(); 328 _db.BeginTran();
232 329
233 // 1. 验证节点配置 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 // 验证节点顺序是否连续(1, 2, 3, ...) 342 // 验证节点顺序是否连续(1, 2, 3, ...)
@@ -340,6 +443,9 @@ namespace NCC.Extend.LqReimbursementApplication @@ -340,6 +443,9 @@ namespace NCC.Extend.LqReimbursementApplication
340 443
341 //关闭事务 444 //关闭事务
342 _db.CommitTran(); 445 _db.CommitTran();
  446 +
  447 + // 返回创建的申请ID
  448 + return new { id = entity.Id };
343 } 449 }
344 catch (Exception) 450 catch (Exception)
345 { 451 {
@@ -484,6 +590,13 @@ namespace NCC.Extend.LqReimbursementApplication @@ -484,6 +590,13 @@ namespace NCC.Extend.LqReimbursementApplication
484 590
485 // 获取原有的关联购买记录ID 591 // 获取原有的关联购买记录ID
486 var oldEntity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id); 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 var oldIds = new List<string>(); 600 var oldIds = new List<string>();
488 if (oldEntity != null && !string.IsNullOrEmpty(oldEntity.PurchaseRecordsId)) 601 if (oldEntity != null && !string.IsNullOrEmpty(oldEntity.PurchaseRecordsId))
489 { 602 {
@@ -582,7 +695,7 @@ namespace NCC.Extend.LqReimbursementApplication @@ -582,7 +695,7 @@ namespace NCC.Extend.LqReimbursementApplication
582 throw new Exception("该申请已经提交审批,不能重复提交"); 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 throw new Exception("节点配置异常,无法提交审批"); 700 throw new Exception("节点配置异常,无法提交审批");
588 } 701 }
@@ -605,30 +718,61 @@ namespace NCC.Extend.LqReimbursementApplication @@ -605,30 +718,61 @@ namespace NCC.Extend.LqReimbursementApplication
605 entity.CurrentNodeOrder = 1; 718 entity.CurrentNodeOrder = 1;
606 entity.CurrentNodeId = firstNode.Id; 719 entity.CurrentNodeId = firstNode.Id;
607 entity.ApprovalStatus = "审批中"; 720 entity.ApprovalStatus = "审批中";
  721 + // 清除退回原因(重新提交审批)
  722 + entity.ReturnedReason = null;
  723 + entity.ReturnedNodeOrder = null;
608 await _db.Updateable(entity).ExecuteCommandAsync(); 724 await _db.Updateable(entity).ExecuteCommandAsync();
609 725
610 - // 为第一个节点的每个审批人创建待审批记录 726 + // 获取第一个节点的所有审批人
611 var firstNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() 727 var firstNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
612 .Where(x => x.ApplicationId == id && x.NodeOrder == 1) 728 .Where(x => x.ApplicationId == id && x.NodeOrder == 1)
613 .ToListAsync(); 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 foreach (var approver in firstNodeApprovers) 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 _db.CommitTran(); 776 _db.CommitTran();
633 } 777 }
634 catch (Exception) 778 catch (Exception)
@@ -678,17 +822,33 @@ namespace NCC.Extend.LqReimbursementApplication @@ -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 .Where(x => x.ApplicationId == id 830 .Where(x => x.ApplicationId == id
684 && x.NodeOrder == entity.CurrentNodeOrder 831 && x.NodeOrder == entity.CurrentNodeOrder
685 && x.ApproverId == userInfo.userId 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 try 854 try
@@ -705,7 +865,7 @@ namespace NCC.Extend.LqReimbursementApplication @@ -705,7 +865,7 @@ namespace NCC.Extend.LqReimbursementApplication
705 throw new Exception("未找到当前节点配置"); 865 throw new Exception("未找到当前节点配置");
706 } 866 }
707 867
708 - // 更新待审批记录为已审批(如果存在待审批记录) 868 + // 查找现有的审批记录(优先查找"待审批"记录)
709 var existingRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>() 869 var existingRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
710 .Where(x => x.ApplicationId == id 870 .Where(x => x.ApplicationId == id
711 && x.NodeOrder == entity.CurrentNodeOrder 871 && x.NodeOrder == entity.CurrentNodeOrder
@@ -713,12 +873,35 @@ namespace NCC.Extend.LqReimbursementApplication @@ -713,12 +873,35 @@ namespace NCC.Extend.LqReimbursementApplication
713 && x.ApprovalResult == "待审批") 873 && x.ApprovalResult == "待审批")
714 .FirstAsync(); 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 if (existingRecord != null) 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 existingRecord.ApprovalResult = result; 901 existingRecord.ApprovalResult = result;
720 existingRecord.ApprovalOpinion = opinion; 902 existingRecord.ApprovalOpinion = opinion;
721 existingRecord.ApprovalTime = DateTime.Now; 903 existingRecord.ApprovalTime = DateTime.Now;
  904 + existingRecord.IsCurrentNode = 1;
722 await _db.Updateable(existingRecord).ExecuteCommandAsync(); 905 await _db.Updateable(existingRecord).ExecuteCommandAsync();
723 } 906 }
724 else 907 else
@@ -761,6 +944,13 @@ namespace NCC.Extend.LqReimbursementApplication @@ -761,6 +944,13 @@ namespace NCC.Extend.LqReimbursementApplication
761 else if (result == "退回") 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 if (entity.CurrentNodeOrder == 1) 954 if (entity.CurrentNodeOrder == 1)
765 { 955 {
766 // 退回到申请人 956 // 退回到申请人
@@ -769,6 +959,7 @@ namespace NCC.Extend.LqReimbursementApplication @@ -769,6 +959,7 @@ namespace NCC.Extend.LqReimbursementApplication
769 entity.ReturnedNodeOrder = 0; 959 entity.ReturnedNodeOrder = 0;
770 entity.ReturnedReason = opinion; 960 entity.ReturnedReason = opinion;
771 entity.CurrentNodeId = null; 961 entity.CurrentNodeId = null;
  962 + // 注意:不退回到发起人时,不删除审批记录,保留所有历史记录(包括退回记录)
772 } 963 }
773 else 964 else
774 { 965 {
@@ -787,31 +978,47 @@ namespace NCC.Extend.LqReimbursementApplication @@ -787,31 +978,47 @@ namespace NCC.Extend.LqReimbursementApplication
787 entity.CurrentNodeId = prevNode.Id; 978 entity.CurrentNodeId = prevNode.Id;
788 } 979 }
789 980
790 - // 清除上一节点的所有审批记录(重新审批)  
791 - await _db.Deleteable<LqReimbursementApprovalRecordEntity>() 981 + // 获取上一节点已有的审批记录
  982 + var prevNodeExistingRecords = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
792 .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) 983 .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
793 - .ExecuteCommandAsync(); 984 + .ToListAsync();
794 985
795 - // 为上一节点的每个审批人创建新的待审批记录 986 + // 获取上一节点的所有审批人
796 var prevNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() 987 var prevNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
797 .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) 988 .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
798 .ToListAsync(); 989 .ToListAsync();
799 990
  991 + // 为上一节点的每个审批人创建待审批记录(如果不存在)
800 foreach (var approver in prevNodeApprovers) 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,6 +1055,11 @@ namespace NCC.Extend.LqReimbursementApplication
848 // 所有人都已通过 1055 // 所有人都已通过
849 shouldMoveToNext = true; 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 if (shouldMoveToNext) 1065 if (shouldMoveToNext)
@@ -952,13 +1164,36 @@ namespace NCC.Extend.LqReimbursementApplication @@ -952,13 +1164,36 @@ namespace NCC.Extend.LqReimbursementApplication
952 [HttpGet("Actions/AllPendingApproval")] 1164 [HttpGet("Actions/AllPendingApproval")]
953 public async Task<dynamic> GetAllPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input) 1165 public async Task<dynamic> GetAllPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input)
954 { 1166 {
955 - var sidx = input.sidx == null ? "id" : input.sidx;  
956 -  
957 // 管理员可以查看所有待审批的申请 1167 // 管理员可以查看所有待审批的申请
958 var query = _db.Queryable<LqReimbursementApplicationEntity>() 1168 var query = _db.Queryable<LqReimbursementApplicationEntity>()
959 .Where(x => x.ApprovalStatus == "审批中") 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 var total = await query.CountAsync(); 1198 var total = await query.CountAsync();
964 var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); 1199 var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
@@ -984,6 +1219,18 @@ namespace NCC.Extend.LqReimbursementApplication @@ -984,6 +1219,18 @@ namespace NCC.Extend.LqReimbursementApplication
984 .GroupBy(x => (string)x.applicationId) 1219 .GroupBy(x => (string)x.applicationId)
985 .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); 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 var result = entities.Select(item => new LqReimbursementApplicationListOutput 1235 var result = entities.Select(item => new LqReimbursementApplicationListOutput
989 { 1236 {
@@ -991,6 +1238,9 @@ namespace NCC.Extend.LqReimbursementApplication @@ -991,6 +1238,9 @@ namespace NCC.Extend.LqReimbursementApplication
991 applicationUserId = item.ApplicationUserId, 1238 applicationUserId = item.ApplicationUserId,
992 applicationUserName = item.ApplicationUserName, 1239 applicationUserName = item.ApplicationUserName,
993 applicationStoreId = item.ApplicationStoreId, 1240 applicationStoreId = item.ApplicationStoreId,
  1241 + applicationStoreName = !string.IsNullOrEmpty(item.ApplicationStoreId) && storeDict.ContainsKey(item.ApplicationStoreId)
  1242 + ? storeDict[item.ApplicationStoreId]
  1243 + : null,
994 applicationTime = item.ApplicationTime, 1244 applicationTime = item.ApplicationTime,
995 amount = item.Amount, 1245 amount = item.Amount,
996 approveUser = item.ApproveUser, 1246 approveUser = item.ApproveUser,
@@ -1019,7 +1269,6 @@ namespace NCC.Extend.LqReimbursementApplication @@ -1019,7 +1269,6 @@ namespace NCC.Extend.LqReimbursementApplication
1019 public async Task<dynamic> GetPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input) 1269 public async Task<dynamic> GetPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input)
1020 { 1270 {
1021 var userInfo = await _userManager.GetUserInfo(); 1271 var userInfo = await _userManager.GetUserInfo();
1022 - var sidx = input.sidx == null ? "id" : input.sidx;  
1023 1272
1024 // 查询当前用户作为审批人的节点 1273 // 查询当前用户作为审批人的节点
1025 var userNodeOrders = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() 1274 var userNodeOrders = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
@@ -1044,16 +1293,71 @@ namespace NCC.Extend.LqReimbursementApplication @@ -1044,16 +1293,71 @@ namespace NCC.Extend.LqReimbursementApplication
1044 1293
1045 var applicationIds = userApplications.Keys.ToList(); 1294 var applicationIds = userApplications.Keys.ToList();
1046 1295
1047 - // 查询这些申请中,当前节点是用户有权限的节点,且状态为"审批中"的申请  
1048 - var query = _db.Queryable<LqReimbursementApplicationEntity>() 1296 + // 先查询所有符合条件的申请(状态为"审批中"且在用户有权限的申请列表中)
  1297 + var allApplications = await _db.Queryable<LqReimbursementApplicationEntity>()
1049 .Where(x => applicationIds.Contains(x.Id) && x.ApprovalStatus == "审批中") 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 .Where(x => userApplications.ContainsKey(x.Id) 1304 .Where(x => userApplications.ContainsKey(x.Id)
1051 && userApplications[x.Id].Contains(x.CurrentNodeOrder ?? 0)) 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 var resultApplicationIds = entities.Select(x => x.Id).ToList(); 1363 var resultApplicationIds = entities.Select(x => x.Id).ToList();
@@ -1076,6 +1380,18 @@ namespace NCC.Extend.LqReimbursementApplication @@ -1076,6 +1380,18 @@ namespace NCC.Extend.LqReimbursementApplication
1076 .GroupBy(x => (string)x.applicationId) 1380 .GroupBy(x => (string)x.applicationId)
1077 .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); 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 var result = entities.Select(item => new LqReimbursementApplicationListOutput 1396 var result = entities.Select(item => new LqReimbursementApplicationListOutput
1081 { 1397 {
@@ -1083,6 +1399,9 @@ namespace NCC.Extend.LqReimbursementApplication @@ -1083,6 +1399,9 @@ namespace NCC.Extend.LqReimbursementApplication
1083 applicationUserId = item.ApplicationUserId, 1399 applicationUserId = item.ApplicationUserId,
1084 applicationUserName = item.ApplicationUserName, 1400 applicationUserName = item.ApplicationUserName,
1085 applicationStoreId = item.ApplicationStoreId, 1401 applicationStoreId = item.ApplicationStoreId,
  1402 + applicationStoreName = !string.IsNullOrEmpty(item.ApplicationStoreId) && storeDict.ContainsKey(item.ApplicationStoreId)
  1403 + ? storeDict[item.ApplicationStoreId]
  1404 + : null,
1086 applicationTime = item.ApplicationTime, 1405 applicationTime = item.ApplicationTime,
1087 amount = item.Amount, 1406 amount = item.Amount,
1088 approveUser = item.ApproveUser, 1407 approveUser = item.ApproveUser,
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
@@ -18,6 +18,7 @@ using NCC.Extend.Entitys.lq_xh_jksyj; @@ -18,6 +18,7 @@ using NCC.Extend.Entitys.lq_xh_jksyj;
18 using NCC.Extend.Entitys.lq_ycsd_jsj; 18 using NCC.Extend.Entitys.lq_ycsd_jsj;
19 using NCC.Extend.Entitys.lq_mdxx; 19 using NCC.Extend.Entitys.lq_mdxx;
20 using NCC.Extend.Entitys.lq_hytk_hytk; 20 using NCC.Extend.Entitys.lq_hytk_hytk;
  21 +using NCC.Extend.Entitys.lq_hytk_jksyj;
21 using NCC.Extend.Entitys.lq_md_xdbhsj; 22 using NCC.Extend.Entitys.lq_md_xdbhsj;
22 using NCC.Extend.Entitys.lq_salary_extra_calculation; 23 using NCC.Extend.Entitys.lq_salary_extra_calculation;
23 using NCC.System.Entitys.Permission; 24 using NCC.System.Entitys.Permission;
@@ -224,6 +225,11 @@ namespace NCC.Extend @@ -224,6 +225,11 @@ namespace NCC.Extend
224 .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) 225 .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
225 .ToListAsync(); 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 // 1.4 战队成员及顾问信息 (lq_jinsanjiao_user + lq_ycsd_jsj) 233 // 1.4 战队成员及顾问信息 (lq_jinsanjiao_user + lq_ycsd_jsj)
228 var teamUserList = await _db.Queryable<LqJinsanjiaoUserEntity>() 234 var teamUserList = await _db.Queryable<LqJinsanjiaoUserEntity>()
229 .Where(x => x.Month == monthStr && x.DeleteMark == 0) 235 .Where(x => x.Month == monthStr && x.DeleteMark == 0)
@@ -272,12 +278,12 @@ namespace NCC.Extend @@ -272,12 +278,12 @@ namespace NCC.Extend
272 // 退款金额 278 // 退款金额
273 var storeRefundList = await _db.Queryable<LqHytkHytkEntity>() 279 var storeRefundList = await _db.Queryable<LqHytkHytkEntity>()
274 .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) 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 .ToListAsync(); 282 .ToListAsync();
277 var storeRefundDict = storeRefundList 283 var storeRefundDict = storeRefundList
278 .Where(x => !string.IsNullOrEmpty(x.Mdbh)) 284 .Where(x => !string.IsNullOrEmpty(x.Mdbh))
279 .GroupBy(x => x.Mdbh) 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 // 1.7 门店信息 (lq_mdxx) 288 // 1.7 门店信息 (lq_mdxx)
283 var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync(); 289 var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
@@ -412,6 +418,26 @@ namespace NCC.Extend @@ -412,6 +418,26 @@ namespace NCC.Extend
412 salary.CooperationPerformance = myPerf.Where(x => (x.PerformanceType ?? "").Trim() == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); 418 salary.CooperationPerformance = myPerf.Where(x => (x.PerformanceType ?? "").Trim() == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0"));
413 salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0")); 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 salary.NewCustomerPerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "是")).Sum(x => decimal.Parse(x.Jksyj ?? "0")); 442 salary.NewCustomerPerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "是")).Sum(x => decimal.Parse(x.Jksyj ?? "0"));
417 salary.UpgradePerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "否")).Sum(x => decimal.Parse(x.Jksyj ?? "0")); 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';