Source code for settler.models

from django.db import models
from django.core import validators

# FIXME: Add UUID for all the models for reference in RESTful APIs.

# FIXME: This double-entry accounting module should be moved into a
# separate app called 'pacioli' that can be used by Settler or
# something else. Name coming from Luca Pacioli of Summa Arithmetica
# fame. [We actually have 'ledger' reserved for this already', and it
# makes more sense in being very understandable. Keep only 'tapestry'
# as something _weird_.]

[docs]class TransactionManager(models.Manager):
[docs] def add_transaction(self, currency, transfers): """Add a single Transaction where 'currency' is the Transaction currency, and 'transfers' is a list of data to create Transfer objects with keys 'account', 'amount' and 'description'. The 'account' is an Account.handle. The Account is created if it doesn't exist. The amounts of all Transfers must add up to a zero. Returns the created Transaction. """ # FIXME: Run in a database transaction! from decimal import Decimal transfer_sum = Decimal() # Create the Transaction to hold the Transfers tx = Transaction.objects.create( currency=currency, ) # Create the Transfers for t in transfers: account = Account.objects.get_or_create( handle=t['account'], currency=tx.currency, )[0] amount = Decimal(t['amount']) Transfer.objects.create( transaction=tx, account=account, amount=amount, description=t.get('description', ''), ) transfer_sum += amount if transfer_sum: # FIXME: Is there a more Django native exception to raise? raise ValueError("Transfers of a Transaction must sum to zero") return tx
[docs]class Transaction(models.Model): """A Transaction represents a single accounting journal entry that groups together one or more debits and credits on Accounts as represented by Transfers. """ # FIXME: We do not have validation on database level that the # Transfers of a Transaction sum to zero. currency = models.CharField( max_length=3, blank=False, validators=[ validators.MinLengthValidator(3), validators.MaxLengthValidator(3), ] ) description = models.CharField(max_length=140, blank=True) objects = TransactionManager() def __str__(self): return "{} {}".format(self.pk, self.currency) class Meta: constraints = [ models.CheckConstraint(check=models.Q(currency__regex=r'^[A-Z]{3}$'), name='transaction_currency_regex_alpha3'), ]
[docs]class Transfer(models.Model): """A Transfer is a single debit or a credit on an Account. Multiple Transfers are grouped together by a Transaction to form a single accounting journal entry. Positive amounts represent debits and negative amounts credits. """ account = models.ForeignKey("settler.Account", on_delete=models.PROTECT) transaction = models.ForeignKey("settler.Transaction", on_delete=models.PROTECT) amount = models.DecimalField(max_digits=11, decimal_places=2) description = models.CharField(max_length=140, blank=True)
[docs]class Account(models.Model): """An Account is an accounting account on which sums can be debited or credited by a Transfer. Each Account is addressable by a unique handle. """ handle = models.CharField(max_length=35, unique=True) currency = models.CharField( max_length=3, blank=False, validators=[ validators.MinLengthValidator(3), validators.MaxLengthValidator(3), ] ) description = models.CharField(max_length=140, blank=True)
[docs] def get_balance(self): from decimal import Decimal # FIXME: There must be a more elegant way to do this! return Account.objects.filter(pk=self.pk).aggregate(balance=models.Sum('transfer__amount'))['balance'] or Decimal()
def __str__(self): return "{} {} {}".format(self.pk, self.handle, self.currency) class Meta: constraints = [ models.CheckConstraint(check=models.Q(currency__regex=r'^[A-Z]{3}$'), name='accoount_currency_regex_alpha3'), ]