Handler.cs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Store.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. using SharedKernel.Helpers;
  6. using SharedKernel.Results;
  7. namespace Application.Features.Admin.Store.Coupon.ExportCodes;
  8. /// <summary>
  9. /// CSV 양식 (DB 컬럼명 기준):
  10. /// ID, Code, Status, IssuedToMemberID, IssuedToMemberName, IssuedToMemberEmail, IssuedAt, UsedAt, CreatedAt
  11. ///
  12. /// Status 인코딩 (사용자 결정 — Option A):
  13. /// 0 = Available (미배포)
  14. /// 1 = Reserved/Issued/Used (배포된 상태로 묶음)
  15. ///
  16. /// 일시는 KST 로 출력 (UTC → Local 변환).
  17. /// </summary>
  18. public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
  19. {
  20. private static readonly string[] Header =
  21. [
  22. "ID",
  23. "Code",
  24. "Status",
  25. "IssuedToMemberID",
  26. "IssuedToMemberName",
  27. "IssuedToMemberEmail",
  28. "IssuedAt",
  29. "UsedAt",
  30. "CreatedAt"
  31. ];
  32. public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
  33. {
  34. var coupon = await db.Coupon.AsNoTracking()
  35. .Where(c => c.ID == request.CouponID)
  36. .Select(c => new { c.ID, c.Name })
  37. .FirstOrDefaultAsync(ct);
  38. if (coupon is null)
  39. {
  40. return Result.Failure<Response>(Error.NotFound("Coupon.NotFound", "쿠폰을 찾을 수 없습니다."));
  41. }
  42. var rows = await db.CouponCode.AsNoTracking()
  43. .Include(c => c.IssuedToMember)
  44. .Where(c => c.CouponID == request.CouponID)
  45. .OrderBy(c => c.ID)
  46. .Select(c => new
  47. {
  48. c.ID,
  49. c.Code,
  50. c.Status,
  51. c.IssuedToMemberID,
  52. Name = c.IssuedToMember != null ? c.IssuedToMember.Name : null,
  53. Email = c.IssuedToMember != null ? c.IssuedToMember.Email : null,
  54. c.IssuedAt,
  55. c.UsedAt,
  56. c.CreatedAt
  57. })
  58. .ToListAsync(ct);
  59. var csvRows = new List<string[]> { Header };
  60. foreach (var r in rows)
  61. {
  62. var status = r.Status == CouponCodeStatus.Available ? "0" : "1";
  63. var memberID = r.IssuedToMemberID?.ToString() ?? "";
  64. var name = r.Name ?? "";
  65. var email = r.Email ?? "";
  66. var issuedAt = r.IssuedAt.HasValue
  67. ? DateTime.SpecifyKind(r.IssuedAt.Value, DateTimeKind.Utc).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")
  68. : "";
  69. var usedAt = r.UsedAt.HasValue
  70. ? DateTime.SpecifyKind(r.UsedAt.Value, DateTimeKind.Utc).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")
  71. : "";
  72. var createdAt = DateTime.SpecifyKind(r.CreatedAt, DateTimeKind.Utc).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
  73. csvRows.Add([
  74. r.ID.ToString(),
  75. r.Code,
  76. status,
  77. memberID,
  78. name,
  79. email,
  80. issuedAt,
  81. usedAt,
  82. createdAt
  83. ]);
  84. }
  85. var bytes = CsvHelper.WriteUtf8Bom(csvRows);
  86. var fileName = $"coupon-codes-{request.CouponID}-{DateTime.Now:yyyyMMdd-HHmmss}.csv";
  87. return Result.Success(new Response(bytes, fileName));
  88. }
  89. }