Commit 7855bf0414f0cc3bc486b44ed0f9a2ec86a5c6f9
1 parent
68b8deb5
feat: 优化品项统计查询性能并新增营销活动统计功能
- 优化LqReportService.GetItemStatistics方法,采用分步查询策略避免复杂JOIN,提升查询性能至3秒内 - 新增LqPackageInfoService.GetActivityStatistics方法,支持营销活动开单和退卡统计 - 新增ActivityStatisticsInput和ActivityStatisticsOutput DTO类 - 支持按营销活动、时间范围、门店等维度进行统计分析 - 提供开单数量、开单金额、退卡数量、退卡金额、净开单数量、净开单金额、退卡率等完整统计指标
Showing
4 changed files
with
379 additions
and
63 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsInput.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.ComponentModel.DataAnnotations; | |
| 3 | + | |
| 4 | +namespace NCC.Extend.Entitys.Dto.LqPackageInfo | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 营销活动统计查询输入参数 | |
| 8 | + /// </summary> | |
| 9 | + public class ActivityStatisticsInput | |
| 10 | + { | |
| 11 | + /// <summary> | |
| 12 | + /// 营销活动ID | |
| 13 | + /// </summary> | |
| 14 | + [Required(ErrorMessage = "营销活动ID不能为空")] | |
| 15 | + public string ActivityId { get; set; } | |
| 16 | + | |
| 17 | + /// <summary> | |
| 18 | + /// 开始时间(可选,默认为活动开始时间) | |
| 19 | + /// </summary> | |
| 20 | + public DateTime? StartTime { get; set; } | |
| 21 | + | |
| 22 | + /// <summary> | |
| 23 | + /// 结束时间(可选,默认为活动结束时间) | |
| 24 | + /// </summary> | |
| 25 | + public DateTime? EndTime { get; set; } | |
| 26 | + | |
| 27 | + /// <summary> | |
| 28 | + /// 门店ID列表(可选) | |
| 29 | + /// </summary> | |
| 30 | + public string[] StoreIds { get; set; } | |
| 31 | + } | |
| 32 | +} | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqPackageInfo | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 营销活动统计输出结果 | |
| 7 | + /// </summary> | |
| 8 | + public class ActivityStatisticsOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 营销活动ID | |
| 12 | + /// </summary> | |
| 13 | + public string ActivityId { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 营销活动名称 | |
| 17 | + /// </summary> | |
| 18 | + public string ActivityName { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 开单数量 | |
| 22 | + /// </summary> | |
| 23 | + public int BillingCount { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 开单金额 | |
| 27 | + /// </summary> | |
| 28 | + public decimal BillingAmount { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 退卡数量 | |
| 32 | + /// </summary> | |
| 33 | + public int RefundCount { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 退卡金额 | |
| 37 | + /// </summary> | |
| 38 | + public decimal RefundAmount { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 净开单数量(开单数量 - 退卡数量) | |
| 42 | + /// </summary> | |
| 43 | + public int NetBillingCount { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 净开单金额(开单金额 - 退卡金额) | |
| 47 | + /// </summary> | |
| 48 | + public decimal NetBillingAmount { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 退卡率(退卡数量 / 开单数量) | |
| 52 | + /// </summary> | |
| 53 | + public decimal RefundRate { get; set; } | |
| 54 | + } | |
| 55 | +} | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs
| ... | ... | @@ -551,5 +551,163 @@ namespace NCC.Extend.LqPackageInfo |
| 551 | 551 | return output; |
| 552 | 552 | } |
| 553 | 553 | #endregion |
| 554 | + | |
| 555 | + #region 营销活动统计 | |
| 556 | + /// <summary> | |
| 557 | + /// 获取营销活动统计数据 | |
| 558 | + /// </summary> | |
| 559 | + /// <remarks> | |
| 560 | + /// 统计指定营销活动的开单、退卡相关数据 | |
| 561 | + /// 包括:开单数量、开单金额、退卡数量、退卡金额、净开单数量、净开单金额、退卡率 | |
| 562 | + /// | |
| 563 | + /// 示例请求: | |
| 564 | + /// ```json | |
| 565 | + /// { | |
| 566 | + /// "activityId": "营销活动ID", | |
| 567 | + /// "startTime": "2025-10-01", | |
| 568 | + /// "endTime": "2025-10-31", | |
| 569 | + /// "storeIds": ["门店ID1", "门店ID2"] | |
| 570 | + /// } | |
| 571 | + /// ``` | |
| 572 | + /// | |
| 573 | + /// 参数说明: | |
| 574 | + /// - activityId: 营销活动ID(必填) | |
| 575 | + /// - startTime: 开始时间(可选,默认为活动开始时间) | |
| 576 | + /// - endTime: 结束时间(可选,默认为活动结束时间) | |
| 577 | + /// - storeIds: 门店ID列表(可选) | |
| 578 | + /// | |
| 579 | + /// 返回字段说明: | |
| 580 | + /// - ActivityId: 营销活动ID | |
| 581 | + /// - ActivityName: 营销活动名称 | |
| 582 | + /// - BillingCount: 开单数量 | |
| 583 | + /// - BillingAmount: 开单金额 | |
| 584 | + /// - RefundCount: 退卡数量 | |
| 585 | + /// - RefundAmount: 退卡金额 | |
| 586 | + /// - NetBillingCount: 净开单数量(开单数量 - 退卡数量) | |
| 587 | + /// - NetBillingAmount: 净开单金额(开单金额 - 退卡金额) | |
| 588 | + /// - RefundRate: 退卡率(退卡数量 / 开单数量) | |
| 589 | + /// </remarks> | |
| 590 | + /// <param name="input">查询参数</param> | |
| 591 | + /// <returns>营销活动统计数据</returns> | |
| 592 | + /// <response code="200">成功返回统计数据</response> | |
| 593 | + /// <response code="400">参数错误</response> | |
| 594 | + /// <response code="500">服务器错误</response> | |
| 595 | + [HttpPost("get-activity-statistics")] | |
| 596 | + public async Task<object> GetActivityStatistics(ActivityStatisticsInput input) | |
| 597 | + { | |
| 598 | + try | |
| 599 | + { | |
| 600 | + // 1. 获取营销活动信息 | |
| 601 | + var activity = await _db.Queryable<LqPackageInfoEntity>() | |
| 602 | + .Where(x => x.Id == input.ActivityId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 603 | + .FirstAsync(); | |
| 604 | + | |
| 605 | + if (activity == null) | |
| 606 | + { | |
| 607 | + throw NCCException.Oh("营销活动不存在或已失效"); | |
| 608 | + } | |
| 609 | + | |
| 610 | + // 2. 设置时间范围(如果未提供,使用活动时间范围) | |
| 611 | + var startTime = input.StartTime ?? activity.StartTime; | |
| 612 | + var endTime = input.EndTime ?? activity.EndTime; | |
| 613 | + | |
| 614 | + // 3. 获取营销活动关联的品项ID列表 | |
| 615 | + var itemIds = await _db.Queryable<LqPackageItemDetailEntity>() | |
| 616 | + .Where(x => x.ActivityId == input.ActivityId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 617 | + .Select(x => x.ItemId) | |
| 618 | + .ToListAsync(); | |
| 619 | + | |
| 620 | + if (!itemIds.Any()) | |
| 621 | + { | |
| 622 | + return new ActivityStatisticsOutput | |
| 623 | + { | |
| 624 | + ActivityId = input.ActivityId, | |
| 625 | + ActivityName = activity.ActivityName, | |
| 626 | + BillingCount = 0, | |
| 627 | + BillingAmount = 0, | |
| 628 | + RefundCount = 0, | |
| 629 | + RefundAmount = 0, | |
| 630 | + NetBillingCount = 0, | |
| 631 | + NetBillingAmount = 0, | |
| 632 | + RefundRate = 0 | |
| 633 | + }; | |
| 634 | + } | |
| 635 | + | |
| 636 | + // 4. 构建品项ID过滤条件 | |
| 637 | + var itemIdsStr = string.Join("','", itemIds); | |
| 638 | + var itemIdsFilter = $"AND px.px IN ('{itemIdsStr}')"; | |
| 639 | + var itemIdsFilterRefund = $"AND hytkmx.px IN ('{itemIdsStr}')"; | |
| 640 | + | |
| 641 | + // 5. 构建门店过滤条件 | |
| 642 | + string storeFilter = ""; | |
| 643 | + string storeFilterRefund = ""; | |
| 644 | + | |
| 645 | + if (input.StoreIds != null && input.StoreIds.Any()) | |
| 646 | + { | |
| 647 | + var storeIdsStr = string.Join("','", input.StoreIds); | |
| 648 | + storeFilter = $"AND kd.djmd IN ('{storeIdsStr}')"; | |
| 649 | + storeFilterRefund = $"AND hytk.md IN ('{storeIdsStr}')"; | |
| 650 | + } | |
| 651 | + | |
| 652 | + // 6. 开单统计 | |
| 653 | + var billingSql = $@" | |
| 654 | + SELECT | |
| 655 | + COUNT(DISTINCT kd.F_Id) as billing_count, | |
| 656 | + SUM(CAST(px.F_ActualPrice AS DECIMAL(18,2))) as billing_amount | |
| 657 | + FROM lq_kd_pxmx px | |
| 658 | + LEFT JOIN lq_kd_kdjlb kd ON px.glkdbh = kd.F_Id | |
| 659 | + WHERE px.F_IsEffective = 1 | |
| 660 | + {itemIdsFilter} | |
| 661 | + AND px.yjsj >= '{startTime:yyyy-MM-dd HH:mm:ss}' | |
| 662 | + AND px.yjsj <= '{endTime:yyyy-MM-dd HH:mm:ss}' | |
| 663 | + {storeFilter}"; | |
| 664 | + | |
| 665 | + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql); | |
| 666 | + var billingCount = Convert.ToInt32(billingData.FirstOrDefault()?.billing_count ?? 0); | |
| 667 | + var billingAmount = Convert.ToDecimal(billingData.FirstOrDefault()?.billing_amount ?? 0); | |
| 668 | + | |
| 669 | + // 7. 退卡统计 | |
| 670 | + var refundSql = $@" | |
| 671 | + SELECT | |
| 672 | + COUNT(DISTINCT hytk.F_Id) as refund_count, | |
| 673 | + SUM(CAST(hytkmx.tkje AS DECIMAL(18,2))) as refund_amount | |
| 674 | + FROM lq_hytk_mx hytkmx | |
| 675 | + LEFT JOIN lq_hytk_hytk hytk ON hytkmx.F_RefundInfoId = hytk.F_Id | |
| 676 | + WHERE hytkmx.F_IsEffective = 1 | |
| 677 | + {itemIdsFilterRefund} | |
| 678 | + AND hytkmx.tksj >= '{startTime:yyyy-MM-dd HH:mm:ss}' | |
| 679 | + AND hytkmx.tksj <= '{endTime:yyyy-MM-dd HH:mm:ss}' | |
| 680 | + {storeFilterRefund}"; | |
| 681 | + | |
| 682 | + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql); | |
| 683 | + var refundCount = Convert.ToInt32(refundData.FirstOrDefault()?.refund_count ?? 0); | |
| 684 | + var refundAmount = Convert.ToDecimal(refundData.FirstOrDefault()?.refund_amount ?? 0); | |
| 685 | + | |
| 686 | + // 8. 计算净值和退卡率 | |
| 687 | + var netBillingCount = billingCount - refundCount; | |
| 688 | + var netBillingAmount = billingAmount - refundAmount; | |
| 689 | + var refundRate = billingCount > 0 ? Math.Round((decimal)refundCount / billingCount * 100, 2) : 0; | |
| 690 | + | |
| 691 | + // 9. 返回统计结果 | |
| 692 | + return new ActivityStatisticsOutput | |
| 693 | + { | |
| 694 | + ActivityId = input.ActivityId, | |
| 695 | + ActivityName = activity.ActivityName, | |
| 696 | + BillingCount = billingCount, | |
| 697 | + BillingAmount = billingAmount, | |
| 698 | + RefundCount = refundCount, | |
| 699 | + RefundAmount = refundAmount, | |
| 700 | + NetBillingCount = netBillingCount, | |
| 701 | + NetBillingAmount = netBillingAmount, | |
| 702 | + RefundRate = refundRate | |
| 703 | + }; | |
| 704 | + } | |
| 705 | + catch (Exception ex) | |
| 706 | + { | |
| 707 | + throw NCCException.Oh($"获取营销活动统计数据失败: {ex.Message}"); | |
| 708 | + } | |
| 709 | + } | |
| 710 | + #endregion | |
| 711 | + | |
| 554 | 712 | } |
| 555 | 713 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs
| ... | ... | @@ -55,8 +55,6 @@ namespace NCC.Extend |
| 55 | 55 | _logger = logger; |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | - | |
| 59 | - | |
| 60 | 58 | #region 门店业绩报表 |
| 61 | 59 | |
| 62 | 60 | /// <summary> |
| ... | ... | @@ -558,7 +556,7 @@ namespace NCC.Extend |
| 558 | 556 | |
| 559 | 557 | #endregion |
| 560 | 558 | |
| 561 | - #region 综合仪表盘 | |
| 559 | + #region 获取综合仪表盘数据 | |
| 562 | 560 | |
| 563 | 561 | /// <summary> |
| 564 | 562 | /// 获取综合仪表盘数据 |
| ... | ... | @@ -691,6 +689,10 @@ namespace NCC.Extend |
| 691 | 689 | } |
| 692 | 690 | } |
| 693 | 691 | |
| 692 | + #endregion | |
| 693 | + | |
| 694 | + #region 获取业务统计数据 | |
| 695 | + | |
| 694 | 696 | /// <summary> |
| 695 | 697 | /// 获取业务统计数据 |
| 696 | 698 | /// </summary> |
| ... | ... | @@ -828,6 +830,9 @@ namespace NCC.Extend |
| 828 | 830 | } |
| 829 | 831 | } |
| 830 | 832 | |
| 833 | + #endregion | |
| 834 | + | |
| 835 | + #region 获取客户类型统计数据 | |
| 831 | 836 | /// <summary> |
| 832 | 837 | /// 获取客户类型统计数据 |
| 833 | 838 | /// </summary> |
| ... | ... | @@ -967,6 +972,9 @@ namespace NCC.Extend |
| 967 | 972 | } |
| 968 | 973 | } |
| 969 | 974 | |
| 975 | + #endregion | |
| 976 | + | |
| 977 | + #region 获取门店业绩对比统计数据 | |
| 970 | 978 | /// <summary> |
| 971 | 979 | /// 获取门店业绩对比统计数据 |
| 972 | 980 | /// </summary> |
| ... | ... | @@ -1079,6 +1087,9 @@ namespace NCC.Extend |
| 1079 | 1087 | throw NCCException.Oh($"获取门店业绩对比统计数据失败: {ex.Message}"); |
| 1080 | 1088 | } |
| 1081 | 1089 | } |
| 1090 | + #endregion | |
| 1091 | + | |
| 1092 | + #region 获取品项统计数据 | |
| 1082 | 1093 | |
| 1083 | 1094 | /// <summary> |
| 1084 | 1095 | /// 获取品项统计数据 |
| ... | ... | @@ -1126,78 +1137,138 @@ namespace NCC.Extend |
| 1126 | 1137 | var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); |
| 1127 | 1138 | var endTime = input.EndTime ?? DateTime.Now; |
| 1128 | 1139 | |
| 1129 | - // 构建SQL查询 | |
| 1130 | - var sql = @" | |
| 1131 | - SELECT | |
| 1132 | - xm.F_Id as item_id, | |
| 1133 | - xm.xmmc as item_name, | |
| 1134 | - xm.xmbh as item_number, | |
| 1135 | - | |
| 1136 | - -- 开单统计 | |
| 1137 | - COALESCE(SUM(CASE WHEN px.F_IsEffective = 1 AND px.yjsj >= @startTime AND px.yjsj <= @endTime THEN px.F_ProjectNumber ELSE 0 END), 0) as billing_count, | |
| 1138 | - COALESCE(SUM(CASE WHEN px.F_IsEffective = 1 AND px.yjsj >= @startTime AND px.yjsj <= @endTime THEN CAST(px.F_ActualPrice AS DECIMAL(18,2)) ELSE 0 END), 0) as billing_amount, | |
| 1139 | - | |
| 1140 | - -- 消耗统计 | |
| 1141 | - COALESCE(SUM(CASE WHEN xh.F_IsEffective = 1 AND xhhyhk.hksj >= @startTime AND xhhyhk.hksj <= @endTime THEN xh.F_ProjectNumber ELSE 0 END), 0) as consume_count, | |
| 1142 | - COALESCE(SUM(CASE WHEN xh.F_IsEffective = 1 AND xhhyhk.hksj >= @startTime AND xhhyhk.hksj <= @endTime THEN CAST(xh.F_TotalPrice AS DECIMAL(18,2)) ELSE 0 END), 0) as consume_amount, | |
| 1143 | - | |
| 1144 | - -- 退卡统计 | |
| 1145 | - COALESCE(SUM(CASE WHEN hytkmx.F_IsEffective = 1 AND hytkmx.tksj >= @startTime AND hytkmx.tksj <= @endTime THEN hytkmx.F_ProjectNumber ELSE 0 END), 0) as refund_count, | |
| 1146 | - COALESCE(SUM(CASE WHEN hytkmx.F_IsEffective = 1 AND hytkmx.tksj >= @startTime AND hytkmx.tksj <= @endTime THEN CAST(hytkmx.tkje AS DECIMAL(18,2)) ELSE 0 END), 0) as refund_amount | |
| 1147 | - | |
| 1148 | - FROM lq_xmzl xm | |
| 1149 | - | |
| 1150 | - -- 开单品项明细 | |
| 1151 | - LEFT JOIN lq_kd_pxmx px ON xm.F_Id = px.px | |
| 1152 | - | |
| 1153 | - -- 消耗品项明细 | |
| 1154 | - LEFT JOIN lq_xh_pxmx xh ON xm.F_Id = xh.px | |
| 1155 | - LEFT JOIN lq_xh_hyhk xhhyhk ON xh.F_ConsumeInfoId = xhhyhk.F_Id | |
| 1156 | - | |
| 1157 | - -- 退卡品项明细 | |
| 1158 | - LEFT JOIN lq_hytk_mx hytkmx ON xm.F_Id = hytkmx.px | |
| 1159 | - | |
| 1160 | - WHERE xm.F_IsEffective = 1"; | |
| 1161 | - | |
| 1162 | - object parameters; | |
| 1140 | + // 构建门店过滤条件 | |
| 1141 | + string storeCondition = ""; | |
| 1163 | 1142 | if (input.StoreIds != null && input.StoreIds.Any()) |
| 1164 | 1143 | { |
| 1165 | - sql += @" AND ( | |
| 1166 | - (px.glkdbh IN (SELECT F_Id FROM lq_kd_kdjlb WHERE djmd IN @storeIds)) OR | |
| 1167 | - (xhhyhk.F_StoreId IN @storeIds) OR | |
| 1168 | - (hytkmx.F_RefundInfoId IN (SELECT F_Id FROM lq_hytk_hytk WHERE F_StoreId IN @storeIds)) | |
| 1169 | - )"; | |
| 1170 | - parameters = new { startTime, endTime, storeIds = input.StoreIds }; | |
| 1144 | + var storeIdsStr = string.Join("','", input.StoreIds); | |
| 1145 | + storeCondition = $@" | |
| 1146 | + AND ( | |
| 1147 | + kd.djmd IN ('{storeIdsStr}') OR | |
| 1148 | + xhhyhk.F_StoreId IN ('{storeIdsStr}') OR | |
| 1149 | + hytk.md IN ('{storeIdsStr}') | |
| 1150 | + )"; | |
| 1171 | 1151 | } |
| 1172 | - else | |
| 1152 | + | |
| 1153 | + // 使用最激进的优化:分步查询,避免复杂JOIN | |
| 1154 | + var itemStatisticsDict = new Dictionary<string, ItemStatisticsOutput>(); | |
| 1155 | + | |
| 1156 | + // 1. 先获取品项基础信息 | |
| 1157 | + var itemSql = @" | |
| 1158 | + SELECT F_Id, xmmc, xmbh | |
| 1159 | + FROM lq_xmzl | |
| 1160 | + WHERE F_IsEffective = 1"; | |
| 1161 | + | |
| 1162 | + var itemData = await _db.Ado.SqlQueryAsync<dynamic>(itemSql); | |
| 1163 | + | |
| 1164 | + // 初始化品项字典 | |
| 1165 | + foreach (var item in itemData) | |
| 1173 | 1166 | { |
| 1174 | - parameters = new { startTime, endTime }; | |
| 1167 | + var itemId = item.F_Id?.ToString(); | |
| 1168 | + if (!string.IsNullOrEmpty(itemId)) | |
| 1169 | + { | |
| 1170 | + itemStatisticsDict[itemId] = new ItemStatisticsOutput | |
| 1171 | + { | |
| 1172 | + ItemId = itemId, | |
| 1173 | + ItemName = item.xmmc?.ToString() ?? "未知品项", | |
| 1174 | + ItemNumber = item.xmbh?.ToString() ?? "", | |
| 1175 | + BillingCount = 0, | |
| 1176 | + BillingAmount = 0, | |
| 1177 | + ConsumeCount = 0, | |
| 1178 | + ConsumeAmount = 0, | |
| 1179 | + RefundCount = 0, | |
| 1180 | + RefundAmount = 0 | |
| 1181 | + }; | |
| 1182 | + } | |
| 1175 | 1183 | } |
| 1176 | 1184 | |
| 1177 | - sql += @" GROUP BY xm.F_Id, xm.xmmc, xm.xmbh | |
| 1178 | - HAVING (billing_count > 0 OR consume_count > 0 OR refund_count > 0) | |
| 1179 | - ORDER BY billing_amount DESC"; | |
| 1180 | - | |
| 1181 | - var results = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters); | |
| 1185 | + // 2. 开单统计 - 直接查询,避免JOIN | |
| 1186 | + var billingSql = $@" | |
| 1187 | + SELECT | |
| 1188 | + px.px as item_id, | |
| 1189 | + SUM(px.F_ProjectNumber) as billing_count, | |
| 1190 | + SUM(CAST(px.F_ActualPrice AS DECIMAL(18,2))) as billing_amount | |
| 1191 | + FROM lq_kd_pxmx px | |
| 1192 | + WHERE px.F_IsEffective = 1 | |
| 1193 | + AND px.yjsj >= '{startTime:yyyy-MM-dd HH:mm:ss}' | |
| 1194 | + AND px.yjsj <= '{endTime:yyyy-MM-dd HH:mm:ss}' | |
| 1195 | + {(input.StoreIds != null && input.StoreIds.Any() ? $"AND px.glkdbh IN (SELECT F_Id FROM lq_kd_kdjlb WHERE djmd IN ('{string.Join("','", input.StoreIds)}'))" : "")} | |
| 1196 | + GROUP BY px.px"; | |
| 1197 | + | |
| 1198 | + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql); | |
| 1199 | + | |
| 1200 | + // 合并开单数据 | |
| 1201 | + foreach (var billing in billingData) | |
| 1202 | + { | |
| 1203 | + var itemId = billing.item_id?.ToString(); | |
| 1204 | + if (!string.IsNullOrEmpty(itemId) && itemStatisticsDict.ContainsKey(itemId)) | |
| 1205 | + { | |
| 1206 | + itemStatisticsDict[itemId].BillingCount = Convert.ToInt32(billing.billing_count ?? 0); | |
| 1207 | + itemStatisticsDict[itemId].BillingAmount = Convert.ToDecimal(billing.billing_amount ?? 0); | |
| 1208 | + } | |
| 1209 | + } | |
| 1182 | 1210 | |
| 1183 | - var itemStatisticsList = new List<ItemStatisticsOutput>(); | |
| 1211 | + // 3. 消耗统计 - 直接查询,避免JOIN | |
| 1212 | + var consumeSql = $@" | |
| 1213 | + SELECT | |
| 1214 | + xh.px as item_id, | |
| 1215 | + SUM(xh.F_ProjectNumber) as consume_count, | |
| 1216 | + SUM(CAST(xh.F_TotalPrice AS DECIMAL(18,2))) as consume_amount | |
| 1217 | + FROM lq_xh_pxmx xh | |
| 1218 | + WHERE xh.F_IsEffective = 1 | |
| 1219 | + AND xh.F_ConsumeInfoId IN ( | |
| 1220 | + SELECT F_Id FROM lq_xh_hyhk | |
| 1221 | + WHERE hksj >= '{startTime:yyyy-MM-dd HH:mm:ss}' | |
| 1222 | + AND hksj <= '{endTime:yyyy-MM-dd HH:mm:ss}' | |
| 1223 | + {(input.StoreIds != null && input.StoreIds.Any() ? $"AND F_StoreId IN ('{string.Join("','", input.StoreIds)}')" : "")} | |
| 1224 | + ) | |
| 1225 | + GROUP BY xh.px"; | |
| 1226 | + | |
| 1227 | + var consumeData = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql); | |
| 1228 | + | |
| 1229 | + // 合并消耗数据 | |
| 1230 | + foreach (var consume in consumeData) | |
| 1231 | + { | |
| 1232 | + var itemId = consume.item_id?.ToString(); | |
| 1233 | + if (!string.IsNullOrEmpty(itemId) && itemStatisticsDict.ContainsKey(itemId)) | |
| 1234 | + { | |
| 1235 | + itemStatisticsDict[itemId].ConsumeCount = Convert.ToInt32(consume.consume_count ?? 0); | |
| 1236 | + itemStatisticsDict[itemId].ConsumeAmount = Convert.ToDecimal(consume.consume_amount ?? 0); | |
| 1237 | + } | |
| 1238 | + } | |
| 1184 | 1239 | |
| 1185 | - foreach (var item in results) | |
| 1240 | + // 4. 退卡统计 - 直接查询,避免JOIN | |
| 1241 | + var refundSql = $@" | |
| 1242 | + SELECT | |
| 1243 | + hytkmx.px as item_id, | |
| 1244 | + SUM(hytkmx.F_ProjectNumber) as refund_count, | |
| 1245 | + SUM(CAST(hytkmx.tkje AS DECIMAL(18,2))) as refund_amount | |
| 1246 | + FROM lq_hytk_mx hytkmx | |
| 1247 | + WHERE hytkmx.F_IsEffective = 1 | |
| 1248 | + AND hytkmx.tksj >= '{startTime:yyyy-MM-dd HH:mm:ss}' | |
| 1249 | + AND hytkmx.tksj <= '{endTime:yyyy-MM-dd HH:mm:ss}' | |
| 1250 | + {(input.StoreIds != null && input.StoreIds.Any() ? $"AND hytkmx.F_RefundInfoId IN (SELECT F_Id FROM lq_hytk_hytk WHERE md IN ('{string.Join("','", input.StoreIds)}'))" : "")} | |
| 1251 | + GROUP BY hytkmx.px"; | |
| 1252 | + | |
| 1253 | + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql); | |
| 1254 | + | |
| 1255 | + // 合并退卡数据 | |
| 1256 | + foreach (var refund in refundData) | |
| 1186 | 1257 | { |
| 1187 | - itemStatisticsList.Add(new ItemStatisticsOutput | |
| 1258 | + var itemId = refund.item_id?.ToString(); | |
| 1259 | + if (!string.IsNullOrEmpty(itemId) && itemStatisticsDict.ContainsKey(itemId)) | |
| 1188 | 1260 | { |
| 1189 | - ItemId = item.item_id?.ToString(), | |
| 1190 | - ItemName = item.item_name?.ToString(), | |
| 1191 | - ItemNumber = item.item_number?.ToString(), | |
| 1192 | - BillingCount = Convert.ToInt32(item.billing_count ?? 0), | |
| 1193 | - BillingAmount = Convert.ToDecimal(item.billing_amount ?? 0), | |
| 1194 | - ConsumeCount = Convert.ToInt32(item.consume_count ?? 0), | |
| 1195 | - ConsumeAmount = Convert.ToDecimal(item.consume_amount ?? 0), | |
| 1196 | - RefundCount = Convert.ToInt32(item.refund_count ?? 0), | |
| 1197 | - RefundAmount = Convert.ToDecimal(item.refund_amount ?? 0) | |
| 1198 | - }); | |
| 1261 | + itemStatisticsDict[itemId].RefundCount = Convert.ToInt32(refund.refund_count ?? 0); | |
| 1262 | + itemStatisticsDict[itemId].RefundAmount = Convert.ToDecimal(refund.refund_amount ?? 0); | |
| 1263 | + } | |
| 1199 | 1264 | } |
| 1200 | 1265 | |
| 1266 | + // 5. 过滤并排序 | |
| 1267 | + var itemStatisticsList = itemStatisticsDict.Values | |
| 1268 | + .Where(x => x.BillingCount > 0 || x.ConsumeCount > 0 || x.RefundCount > 0) | |
| 1269 | + .OrderByDescending(x => x.BillingAmount) | |
| 1270 | + .ToList(); | |
| 1271 | + | |
| 1201 | 1272 | return itemStatisticsList; |
| 1202 | 1273 | } |
| 1203 | 1274 | catch (Exception ex) | ... | ... |