MemberInventory.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. using System.ComponentModel.DataAnnotations;
  2. using System.ComponentModel.DataAnnotations.Schema;
  3. using Domain.Entities.Members;
  4. namespace Domain.Entities.Store;
  5. /// <summary>
  6. /// 회원 보관함. Digital 상품 결제 완료 시 1 row INSERT.
  7. /// 사용자가 보관함에서 "사용" 누르면 UsedAt 마킹 + CouponCode.MarkUsed() 호출.
  8. /// </summary>
  9. public class MemberInventory
  10. {
  11. [ForeignKey(nameof(MemberID))]
  12. public virtual Member? Member { get; private set; }
  13. [ForeignKey(nameof(OrderItemID))]
  14. public virtual OrderItem? OrderItem { get; private set; }
  15. [ForeignKey(nameof(CouponCodeID))]
  16. public virtual CouponCode? CouponCode { get; private set; }
  17. [Key]
  18. public int ID { get; private set; }
  19. public int MemberID { get; private set; }
  20. public int OrderItemID { get; private set; }
  21. public int CouponCodeID { get; private set; }
  22. public DateTime AcquiredAt { get; private set; } = DateTime.UtcNow;
  23. /// <summary>사용자가 "사용" 클릭한 시점 (코드 노출 후). null = 미사용.</summary>
  24. public DateTime? UsedAt { get; private set; }
  25. /// <summary>쿠폰 만료 일시 (Coupon.ExpiresAt 스냅샷). 만료 후 보관함에서 자동 제외.</summary>
  26. public DateTime? ExpiresAt { get; private set; }
  27. /// <summary>사용자 보관함에서 소프트 삭제된 일시. null = 보관함 노출. 값 있음 = 사용자 화면에서 숨김 (DB 행은 보존, 환불·통계에 사용).</summary>
  28. public DateTime? DeletedAt { get; private set; }
  29. private MemberInventory() { }
  30. public static MemberInventory Create(int memberID, int orderItemID, int couponCodeID, DateTime? expiresAt = null)
  31. {
  32. if (memberID <= 0)
  33. {
  34. throw new ArgumentOutOfRangeException(nameof(memberID));
  35. }
  36. if (orderItemID <= 0)
  37. {
  38. throw new ArgumentOutOfRangeException(nameof(orderItemID));
  39. }
  40. if (couponCodeID <= 0)
  41. {
  42. throw new ArgumentOutOfRangeException(nameof(couponCodeID));
  43. }
  44. return new MemberInventory
  45. {
  46. MemberID = memberID,
  47. OrderItemID = orderItemID,
  48. CouponCodeID = couponCodeID,
  49. ExpiresAt = expiresAt
  50. };
  51. }
  52. public void MarkUsed()
  53. {
  54. if (UsedAt.HasValue)
  55. {
  56. throw new InvalidOperationException("이미 사용한 쿠폰입니다.");
  57. }
  58. UsedAt = DateTime.UtcNow;
  59. }
  60. /// <summary>
  61. /// 사용자 보관함에서 소프트 삭제. DB row 는 유지되며 DeletedAt 만 마킹.
  62. /// 환불 시점에는 DeletedAt 무시하고 정상 처리 (refund handler 가 모든 inventory 를 본다).
  63. /// </summary>
  64. public void SoftDelete()
  65. {
  66. if (DeletedAt.HasValue)
  67. {
  68. throw new InvalidOperationException("이미 삭제된 보관함 항목입니다.");
  69. }
  70. DeletedAt = DateTime.UtcNow;
  71. }
  72. /// <summary>
  73. /// 관리자가 연결된 CouponCode 를 일괄 만료시킬 때 호출. ExpiresAt 을 즉시 now 로 설정해
  74. /// 회원 보관함에서 자연 만료된 것처럼 표시되도록 한다 (기존 IsExpired UI 재사용).
  75. /// </summary>
  76. public void ForceExpire()
  77. {
  78. ExpiresAt = DateTime.UtcNow;
  79. }
  80. /// <summary>
  81. /// 관리자가 만료 처리한 CouponCode 를 복귀시킬 때 호출. ExpiresAt 을 null 로 해제 (영구 사용 가능 상태).
  82. /// </summary>
  83. public void ClearExpiry()
  84. {
  85. ExpiresAt = null;
  86. }
  87. }