| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- using System.ComponentModel.DataAnnotations;
- using System.ComponentModel.DataAnnotations.Schema;
- using Domain.Entities.Store.ValueObject;
- namespace Domain.Entities.Store;
- /// <summary>
- /// 통합 환불 이력 (취소/반품/교환/환불). Order N:N (한 주문에 여러 환불 가능).
- /// 흐름: Requested -> Approved (관리자) -> Completed (실제 처리, Wallet/재고/쿠폰 환원).
- /// </summary>
- public class OrderRefund
- {
- [ForeignKey(nameof(OrderID))]
- public virtual Order? Order { get; private set; }
- /// <summary>
- /// 환불 단위 — 쿠폰 별 후처리(회수/만료) 정책. OrderRefund 1건에 N개의 OrderRefundItem.
- /// 자동 환불(Cancel)이나 옛 환불은 비어있을 수 있음 (Amount 만 사용).
- /// </summary>
- public virtual ICollection<OrderRefundItem> Items { get; private set; } = new List<OrderRefundItem>();
- [Key]
- public int ID { get; private set; }
- public int OrderID { get; private set; }
- public RefundType Type { get; private set; }
- /// <summary>사유 유형. RefundType 과 직교한 표준화 카테고리. 통계·필터링에 사용.</summary>
- public RefundReasonType ReasonType { get; private set; } = RefundReasonType.Other;
- public RefundStatus Status { get; private set; } = RefundStatus.Requested;
- public int Amount { get; private set; }
- /// <summary>사용자가 직접 작성한 상세 사유. ReasonType 외 보조 설명. ReasonType=Other 인 경우 호출자가 필수 처리.</summary>
- public string Reason { get; private set; } = default!;
- public string? AdminMemo { get; private set; }
- /// <summary>처리 관리자 (ASP.NET Identity ApplicationUser.Id). 자동 환불(Cancel)은 null.</summary>
- public string? AdminUserId { get; private set; }
- /// <summary>처리 관리자 Email 스냅샷 (표시용). Member FK 없이 감사 표시 유지.</summary>
- public string? AdminEmail { get; private set; }
- public DateTime RequestedAt { get; private set; } = DateTime.UtcNow;
- public DateTime? ResolvedAt { get; private set; }
- private OrderRefund() { }
- public static OrderRefund Create(int orderID, RefundType type, RefundReasonType reasonType, int amount, string reason)
- {
- if (orderID <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(orderID));
- }
- if (amount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be positive.");
- }
- if (string.IsNullOrWhiteSpace(reason))
- {
- throw new ArgumentException("Reason is required.", nameof(reason));
- }
- if (reason.Length > 500)
- {
- throw new ArgumentOutOfRangeException(nameof(reason));
- }
- return new OrderRefund
- {
- OrderID = orderID,
- Type = type,
- ReasonType = reasonType,
- Amount = amount,
- Reason = reason
- };
- }
- public void Approve(string adminUserId, string? adminEmail, string? adminMemo = null)
- {
- if (Status != RefundStatus.Requested)
- {
- throw new InvalidOperationException($"Cannot approve from status {Status}.");
- }
- if (string.IsNullOrWhiteSpace(adminUserId))
- {
- throw new ArgumentException("AdminUserId is required.", nameof(adminUserId));
- }
- Status = RefundStatus.Approved;
- AdminUserId = adminUserId;
- AdminEmail = adminEmail;
- AdminMemo = adminMemo;
- }
- public void Reject(string adminUserId, string? adminEmail, string adminMemo)
- {
- if (Status != RefundStatus.Requested)
- {
- throw new InvalidOperationException($"Cannot reject from status {Status}.");
- }
- if (string.IsNullOrWhiteSpace(adminUserId))
- {
- throw new ArgumentException("AdminUserId is required.", nameof(adminUserId));
- }
- if (string.IsNullOrWhiteSpace(adminMemo))
- {
- throw new ArgumentException("Rejection requires admin memo.", nameof(adminMemo));
- }
- Status = RefundStatus.Rejected;
- AdminUserId = adminUserId;
- AdminEmail = adminEmail;
- AdminMemo = adminMemo;
- ResolvedAt = DateTime.UtcNow;
- }
- public void Complete()
- {
- if (Status != RefundStatus.Approved)
- {
- throw new InvalidOperationException($"Cannot complete from status {Status}.");
- }
- Status = RefundStatus.Completed;
- ResolvedAt = DateTime.UtcNow;
- }
- }
|