Basic handling of multiple commodities

This commit is contained in:
RunasSudo 2022-12-24 19:45:34 +11:00
parent f76e0d3dcb
commit 5d13e5cbef
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 51 additions and 16 deletions

View File

@ -20,7 +20,7 @@ from .. import AMOUNT_DPS
from ..database import db_session
from ..models import TrialBalancer
from ..webapp import all_transactions, app
from .models import BalanceAssertion, GeneralJournalPosting, GeneralJournalTransaction
from .models import Amount, BalanceAssertion, GeneralJournalPosting, GeneralJournalTransaction
from datetime import datetime
@ -40,15 +40,15 @@ def general_journal_new():
postings=[]
)
for account, sign, amount in zip(request.form.getlist('account'), request.form.getlist('sign'), request.form.getlist('amount')):
quantity = round(float(amount) * (10**AMOUNT_DPS))
for account, sign, amount_str in zip(request.form.getlist('account'), request.form.getlist('sign'), request.form.getlist('amount')):
amount = Amount.parse(amount_str)
if sign == 'cr':
quantity = -quantity
amount = -amount
posting = GeneralJournalPosting(
account=account,
quantity=quantity,
commodity='$' # TODO: Commodities
quantity=amount.quantity,
commodity=amount.commodity
)
transaction.postings.append(posting)

View File

@ -25,8 +25,7 @@ class Transaction:
def assert_valid(self):
"""Assert that debits equal credits, and commodities are compatible"""
if any(p.commodity != '$' for p in self.postings):
# FIXME: Allow non-$ commodities
if any(p.commodity != self.postings[0].commodity for p in self.postings[1:]):
raise AssertionError('Transaction contains multiple commodities')
if sum(p.quantity for p in self.postings) != 0:
@ -49,15 +48,51 @@ class Amount:
self.quantity = quantity
self.commodity = commodity
@classmethod
def parse(self, amount_str):
if ' ' not in amount_str:
# Default commodity
quantity = round(float(amount_str) * (10**AMOUNT_DPS))
return Amount(quantity, '$') # TODO: Customisable default commodity
quantity_str = amount_str[:amount_str.index(' ')]
quantity = round(float(quantity_str) * (10**AMOUNT_DPS))
commodity = amount_str[amount_str.index(' ')+1:]
return Amount(quantity, commodity)
def __abs__(self):
return Amount(abs(self.quantity), self.commodity)
def __neg__(self):
return Amount(-self.quantity, self.commodity)
def format(self):
return '{0}{1:,.{dps}f}'.format(self.commodity, self.quantity / (10**AMOUNT_DPS), dps=AMOUNT_DPS)
if len(self.commodity) == 1:
return '{0}{1:,.{dps}f}'.format(self.commodity, self.quantity / (10**AMOUNT_DPS), dps=AMOUNT_DPS)
else:
return '{1:,.{dps}f} {0}'.format(self.commodity, self.quantity / (10**AMOUNT_DPS), dps=AMOUNT_DPS)
def quantity_string(self):
return '{:.{dps}f}'.format(self.quantity / (10**AMOUNT_DPS), dps=AMOUNT_DPS)
def as_cost(self):
"""Convert commodity to reporting currency in cost basis"""
if self.commodity == '$':
return self
# TODO: Refactor this
if '{{' in self.commodity:
cost = float(self.commodity[self.commodity.index('{{')+2:self.commodity.index('}}')])
return Amount(round(cost * (10**AMOUNT_DPS)), '$')
elif '{' in self.commodity:
cost = float(self.commodity[self.commodity.index('{')+1:self.commodity.index('}')])
return Amount(round(cost * self.quantity), '$') # FIXME: Custom reporting currency
else:
raise Exception('No cost base for commodity {}'.format(self.commodity))
class TrialBalancer:
"""
Applies transactions to generate a trial balance
@ -70,10 +105,10 @@ class TrialBalancer:
for transaction in transactions:
for posting in transaction.postings:
if posting.account not in self.accounts:
self.accounts[posting.account] = Amount(0, '$') # FIXME: Other commodities
self.accounts[posting.account] = Amount(0, '$')
# FIXME: Handle commodities
self.accounts[posting.account].quantity += posting.quantity
# FIXME: Handle commodities better
self.accounts[posting.account].quantity += posting.amount().as_cost().quantity
def transfer_balance(self, source_account, destination_account, description=None):
"""Transfer the balance of the source account to the destination account"""

View File

@ -54,7 +54,7 @@
<td>
<div class="input-group">
<div class="input-group-text">$</div>
<input type="number" name="amount" step="0.01" value="" class="form-control">
<input type="text" name="amount" value="" class="form-control">
</div>
<td></td>
</tr>
@ -74,7 +74,7 @@
<td>
<div class="input-group">
<div class="input-group-text">$</div>
<input type="number" name="amount" step="0.01" value="" class="form-control">
<input type="text" name="amount" value="" class="form-control">
</div>
</td>
</tr>

View File

@ -37,7 +37,7 @@
{% for transaction in transactions %}
{% if transaction.postings|length == 2 %}
{% for posting in transaction.postings if posting.account == account %}
{% set _ = running_total.__setattr__('quantity', running_total.quantity + posting.quantity) %}
{% set _ = running_total.__setattr__('quantity', running_total.quantity + posting.amount().as_cost().quantity) %}
<tr>
<td>{{ transaction.dt.strftime('%Y-%m-%d') }}</td>
<td>{{ transaction.description }}</td>
@ -58,7 +58,7 @@
<td></td>
</tr>
{% for posting in transaction.postings if posting.account == account %}
{% set _ = running_total.__setattr__('quantity', running_total.quantity + posting.quantity) %}
{% set _ = running_total.__setattr__('quantity', running_total.quantity + posting.amount().as_cost().quantity) %}
<tr>
<td></td>
<td class="text-end"><i>{{ 'Dr' if posting.quantity >= 0 else 'Cr' }}</i></td>