using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Domain.Entities.Store.ValueObject;
namespace Domain.Entities.Store;
///
/// 통합 환불 이력 (취소/반품/교환/환불). Order N:N (한 주문에 여러 환불 가능).
/// 흐름: Requested -> Approved (관리자) -> Completed (실제 처리, Wallet/재고/쿠폰 환원).
///
public class OrderRefund
{
[ForeignKey(nameof(OrderID))]
public virtual Order? Order { get; private set; }
///
/// 환불 단위 — 쿠폰 별 후처리(회수/만료) 정책. OrderRefund 1건에 N개의 OrderRefundItem.
/// 자동 환불(Cancel)이나 옛 환불은 비어있을 수 있음 (Amount 만 사용).
///
public virtual ICollection Items { get; private set; } = new List();
[Key]
public int ID { get; private set; }
public int OrderID { get; private set; }
public RefundType Type { get; private set; }
/// 사유 유형. RefundType 과 직교한 표준화 카테고리. 통계·필터링에 사용.
public RefundReasonType ReasonType { get; private set; } = RefundReasonType.Other;
public RefundStatus Status { get; private set; } = RefundStatus.Requested;
public int Amount { get; private set; }
/// 사용자가 직접 작성한 상세 사유. ReasonType 외 보조 설명. ReasonType=Other 인 경우 호출자가 필수 처리.
public string Reason { get; private set; } = default!;
public string? AdminMemo { get; private set; }
/// 처리 관리자 (ASP.NET Identity ApplicationUser.Id). 자동 환불(Cancel)은 null.
public string? AdminUserId { get; private set; }
/// 처리 관리자 Email 스냅샷 (표시용). Member FK 없이 감사 표시 유지.
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;
}
}