using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Domain.Entities.Members; namespace Domain.Entities.Store; /// /// 회원 보관함. Digital 상품 결제 완료 시 1 row INSERT. /// 사용자가 보관함에서 "사용" 누르면 UsedAt 마킹 + CouponCode.MarkUsed() 호출. /// public class MemberInventory { [ForeignKey(nameof(MemberID))] public virtual Member? Member { get; private set; } [ForeignKey(nameof(OrderItemID))] public virtual OrderItem? OrderItem { get; private set; } [ForeignKey(nameof(CouponCodeID))] public virtual CouponCode? CouponCode { get; private set; } [Key] public int ID { get; private set; } public int MemberID { get; private set; } public int OrderItemID { get; private set; } public int CouponCodeID { get; private set; } public DateTime AcquiredAt { get; private set; } = DateTime.UtcNow; /// 사용자가 "사용" 클릭한 시점 (코드 노출 후). null = 미사용. public DateTime? UsedAt { get; private set; } /// 쿠폰 만료 일시 (Coupon.ExpiresAt 스냅샷). 만료 후 보관함에서 자동 제외. public DateTime? ExpiresAt { get; private set; } /// 사용자 보관함에서 소프트 삭제된 일시. null = 보관함 노출. 값 있음 = 사용자 화면에서 숨김 (DB 행은 보존, 환불·통계에 사용). public DateTime? DeletedAt { get; private set; } private MemberInventory() { } public static MemberInventory Create(int memberID, int orderItemID, int couponCodeID, DateTime? expiresAt = null) { if (memberID <= 0) { throw new ArgumentOutOfRangeException(nameof(memberID)); } if (orderItemID <= 0) { throw new ArgumentOutOfRangeException(nameof(orderItemID)); } if (couponCodeID <= 0) { throw new ArgumentOutOfRangeException(nameof(couponCodeID)); } return new MemberInventory { MemberID = memberID, OrderItemID = orderItemID, CouponCodeID = couponCodeID, ExpiresAt = expiresAt }; } public void MarkUsed() { if (UsedAt.HasValue) { throw new InvalidOperationException("이미 사용한 쿠폰입니다."); } UsedAt = DateTime.UtcNow; } /// /// 사용자 보관함에서 소프트 삭제. DB row 는 유지되며 DeletedAt 만 마킹. /// 환불 시점에는 DeletedAt 무시하고 정상 처리 (refund handler 가 모든 inventory 를 본다). /// public void SoftDelete() { if (DeletedAt.HasValue) { throw new InvalidOperationException("이미 삭제된 보관함 항목입니다."); } DeletedAt = DateTime.UtcNow; } /// /// 관리자가 연결된 CouponCode 를 일괄 만료시킬 때 호출. ExpiresAt 을 즉시 now 로 설정해 /// 회원 보관함에서 자연 만료된 것처럼 표시되도록 한다 (기존 IsExpired UI 재사용). /// public void ForceExpire() { ExpiresAt = DateTime.UtcNow; } /// /// 관리자가 만료 처리한 CouponCode 를 복귀시킬 때 호출. ExpiresAt 을 null 로 해제 (영구 사용 가능 상태). /// public void ClearExpiry() { ExpiresAt = null; } }