Handler.cs 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Store.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Features.Api.Store.Orders.List;
  6. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  7. {
  8. public async Task<Response> Handle(Query request, CancellationToken ct)
  9. {
  10. var query = db.Order.AsNoTracking()
  11. .Include(o => o.Channel)
  12. .Include(o => o.Items)
  13. .ThenInclude(i => i.Product)
  14. .Where(o => o.MemberID == request.MemberID);
  15. if (request.Status.HasValue)
  16. {
  17. query = query.Where(o => o.Status == request.Status.Value);
  18. }
  19. else
  20. {
  21. // Pending(미결제) 은 사용자 뷰에서 제외
  22. query = query.Where(o => o.Status != OrderStatus.Pending);
  23. }
  24. // 기간 필터: Year 명시 → 해당 연도 / 미명시 → 최근 6개월 (CreatedAt UTC 기준)
  25. if (request.Year.HasValue)
  26. {
  27. var start = new DateTime(request.Year.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  28. var end = start.AddYears(1);
  29. query = query.Where(o => o.CreatedAt >= start && o.CreatedAt < end);
  30. }
  31. else
  32. {
  33. var sixMonthsAgo = DateTime.UtcNow.AddMonths(-6);
  34. query = query.Where(o => o.CreatedAt >= sixMonthsAgo);
  35. }
  36. // 상품명 검색 — 주문 라인 중 1개라도 매칭되면 주문 노출
  37. if (!string.IsNullOrWhiteSpace(request.Keyword))
  38. {
  39. var kw = request.Keyword.Trim();
  40. query = query.Where(o => o.Items.Any(i => i.Product!.Name.Contains(kw)));
  41. }
  42. var total = await query.CountAsync(ct);
  43. var list = await query
  44. .OrderByDescending(o => o.ID)
  45. .Skip((request.Page - 1) * request.PerPage)
  46. .Take(request.PerPage)
  47. .Select(o => new
  48. {
  49. o.ID,
  50. o.OrderNumber,
  51. o.TotalAmount,
  52. o.Status,
  53. o.ChannelID,
  54. ChannelName = o.Channel != null ? o.Channel.Name : null,
  55. HasPhysical = o.Items.Any(i => i.Product!.Type == ProductType.Physical),
  56. HasDigital = o.Items.Any(i => i.Product!.Type == ProductType.Digital),
  57. o.CreatedAt,
  58. o.PaidAt,
  59. ItemCount = o.Items.Count,
  60. FirstItemName = o.Items.OrderBy(i => i.ID).Select(i => i.Product!.Name).FirstOrDefault() ?? "",
  61. FirstItemThumbnail = o.Items.OrderBy(i => i.ID).Select(i => i.Product!.Thumbnail).FirstOrDefault(),
  62. ShipmentStatus = db.Shipment.Where(s => s.OrderID == o.ID).Select(s => (ShipmentStatus?)s.Status).FirstOrDefault(),
  63. HasActiveRefundRequest = db.OrderRefund.Any(r => r.OrderID == o.ID && r.Status == RefundStatus.Requested)
  64. })
  65. .ToListAsync(ct);
  66. return new Response(
  67. total,
  68. [.. list.Select(c => new Response.Row(
  69. c.ID,
  70. c.OrderNumber,
  71. c.TotalAmount,
  72. c.Status,
  73. c.ChannelID,
  74. c.ChannelName,
  75. c.HasPhysical,
  76. c.HasDigital,
  77. c.CreatedAt,
  78. c.PaidAt,
  79. c.ItemCount,
  80. c.FirstItemName,
  81. c.FirstItemThumbnail,
  82. c.ShipmentStatus,
  83. c.HasActiveRefundRequest
  84. ))]
  85. );
  86. }
  87. }