Basic handling of multiple commodities
This commit is contained in:
parent
f76e0d3dcb
commit
5d13e5cbef
@ -20,7 +20,7 @@ from .. import AMOUNT_DPS
|
|||||||
from ..database import db_session
|
from ..database import db_session
|
||||||
from ..models import TrialBalancer
|
from ..models import TrialBalancer
|
||||||
from ..webapp import all_transactions, app
|
from ..webapp import all_transactions, app
|
||||||
from .models import BalanceAssertion, GeneralJournalPosting, GeneralJournalTransaction
|
from .models import Amount, BalanceAssertion, GeneralJournalPosting, GeneralJournalTransaction
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@ -40,15 +40,15 @@ def general_journal_new():
|
|||||||
postings=[]
|
postings=[]
|
||||||
)
|
)
|
||||||
|
|
||||||
for account, sign, amount in zip(request.form.getlist('account'), request.form.getlist('sign'), request.form.getlist('amount')):
|
for account, sign, amount_str in zip(request.form.getlist('account'), request.form.getlist('sign'), request.form.getlist('amount')):
|
||||||
quantity = round(float(amount) * (10**AMOUNT_DPS))
|
amount = Amount.parse(amount_str)
|
||||||
if sign == 'cr':
|
if sign == 'cr':
|
||||||
quantity = -quantity
|
amount = -amount
|
||||||
|
|
||||||
posting = GeneralJournalPosting(
|
posting = GeneralJournalPosting(
|
||||||
account=account,
|
account=account,
|
||||||
quantity=quantity,
|
quantity=amount.quantity,
|
||||||
commodity='$' # TODO: Commodities
|
commodity=amount.commodity
|
||||||
)
|
)
|
||||||
transaction.postings.append(posting)
|
transaction.postings.append(posting)
|
||||||
|
|
||||||
|
@ -25,8 +25,7 @@ class Transaction:
|
|||||||
def assert_valid(self):
|
def assert_valid(self):
|
||||||
"""Assert that debits equal credits, and commodities are compatible"""
|
"""Assert that debits equal credits, and commodities are compatible"""
|
||||||
|
|
||||||
if any(p.commodity != '$' for p in self.postings):
|
if any(p.commodity != self.postings[0].commodity for p in self.postings[1:]):
|
||||||
# FIXME: Allow non-$ commodities
|
|
||||||
raise AssertionError('Transaction contains multiple commodities')
|
raise AssertionError('Transaction contains multiple commodities')
|
||||||
|
|
||||||
if sum(p.quantity for p in self.postings) != 0:
|
if sum(p.quantity for p in self.postings) != 0:
|
||||||
@ -49,15 +48,51 @@ class Amount:
|
|||||||
self.quantity = quantity
|
self.quantity = quantity
|
||||||
self.commodity = commodity
|
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):
|
def __abs__(self):
|
||||||
return Amount(abs(self.quantity), self.commodity)
|
return Amount(abs(self.quantity), self.commodity)
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
return Amount(-self.quantity, self.commodity)
|
||||||
|
|
||||||
def format(self):
|
def format(self):
|
||||||
|
if len(self.commodity) == 1:
|
||||||
return '{0}{1:,.{dps}f}'.format(self.commodity, self.quantity / (10**AMOUNT_DPS), dps=AMOUNT_DPS)
|
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):
|
def quantity_string(self):
|
||||||
return '{:.{dps}f}'.format(self.quantity / (10**AMOUNT_DPS), dps=AMOUNT_DPS)
|
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:
|
class TrialBalancer:
|
||||||
"""
|
"""
|
||||||
Applies transactions to generate a trial balance
|
Applies transactions to generate a trial balance
|
||||||
@ -70,10 +105,10 @@ class TrialBalancer:
|
|||||||
for transaction in transactions:
|
for transaction in transactions:
|
||||||
for posting in transaction.postings:
|
for posting in transaction.postings:
|
||||||
if posting.account not in self.accounts:
|
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
|
# FIXME: Handle commodities better
|
||||||
self.accounts[posting.account].quantity += posting.quantity
|
self.accounts[posting.account].quantity += posting.amount().as_cost().quantity
|
||||||
|
|
||||||
def transfer_balance(self, source_account, destination_account, description=None):
|
def transfer_balance(self, source_account, destination_account, description=None):
|
||||||
"""Transfer the balance of the source account to the destination account"""
|
"""Transfer the balance of the source account to the destination account"""
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-text">$</div>
|
<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>
|
</div>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -74,7 +74,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-text">$</div>
|
<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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
{% for transaction in transactions %}
|
{% for transaction in transactions %}
|
||||||
{% if transaction.postings|length == 2 %}
|
{% if transaction.postings|length == 2 %}
|
||||||
{% for posting in transaction.postings if posting.account == account %}
|
{% 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>
|
<tr>
|
||||||
<td>{{ transaction.dt.strftime('%Y-%m-%d') }}</td>
|
<td>{{ transaction.dt.strftime('%Y-%m-%d') }}</td>
|
||||||
<td>{{ transaction.description }}</td>
|
<td>{{ transaction.description }}</td>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for posting in transaction.postings if posting.account == account %}
|
{% 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>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class="text-end"><i>{{ 'Dr' if posting.quantity >= 0 else 'Cr' }}</i></td>
|
<td class="text-end"><i>{{ 'Dr' if posting.quantity >= 0 else 'Cr' }}</i></td>
|
||||||
|
Loading…
Reference in New Issue
Block a user