from django.db import models from django.utils import timezone from django.core.exceptions import ValidationError from django_choices_field import TextChoicesField from core.models.base import BaseModel, Address, Contact from core.models.customer import Customer from core.models.enums import StatusChoices class Account(BaseModel): """Account model belonging to a customer""" customer = models.ForeignKey(Customer, on_delete=models.PROTECT, related_name='accounts') name = models.CharField(max_length=200) status = TextChoicesField(choices_enum=StatusChoices, default=StatusChoices.ACTIVE, help_text="Current status of the account") start_date = models.DateField(default=timezone.now) end_date = models.DateField(blank=True, null=True) class Meta: ordering = ['name'] verbose_name = "Account" verbose_name_plural = "Accounts" indexes = [ models.Index(fields=['customer', 'status']), models.Index(fields=['status', 'start_date']) ] constraints = [ models.UniqueConstraint( fields=['customer', 'name'], name='unique_account_name_per_customer' ) ] def __str__(self): return f"{self.name} ({self.customer.name})" @property def is_active(self): """Check if the account is currently active based on dates and status""" today = timezone.now().date() return self.status == 'ACTIVE' and self.start_date <= today and ( self.end_date is None or self.end_date >= today) @property def primary_address(self): """Get the primary address for this account""" return self.addresses.filter(is_primary=True, is_active=True).first() def clean(self): """Validate account data""" if self.end_date and self.start_date and self.end_date < self.start_date: raise ValidationError("End date cannot be earlier than start date") class AccountAddress(Address): """Physical address information for an account""" account = models.ForeignKey('Account', on_delete=models.PROTECT, related_name='addresses') name = models.CharField(max_length=200, blank=True) is_active = models.BooleanField(default=True) is_primary = models.BooleanField(default=False) notes = models.TextField(blank=True) class Meta: verbose_name = "Account Address" verbose_name_plural = "Account Addresses" indexes = [ models.Index(fields=['account', 'is_active']), ] constraints = [ models.UniqueConstraint( fields=['account'], condition=models.Q(is_primary=True, is_active=True), name='unique_primary_address_per_account' ) ] def save(self, *args, **kwargs): if self.is_active and not AccountAddress.objects.filter( account=self.account, is_active=True ).exclude(pk=self.pk).exists(): self.is_primary = True super().save(*args, **kwargs) def clean(self): """Validate address data""" if self.is_primary and not self.is_active: raise ValidationError("Primary address must be active") def __str__(self): primary_indicator = " (Primary)" if self.is_primary else "" return f"{self.account.name} - {self.street_address}{primary_indicator}" class AccountContact(Contact): """Contact information for an account""" account = models.ForeignKey('Account', on_delete=models.PROTECT, related_name='contacts') email = models.EmailField(blank=True) is_active = models.BooleanField(default=True) is_primary = models.BooleanField(default=False) notes = models.TextField(blank=True) class Meta: verbose_name = "Account Contact" verbose_name_plural = "Account Contacts" indexes = [ models.Index(fields=['account', 'is_active']), ] constraints = [ # Only one primary contact per account models.UniqueConstraint( fields=['account'], condition=models.Q(is_primary=True, is_active=True), name='unique_primary_contact_per_account' ), # Prevent duplicate phone numbers for the same account models.UniqueConstraint( fields=['account', 'phone'], condition=models.Q(is_active=True, phone__isnull=False) & ~models.Q(phone=''), name='unique_phone_per_account' ), # Prevent duplicate emails for the same account (when email provided) models.UniqueConstraint( fields=['account', 'email'], condition=models.Q(is_active=True, email__isnull=False) & ~models.Q(email=''), name='unique_email_per_account' ) ] def save(self, *args, **kwargs): # Auto-set first active contact as primary if self.is_active and not AccountContact.objects.filter( account=self.account, is_active=True ).exclude(pk=self.pk).exists(): self.is_primary = True super().save(*args, **kwargs) def clean(self): """Validate contact data""" # Ensure primary contacts are active if self.is_primary and not self.is_active: raise ValidationError("Primary contact must be active") # Ensure we have at least phone or email if not self.phone and not self.email: raise ValidationError("Contact must have either phone number or email address") def __str__(self): primary_indicator = " (Primary)" if self.is_primary else "" return f"{self.full_name} - {self.account.name}{primary_indicator}"