OrderRefund.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. using System.ComponentModel.DataAnnotations;
  2. using System.ComponentModel.DataAnnotations.Schema;
  3. using Domain.Entities.Store.ValueObject;
  4. namespace Domain.Entities.Store;
  5. /// <summary>
  6. /// 통합 환불 이력 (취소/반품/교환/환불). Order N:N (한 주문에 여러 환불 가능).
  7. /// 흐름: Requested -> Approved (관리자) -> Completed (실제 처리, Wallet/재고/쿠폰 환원).
  8. /// </summary>
  9. public class OrderRefund
  10. {
  11. [ForeignKey(nameof(OrderID))]
  12. public virtual Order? Order { get; private set; }
  13. /// <summary>
  14. /// 환불 단위 — 쿠폰 별 후처리(회수/만료) 정책. OrderRefund 1건에 N개의 OrderRefundItem.
  15. /// 자동 환불(Cancel)이나 옛 환불은 비어있을 수 있음 (Amount 만 사용).
  16. /// </summary>
  17. public virtual ICollection<OrderRefundItem> Items { get; private set; } = new List<OrderRefundItem>();
  18. [Key]
  19. public int ID { get; private set; }
  20. public int OrderID { get; private set; }
  21. public RefundType Type { get; private set; }
  22. /// <summary>사유 유형. RefundType 과 직교한 표준화 카테고리. 통계·필터링에 사용.</summary>
  23. public RefundReasonType ReasonType { get; private set; } = RefundReasonType.Other;
  24. public RefundStatus Status { get; private set; } = RefundStatus.Requested;
  25. public int Amount { get; private set; }
  26. /// <summary>사용자가 직접 작성한 상세 사유. ReasonType 외 보조 설명. ReasonType=Other 인 경우 호출자가 필수 처리.</summary>
  27. public string Reason { get; private set; } = default!;
  28. public string? AdminMemo { get; private set; }
  29. /// <summary>처리 관리자 (ASP.NET Identity ApplicationUser.Id). 자동 환불(Cancel)은 null.</summary>
  30. public string? AdminUserId { get; private set; }
  31. /// <summary>처리 관리자 Email 스냅샷 (표시용). Member FK 없이 감사 표시 유지.</summary>
  32. public string? AdminEmail { get; private set; }
  33. public DateTime RequestedAt { get; private set; } = DateTime.UtcNow;
  34. public DateTime? ResolvedAt { get; private set; }
  35. private OrderRefund() { }
  36. public static OrderRefund Create(int orderID, RefundType type, RefundReasonType reasonType, int amount, string reason)
  37. {
  38. if (orderID <= 0)
  39. {
  40. throw new ArgumentOutOfRangeException(nameof(orderID));
  41. }
  42. if (amount <= 0)
  43. {
  44. throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be positive.");
  45. }
  46. if (string.IsNullOrWhiteSpace(reason))
  47. {
  48. throw new ArgumentException("Reason is required.", nameof(reason));
  49. }
  50. if (reason.Length > 500)
  51. {
  52. throw new ArgumentOutOfRangeException(nameof(reason));
  53. }
  54. return new OrderRefund
  55. {
  56. OrderID = orderID,
  57. Type = type,
  58. ReasonType = reasonType,
  59. Amount = amount,
  60. Reason = reason
  61. };
  62. }
  63. public void Approve(string adminUserId, string? adminEmail, string? adminMemo = null)
  64. {
  65. if (Status != RefundStatus.Requested)
  66. {
  67. throw new InvalidOperationException($"Cannot approve from status {Status}.");
  68. }
  69. if (string.IsNullOrWhiteSpace(adminUserId))
  70. {
  71. throw new ArgumentException("AdminUserId is required.", nameof(adminUserId));
  72. }
  73. Status = RefundStatus.Approved;
  74. AdminUserId = adminUserId;
  75. AdminEmail = adminEmail;
  76. AdminMemo = adminMemo;
  77. }
  78. public void Reject(string adminUserId, string? adminEmail, string adminMemo)
  79. {
  80. if (Status != RefundStatus.Requested)
  81. {
  82. throw new InvalidOperationException($"Cannot reject from status {Status}.");
  83. }
  84. if (string.IsNullOrWhiteSpace(adminUserId))
  85. {
  86. throw new ArgumentException("AdminUserId is required.", nameof(adminUserId));
  87. }
  88. if (string.IsNullOrWhiteSpace(adminMemo))
  89. {
  90. throw new ArgumentException("Rejection requires admin memo.", nameof(adminMemo));
  91. }
  92. Status = RefundStatus.Rejected;
  93. AdminUserId = adminUserId;
  94. AdminEmail = adminEmail;
  95. AdminMemo = adminMemo;
  96. ResolvedAt = DateTime.UtcNow;
  97. }
  98. public void Complete()
  99. {
  100. if (Status != RefundStatus.Approved)
  101. {
  102. throw new InvalidOperationException($"Cannot complete from status {Status}.");
  103. }
  104. Status = RefundStatus.Completed;
  105. ResolvedAt = DateTime.UtcNow;
  106. }
  107. }