using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Domain.Entities.Store.ValueObject; namespace Domain.Entities.Store; /// /// 쿠폰 메타. 실제 코드 풀은 CouponCode 1:N 으로 관리. /// ProductID 필수 (어떤 Digital 상품에 발급할 쿠폰인지), GameID 필수 (사용자 정책: 게임 필수 연결). /// public class Coupon { [ForeignKey(nameof(ProductID))] public virtual Product? Product { get; private set; } [ForeignKey(nameof(GameID))] public virtual Game? Game { get; private set; } [Key] public int ID { get; private set; } public int ProductID { get; private set; } public int GameID { get; private set; } public string Name { get; private set; } = default!; public DateTime? ExpiresAt { get; private set; } public CouponUsagePolicy UsagePolicy { get; private set; } = CouponUsagePolicy.SingleUse; /// 누적 발급 가능 코드 수 (CouponCode 풀의 총 row 수). 통계용. public int GeneratedCount { get; private set; } /// 실제 사용된 코드 수 (Status=Used). 통계용. public int UsedCount { get; private set; } /// 잔여 코드 부족 알림 임계치 (Available 코드가 이 값 미만이면 Admin 알림). public int LowStockThreshold { get; private set; } = 10; public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; private set; } private Coupon() { } public static Coupon Create( int productID, int gameID, string name, CouponUsagePolicy usagePolicy = CouponUsagePolicy.SingleUse, DateTime? expiresAt = null, int lowStockThreshold = 10 ) { if (productID <= 0) { throw new ArgumentOutOfRangeException(nameof(productID), "Product is required."); } if (gameID <= 0) { throw new ArgumentOutOfRangeException(nameof(gameID), "Game is required."); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Name is required.", nameof(name)); } if (name.Length > 200) { throw new ArgumentOutOfRangeException(nameof(name)); } if (lowStockThreshold < 0) { throw new ArgumentOutOfRangeException(nameof(lowStockThreshold)); } return new Coupon { ProductID = productID, GameID = gameID, Name = name, UsagePolicy = usagePolicy, ExpiresAt = expiresAt, LowStockThreshold = lowStockThreshold }; } public void Update(string name, DateTime? expiresAt, int lowStockThreshold) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Name is required.", nameof(name)); } if (lowStockThreshold < 0) { throw new ArgumentOutOfRangeException(nameof(lowStockThreshold)); } Name = name; ExpiresAt = expiresAt; LowStockThreshold = lowStockThreshold; UpdatedAt = DateTime.UtcNow; } /// BulkUpload 후 발급 가능 코드 수 누적. public void IncreaseGeneratedCount(int delta) { if (delta <= 0) { throw new ArgumentOutOfRangeException(nameof(delta)); } GeneratedCount += delta; UpdatedAt = DateTime.UtcNow; } /// 코드 1개가 Used 상태로 전이될 때 호출 (실제 사용 보고용 — 결제 발급 시점이 아니라 사용자가 사용 누른 시점). public void IncreaseUsedCount(int delta = 1) { if (delta <= 0) { throw new ArgumentOutOfRangeException(nameof(delta)); } UsedCount += delta; UpdatedAt = DateTime.UtcNow; } }