Commit caf75b4a0a499559077f3dbd22483a2ce8b82182
1 parent
07c554d7
feat: 优化开单记录汇总接口,添加营销活动和付款医院信息;修复新店保护阶段验证;添加科技部归类字段SQL脚本
Showing
12 changed files
with
978 additions
and
106 deletions
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'; | ... | ... |