from django.core.validators import MinValueValidator from django.db import models from django.db.models import F, Q from .base import TimestampedModel class CardStatement(TimestampedModel): account = models.ForeignKey("finance.Account", on_delete=models.PROTECT, related_name="statements") period_start = models.DateField() period_end = models.DateField() due_date = models.DateField() statement_balance = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True) class Meta: constraints = [ models.CheckConstraint(condition=Q(period_end__gte=F("period_start")), name="statement_period_order"), models.CheckConstraint(condition=Q(due_date__gte=F("period_end")), name="statement_due_order"), models.UniqueConstraint(fields=["account", "period_end"], name="statement_account_period_unique"), ] class InstallmentPlan(TimestampedModel): account = models.ForeignKey("finance.Account", on_delete=models.PROTECT, related_name="installment_plans") description = models.TextField(blank=True) total_installments = models.PositiveIntegerField(validators=[MinValueValidator(1)]) class Meta: constraints = [models.CheckConstraint(condition=Q(total_installments__gt=0), name="plan_positive_total")] class Transaction(TimestampedModel): class Kind(models.TextChoices): INCOME = "INCOME", "Income" EXPENSE = "EXPENSE", "Expense" TRANSFER = "TRANSFER", "Transfer" REFUND = "REFUND", "Refund" OPENING = "OPENING", "Opening" account = models.ForeignKey("finance.Account", on_delete=models.PROTECT, related_name="transactions") amount = models.DecimalField(max_digits=18, decimal_places=2) posting_date = models.DateField() occurrence_date = models.DateField(null=True, blank=True) description = models.TextField(blank=True) kind = models.CharField(max_length=16, choices=Kind.choices) payee = models.ForeignKey("finance.Payee", on_delete=models.PROTECT, related_name="transactions", null=True, blank=True) category = models.ForeignKey("finance.Category", on_delete=models.PROTECT, related_name="transactions", null=True, blank=True) tags = models.ManyToManyField("finance.Tag", blank=True, related_name="transactions") statement = models.ForeignKey(CardStatement, on_delete=models.PROTECT, related_name="transactions", null=True, blank=True) refund_of = models.ForeignKey("self", on_delete=models.PROTECT, related_name="refunds", null=True, blank=True) installment_plan = models.ForeignKey(InstallmentPlan, on_delete=models.PROTECT, related_name="transactions", null=True, blank=True) installment_number = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1)]) class Meta: constraints = [ models.CheckConstraint(condition=Q(kind__in=["INCOME", "EXPENSE", "TRANSFER", "REFUND", "OPENING"]), name="transaction_valid_kind"), models.CheckConstraint(condition=~Q(amount=0), name="transaction_nonzero_amount"), models.CheckConstraint(condition=(Q(kind__in=["INCOME", "REFUND"], amount__gt=0) | Q(kind="EXPENSE", amount__lt=0) | Q(kind__in=["TRANSFER", "OPENING"])), name="transaction_kind_sign"), models.UniqueConstraint(fields=["account"], condition=Q(kind="OPENING"), name="transaction_one_opening"), models.CheckConstraint(condition=(~Q(kind="OPENING") | Q(category__isnull=True, payee__isnull=True, statement__isnull=True, refund_of__isnull=True, installment_plan__isnull=True, installment_number__isnull=True)), name="transaction_opening_unassociated"), models.CheckConstraint(condition=(~Q(kind="TRANSFER") | Q(category__isnull=True, refund_of__isnull=True, installment_plan__isnull=True, installment_number__isnull=True)), name="transaction_transfer_unclassified"), models.CheckConstraint(condition=Q(refund_of__isnull=True) | Q(kind="REFUND"), name="transaction_refund_link_kind"), models.CheckConstraint(condition=Q(refund_of__isnull=True) | ~Q(refund_of=F("id")), name="transaction_refund_not_self"), models.CheckConstraint(condition=(Q(installment_plan__isnull=True, installment_number__isnull=True) | Q(installment_plan__isnull=False, installment_number__isnull=False)), name="transaction_installment_pair"), models.CheckConstraint(condition=Q(installment_number__isnull=True) | Q(installment_number__gt=0), name="transaction_installment_positive"), models.CheckConstraint(condition=Q(installment_plan__isnull=True) | Q(kind="EXPENSE"), name="transaction_installment_expense"), models.UniqueConstraint(fields=["installment_plan", "installment_number"], condition=Q(installment_plan__isnull=False), name="transaction_installment_unique"), ] class Transfer(TimestampedModel): outgoing = models.OneToOneField(Transaction, on_delete=models.PROTECT, related_name="outgoing_transfer") incoming = models.OneToOneField(Transaction, on_delete=models.PROTECT, related_name="incoming_transfer") class Meta: constraints = [models.CheckConstraint(condition=~Q(outgoing=F("incoming")), name="transfer_distinct_legs")]