from django.db import models class DiscountCode(models.Model): TYPE_CHOICES = [('percent', 'درصدی'), ('fixed', 'مبلغ ثابت')] code = models.CharField(max_length=30, unique=True) type = models.CharField(max_length=10, choices=TYPE_CHOICES) value = models.PositiveIntegerField() max_uses = models.PositiveIntegerField(default=1) expires_at = models.DateTimeField(null=True, blank=True) def __str__(self): return self.code class Redemption(models.Model): discount_code = models.ForeignKey(DiscountCode, on_delete=models.CASCADE, related_name='redemptions') user_id = models.IntegerField(db_index=True) # ارجاع سبک به accounts.User order_id = models.IntegerField() # ارجاع سبک به payment.Order redeemed_at = models.DateTimeField(auto_now_add=True) class Meta: # هر کاربر برای یک کد تخفیف حداکثر یک‌بار می‌تونه redeem کنه؛ # قبلاً هیچ محدودیتی در سطح دیتابیس نبود و همراه با نبود چک max_uses # در RedeemDiscountView، امکان استفاده‌ی نامحدود از یک کد وجود داشت. constraints = [ models.UniqueConstraint( fields=['discount_code', 'user_id'], name='unique_redemption_per_user_per_code' ) ] def __str__(self): return f"redemption-{self.discount_code_id}-order{self.order_id}" class Offer(models.Model): """ آفر رایگان/تخفیف مستقل از DiscountCode: برخلاف DiscountCode که روی مبلغِ یک سفارش اثر می‌ذاره، Offer دسترسی (اشتراک) رایگان می‌ده — یا برای مدت محدود ('trial'، مثلاً یک هفته بعد از ثبت‌نام) یا کامل و نامحدود ('free_full'). طبق درخواست سینیور، اعمال این آفرها signal-based و قابل‌تغییره: کدوم آفر، با چه شرطی، روی کدوم کاربر اعمال بشه بدون دست‌زدن به accounts یا subscription قابل تغییره (فقط discount/receivers.py و discount/services.py). """ KIND_CHOICES = [ ('trial', 'دوره‌ی رایگان محدود'), ('free_full', 'دسترسی کامل رایگان'), ] name = models.CharField(max_length=100) kind = models.CharField(max_length=10, choices=KIND_CHOICES) # فقط برای kind='trial' معنی داره؛ برای free_full خالی می‌مونه (یعنی نامحدود). duration_days = models.PositiveIntegerField(null=True, blank=True) # اگه پر باشه، کاربر می‌تونه بعداً (نه فقط هنگام ثبت‌نام) با این کد آفر # رو خودش اعمال کنه؛ دقیقاً مشابه DiscountCode.code ولی برای اشتراک، # نه تخفیفِ یک سفارش. code = models.CharField(max_length=30, unique=True, null=True, blank=True) # اگه True باشه، همون لحظه‌ی signal اول (accounts.signals.user_registered) # بدون نیاز به وارد کردن کد، خودکار به کاربر تازه‌ثبت‌نام‌شده اختصاص داده می‌شه. auto_assign_on_signup = models.BooleanField(default=False) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class UserOffer(models.Model): """ثبتِ اینکه کدوم آفر به کدوم کاربر، از چه مسیری، چه زمانی اختصاص داده شده.""" SOURCE_CHOICES = [ ('signup_auto', 'خودکار هنگام ثبت‌نام'), ('code', 'وارد کردن کد'), ('manual', 'اختصاص دستی/پشتیبانی'), ] STATUS_CHOICES = [ ('active', 'فعال'), ('expired', 'منقضی'), ('revoked', 'لغوشده'), ] user_id = models.IntegerField(db_index=True) # ارجاع سبک به accounts.User offer = models.ForeignKey(Offer, on_delete=models.CASCADE, related_name='user_offers') source = models.CharField(max_length=15, choices=SOURCE_CHOICES) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='active') granted_at = models.DateTimeField(auto_now_add=True) expires_at = models.DateTimeField(null=True, blank=True) # None یعنی نامحدود (free_full) class Meta: ordering = ['-granted_at'] # هر کاربر برای یک آفرِ مشخص فقط یک‌بار می‌تونه دریافت کنه (چه # auto چه با کد)؛ جلوی گرفتن مکرر یک آفرِ trial رو می‌گیره. constraints = [ models.UniqueConstraint(fields=['offer', 'user_id'], name='unique_offer_per_user') ] def __str__(self): return f"useroffer-{self.offer_id}-user{self.user_id}-{self.status}"