Reimplement balance sheet and P&L

This commit is contained in:
RunasSudo 2020-03-20 22:14:05 +11:00
parent 2bc6bb0e22
commit fda9fbaa6f
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
5 changed files with 52 additions and 41 deletions

View File

@ -69,17 +69,15 @@ def trial():
trial_balances = [accounting.add_unrealized_gains(accounting.trial_balance(l, d, p), report_currency) for d, p in zip(dates, pstarts)] trial_balances = [accounting.add_unrealized_gains(accounting.trial_balance(l, d, p), report_currency) for d, p in zip(dates, pstarts)]
# Delete accounts with always zero balances # Delete accounts with always zero balances
accounts = list(trial_balances[0].ledger.accounts.values()) accounts = sorted(l.accounts.values(), key=lambda a: a.name)
for account in accounts[:]: for account in accounts[:]:
if all(t.get_balance(account) == 0 for t in trial_balances): if all(t.get_balance(account) == 0 for t in trial_balances):
accounts.remove(account) accounts.remove(account)
return flask.render_template('trial_multiple.html', trial_balances=trial_balances, accounts=sorted(accounts, key=lambda a: a.name), report_currency=report_currency) return flask.render_template('trial_multiple.html', trial_balances=trial_balances, accounts=accounts, report_currency=report_currency)
@app.route('/balance') @app.route('/balance')
def balance(): def balance():
raise Exception('NYI')
date = datetime.strptime(flask.request.args['date'], '%Y-%m-%d') date = datetime.strptime(flask.request.args['date'], '%Y-%m-%d')
pstart = datetime.strptime(flask.request.args['pstart'], '%Y-%m-%d') pstart = datetime.strptime(flask.request.args['pstart'], '%Y-%m-%d')
compare = int(flask.request.args['compare']) compare = int(flask.request.args['compare'])
@ -88,20 +86,20 @@ def balance():
dates = [date.replace(year=date.year - i) for i in range(0, compare + 1)] dates = [date.replace(year=date.year - i) for i in range(0, compare + 1)]
pstarts = [pstart.replace(year=pstart.year - i) for i in range(0, compare + 1)] pstarts = [pstart.replace(year=pstart.year - i) for i in range(0, compare + 1)]
balance_sheets = [accounting.balance_sheet(d, p) for d, p in zip(dates, pstarts)] report_currency = Currency(*config['report_currency'])
l = ledger.raw_transactions_at_date(date)
balance_sheets = [accounting.balance_sheet(accounting.add_unrealized_gains(accounting.trial_balance(l, d, p), report_currency)) for d, p in zip(dates, pstarts)]
# Delete accounts with always zero balances # Delete accounts with always zero balances
accounts = list(balance_sheets[0].accounts.values()) accounts = sorted(l.accounts.values(), key=lambda a: a.name)
for account in accounts[:]: for account in accounts[:]:
if all(b.get_balance(account.name) == 0 and b.get_total(account.name) == 0 for b in balance_sheets): if all(b.get_balance(account) == 0 and b.get_total(account) == 0 for b in balance_sheets):
accounts.remove(account) accounts.remove(account)
return flask.render_template('balance.html', balance_sheets=balance_sheets, accounts=accounts, config=ledger.config) return flask.render_template('balance.html', ledger=l, balance_sheets=balance_sheets, accounts=accounts, config=config, report_currency=report_currency)
@app.route('/pandl') @app.route('/pandl')
def pandl(): def pandl():
raise Exception('NYI')
date_beg = datetime.strptime(flask.request.args['date_beg'], '%Y-%m-%d') date_beg = datetime.strptime(flask.request.args['date_beg'], '%Y-%m-%d')
date_end = datetime.strptime(flask.request.args['date_end'], '%Y-%m-%d') date_end = datetime.strptime(flask.request.args['date_end'], '%Y-%m-%d')
compare = int(flask.request.args['compare']) compare = int(flask.request.args['compare'])
@ -110,12 +108,14 @@ def pandl():
dates_beg = [date_beg.replace(year=date_beg.year - i) for i in range(0, compare + 1)] dates_beg = [date_beg.replace(year=date_beg.year - i) for i in range(0, compare + 1)]
dates_end = [date_end.replace(year=date_end.year - i) for i in range(0, compare + 1)] dates_end = [date_end.replace(year=date_end.year - i) for i in range(0, compare + 1)]
pandls = [ledger.trial_balance(de, db) for de, db in zip(dates_end, dates_beg)] report_currency = Currency(*config['report_currency'])
l = ledger.raw_transactions_at_date(date_end)
pandls = [accounting.trial_balance(l, de, db) for de, db in zip(dates_end, dates_beg)]
# Delete accounts with always zero balances # Delete accounts with always zero balances
accounts = list(pandls[0].accounts.values()) accounts = sorted(l.accounts.values(), key=lambda a: a.name)
for account in accounts[:]: for account in accounts[:]:
if all(p.get_balance(account.name) == 0 and p.get_total(account.name) == 0 for p in pandls): if all(p.get_balance(account) == 0 and p.get_total(account) == 0 for p in pandls):
accounts.remove(account) accounts.remove(account)
if date_end == (date_beg.replace(year=date_beg.year + 1) - timedelta(days=1)): if date_end == (date_beg.replace(year=date_beg.year + 1) - timedelta(days=1)):
@ -125,7 +125,7 @@ def pandl():
else: else:
period = 'period from {} to {}'.format(date_beg.strftime('%d %B %Y'), date_end.strftime('%d %B %Y')) period = 'period from {} to {}'.format(date_beg.strftime('%d %B %Y'), date_end.strftime('%d %B %Y'))
return flask.render_template('pandl.html', period=period, pandls=pandls, accounts=accounts, config=ledger.config) return flask.render_template('pandl.html', period=period, ledger=l, pandls=pandls, accounts=accounts, config=config, report_currency=report_currency)
@app.route('/transactions') @app.route('/transactions')
def transactions(): def transactions():

View File

@ -19,6 +19,21 @@ from decimal import Decimal
from .model import * from .model import *
def trial_balance(ledger, date, pstart):
tb = TrialBalance(ledger, date, pstart)
for transaction in ledger.transactions:
if transaction.date > date:
continue
for posting in transaction.postings:
if (posting.account.is_income or posting.account.is_expense) and transaction.date < pstart:
tb.balances[config['retained_earnings']] = tb.get_balance(ledger.get_account(config['retained_earnings'])) + posting.amount
else:
tb.balances[posting.account.name] = tb.get_balance(posting.account) + posting.amount
return tb
def add_unrealized_gains(tb, currency): def add_unrealized_gains(tb, currency):
for account in list(tb.ledger.accounts.values()): for account in list(tb.ledger.accounts.values()):
if not account.is_market: if not account.is_market:
@ -36,17 +51,11 @@ def add_unrealized_gains(tb, currency):
return trial_balance(tb.ledger, tb.date, tb.pstart) return trial_balance(tb.ledger, tb.date, tb.pstart)
def trial_balance(ledger, date, pstart): def balance_sheet(tb):
tb = TrialBalance(ledger, date, pstart) # Calculate Profit/Loss
total_pandl = tb.get_total(tb.ledger.get_account(config['income_account'])) + tb.get_total(tb.ledger.get_account(config['expenses_account']))
for transaction in ledger.transactions: # Add Current Year Earnings account
if transaction.date > date: tb.balances[config['current_year_earnings']] = tb.get_balance(tb.ledger.get_account(config['current_year_earnings'])) + total_pandl
continue
for posting in transaction.postings:
if (posting.account.is_income or posting.account.is_expense) and transaction.date < pstart:
tb.balances[config['retained_earnings']] = tb.get_balance(ledger.get_account(config['retained_earnings'])) + posting.amount
else:
tb.balances[posting.account.name] = tb.get_balance(posting.account) + posting.amount
return tb return tb

View File

@ -26,7 +26,7 @@
{% endif %} {% endif %}
</td> </td>
{% for balance_sheet in balance_sheets %} {% for balance_sheet in balance_sheets %}
{% set amount = balance_sheet.get_balance(account.name) * (-1 if invert else 1) %} {% set amount = (-balance_sheet.get_balance(account) if invert else balance_sheet.get_balance(account)).exchange(report_currency, True) %}
<td>{% if amount != 0 %}<a href="/transactions?{{ {'date': balance_sheet.date.strftime('%Y-%m-%d'), 'pstart': balance_sheet.pstart.strftime('%Y-%m-%d'), 'account': account.name}|urlencode }}">{{ amount|a }}</a>{% endif %}</td> <td>{% if amount != 0 %}<a href="/transactions?{{ {'date': balance_sheet.date.strftime('%Y-%m-%d'), 'pstart': balance_sheet.pstart.strftime('%Y-%m-%d'), 'account': account.name}|urlencode }}">{{ amount|a }}</a>{% endif %}</td>
{% endfor %} {% endfor %}
</tr> </tr>
@ -37,7 +37,7 @@
{% endmacro %} {% endmacro %}
{% macro do_accounts(root, label, invert, year_headers) %} {% macro do_accounts(root, label, invert, year_headers) %}
{% for account_class in balance_sheets[0].get_account(root).children if account_class in accounts %} {% for account_class in root.children if account_class in accounts %}
<tr> <tr>
{% if loop.first and year_headers %} {% if loop.first and year_headers %}
<th class="h2">{{ account_class.bits[-1] }} {{ label }}</th> <th class="h2">{{ account_class.bits[-1] }} {{ label }}</th>
@ -53,14 +53,14 @@
<tr class="total"> <tr class="total">
<td>Total {{ account_class.bits[-1] }} {{ label }}</td> <td>Total {{ account_class.bits[-1] }} {{ label }}</td>
{% for balance_sheet in balance_sheets %}<td>{{ (balance_sheet.get_total(account_class.name) * (-1 if invert else 1))|a }}</td>{% endfor %} {% for balance_sheet in balance_sheets %}<td>{{ (-balance_sheet.get_total(account_class) if invert else balance_sheet.get_total(account_class)).exchange(report_currency, True)|a }}</td>{% endfor %}
</tr> </tr>
<tr><td colspan="2">&nbsp;</td></tr> <tr><td colspan="2">&nbsp;</td></tr>
{% endfor %} {% endfor %}
<tr class="total"> <tr class="total">
<td>Total {{ label }}</td> <td>Total {{ label }}</td>
{% for balance_sheet in balance_sheets %}<td>{{ (balance_sheet.get_total(root) * (-1 if invert else 1))|a }}</td>{% endfor %} {% for balance_sheet in balance_sheets %}<td>{{ (-balance_sheet.get_total(root) if invert else balance_sheet.get_total(root)).exchange(report_currency, True)|a }}</td>{% endfor %}
</tr> </tr>
{% endmacro %} {% endmacro %}
@ -79,24 +79,24 @@
<table class="ledger onedesc"> <table class="ledger onedesc">
{# Assets #} {# Assets #}
<tr><th class="h1" colspan="{{ balance_sheets|length + 1 }}">Assets</th></tr> <tr><th class="h1" colspan="{{ balance_sheets|length + 1 }}">Assets</th></tr>
{{ do_accounts(config['assets_account'], 'Assets', False, True) }} {{ do_accounts(ledger.get_account(config['assets_account']), 'Assets', False, True) }}
<tr><td colspan="2">&nbsp;</td></tr> <tr><td colspan="2">&nbsp;</td></tr>
{# Liabilities #} {# Liabilities #}
<tr><th class="h1" colspan="{{ balance_sheets|length + 1 }}">Liabilities</th></tr> <tr><th class="h1" colspan="{{ balance_sheets|length + 1 }}">Liabilities</th></tr>
{{ do_accounts(config['liabilities_account'], 'Liabilities', True, False) }} {{ do_accounts(ledger.get_account(config['liabilities_account']), 'Liabilities', True, False) }}
<tr><td colspan="2">&nbsp;</td></tr> <tr><td colspan="2">&nbsp;</td></tr>
{# Equity #} {# Equity #}
<tr><th class="h1" colspan="{{ balance_sheets|length + 1 }}">Equity</th></tr> <tr><th class="h1" colspan="{{ balance_sheets|length + 1 }}">Equity</th></tr>
{% for account in balance_sheets[0].get_account(config['equity_account']).children if account in accounts %} {% for account in ledger.get_account(config['equity_account']).children if account in accounts %}
{{ print_rows(account, invert=True) }} {{ print_rows(account, invert=True) }}
{% endfor %} {% endfor %}
<tr class="total"> <tr class="total">
<td>Total Equity</td> <td>Total Equity</td>
{% for balance_sheet in balance_sheets %}<td>{{ -balance_sheet.get_total(config['equity_account'])|a }}</td>{% endfor %} {% for balance_sheet in balance_sheets %}<td>{{ -balance_sheet.get_total(ledger.get_account(config['equity_account'])).exchange(report_currency, True)|a }}</td>{% endfor %}
</tr> </tr>
</table> </table>
</body> </body>

View File

@ -26,7 +26,7 @@
{% endif %} {% endif %}
</td> </td>
{% for pandl in pandls %} {% for pandl in pandls %}
{% set amount = pandl.get_balance(account.name) * (-1 if invert else 1) %} {% set amount = (-pandl.get_balance(account) if invert else pandl.get_balance(account)).exchange(report_currency, True) %}
<td>{% if amount != 0 %}<a href="/transactions?{{ {'date': pandl.date.strftime('%Y-%m-%d'), 'pstart': pandl.pstart.strftime('%Y-%m-%d'), 'account': account.name}|urlencode }}">{{ amount|a }}</a>{% endif %}</td> <td>{% if amount != 0 %}<a href="/transactions?{{ {'date': pandl.date.strftime('%Y-%m-%d'), 'pstart': pandl.pstart.strftime('%Y-%m-%d'), 'account': account.name}|urlencode }}">{{ amount|a }}</a>{% endif %}</td>
{% endfor %} {% endfor %}
</tr> </tr>
@ -47,13 +47,13 @@
{% endif %} {% endif %}
</tr> </tr>
{% for account in pandls[0].get_account(root).children if account in accounts %} {% for account in root.children if account in accounts %}
{{ print_rows(account, invert=invert) }} {{ print_rows(account, invert=invert) }}
{% endfor %} {% endfor %}
<tr class="total"> <tr class="total">
<td>Total {{ label }}</td> <td>Total {{ label }}</td>
{% for pandl in pandls %}<td>{{ (pandl.get_total(root) * (-1 if invert else 1))|a }}</td>{% endfor %} {% for pandl in pandls %}<td>{{ (-pandl.get_total(root) if invert else pandl.get_total(root)).exchange(report_currency, True)|a }}</td>{% endfor %}
</tr> </tr>
{% endmacro %} {% endmacro %}
@ -70,15 +70,15 @@
<h2>For the {{ period }}</h2> <h2>For the {{ period }}</h2>
<table class="ledger onedesc"> <table class="ledger onedesc">
{{ do_accounts(config['income_account'], 'Income', True, True) }} {{ do_accounts(ledger.get_account(config['income_account']), 'Income', True, True) }}
<tr><td colspan="2">&nbsp;</td></tr> <tr><td colspan="2">&nbsp;</td></tr>
{{ do_accounts(config['expenses_account'], 'Expenses', False, False) }} {{ do_accounts(ledger.get_account(config['expenses_account']), 'Expenses', False, False) }}
<tr><td colspan="2">&nbsp;</td></tr> <tr><td colspan="2">&nbsp;</td></tr>
<tr class="total"> <tr class="total">
<td>Net Surplus (Loss)</td> <td>Net Surplus (Loss)</td>
{% for pandl in pandls %}<td>{{ -(pandl.get_total(config['income_account']) + pandl.get_total(config['expenses_account']))|a }}</td>{% endfor %} {% for pandl in pandls %}<td>{{ -(pandl.get_total(ledger.get_account(config['income_account'])) + pandl.get_total(ledger.get_account(config['expenses_account']))).exchange(report_currency, True)|a }}</td>{% endfor %}
</tr> </tr>
</table> </table>
</body> </body>

View File

@ -245,7 +245,7 @@ class Balance:
elif isinstance(other, Amount): elif isinstance(other, Amount):
raise Exception('NYI') raise Exception('NYI')
elif other == 0: elif other == 0:
return len(self.amounts) == 0 return all(a == 0 for a in self.amounts)
else: else:
raise TypeError('Cannot compare Balance with non-zero number') raise TypeError('Cannot compare Balance with non-zero number')
@ -265,6 +265,8 @@ class Balance:
new_amount = Amount(0, other.currency) new_amount = Amount(0, other.currency)
new_amounts.append(new_amount) new_amounts.append(new_amount)
new_amount.amount += other.amount new_amount.amount += other.amount
elif other == 0:
pass
else: else:
raise Exception('NYI') raise Exception('NYI')
@ -296,4 +298,4 @@ class TrialBalance:
return self.balances.get(account.name, Balance()) return self.balances.get(account.name, Balance())
def get_total(self, account): def get_total(self, account):
return self.get_balance(account) + sum(self.get_total(a) for a in account.children) return self.get_balance(account) + sum((self.get_total(a) for a in account.children), Balance())