Commit 27de7fa29b3e7b54a2c760bf5ba8771e8fdfa881
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.
Showing
2 changed files
with
88 additions
and
19 deletions
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
| ... | ... | @@ -249,6 +249,53 @@ namespace NCC.Extend.LqKhxx |
| 249 | 249 | .MergeTable() |
| 250 | 250 | .OrderBy(sidx + " " + input.sort) |
| 251 | 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 | 299 | return PageResult<LqKhxxListOutput>.SqlSugarPageResult(data); |
| 253 | 300 | } |
| 254 | 301 | #endregion | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/MemberPortraitService.cs
| ... | ... | @@ -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 | 69 | /// </remarks> |
| 70 | 70 | /// <param name="memberId">会员ID</param> |
| 71 | 71 | /// <returns>会员画像概览数据</returns> |
| ... | ... | @@ -84,13 +84,19 @@ namespace NCC.Extend |
| 84 | 84 | { |
| 85 | 85 | // 基础信息 |
| 86 | 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 | 89 | if (member == null) |
| 90 | 90 | { |
| 91 | 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 | 101 | string storeName = null; |
| 96 | 102 | if (!string.IsNullOrEmpty(member.Gsmd)) |
| ... | ... | @@ -233,24 +239,24 @@ namespace NCC.Extend |
| 233 | 239 | // 退卡总金额(如实体中未维护,则从退卡表统计) |
| 234 | 240 | var refundTotal = await _db.Queryable<LqHytkHytkEntity>() |
| 235 | 241 | .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 236 | - .Where(x => x.Hy == member.Id) | |
| 242 | + .Where(x => memberKeys.Contains(x.Hy)) | |
| 237 | 243 | .SumAsync(x => (decimal?)x.Tkje) ?? 0m; |
| 238 | 244 | |
| 239 | 245 | behavior.TotalRefundAmount = refundTotal; |
| 240 | 246 | |
| 241 | 247 | // 开单统计 |
| 242 | 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 | 250 | .CountAsync(); |
| 245 | 251 | |
| 246 | 252 | if (behavior.BillingCount > 0) |
| 247 | 253 | { |
| 248 | 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 | 256 | .MinAsync(x => x.Kdrq); |
| 251 | 257 | |
| 252 | 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 | 260 | .MaxAsync(x => x.Kdrq); |
| 255 | 261 | |
| 256 | 262 | behavior.AvgBillingAmount = behavior.TotalBillingAmount > 0 && behavior.BillingCount > 0 |
| ... | ... | @@ -266,17 +272,17 @@ namespace NCC.Extend |
| 266 | 272 | |
| 267 | 273 | // 消耗统计 |
| 268 | 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 | 276 | .CountAsync(); |
| 271 | 277 | |
| 272 | 278 | if (behavior.ConsumeCount > 0) |
| 273 | 279 | { |
| 274 | 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 | 282 | .MinAsync(x => x.Hksj); |
| 277 | 283 | |
| 278 | 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 | 286 | .MaxAsync(x => x.Hksj); |
| 281 | 287 | |
| 282 | 288 | behavior.AvgConsumeAmount = behavior.TotalConsumeAmount > 0 && behavior.ConsumeCount > 0 |
| ... | ... | @@ -293,7 +299,7 @@ namespace NCC.Extend |
| 293 | 299 | // 退卡次数 |
| 294 | 300 | behavior.RefundCount = await _db.Queryable<LqHytkHytkEntity>() |
| 295 | 301 | .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 296 | - .Where(x => x.Hy == member.Id) | |
| 302 | + .Where(x => memberKeys.Contains(x.Hy)) | |
| 297 | 303 | .CountAsync(); |
| 298 | 304 | |
| 299 | 305 | // 近12个月趋势(以当前月份往前推12个月) |
| ... | ... | @@ -310,11 +316,11 @@ namespace NCC.Extend |
| 310 | 316 | WHERE kd.F_IsEffective = 1 |
| 311 | 317 | AND kd.kdrq >= @trendStart |
| 312 | 318 | AND kd.kdrq <= @trendEnd |
| 313 | - AND kd.kdhy = @memberId | |
| 319 | + AND kd.kdhy IN (@memberId1, @memberId2) | |
| 314 | 320 | GROUP BY DATE_FORMAT(kd.kdrq, '%Y-%m')"; |
| 315 | 321 | |
| 316 | 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 | 326 | var consumeTrendSql = $@" |
| ... | ... | @@ -325,11 +331,11 @@ namespace NCC.Extend |
| 325 | 331 | WHERE xh.F_IsEffective = 1 |
| 326 | 332 | AND xh.hksj >= @trendStart |
| 327 | 333 | AND xh.hksj <= @trendEnd |
| 328 | - AND xh.hy = @memberId | |
| 334 | + AND xh.hy IN (@memberId1, @memberId2) | |
| 329 | 335 | GROUP BY DATE_FORMAT(xh.hksj, '%Y-%m')"; |
| 330 | 336 | |
| 331 | 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 | 341 | var refundTrendSql = $@" |
| ... | ... | @@ -340,11 +346,11 @@ namespace NCC.Extend |
| 340 | 346 | WHERE hytk.F_IsEffective = 1 |
| 341 | 347 | AND hytk.tksj >= @trendStart |
| 342 | 348 | AND hytk.tksj <= @trendEnd |
| 343 | - AND hytk.hy = @memberId | |
| 349 | + AND hytk.hy IN (@memberId1, @memberId2) | |
| 344 | 350 | GROUP BY DATE_FORMAT(hytk.tksj, '%Y-%m')"; |
| 345 | 351 | |
| 346 | 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 | 356 | var trendDict = new Dictionary<string, MemberMonthlyTrendPoint>(); |
| ... | ... | @@ -389,7 +395,7 @@ namespace NCC.Extend |
| 389 | 395 | |
| 390 | 396 | // 查询权益明细 |
| 391 | 397 | var baseItems = await _db.Queryable<LqKdPxmxEntity>() |
| 392 | - .Where(x => x.MemberId == memberId) | |
| 398 | + .Where(x => memberKeys.Contains(x.MemberId)) | |
| 393 | 399 | .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 394 | 400 | .Select(x => new |
| 395 | 401 | { |
| ... | ... | @@ -484,7 +490,7 @@ namespace NCC.Extend |
| 484 | 490 | |
| 485 | 491 | // 品项类型偏好(按品项类型统计消费金额和次数) |
| 486 | 492 | var itemTypeStats = await _db.Queryable<LqXhPxmxEntity>() |
| 487 | - .Where(x => x.MemberId == memberId) | |
| 493 | + .Where(x => memberKeys.Contains(x.MemberId)) | |
| 488 | 494 | .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 489 | 495 | .GroupBy(x => x.SourceType) |
| 490 | 496 | .Select(x => new |
| ... | ... | @@ -506,7 +512,7 @@ namespace NCC.Extend |
| 506 | 512 | |
| 507 | 513 | // 门店偏好(按门店统计消费金额和次数) |
| 508 | 514 | var storeStats = await _db.Queryable<LqXhHyhkEntity>() |
| 509 | - .Where(x => x.Hy == memberId) | |
| 515 | + .Where(x => memberKeys.Contains(x.Hy)) | |
| 510 | 516 | .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 511 | 517 | .GroupBy(x => x.Md) |
| 512 | 518 | .Select(x => new |
| ... | ... | @@ -517,6 +523,22 @@ namespace NCC.Extend |
| 517 | 523 | }) |
| 518 | 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 | 543 | var storeIds = storeStats.Select(x => x.StoreId).ToList(); |
| 522 | 544 | var storeNames = await _db.Queryable<LqMdxxEntity>() | ... | ... |