Commit 7855bf0414f0cc3bc486b44ed0f9a2ec86a5c6f9

Authored by “wangming”
1 parent 68b8deb5

feat: 优化品项统计查询性能并新增营销活动统计功能

- 优化LqReportService.GetItemStatistics方法,采用分步查询策略避免复杂JOIN,提升查询性能至3秒内
- 新增LqPackageInfoService.GetActivityStatistics方法,支持营销活动开单和退卡统计
- 新增ActivityStatisticsInput和ActivityStatisticsOutput DTO类
- 支持按营销活动、时间范围、门店等维度进行统计分析
- 提供开单数量、开单金额、退卡数量、退卡金额、净开单数量、净开单金额、退卡率等完整统计指标
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)
... ...