Handler.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Developers.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Features.Admin.Developers.Purchases.Search;
  6. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  7. {
  8. public async Task<Response> Handle(Query request, CancellationToken ct)
  9. {
  10. var page = Math.Max(1, request.PageNum);
  11. var size = Math.Clamp(request.PerPage, 1, 100);
  12. var query = db.ApiPurchase.AsNoTracking().AsQueryable();
  13. if (request.Status.HasValue)
  14. {
  15. query = query.Where(c => c.Status == request.Status.Value);
  16. }
  17. if (!string.IsNullOrWhiteSpace(request.Keyword))
  18. {
  19. var keyword = request.Keyword.Trim();
  20. var upper = keyword.ToUpperInvariant();
  21. query = query.Where(c => c.OrderID == keyword || c.Channel!.DonationCode == upper || c.Game!.Code == keyword || c.Channel!.Name.Contains(keyword));
  22. }
  23. if (request.From.HasValue)
  24. {
  25. query = query.Where(c => c.CreatedAt >= request.From.Value);
  26. }
  27. if (request.To.HasValue)
  28. {
  29. var to = request.To.Value.Date.AddDays(1);
  30. query = query.Where(c => c.CreatedAt < to);
  31. }
  32. if (request.OnlyMismatch)
  33. {
  34. query = query.Where(c => c.PriceMismatched);
  35. }
  36. var total = await query.CountAsync(ct);
  37. // 집계 (검색 조건 범위) — 이상 패턴 검토용
  38. var summaryRaw = await query
  39. .GroupBy(c => 1)
  40. .Select(g => new
  41. {
  42. TotalOrderPrice = g.Sum(c => (long)c.OrderPrice),
  43. TotalCommission = g.Sum(c => (long)c.CommissionAmount),
  44. PendingCount = g.Count(c => c.Status == ApiPurchaseStatus.Pending),
  45. ConfirmedCount = g.Count(c => c.Status == ApiPurchaseStatus.Confirmed),
  46. CanceledCount = g.Count(c => c.Status == ApiPurchaseStatus.Canceled),
  47. MismatchCount = g.Count(c => c.PriceMismatched)
  48. })
  49. .FirstOrDefaultAsync(ct);
  50. var totalShortfall = await db.ApiPurchaseCancel.AsNoTracking()
  51. .Where(x => query.Any(c => c.ID == x.PurchaseID))
  52. .SumAsync(x => (long?)x.ShortfallAmount, ct) ?? 0;
  53. var summary = new Response.SummaryData(
  54. summaryRaw?.TotalOrderPrice ?? 0,
  55. summaryRaw?.TotalCommission ?? 0,
  56. summaryRaw?.PendingCount ?? 0,
  57. summaryRaw?.ConfirmedCount ?? 0,
  58. summaryRaw?.CanceledCount ?? 0,
  59. summaryRaw?.MismatchCount ?? 0,
  60. totalShortfall
  61. );
  62. var list = await query
  63. .OrderByDescending(c => c.ID)
  64. .Skip((page - 1) * size)
  65. .Take(size)
  66. .Select(c => new Response.Row(
  67. c.ID,
  68. c.Application!.Name,
  69. c.Channel!.Name,
  70. c.Channel!.DonationCode,
  71. c.Game!.KorName,
  72. c.OrderID,
  73. c.Marketplace,
  74. c.ProductID,
  75. c.OrderPrice,
  76. c.CommissionRate,
  77. c.CommissionAmount,
  78. c.CatalogPrice,
  79. c.PriceMismatched,
  80. c.Status,
  81. c.ConfirmDueAt,
  82. c.ConfirmedAt,
  83. c.CanceledAt,
  84. c.CreatedAt,
  85. db.ApiPurchaseCancel.Where(x => x.PurchaseID == c.ID).Select(x => (bool?)x.WasConfirmed).FirstOrDefault(),
  86. db.ApiPurchaseCancel.Where(x => x.PurchaseID == c.ID).Select(x => (int?)x.RecoveredAmount).FirstOrDefault(),
  87. db.ApiPurchaseCancel.Where(x => x.PurchaseID == c.ID).Select(x => (int?)x.ShortfallAmount).FirstOrDefault()
  88. ))
  89. .ToListAsync(ct);
  90. return new Response(total, summary, list);
  91. }
  92. }