Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to ensure balanced transactions in a double-entry accounting app?

What's the best way to ensure that transactions are always balanced in double-entry accounting?

I'm creating a double-entry accounting app in Django. I have these models:

class Account(models.Model):
    TYPE_CHOICES = (
        ('asset', 'Asset'),
        ('liability', 'Liability'),
        ('equity', 'Equity'),
        ('revenue', 'Revenue'),
        ('expense', 'Expense'),
    )

    num = models.IntegerField()
    type = models.CharField(max_length=20, choices=TYPE_CHOICES, blank=False)
    description = models.CharField(max_length=1000)


class Transaction(models.Model):
    date = models.DateField()
    description = models.CharField(max_length=1000)
    notes = models.CharField(max_length=1000, blank=True)


class Entry(models.Model):
    TYPE_CHOICES = (
        ('debit', 'Debit'),
        ('credit', 'Credit'),
    )

    transaction = models.ForeignKey(Transaction, related_name='entries')
    type = models.CharField(max_length=10, choices=TYPE_CHOICES, blank=False)
    account = models.ForeignKey(Account, related_name='entries')
    amount = models.DecimalField(max_digits=11, decimal_places=2)

I'd like to enforce balanced transactions at the model level but there doesn't seem to be hooks in the right place. For example, Transaction.clean won't work because transactions get saved first, then entries are added due to the Entry.transaction ForeignKey.

I'd like balance checking to work within admin also. Currently, I use an EntryInlineFormSet with a clean method that checks balance in admin but this doesn't help when adding transactions from a script. I'm open to changing my models to make this easier.

like image 891
Ryan Nowakowski Avatar asked Oct 14 '22 20:10

Ryan Nowakowski


2 Answers

(Hi Ryan! -- Steve Traugott)

It's been a while since you posted this, so I'm sure you're way past this puzzle. For others and posterity, I have to say yes, you need to be able to split transactions, and no, you don't want to take the naive approach and assume that transaction legs will always be in pairs, because they won't. You need to be able to do N-way splits, where N is any positive integer greater than 1. Ryan has the right structure here.

What Ryan calls Entry I usually call Leg, as in transaction leg, and I'm usually working with bare Python on top of some SQL database. I haven't used Django yet, but I'd be surprised (shocked) if Django doesn't support something like the following: Rather than use the native db row ID for transaction ID, I instead usually generate a unique transaction ID from some other source, store that in both the Transaction and Leg objects, do my final check to ensure debits and credits balance, and then commit both Transaction and Legs to the db in one SQL transaction.

Ryan, is that more or less what you wound up doing?

like image 131
stevegt Avatar answered Dec 05 '22 23:12

stevegt


This may sound terribly naive, but why not just record each transaction in a single record containing "to account" and "from account" foreign keys that link to an accounts table instead of trying to create two records for each transaction? From my point of view, it seems that the essence of "double-entry" is that transactions always move money from one account to another. There is no advantage using two records to store such transactions and many disadvantages.

like image 22
Rick Jafrate Avatar answered Dec 05 '22 21:12

Rick Jafrate