Commit 27de7fa29b3e7b54a2c760bf5ba8771e8fdfa881

Authored by “wangming”
1 parent f4739ed5

Enhance member data processing in LqKhxxService and MemberPortraitService to sup…

…port both member ID and archive number for improved data retrieval. Implement billing, consumption, and refund statistics aggregation to avoid N+1 issues, ensuring accurate financial summaries for members.
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
@@ -249,6 +249,53 @@ namespace NCC.Extend.LqKhxx @@ -249,6 +249,53 @@ namespace NCC.Extend.LqKhxx
249 .MergeTable() 249 .MergeTable()
250 .OrderBy(sidx + " " + input.sort) 250 .OrderBy(sidx + " " + input.sort)
251 .ToPagedListAsync(input.currentPage, input.pageSize); 251 .ToPagedListAsync(input.currentPage, input.pageSize);
  252 +
  253 + // 兜底:若累计字段未维护,列表会显示 0,这里按本页会员批量汇总开单/耗卡/退卡金额,避免 N+1
  254 + // 关联键:开单 lq_kd_kdjlb.kdhy / 耗卡 lq_xh_hyhk.hy / 退卡 lq_hytk_hytk.hy 均存会员 F_Id(即 LqKhxxEntity.Id)
  255 + if (data?.list != null && data.list.Any())
  256 + {
  257 + var memberIds = data.list.Select(x => x.id).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToArray();
  258 + if (memberIds.Length > 0)
  259 + {
  260 + var billingStats = await _db.Queryable<LqKdKdjlbEntity>()
  261 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  262 + .In(x => x.Kdhy, memberIds)
  263 + .GroupBy(x => x.Kdhy)
  264 + .Select(x => new { MemberId = x.Kdhy, Amount = SqlFunc.AggregateSum(x.Sfyj) })
  265 + .ToListAsync();
  266 +
  267 + var consumeStats = await _db.Queryable<LqXhHyhkEntity>()
  268 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  269 + .In(x => x.Hy, memberIds)
  270 + .GroupBy(x => x.Hy)
  271 + .Select(x => new { MemberId = x.Hy, Amount = SqlFunc.AggregateSum(x.Xfje) })
  272 + .ToListAsync();
  273 +
  274 + var refundStats = await _db.Queryable<LqHytkHytkEntity>()
  275 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  276 + .In(x => x.Hy, memberIds)
  277 + .GroupBy(x => x.Hy)
  278 + .Select(x => new { MemberId = x.Hy, Amount = SqlFunc.AggregateSum(x.ActualRefundAmount) })
  279 + .ToListAsync();
  280 +
  281 + var billingMap = billingStats.ToDictionary(x => x.MemberId, x => Convert.ToDecimal(x.Amount));
  282 + var consumeMap = consumeStats.ToDictionary(x => x.MemberId, x => Convert.ToDecimal(x.Amount));
  283 + var refundMap = refundStats.ToDictionary(x => x.MemberId, x => Convert.ToDecimal(x.Amount));
  284 +
  285 + foreach (var row in data.list)
  286 + {
  287 + var billingAmount = billingMap.TryGetValue(row.id, out var b) ? b : 0m;
  288 + var consumeAmount = consumeMap.TryGetValue(row.id, out var c) ? c : 0m;
  289 + var refundAmount = refundMap.TryGetValue(row.id, out var r) ? r : 0m;
  290 +
  291 + row.totalBillingAmount = billingAmount;
  292 + row.totalConsumeAmount = consumeAmount;
  293 +
  294 + var remaining = billingAmount - consumeAmount - refundAmount;
  295 + row.remainingRightsAmount = remaining < 0m ? 0m : remaining;
  296 + }
  297 + }
  298 + }
252 return PageResult<LqKhxxListOutput>.SqlSugarPageResult(data); 299 return PageResult<LqKhxxListOutput>.SqlSugarPageResult(data);
253 } 300 }
254 #endregion 301 #endregion
netcore/src/Modularity/Extend/NCC.Extend/MemberPortraitService.cs
@@ -65,7 +65,7 @@ namespace NCC.Extend @@ -65,7 +65,7 @@ namespace NCC.Extend
65 /// ``` 65 /// ```
66 /// 66 ///
67 /// 参数说明: 67 /// 参数说明:
68 - /// - memberId: 会员主键ID(lq_khxx.F_Id 68 + /// - memberId: 会员标识(支持会员主键ID:lq_khxx.F_Id,或会员编号/档案号:lq_khxx.Dah
69 /// </remarks> 69 /// </remarks>
70 /// <param name="memberId">会员ID</param> 70 /// <param name="memberId">会员ID</param>
71 /// <returns>会员画像概览数据</returns> 71 /// <returns>会员画像概览数据</returns>
@@ -84,13 +84,19 @@ namespace NCC.Extend @@ -84,13 +84,19 @@ namespace NCC.Extend
84 { 84 {
85 // 基础信息 85 // 基础信息
86 var member = await _db.Queryable<LqKhxxEntity>() 86 var member = await _db.Queryable<LqKhxxEntity>()
87 - .FirstAsync(x => x.Id == memberId && x.IsEffective == StatusEnum.有效.GetHashCode()); 87 + .FirstAsync(x => (x.Id == memberId || x.Dah == memberId) && x.IsEffective == StatusEnum.有效.GetHashCode());
88 88
89 if (member == null) 89 if (member == null)
90 { 90 {
91 throw NCCException.Oh($"会员不存在或已失效, memberId={memberId}"); 91 throw NCCException.Oh($"会员不存在或已失效, memberId={memberId}");
92 } 92 }
93 93
  94 + // 兼容:部分业务表可能存会员主键Id,部分可能存会员档案号Dah
  95 + var memberKeys = new List<string> { member.Id, member.Dah }
  96 + .Where(x => !string.IsNullOrEmpty(x))
  97 + .Distinct()
  98 + .ToList();
  99 +
94 // 查询门店名称 100 // 查询门店名称
95 string storeName = null; 101 string storeName = null;
96 if (!string.IsNullOrEmpty(member.Gsmd)) 102 if (!string.IsNullOrEmpty(member.Gsmd))
@@ -233,24 +239,24 @@ namespace NCC.Extend @@ -233,24 +239,24 @@ namespace NCC.Extend
233 // 退卡总金额(如实体中未维护,则从退卡表统计) 239 // 退卡总金额(如实体中未维护,则从退卡表统计)
234 var refundTotal = await _db.Queryable<LqHytkHytkEntity>() 240 var refundTotal = await _db.Queryable<LqHytkHytkEntity>()
235 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) 241 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
236 - .Where(x => x.Hy == member.Id) 242 + .Where(x => memberKeys.Contains(x.Hy))
237 .SumAsync(x => (decimal?)x.Tkje) ?? 0m; 243 .SumAsync(x => (decimal?)x.Tkje) ?? 0m;
238 244
239 behavior.TotalRefundAmount = refundTotal; 245 behavior.TotalRefundAmount = refundTotal;
240 246
241 // 开单统计 247 // 开单统计
242 behavior.BillingCount = await _db.Queryable<LqKdKdjlbEntity>() 248 behavior.BillingCount = await _db.Queryable<LqKdKdjlbEntity>()
243 - .Where(x => x.IsEffective == 1 && x.Kdhy == member.Id) 249 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Kdhy))
244 .CountAsync(); 250 .CountAsync();
245 251
246 if (behavior.BillingCount > 0) 252 if (behavior.BillingCount > 0)
247 { 253 {
248 behavior.FirstBillingTime = await _db.Queryable<LqKdKdjlbEntity>() 254 behavior.FirstBillingTime = await _db.Queryable<LqKdKdjlbEntity>()
249 - .Where(x => x.IsEffective == 1 && x.Kdhy == member.Id) 255 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Kdhy))
250 .MinAsync(x => x.Kdrq); 256 .MinAsync(x => x.Kdrq);
251 257
252 behavior.LastBillingTime = await _db.Queryable<LqKdKdjlbEntity>() 258 behavior.LastBillingTime = await _db.Queryable<LqKdKdjlbEntity>()
253 - .Where(x => x.IsEffective == 1 && x.Kdhy == member.Id) 259 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Kdhy))
254 .MaxAsync(x => x.Kdrq); 260 .MaxAsync(x => x.Kdrq);
255 261
256 behavior.AvgBillingAmount = behavior.TotalBillingAmount > 0 && behavior.BillingCount > 0 262 behavior.AvgBillingAmount = behavior.TotalBillingAmount > 0 && behavior.BillingCount > 0
@@ -266,17 +272,17 @@ namespace NCC.Extend @@ -266,17 +272,17 @@ namespace NCC.Extend
266 272
267 // 消耗统计 273 // 消耗统计
268 behavior.ConsumeCount = await _db.Queryable<LqXhHyhkEntity>() 274 behavior.ConsumeCount = await _db.Queryable<LqXhHyhkEntity>()
269 - .Where(x => x.IsEffective == 1 && x.Hy == member.Id) 275 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Hy))
270 .CountAsync(); 276 .CountAsync();
271 277
272 if (behavior.ConsumeCount > 0) 278 if (behavior.ConsumeCount > 0)
273 { 279 {
274 behavior.FirstConsumeTime = await _db.Queryable<LqXhHyhkEntity>() 280 behavior.FirstConsumeTime = await _db.Queryable<LqXhHyhkEntity>()
275 - .Where(x => x.IsEffective == 1 && x.Hy == member.Id) 281 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Hy))
276 .MinAsync(x => x.Hksj); 282 .MinAsync(x => x.Hksj);
277 283
278 behavior.LastConsumeTime = await _db.Queryable<LqXhHyhkEntity>() 284 behavior.LastConsumeTime = await _db.Queryable<LqXhHyhkEntity>()
279 - .Where(x => x.IsEffective == 1 && x.Hy == member.Id) 285 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Hy))
280 .MaxAsync(x => x.Hksj); 286 .MaxAsync(x => x.Hksj);
281 287
282 behavior.AvgConsumeAmount = behavior.TotalConsumeAmount > 0 && behavior.ConsumeCount > 0 288 behavior.AvgConsumeAmount = behavior.TotalConsumeAmount > 0 && behavior.ConsumeCount > 0
@@ -293,7 +299,7 @@ namespace NCC.Extend @@ -293,7 +299,7 @@ namespace NCC.Extend
293 // 退卡次数 299 // 退卡次数
294 behavior.RefundCount = await _db.Queryable<LqHytkHytkEntity>() 300 behavior.RefundCount = await _db.Queryable<LqHytkHytkEntity>()
295 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) 301 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
296 - .Where(x => x.Hy == member.Id) 302 + .Where(x => memberKeys.Contains(x.Hy))
297 .CountAsync(); 303 .CountAsync();
298 304
299 // 近12个月趋势(以当前月份往前推12个月) 305 // 近12个月趋势(以当前月份往前推12个月)
@@ -310,11 +316,11 @@ namespace NCC.Extend @@ -310,11 +316,11 @@ namespace NCC.Extend
310 WHERE kd.F_IsEffective = 1 316 WHERE kd.F_IsEffective = 1
311 AND kd.kdrq >= @trendStart 317 AND kd.kdrq >= @trendStart
312 AND kd.kdrq <= @trendEnd 318 AND kd.kdrq <= @trendEnd
313 - AND kd.kdhy = @memberId 319 + AND kd.kdhy IN (@memberId1, @memberId2)
314 GROUP BY DATE_FORMAT(kd.kdrq, '%Y-%m')"; 320 GROUP BY DATE_FORMAT(kd.kdrq, '%Y-%m')";
315 321
316 var billingTrend = await _db.Ado.SqlQueryAsync<dynamic>(billingTrendSql, 322 var billingTrend = await _db.Ado.SqlQueryAsync<dynamic>(billingTrendSql,
317 - new { trendStart, trendEnd, memberId }); 323 + new { trendStart, trendEnd, memberId1 = member.Id, memberId2 = member.Dah });
318 324
319 // 耗卡趋势 325 // 耗卡趋势
320 var consumeTrendSql = $@" 326 var consumeTrendSql = $@"
@@ -325,11 +331,11 @@ namespace NCC.Extend @@ -325,11 +331,11 @@ namespace NCC.Extend
325 WHERE xh.F_IsEffective = 1 331 WHERE xh.F_IsEffective = 1
326 AND xh.hksj >= @trendStart 332 AND xh.hksj >= @trendStart
327 AND xh.hksj <= @trendEnd 333 AND xh.hksj <= @trendEnd
328 - AND xh.hy = @memberId 334 + AND xh.hy IN (@memberId1, @memberId2)
329 GROUP BY DATE_FORMAT(xh.hksj, '%Y-%m')"; 335 GROUP BY DATE_FORMAT(xh.hksj, '%Y-%m')";
330 336
331 var consumeTrend = await _db.Ado.SqlQueryAsync<dynamic>(consumeTrendSql, 337 var consumeTrend = await _db.Ado.SqlQueryAsync<dynamic>(consumeTrendSql,
332 - new { trendStart, trendEnd, memberId }); 338 + new { trendStart, trendEnd, memberId1 = member.Id, memberId2 = member.Dah });
333 339
334 // 退卡趋势 340 // 退卡趋势
335 var refundTrendSql = $@" 341 var refundTrendSql = $@"
@@ -340,11 +346,11 @@ namespace NCC.Extend @@ -340,11 +346,11 @@ namespace NCC.Extend
340 WHERE hytk.F_IsEffective = 1 346 WHERE hytk.F_IsEffective = 1
341 AND hytk.tksj >= @trendStart 347 AND hytk.tksj >= @trendStart
342 AND hytk.tksj <= @trendEnd 348 AND hytk.tksj <= @trendEnd
343 - AND hytk.hy = @memberId 349 + AND hytk.hy IN (@memberId1, @memberId2)
344 GROUP BY DATE_FORMAT(hytk.tksj, '%Y-%m')"; 350 GROUP BY DATE_FORMAT(hytk.tksj, '%Y-%m')";
345 351
346 var refundTrend = await _db.Ado.SqlQueryAsync<dynamic>(refundTrendSql, 352 var refundTrend = await _db.Ado.SqlQueryAsync<dynamic>(refundTrendSql,
347 - new { trendStart, trendEnd, memberId }); 353 + new { trendStart, trendEnd, memberId1 = member.Id, memberId2 = member.Dah });
348 354
349 // 组合趋势数据,补全没有数据的月份 355 // 组合趋势数据,补全没有数据的月份
350 var trendDict = new Dictionary<string, MemberMonthlyTrendPoint>(); 356 var trendDict = new Dictionary<string, MemberMonthlyTrendPoint>();
@@ -389,7 +395,7 @@ namespace NCC.Extend @@ -389,7 +395,7 @@ namespace NCC.Extend
389 395
390 // 查询权益明细 396 // 查询权益明细
391 var baseItems = await _db.Queryable<LqKdPxmxEntity>() 397 var baseItems = await _db.Queryable<LqKdPxmxEntity>()
392 - .Where(x => x.MemberId == memberId) 398 + .Where(x => memberKeys.Contains(x.MemberId))
393 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) 399 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
394 .Select(x => new 400 .Select(x => new
395 { 401 {
@@ -484,7 +490,7 @@ namespace NCC.Extend @@ -484,7 +490,7 @@ namespace NCC.Extend
484 490
485 // 品项类型偏好(按品项类型统计消费金额和次数) 491 // 品项类型偏好(按品项类型统计消费金额和次数)
486 var itemTypeStats = await _db.Queryable<LqXhPxmxEntity>() 492 var itemTypeStats = await _db.Queryable<LqXhPxmxEntity>()
487 - .Where(x => x.MemberId == memberId) 493 + .Where(x => memberKeys.Contains(x.MemberId))
488 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) 494 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
489 .GroupBy(x => x.SourceType) 495 .GroupBy(x => x.SourceType)
490 .Select(x => new 496 .Select(x => new
@@ -506,7 +512,7 @@ namespace NCC.Extend @@ -506,7 +512,7 @@ namespace NCC.Extend
506 512
507 // 门店偏好(按门店统计消费金额和次数) 513 // 门店偏好(按门店统计消费金额和次数)
508 var storeStats = await _db.Queryable<LqXhHyhkEntity>() 514 var storeStats = await _db.Queryable<LqXhHyhkEntity>()
509 - .Where(x => x.Hy == memberId) 515 + .Where(x => memberKeys.Contains(x.Hy))
510 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) 516 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
511 .GroupBy(x => x.Md) 517 .GroupBy(x => x.Md)
512 .Select(x => new 518 .Select(x => new
@@ -517,6 +523,22 @@ namespace NCC.Extend @@ -517,6 +523,22 @@ namespace NCC.Extend
517 }) 523 })
518 .ToListAsync(); 524 .ToListAsync();
519 525
  526 + // 兜底:累计字段为0时,按明细实时汇总(口径与次数统计一致)
  527 + if (behavior.TotalBillingAmount <= 0m)
  528 + {
  529 + behavior.TotalBillingAmount = await _db.Queryable<LqKdKdjlbEntity>()
  530 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Kdhy))
  531 + .SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
  532 + }
  533 + if (behavior.TotalConsumeAmount <= 0m)
  534 + {
  535 + behavior.TotalConsumeAmount = await _db.Queryable<LqXhHyhkEntity>()
  536 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && memberKeys.Contains(x.Hy))
  537 + .SumAsync(x => (decimal?)x.Xfje) ?? 0m;
  538 + }
  539 + // 剩余权益金额优先按权益明细计算(更可靠)
  540 + behavior.RemainingRightsAmount = remainingItems.Sum(x => x.RemainingValue);
  541 +
520 // 查询门店名称 542 // 查询门店名称
521 var storeIds = storeStats.Select(x => x.StoreId).ToList(); 543 var storeIds = storeStats.Select(x => x.StoreId).ToList();
522 var storeNames = await _db.Queryable<LqMdxxEntity>() 544 var storeNames = await _db.Queryable<LqMdxxEntity>()