You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

76 lines
5.1 KiB

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")]