Commit 5c4ab8d3509da6b0be688e6606d251044d661afa
1 parent
b1bff74f
111
Showing
5 changed files
with
412 additions
and
107 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsInput.cs
0 → 100644
| 1 | +using System; | ||
| 2 | + | ||
| 3 | +namespace NCC.Extend.Entitys.Dto.LqDailyReport | ||
| 4 | +{ | ||
| 5 | + /// <summary> | ||
| 6 | + /// 储值扣减金额统计输入参数 | ||
| 7 | + /// </summary> | ||
| 8 | + public class DeductAmountStatisticsInput | ||
| 9 | + { | ||
| 10 | + /// <summary> | ||
| 11 | + /// 开始时间(可选,不传则默认为当月1号) | ||
| 12 | + /// </summary> | ||
| 13 | + public DateTime? StartTime { get; set; } | ||
| 14 | + | ||
| 15 | + /// <summary> | ||
| 16 | + /// 结束时间(可选,不传则默认为今天) | ||
| 17 | + /// </summary> | ||
| 18 | + public DateTime? EndTime { get; set; } | ||
| 19 | + } | ||
| 20 | +} | ||
| 21 | + |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsOutput.cs
0 → 100644
| 1 | +using System; | ||
| 2 | + | ||
| 3 | +namespace NCC.Extend.Entitys.Dto.LqDailyReport | ||
| 4 | +{ | ||
| 5 | + /// <summary> | ||
| 6 | + /// 储值扣减金额统计输出结果 | ||
| 7 | + /// </summary> | ||
| 8 | + public class DeductAmountStatisticsOutput | ||
| 9 | + { | ||
| 10 | + /// <summary> | ||
| 11 | + /// 医美储扣金额 | ||
| 12 | + /// </summary> | ||
| 13 | + public decimal YiMeiAmount { get; set; } | ||
| 14 | + | ||
| 15 | + /// <summary> | ||
| 16 | + /// 生美储扣金额 | ||
| 17 | + /// </summary> | ||
| 18 | + public decimal ShengMeiAmount { get; set; } | ||
| 19 | + | ||
| 20 | + /// <summary> | ||
| 21 | + /// 科美储扣金额 | ||
| 22 | + /// </summary> | ||
| 23 | + public decimal KeMeiAmount { get; set; } | ||
| 24 | + | ||
| 25 | + /// <summary> | ||
| 26 | + /// 其他品项储扣金额 | ||
| 27 | + /// </summary> | ||
| 28 | + public decimal OtherAmount { get; set; } | ||
| 29 | + | ||
| 30 | + /// <summary> | ||
| 31 | + /// 总储扣金额 | ||
| 32 | + /// </summary> | ||
| 33 | + public decimal TotalAmount { get; set; } | ||
| 34 | + } | ||
| 35 | +} | ||
| 36 | + |
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqDailyReport/ILqDailyReportService.cs
| @@ -57,6 +57,13 @@ namespace NCC.Extend.Interfaces.LqDailyReport | @@ -57,6 +57,13 @@ namespace NCC.Extend.Interfaces.LqDailyReport | ||
| 57 | /// <param name="input">查询参数</param> | 57 | /// <param name="input">查询参数</param> |
| 58 | /// <returns>科技部老师统计列表</returns> | 58 | /// <returns>科技部老师统计列表</returns> |
| 59 | Task<List<TechTeacherDailyStatisticsOutput>> GetTechTeacherDailyStatistics(TechTeacherDailyStatisticsInput input); | 59 | Task<List<TechTeacherDailyStatisticsOutput>> GetTechTeacherDailyStatistics(TechTeacherDailyStatisticsInput input); |
| 60 | + | ||
| 61 | + /// <summary> | ||
| 62 | + /// 获取储值扣减金额统计 | ||
| 63 | + /// </summary> | ||
| 64 | + /// <param name="input">查询参数</param> | ||
| 65 | + /// <returns>储值扣减金额统计结果</returns> | ||
| 66 | + Task<DeductAmountStatisticsOutput> GetDeductAmountStatistics(DeductAmountStatisticsInput input); | ||
| 60 | } | 67 | } |
| 61 | } | 68 | } |
| 62 | 69 |
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
| @@ -456,7 +456,7 @@ namespace NCC.Extend | @@ -456,7 +456,7 @@ namespace NCC.Extend | ||
| 456 | var actualCompletedPerformance = unit.BillingPerformance - unit.RefundPerformance; // 实际完成业绩 | 456 | var actualCompletedPerformance = unit.BillingPerformance - unit.RefundPerformance; // 实际完成业绩 |
| 457 | unit.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩 | 457 | unit.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩 |
| 458 | var completionRate = unit.TargetPerformance > 0 ? (actualCompletedPerformance / unit.TargetPerformance * 100m) : 0m; | 458 | var completionRate = unit.TargetPerformance > 0 ? (actualCompletedPerformance / unit.TargetPerformance * 100m) : 0m; |
| 459 | - unit.CompletionRate = decimal.Round(completionRate, 2); | 459 | + unit.CompletionRate = decimal.Round(completionRate, 2); |
| 460 | } | 460 | } |
| 461 | 461 | ||
| 462 | var outputList = businessUnitDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList(); | 462 | var outputList = businessUnitDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList(); |
| @@ -472,6 +472,16 @@ namespace NCC.Extend | @@ -472,6 +472,16 @@ namespace NCC.Extend | ||
| 472 | /// <remarks> | 472 | /// <remarks> |
| 473 | /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率 | 473 | /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率 |
| 474 | /// | 474 | /// |
| 475 | + /// 业绩统计规则: | ||
| 476 | + /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单和退卡业绩 | ||
| 477 | + /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单和退卡业绩 | ||
| 478 | + /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单和退卡业绩 | ||
| 479 | + /// | ||
| 480 | + /// 业绩分配规则: | ||
| 481 | + /// - 生美品项的开单和退卡业绩全部归教育部(不按目标比例分配) | ||
| 482 | + /// - 科美品项的开单和退卡业绩全部归科技部(不按目标比例分配) | ||
| 483 | + /// - 医美品项的开单和退卡业绩全部归大项目部(不按目标比例分配) | ||
| 484 | + /// | ||
| 475 | /// 示例请求: | 485 | /// 示例请求: |
| 476 | /// ```json | 486 | /// ```json |
| 477 | /// { | 487 | /// { |
| @@ -490,8 +500,8 @@ namespace NCC.Extend | @@ -490,8 +500,8 @@ namespace NCC.Extend | ||
| 490 | /// - DepartmentId: 部门ID | 500 | /// - DepartmentId: 部门ID |
| 491 | /// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部) | 501 | /// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部) |
| 492 | /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0) | 502 | /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0) |
| 493 | - /// - BillingPerformance: 开单业绩(指定时间范围内的开单业绩总和) | ||
| 494 | - /// - RefundPerformance: 退款业绩(指定时间范围内的退卡业绩总和) | 503 | + /// - BillingPerformance: 开单业绩(指定时间范围内,按品项分类过滤后的开单业绩总和,按门店目标比例分配) |
| 504 | + /// - RefundPerformance: 退款业绩(指定时间范围内,按品项分类过滤后的退卡业绩总和,按门店目标比例分配) | ||
| 495 | /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容) | 505 | /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容) |
| 496 | /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩) | 506 | /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩) |
| 497 | /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100) | 507 | /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100) |
| @@ -520,12 +530,12 @@ namespace NCC.Extend | @@ -520,12 +530,12 @@ namespace NCC.Extend | ||
| 520 | departmentFilter = $"AND o.F_Id IN ('{filterDeptIdsStr}')"; | 530 | departmentFilter = $"AND o.F_Id IN ('{filterDeptIdsStr}')"; |
| 521 | } | 531 | } |
| 522 | 532 | ||
| 523 | - // 天王团部门信息(字段名、目标字段名和部门名称的映射) | ||
| 524 | - var tianwangDepartments = new Dictionary<string, (string fieldName, string targetFieldName, string[] departmentNames)> | 533 | + // 天王团部门信息(字段名、目标字段名、部门名称和品项分类的映射) |
| 534 | + var tianwangDepartments = new Dictionary<string, (string fieldName, string targetFieldName, string[] departmentNames, string itemType)> | ||
| 525 | { | 535 | { |
| 526 | - { "jyb", ("F_EducationDepartment", "F_EducationDepartmentTarget", new[] { "教育一部", "教育二部" }) }, | ||
| 527 | - { "kjb", ("F_TechDepartment", "F_TechDepartmentTarget", new[] { "科技一部", "科技二部" }) }, | ||
| 528 | - { "dxmb", ("F_MajorProjectDepartment", "F_MajorProjectDepartmentTarget", new[] { "大项目一部", "大项目二部" }) } | 536 | + { "jyb", ("F_EducationDepartment", "F_EducationDepartmentTarget", new[] { "教育一部", "教育二部" }, "生美") }, |
| 537 | + { "kjb", ("F_TechDepartment", "F_TechDepartmentTarget", new[] { "科技一部", "科技二部" }, "科美") }, | ||
| 538 | + { "dxmb", ("F_MajorProjectDepartment", "F_MajorProjectDepartmentTarget", new[] { "大项目一部", "大项目二部" }, "医美") } | ||
| 529 | }; | 539 | }; |
| 530 | 540 | ||
| 531 | // 分步查询,提高效率 | 541 | // 分步查询,提高效率 |
| @@ -585,122 +595,275 @@ namespace NCC.Extend | @@ -585,122 +595,275 @@ namespace NCC.Extend | ||
| 585 | return new List<TianwangGroupPerformanceCompletionOutput>(); | 595 | return new List<TianwangGroupPerformanceCompletionOutput>(); |
| 586 | } | 596 | } |
| 587 | 597 | ||
| 588 | - // 第二步:统计各天王团的开单业绩(按门店目标比例分配,避免重复计算) | ||
| 589 | - // 使用目标表中的部门ID进行关联,而不是门店表中的部门字段 | ||
| 590 | - var deptIdsStrForQuery = string.Join("','", departmentDict.Keys); | ||
| 591 | - var billingPerformanceSql = $@" | 598 | + // 第二步:优化策略 - 先获取门店列表,只查询这些门店的数据,大幅提升性能 |
| 599 | + var deptIdsStr = string.Join("','", departmentDict.Keys); | ||
| 600 | + | ||
| 601 | + // 2.1 先快速获取有目标记录的门店ID列表(使用索引,非常快,通常<100ms) | ||
| 602 | + var storeIdsSql = $@" | ||
| 603 | + SELECT DISTINCT F_StoreId | ||
| 604 | + FROM lq_md_target | ||
| 605 | + WHERE F_Month = '{month}' | ||
| 606 | + AND ( | ||
| 607 | + F_EducationDepartment IN ('{deptIdsStr}') | ||
| 608 | + OR F_TechDepartment IN ('{deptIdsStr}') | ||
| 609 | + OR F_MajorProjectDepartment IN ('{deptIdsStr}') | ||
| 610 | + )"; | ||
| 611 | + | ||
| 612 | + var storeIdsData = await _db.Ado.SqlQueryAsync<dynamic>(storeIdsSql); | ||
| 613 | + var storeIds = new List<string>(); | ||
| 614 | + foreach (var row in storeIdsData ?? Enumerable.Empty<dynamic>()) | ||
| 615 | + { | ||
| 616 | + var storeId = row?.F_StoreId?.ToString(); | ||
| 617 | + if (!string.IsNullOrEmpty(storeId)) | ||
| 618 | + { | ||
| 619 | + storeIds.Add(storeId); | ||
| 620 | + } | ||
| 621 | + } | ||
| 622 | + | ||
| 623 | + if (!storeIds.Any()) | ||
| 624 | + { | ||
| 625 | + return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList(); | ||
| 626 | + } | ||
| 627 | + | ||
| 628 | + var storeIdsStr = string.Join("','", storeIds); | ||
| 629 | + | ||
| 630 | + // 2.2 查询目标表数据(一次性查询,数据量小) | ||
| 631 | + // 直接查询,不使用 MAX(),因为每个门店在目标表中应该只有一条记录 | ||
| 632 | + var targetSql = $@" | ||
| 633 | + SELECT | ||
| 634 | + F_StoreId, | ||
| 635 | + F_EducationDepartment, | ||
| 636 | + F_TechDepartment, | ||
| 637 | + F_MajorProjectDepartment | ||
| 638 | + FROM lq_md_target | ||
| 639 | + WHERE F_Month = '{month}' | ||
| 640 | + AND F_StoreId IN ('{storeIdsStr}')"; | ||
| 641 | + | ||
| 642 | + var targetData = await _db.Ado.SqlQueryAsync<dynamic>(targetSql); | ||
| 643 | + var targetDict = new Dictionary<string, dynamic>(); | ||
| 644 | + foreach (var target in targetData ?? Enumerable.Empty<dynamic>()) | ||
| 645 | + { | ||
| 646 | + var storeId = target?.F_StoreId?.ToString(); | ||
| 647 | + if (!string.IsNullOrEmpty(storeId)) | ||
| 648 | + { | ||
| 649 | + targetDict[storeId] = target; | ||
| 650 | + } | ||
| 651 | + } | ||
| 652 | + | ||
| 653 | + // 2.3 从品项明细表统计业绩,按门店+品项分类分组 | ||
| 654 | + // 重要:业绩从品项明细表(lq_kd_pxmx)的 F_ActualPrice 获取,不是从开单记录表的 sfyj 获取 | ||
| 655 | + // 因为一个开单可能包含多个品项,每个品项属于不同的分类(生美、科美、医美) | ||
| 656 | + // 例如:开单A实付500元,包含科美100元、医美250元、生美150元 | ||
| 657 | + // 那么科技部统计100元,大项目部统计250元,教育部统计150元 | ||
| 658 | + var billingSql = $@" | ||
| 592 | SELECT | 659 | SELECT |
| 593 | - o.F_Id as DepartmentId, | ||
| 594 | - COALESCE(SUM( | ||
| 595 | - CASE | ||
| 596 | - -- 如果总目标 > 0,按目标比例分配 | ||
| 597 | - WHEN COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0) > 0 THEN | ||
| 598 | - CASE | ||
| 599 | - WHEN target.F_EducationDepartment = o.F_Id THEN | ||
| 600 | - billing.sfyj * COALESCE(target.F_EducationDepartmentTarget, 0) / | ||
| 601 | - (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0)) | ||
| 602 | - WHEN target.F_TechDepartment = o.F_Id THEN | ||
| 603 | - billing.sfyj * COALESCE(target.F_TechDepartmentTarget, 0) / | ||
| 604 | - (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0)) | ||
| 605 | - WHEN target.F_MajorProjectDepartment = o.F_Id THEN | ||
| 606 | - billing.sfyj * COALESCE(target.F_MajorProjectDepartmentTarget, 0) / | ||
| 607 | - (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0)) | ||
| 608 | - ELSE 0 | ||
| 609 | - END | ||
| 610 | - ELSE 0 | ||
| 611 | - END | ||
| 612 | - ), 0) as BillingPerformance | ||
| 613 | - FROM lq_kd_kdjlb billing | ||
| 614 | - INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id | ||
| 615 | - INNER JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}' | ||
| 616 | - INNER JOIN base_organize o ON ( | ||
| 617 | - (target.F_EducationDepartment = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部')) | ||
| 618 | - OR (target.F_TechDepartment = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部')) | ||
| 619 | - OR (target.F_MajorProjectDepartment = o.F_Id AND o.F_FullName IN ('大项目一部', '大项目二部')) | ||
| 620 | - ) | ||
| 621 | - WHERE billing.F_IsEffective = 1 | ||
| 622 | - AND o.F_Category = 'department' | ||
| 623 | - AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1) | 660 | + temp.djmd as StoreId, |
| 661 | + item.qt2 as ItemType, | ||
| 662 | + COALESCE(SUM(temp.F_ActualPrice), 0) as StoreAmount | ||
| 663 | + FROM ( | ||
| 664 | + SELECT pxmx.px, pxmx.F_ActualPrice, billing.djmd | ||
| 665 | + FROM lq_kd_pxmx pxmx | ||
| 666 | + INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id | ||
| 667 | + WHERE pxmx.F_IsEffective = 1 | ||
| 668 | + AND billing.F_IsEffective = 1 | ||
| 669 | + AND billing.djmd IN ('{storeIdsStr}') | ||
| 670 | + AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00' | ||
| 671 | + AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' | ||
| 672 | + ) temp | ||
| 673 | + INNER JOIN lq_xmzl item ON temp.px = item.F_Id | ||
| 674 | + WHERE item.F_IsEffective = 1 | ||
| 675 | + AND item.qt2 IN ('生美', '科美', '医美') | ||
| 676 | + GROUP BY temp.djmd, item.qt2"; | ||
| 677 | + | ||
| 678 | + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql); | ||
| 679 | + | ||
| 680 | + // 2.4 从退卡明细表统计退卡业绩,按门店+品项分类分组 | ||
| 681 | + // 重要:退卡业绩从退卡明细表(lq_hytk_mx)的 tkje 获取,按品项分类统计 | ||
| 682 | + var refundSql = $@" | ||
| 683 | + SELECT | ||
| 684 | + temp.md as StoreId, | ||
| 685 | + item.qt2 as ItemType, | ||
| 686 | + COALESCE(SUM(temp.tkje), 0) as StoreRefundAmount | ||
| 687 | + FROM ( | ||
| 688 | + SELECT refund_mx.px, refund_mx.tkje, refund.md | ||
| 689 | + FROM lq_hytk_mx refund_mx | ||
| 690 | + INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id | ||
| 691 | + WHERE refund_mx.F_IsEffective = 1 | ||
| 692 | + AND refund.F_IsEffective = 1 | ||
| 693 | + AND refund.md IN ('{storeIdsStr}') | ||
| 694 | + AND refund.tksj >= '{startDate:yyyy-MM-dd} 00:00:00' | ||
| 695 | + AND refund.tksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' | ||
| 696 | + ) temp | ||
| 697 | + INNER JOIN lq_xmzl item ON temp.px = item.F_Id | ||
| 698 | + WHERE item.F_IsEffective = 1 | ||
| 699 | + AND item.qt2 IN ('生美', '科美', '医美') | ||
| 700 | + GROUP BY temp.md, item.qt2"; | ||
| 701 | + | ||
| 702 | + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql); | ||
| 703 | + | ||
| 704 | + // 2.5 从储扣明细表统计储扣金额,按门店+品项分类分组 | ||
| 705 | + // 重要:储扣金额从储扣明细表(lq_kd_deductinfo)的 F_Amount 获取,按品项分类统计 | ||
| 706 | + var deductSql = $@" | ||
| 707 | + SELECT | ||
| 708 | + billing.djmd as StoreId, | ||
| 709 | + item.qt2 as ItemType, | ||
| 710 | + COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount | ||
| 711 | + FROM lq_kd_deductinfo deduct | ||
| 712 | + INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id | ||
| 713 | + INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id | ||
| 714 | + WHERE deduct.F_IsEffective = 1 | ||
| 715 | + AND billing.F_IsEffective = 1 | ||
| 716 | + AND item.F_IsEffective = 1 | ||
| 717 | + AND billing.djmd IN ('{storeIdsStr}') | ||
| 624 | AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00' | 718 | AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00' |
| 625 | AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' | 719 | AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' |
| 626 | - AND o.F_Id IN ('{deptIdsStrForQuery}') | ||
| 627 | - GROUP BY o.F_Id"; | 720 | + AND item.qt2 IN ('生美', '科美', '医美') |
| 721 | + GROUP BY billing.djmd, item.qt2"; | ||
| 722 | + | ||
| 723 | + var deductData = await _db.Ado.SqlQueryAsync<dynamic>(deductSql); | ||
| 724 | + | ||
| 725 | + // 2.6 根据品项分类和目标表分配业绩到对应部门 | ||
| 726 | + // 分配规则: | ||
| 727 | + // - 生美品项 → 分配给教育部门(F_EducationDepartment) | ||
| 728 | + // - 科美品项 → 分配给科技部门(F_TechDepartment) | ||
| 729 | + // - 医美品项 → 分配给大项目部门(F_MajorProjectDepartment) | ||
| 730 | + // 注意:一个门店可能同时属于多个部门,但每个品项的业绩只分配给对应的一个部门 | ||
| 731 | + foreach (var billing in billingData ?? Enumerable.Empty<dynamic>()) | ||
| 732 | + { | ||
| 733 | + var storeId = billing?.StoreId?.ToString(); | ||
| 734 | + var itemType = billing?.ItemType?.ToString(); | ||
| 735 | + var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m; | ||
| 628 | 736 | ||
| 629 | - var billingPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(billingPerformanceSql); | 737 | + if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeAmount <= 0 || !targetDict.ContainsKey(storeId)) |
| 738 | + continue; | ||
| 630 | 739 | ||
| 631 | - // 第三步:统计各天王团的退卡金额(按门店目标比例分配,避免重复计算) | ||
| 632 | - // 使用目标表中的部门ID进行关联,而不是门店表中的部门字段 | ||
| 633 | - var refundPerformanceSql = $@" | ||
| 634 | - SELECT | ||
| 635 | - o.F_Id as DepartmentId, | ||
| 636 | - COALESCE(SUM( | ||
| 637 | - CASE | ||
| 638 | - -- 如果总目标 > 0,按目标比例分配 | ||
| 639 | - WHEN COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0) > 0 THEN | ||
| 640 | - CASE | ||
| 641 | - WHEN target.F_EducationDepartment = o.F_Id THEN | ||
| 642 | - refund.F_ActualRefundAmount * COALESCE(target.F_EducationDepartmentTarget, 0) / | ||
| 643 | - (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0)) | ||
| 644 | - WHEN target.F_TechDepartment = o.F_Id THEN | ||
| 645 | - refund.F_ActualRefundAmount * COALESCE(target.F_TechDepartmentTarget, 0) / | ||
| 646 | - (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0)) | ||
| 647 | - WHEN target.F_MajorProjectDepartment = o.F_Id THEN | ||
| 648 | - refund.F_ActualRefundAmount * COALESCE(target.F_MajorProjectDepartmentTarget, 0) / | ||
| 649 | - (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0)) | ||
| 650 | - ELSE 0 | ||
| 651 | - END | ||
| 652 | - ELSE 0 | ||
| 653 | - END | ||
| 654 | - ), 0) as RefundPerformance | ||
| 655 | - FROM lq_hytk_hytk refund | ||
| 656 | - INNER JOIN lq_mdxx store ON refund.md = store.F_Id | ||
| 657 | - INNER JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}' | ||
| 658 | - INNER JOIN base_organize o ON ( | ||
| 659 | - (target.F_EducationDepartment = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部')) | ||
| 660 | - OR (target.F_TechDepartment = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部')) | ||
| 661 | - OR (target.F_MajorProjectDepartment = o.F_Id AND o.F_FullName IN ('大项目一部', '大项目二部')) | ||
| 662 | - ) | ||
| 663 | - WHERE refund.F_IsEffective = 1 | ||
| 664 | - AND o.F_Category = 'department' | ||
| 665 | - AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1) | ||
| 666 | - AND refund.tksj >= '{startDate:yyyy-MM-dd} 00:00:00' | ||
| 667 | - AND refund.tksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' | ||
| 668 | - AND o.F_Id IN ('{deptIdsStrForQuery}') | ||
| 669 | - GROUP BY o.F_Id"; | 740 | + var target = targetDict[storeId]; |
| 741 | + if (target == null) | ||
| 742 | + continue; | ||
| 670 | 743 | ||
| 671 | - var refundPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(refundPerformanceSql); | 744 | + string targetDeptId = null; |
| 672 | 745 | ||
| 673 | - // 第四步:合并开单业绩数据 | ||
| 674 | - foreach (var item in billingPerformanceData ?? Enumerable.Empty<dynamic>()) | 746 | + // 根据品项分类,分配到对应的部门 |
| 747 | + if (itemType == "生美") | ||
| 748 | + { | ||
| 749 | + // 生美品项 → 教育部门 | ||
| 750 | + targetDeptId = target.F_EducationDepartment?.ToString(); | ||
| 751 | + } | ||
| 752 | + else if (itemType == "科美") | ||
| 753 | + { | ||
| 754 | + // 科美品项 → 科技部门 | ||
| 755 | + targetDeptId = target.F_TechDepartment?.ToString(); | ||
| 756 | + } | ||
| 757 | + else if (itemType == "医美") | ||
| 758 | + { | ||
| 759 | + // 医美品项 → 大项目部门 | ||
| 760 | + targetDeptId = target.F_MajorProjectDepartment?.ToString(); | ||
| 761 | + } | ||
| 762 | + | ||
| 763 | + // 只分配给在查询列表中的部门,避免重复统计 | ||
| 764 | + if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId)) | ||
| 765 | + { | ||
| 766 | + departmentDict[targetDeptId].BillingPerformance += storeAmount; | ||
| 767 | + } | ||
| 768 | + } | ||
| 769 | + | ||
| 770 | + // 2.6.1 减去储扣金额 | ||
| 771 | + foreach (var deduct in deductData ?? Enumerable.Empty<dynamic>()) | ||
| 772 | + { | ||
| 773 | + var storeId = deduct?.StoreId?.ToString(); | ||
| 774 | + var itemType = deduct?.ItemType?.ToString(); | ||
| 775 | + var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m; | ||
| 776 | + | ||
| 777 | + if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeDeductAmount <= 0 || !targetDict.ContainsKey(storeId)) | ||
| 778 | + continue; | ||
| 779 | + | ||
| 780 | + var target = targetDict[storeId]; | ||
| 781 | + if (target == null) | ||
| 782 | + continue; | ||
| 783 | + | ||
| 784 | + string targetDeptId = null; | ||
| 785 | + | ||
| 786 | + // 根据品项分类,从对应的部门减去储扣金额 | ||
| 787 | + if (itemType == "生美") | ||
| 788 | + { | ||
| 789 | + // 生美品项储扣 → 从教育部门减去 | ||
| 790 | + targetDeptId = target.F_EducationDepartment?.ToString(); | ||
| 791 | + } | ||
| 792 | + else if (itemType == "科美") | ||
| 675 | { | 793 | { |
| 676 | - var departmentId = item.DepartmentId.ToString(); | ||
| 677 | - if (departmentDict.ContainsKey(departmentId)) | 794 | + // 科美品项储扣 → 从科技部门减去 |
| 795 | + targetDeptId = target.F_TechDepartment?.ToString(); | ||
| 796 | + } | ||
| 797 | + else if (itemType == "医美") | ||
| 798 | + { | ||
| 799 | + // 医美品项储扣 → 从大项目部门减去 | ||
| 800 | + targetDeptId = target.F_MajorProjectDepartment?.ToString(); | ||
| 801 | + } | ||
| 802 | + | ||
| 803 | + // 只从在查询列表中的部门减去,避免重复统计 | ||
| 804 | + if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId)) | ||
| 678 | { | 805 | { |
| 679 | - departmentDict[departmentId].BillingPerformance = decimal.Round(Convert.ToDecimal(item.BillingPerformance), 2); | 806 | + departmentDict[targetDeptId].BillingPerformance -= storeDeductAmount; |
| 680 | } | 807 | } |
| 681 | } | 808 | } |
| 682 | 809 | ||
| 683 | - // 第五步:合并退卡金额数据 | ||
| 684 | - foreach (var item in refundPerformanceData ?? Enumerable.Empty<dynamic>()) | 810 | + // 2.7 根据品项分类和目标表分配退卡业绩到对应部门 |
| 811 | + // 分配规则与开单业绩相同: | ||
| 812 | + // - 生美品项退卡 → 分配给教育部门 | ||
| 813 | + // - 科美品项退卡 → 分配给科技部门 | ||
| 814 | + // - 医美品项退卡 → 分配给大项目部门 | ||
| 815 | + foreach (var refund in refundData ?? Enumerable.Empty<dynamic>()) | ||
| 685 | { | 816 | { |
| 686 | - var departmentId = item.DepartmentId.ToString(); | ||
| 687 | - if (departmentDict.ContainsKey(departmentId)) | 817 | + var storeId = refund?.StoreId?.ToString(); |
| 818 | + var itemType = refund?.ItemType?.ToString(); | ||
| 819 | + var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m; | ||
| 820 | + | ||
| 821 | + if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeRefundAmount <= 0 || !targetDict.ContainsKey(storeId)) | ||
| 822 | + continue; | ||
| 823 | + | ||
| 824 | + var target = targetDict[storeId]; | ||
| 825 | + if (target == null) | ||
| 826 | + continue; | ||
| 827 | + | ||
| 828 | + string targetDeptId = null; | ||
| 829 | + | ||
| 830 | + // 根据品项分类,分配到对应的部门 | ||
| 831 | + if (itemType == "生美") | ||
| 832 | + { | ||
| 833 | + // 生美品项退卡 → 教育部门 | ||
| 834 | + targetDeptId = target.F_EducationDepartment?.ToString(); | ||
| 835 | + } | ||
| 836 | + else if (itemType == "科美") | ||
| 688 | { | 837 | { |
| 689 | - departmentDict[departmentId].RefundPerformance = decimal.Round(Convert.ToDecimal(item.RefundPerformance), 2); | 838 | + // 科美品项退卡 → 科技部门 |
| 839 | + targetDeptId = target.F_TechDepartment?.ToString(); | ||
| 840 | + } | ||
| 841 | + else if (itemType == "医美") | ||
| 842 | + { | ||
| 843 | + // 医美品项退卡 → 大项目部门 | ||
| 844 | + targetDeptId = target.F_MajorProjectDepartment?.ToString(); | ||
| 845 | + } | ||
| 846 | + | ||
| 847 | + // 只分配给在查询列表中的部门,避免重复统计 | ||
| 848 | + if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId)) | ||
| 849 | + { | ||
| 850 | + departmentDict[targetDeptId].RefundPerformance += storeRefundAmount; | ||
| 690 | } | 851 | } |
| 691 | } | 852 | } |
| 692 | 853 | ||
| 693 | - // 第六步:计算实际完成业绩和完成率 | 854 | + // 第三步:计算实际完成业绩和完成率,并保留两位小数 |
| 694 | foreach (var dept in departmentDict.Values) | 855 | foreach (var dept in departmentDict.Values) |
| 695 | { | 856 | { |
| 857 | + dept.BillingPerformance = decimal.Round(dept.BillingPerformance, 2); | ||
| 858 | + dept.RefundPerformance = decimal.Round(dept.RefundPerformance, 2); | ||
| 696 | dept.ActualPerformance = dept.BillingPerformance; // 开单业绩 | 859 | dept.ActualPerformance = dept.BillingPerformance; // 开单业绩 |
| 697 | var actualCompletedPerformance = dept.BillingPerformance - dept.RefundPerformance; // 实际完成业绩 | 860 | var actualCompletedPerformance = dept.BillingPerformance - dept.RefundPerformance; // 实际完成业绩 |
| 698 | dept.CompletedPerformance = decimal.Round(actualCompletedPerformance, 2); // 实际完成业绩,保留两位小数 | 861 | dept.CompletedPerformance = decimal.Round(actualCompletedPerformance, 2); // 实际完成业绩,保留两位小数 |
| 699 | 862 | ||
| 700 | - var completionRate = dept.TargetPerformance > 0 | 863 | + var completionRate = dept.TargetPerformance > 0 |
| 701 | ? (actualCompletedPerformance / dept.TargetPerformance * 100m) | 864 | ? (actualCompletedPerformance / dept.TargetPerformance * 100m) |
| 702 | - : 0m; | ||
| 703 | - dept.CompletionRate = decimal.Round(completionRate, 2); | 865 | + : 0m; |
| 866 | + dept.CompletionRate = decimal.Round(completionRate, 2); | ||
| 704 | } | 867 | } |
| 705 | 868 | ||
| 706 | var outputList = departmentDict.Values | 869 | var outputList = departmentDict.Values |
| @@ -1420,6 +1583,86 @@ namespace NCC.Extend | @@ -1420,6 +1583,86 @@ namespace NCC.Extend | ||
| 1420 | } | 1583 | } |
| 1421 | #endregion | 1584 | #endregion |
| 1422 | 1585 | ||
| 1586 | + #region 获取储值扣减金额统计 | ||
| 1587 | + /// <summary> | ||
| 1588 | + /// 获取储值扣减金额统计 | ||
| 1589 | + /// </summary> | ||
| 1590 | + /// <remarks> | ||
| 1591 | + /// 根据指定时间范围统计储值扣减金额,按品项分类(医美、生美、科美、其他)分组统计 | ||
| 1592 | + /// | ||
| 1593 | + /// 示例请求: | ||
| 1594 | + /// ```json | ||
| 1595 | + /// { | ||
| 1596 | + /// "startTime": "2025-11-01", | ||
| 1597 | + /// "endTime": "2025-11-30" | ||
| 1598 | + /// } | ||
| 1599 | + /// ``` | ||
| 1600 | + /// | ||
| 1601 | + /// 参数说明: | ||
| 1602 | + /// - startTime: 开始时间,可选,不传则默认为当月1号 | ||
| 1603 | + /// - endTime: 结束时间,可选,不传则默认为今天 | ||
| 1604 | + /// | ||
| 1605 | + /// 返回说明: | ||
| 1606 | + /// - YiMeiAmount: 医美储扣金额 | ||
| 1607 | + /// - ShengMeiAmount: 生美储扣金额 | ||
| 1608 | + /// - KeMeiAmount: 科美储扣金额 | ||
| 1609 | + /// - OtherAmount: 其他品项储扣金额 | ||
| 1610 | + /// - TotalAmount: 总储扣金额 | ||
| 1611 | + /// </remarks> | ||
| 1612 | + /// <param name="input">查询参数</param> | ||
| 1613 | + /// <returns>储值扣减金额统计结果</returns> | ||
| 1614 | + /// <response code="200">成功返回储值扣减金额统计结果</response> | ||
| 1615 | + /// <response code="400">日期格式错误或参数无效</response> | ||
| 1616 | + /// <response code="500">服务器内部错误</response> | ||
| 1617 | + [AllowAnonymous] | ||
| 1618 | + [HttpPost("get-deduct-amount-statistics")] | ||
| 1619 | + public async Task<DeductAmountStatisticsOutput> GetDeductAmountStatistics(DeductAmountStatisticsInput input) | ||
| 1620 | + { | ||
| 1621 | + // 获取时间范围 | ||
| 1622 | + var (startDate, endDate) = GetTimeRange(input.StartTime, input.EndTime); | ||
| 1623 | + | ||
| 1624 | + // 统计储扣金额,按品项分类分组 | ||
| 1625 | + var sql = $@" | ||
| 1626 | + SELECT | ||
| 1627 | + COALESCE(SUM(CASE WHEN item.qt2 = '医美' THEN deduct.F_Amount ELSE 0 END), 0) as YiMeiAmount, | ||
| 1628 | + COALESCE(SUM(CASE WHEN item.qt2 = '生美' THEN deduct.F_Amount ELSE 0 END), 0) as ShengMeiAmount, | ||
| 1629 | + COALESCE(SUM(CASE WHEN item.qt2 = '科美' THEN deduct.F_Amount ELSE 0 END), 0) as KeMeiAmount, | ||
| 1630 | + COALESCE(SUM(CASE WHEN item.qt2 NOT IN ('医美', '生美', '科美') OR item.qt2 IS NULL THEN deduct.F_Amount ELSE 0 END), 0) as OtherAmount, | ||
| 1631 | + COALESCE(SUM(deduct.F_Amount), 0) as TotalAmount | ||
| 1632 | + FROM lq_kd_deductinfo deduct | ||
| 1633 | + INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id | ||
| 1634 | + LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id AND item.F_IsEffective = 1 | ||
| 1635 | + WHERE deduct.F_IsEffective = 1 | ||
| 1636 | + AND billing.F_IsEffective = 1 | ||
| 1637 | + AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00' | ||
| 1638 | + AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'"; | ||
| 1639 | + | ||
| 1640 | + var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); | ||
| 1641 | + var data = result.FirstOrDefault(); | ||
| 1642 | + | ||
| 1643 | + if (data == null) | ||
| 1644 | + { | ||
| 1645 | + return new DeductAmountStatisticsOutput | ||
| 1646 | + { | ||
| 1647 | + YiMeiAmount = 0, | ||
| 1648 | + ShengMeiAmount = 0, | ||
| 1649 | + KeMeiAmount = 0, | ||
| 1650 | + OtherAmount = 0, | ||
| 1651 | + TotalAmount = 0 | ||
| 1652 | + }; | ||
| 1653 | + } | ||
| 1654 | + | ||
| 1655 | + return new DeductAmountStatisticsOutput | ||
| 1656 | + { | ||
| 1657 | + YiMeiAmount = decimal.Round(data.YiMeiAmount != null ? Convert.ToDecimal(data.YiMeiAmount) : 0m, 2), | ||
| 1658 | + ShengMeiAmount = decimal.Round(data.ShengMeiAmount != null ? Convert.ToDecimal(data.ShengMeiAmount) : 0m, 2), | ||
| 1659 | + KeMeiAmount = decimal.Round(data.KeMeiAmount != null ? Convert.ToDecimal(data.KeMeiAmount) : 0m, 2), | ||
| 1660 | + OtherAmount = decimal.Round(data.OtherAmount != null ? Convert.ToDecimal(data.OtherAmount) : 0m, 2), | ||
| 1661 | + TotalAmount = decimal.Round(data.TotalAmount != null ? Convert.ToDecimal(data.TotalAmount) : 0m, 2) | ||
| 1662 | + }; | ||
| 1663 | + } | ||
| 1664 | + #endregion | ||
| 1665 | + | ||
| 1423 | 1666 | ||
| 1424 | } | 1667 | } |
| 1425 | } | 1668 | } |
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
| @@ -3588,19 +3588,17 @@ namespace NCC.Extend.LqStatistics | @@ -3588,19 +3588,17 @@ namespace NCC.Extend.LqStatistics | ||
| 3588 | /// </summary> | 3588 | /// </summary> |
| 3589 | private async Task<(int TotalCount, decimal OriginalCount, decimal OvertimeCount, decimal AccompaniedCount)> GetConsumeProjectCountDetails(string userId, string month) | 3589 | private async Task<(int TotalCount, decimal OriginalCount, decimal OvertimeCount, decimal AccompaniedCount)> GetConsumeProjectCountDetails(string userId, string month) |
| 3590 | { | 3590 | { |
| 3591 | + // 直接从健康师业绩表统计项目数,使用业绩时间字段过滤,不需要JOIN其他表 | ||
| 3591 | var sql = $@" | 3592 | var sql = $@" |
| 3592 | SELECT | 3593 | SELECT |
| 3593 | - COALESCE(SUM(pxmx.F_ProjectNumber), 0) as TotalCount, | ||
| 3594 | - COALESCE(SUM(COALESCE(pxmx.F_OriginalProjectNumber, pxmx.F_ProjectNumber)), 0) as OriginalCount, | ||
| 3595 | - COALESCE(SUM(COALESCE(pxmx.F_OvertimeProjectNumber, 0)), 0) as OvertimeCount, | 3594 | + COALESCE(SUM(jksyj.F_kdpxNumber), 0) as TotalCount, |
| 3595 | + COALESCE(SUM(COALESCE(jksyj.F_OriginalKdpxNumber, jksyj.F_kdpxNumber)), 0) as OriginalCount, | ||
| 3596 | + COALESCE(SUM(COALESCE(jksyj.F_OvertimeKdpxNumber, 0)), 0) as OvertimeCount, | ||
| 3596 | COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as AccompaniedCount | 3597 | COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as AccompaniedCount |
| 3597 | FROM lq_xh_jksyj jksyj | 3598 | FROM lq_xh_jksyj jksyj |
| 3598 | - INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | ||
| 3599 | - INNER JOIN lq_xh_pxmx pxmx ON pxmx.F_ConsumeInfoId = hyhk.F_Id | ||
| 3600 | WHERE jksyj.jkszh = '{userId}' | 3599 | WHERE jksyj.jkszh = '{userId}' |
| 3601 | AND jksyj.F_IsEffective = 1 | 3600 | AND jksyj.F_IsEffective = 1 |
| 3602 | - AND hyhk.F_IsEffective = 1 | ||
| 3603 | - AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'"; | 3601 | + AND DATE_FORMAT(jksyj.yjsj, '%Y%m') = '{month}'"; |
| 3604 | 3602 | ||
| 3605 | var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); | 3603 | var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); |
| 3606 | var data = result.FirstOrDefault(); | 3604 | var data = result.FirstOrDefault(); |