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