Coupon.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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. /// 쿠폰 메타. 실제 코드 풀은 CouponCode 1:N 으로 관리.
  7. /// ProductID 필수 (어떤 Digital 상품에 발급할 쿠폰인지), GameID 필수 (사용자 정책: 게임 필수 연결).
  8. /// </summary>
  9. public class Coupon
  10. {
  11. [ForeignKey(nameof(ProductID))]
  12. public virtual Product? Product { get; private set; }
  13. [ForeignKey(nameof(GameID))]
  14. public virtual Game? Game { get; private set; }
  15. [Key]
  16. public int ID { get; private set; }
  17. public int ProductID { get; private set; }
  18. public int GameID { get; private set; }
  19. public string Name { get; private set; } = default!;
  20. public DateTime? ExpiresAt { get; private set; }
  21. public CouponUsagePolicy UsagePolicy { get; private set; } = CouponUsagePolicy.SingleUse;
  22. /// <summary>누적 발급 가능 코드 수 (CouponCode 풀의 총 row 수). 통계용.</summary>
  23. public int GeneratedCount { get; private set; }
  24. /// <summary>실제 사용된 코드 수 (Status=Used). 통계용.</summary>
  25. public int UsedCount { get; private set; }
  26. /// <summary>잔여 코드 부족 알림 임계치 (Available 코드가 이 값 미만이면 Admin 알림).</summary>
  27. public int LowStockThreshold { get; private set; } = 10;
  28. public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
  29. public DateTime? UpdatedAt { get; private set; }
  30. private Coupon() { }
  31. public static Coupon Create(
  32. int productID,
  33. int gameID,
  34. string name,
  35. CouponUsagePolicy usagePolicy = CouponUsagePolicy.SingleUse,
  36. DateTime? expiresAt = null,
  37. int lowStockThreshold = 10
  38. ) {
  39. if (productID <= 0)
  40. {
  41. throw new ArgumentOutOfRangeException(nameof(productID), "Product is required.");
  42. }
  43. if (gameID <= 0)
  44. {
  45. throw new ArgumentOutOfRangeException(nameof(gameID), "Game is required.");
  46. }
  47. if (string.IsNullOrWhiteSpace(name))
  48. {
  49. throw new ArgumentException("Name is required.", nameof(name));
  50. }
  51. if (name.Length > 200)
  52. {
  53. throw new ArgumentOutOfRangeException(nameof(name));
  54. }
  55. if (lowStockThreshold < 0)
  56. {
  57. throw new ArgumentOutOfRangeException(nameof(lowStockThreshold));
  58. }
  59. return new Coupon
  60. {
  61. ProductID = productID,
  62. GameID = gameID,
  63. Name = name,
  64. UsagePolicy = usagePolicy,
  65. ExpiresAt = expiresAt,
  66. LowStockThreshold = lowStockThreshold
  67. };
  68. }
  69. public void Update(string name, DateTime? expiresAt, int lowStockThreshold)
  70. {
  71. if (string.IsNullOrWhiteSpace(name))
  72. {
  73. throw new ArgumentException("Name is required.", nameof(name));
  74. }
  75. if (lowStockThreshold < 0)
  76. {
  77. throw new ArgumentOutOfRangeException(nameof(lowStockThreshold));
  78. }
  79. Name = name;
  80. ExpiresAt = expiresAt;
  81. LowStockThreshold = lowStockThreshold;
  82. UpdatedAt = DateTime.UtcNow;
  83. }
  84. /// <summary>BulkUpload 후 발급 가능 코드 수 누적.</summary>
  85. public void IncreaseGeneratedCount(int delta)
  86. {
  87. if (delta <= 0)
  88. {
  89. throw new ArgumentOutOfRangeException(nameof(delta));
  90. }
  91. GeneratedCount += delta;
  92. UpdatedAt = DateTime.UtcNow;
  93. }
  94. /// <summary>코드 1개가 Used 상태로 전이될 때 호출 (실제 사용 보고용 — 결제 발급 시점이 아니라 사용자가 사용 누른 시점).</summary>
  95. public void IncreaseUsedCount(int delta = 1)
  96. {
  97. if (delta <= 0)
  98. {
  99. throw new ArgumentOutOfRangeException(nameof(delta));
  100. }
  101. UsedCount += delta;
  102. UpdatedAt = DateTime.UtcNow;
  103. }
  104. }