""" Billing Models for Credit System """ from django.db import models from django.core.validators import MinValueValidator from django.conf import settings from igny8_core.auth.models import AccountBaseModel class CreditTransaction(AccountBaseModel): """Track all credit transactions (additions, deductions)""" TRANSACTION_TYPE_CHOICES = [ ('purchase', 'Purchase'), ('subscription', 'Subscription Renewal'), ('refund', 'Refund'), ('deduction', 'Usage Deduction'), ('adjustment', 'Manual Adjustment'), ] transaction_type = models.CharField(max_length=20, choices=TRANSACTION_TYPE_CHOICES, db_index=True) amount = models.IntegerField(help_text="Positive for additions, negative for deductions") balance_after = models.IntegerField(help_text="Credit balance after this transaction") description = models.CharField(max_length=255) metadata = models.JSONField(default=dict, help_text="Additional context (AI call details, etc.)") created_at = models.DateTimeField(auto_now_add=True) class Meta: app_label = 'billing' db_table = 'igny8_credit_transactions' ordering = ['-created_at'] indexes = [ models.Index(fields=['account', 'transaction_type']), models.Index(fields=['account', 'created_at']), ] def __str__(self): account = getattr(self, 'account', None) return f"{self.get_transaction_type_display()} - {self.amount} credits - {account.name if account else 'No Account'}" class CreditUsageLog(AccountBaseModel): """Detailed log of credit usage per AI operation""" OPERATION_TYPE_CHOICES = [ ('clustering', 'Keyword Clustering'), ('idea_generation', 'Content Ideas Generation'), ('content_generation', 'Content Generation'), ('image_generation', 'Image Generation'), ('reparse', 'Content Reparse'), ('ideas', 'Content Ideas Generation'), # Legacy ('content', 'Content Generation'), # Legacy ('images', 'Image Generation'), # Legacy ] operation_type = models.CharField(max_length=50, choices=OPERATION_TYPE_CHOICES, db_index=True) credits_used = models.IntegerField(validators=[MinValueValidator(0)]) cost_usd = models.DecimalField(max_digits=10, decimal_places=4, null=True, blank=True) model_used = models.CharField(max_length=100, blank=True) tokens_input = models.IntegerField(null=True, blank=True, validators=[MinValueValidator(0)]) tokens_output = models.IntegerField(null=True, blank=True, validators=[MinValueValidator(0)]) related_object_type = models.CharField(max_length=50, blank=True) # 'keyword', 'cluster', 'task' related_object_id = models.IntegerField(null=True, blank=True) metadata = models.JSONField(default=dict) created_at = models.DateTimeField(auto_now_add=True) class Meta: app_label = 'billing' db_table = 'igny8_credit_usage_logs' ordering = ['-created_at'] indexes = [ models.Index(fields=['account', 'operation_type']), models.Index(fields=['account', 'created_at']), models.Index(fields=['account', 'operation_type', 'created_at']), ] def __str__(self): account = getattr(self, 'account', None) return f"{self.get_operation_type_display()} - {self.credits_used} credits - {account.name if account else 'No Account'}" class CreditCostConfig(models.Model): """ Configurable credit costs per AI function Admin-editable alternative to hardcoded constants """ # Operation identification operation_type = models.CharField( max_length=50, unique=True, choices=CreditUsageLog.OPERATION_TYPE_CHOICES, help_text="AI operation type" ) # Cost configuration credits_cost = models.IntegerField( validators=[MinValueValidator(0)], help_text="Credits required for this operation" ) # Unit of measurement UNIT_CHOICES = [ ('per_request', 'Per Request'), ('per_100_words', 'Per 100 Words'), ('per_200_words', 'Per 200 Words'), ('per_item', 'Per Item'), ('per_image', 'Per Image'), ] unit = models.CharField( max_length=50, default='per_request', choices=UNIT_CHOICES, help_text="What the cost applies to" ) # Metadata display_name = models.CharField(max_length=100, help_text="Human-readable name") description = models.TextField(blank=True, help_text="What this operation does") # Status is_active = models.BooleanField(default=True, help_text="Enable/disable this operation") # Audit fields created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='credit_cost_updates', help_text="Admin who last updated" ) # Change tracking previous_cost = models.IntegerField( null=True, blank=True, help_text="Cost before last update (for audit trail)" ) class Meta: app_label = 'billing' db_table = 'igny8_credit_cost_config' verbose_name = 'Credit Cost Configuration' verbose_name_plural = 'Credit Cost Configurations' ordering = ['operation_type'] def __str__(self): return f"{self.display_name} - {self.credits_cost} credits {self.unit}" def save(self, *args, **kwargs): # Track cost changes if self.pk: try: old = CreditCostConfig.objects.get(pk=self.pk) if old.credits_cost != self.credits_cost: self.previous_cost = old.credits_cost except CreditCostConfig.DoesNotExist: pass super().save(*args, **kwargs)