Implement separate accounting of specified P&L accounts

This commit is contained in:
RunasSudo 2020-04-16 03:52:39 +10:00
parent 50c2522654
commit 0c8f4a2ad4
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
6 changed files with 87 additions and 2 deletions

View File

@ -21,6 +21,7 @@ ledger-pyreport is a lightweight Flask webapp for generating interactive and pri
* Accounts for both profit and loss, and other comprehensive income * Accounts for both profit and loss, and other comprehensive income
* Simulates annual closing of books, with presentation of income/expenses on the balance sheet as retained earnings and current year earnings * Simulates annual closing of books, with presentation of income/expenses on the balance sheet as retained earnings and current year earnings
* Can simulate cash basis accounting, using FIFO methodology to recode transactions involving liabilities and non-cash assets * Can simulate cash basis accounting, using FIFO methodology to recode transactions involving liabilities and non-cash assets
* Can separately report specified categories of income and expense, reporting per-category net profit
* Extensible through custom programming hooks * Extensible through custom programming hooks
## Background, demo and screenshots ## Background, demo and screenshots

View File

@ -11,6 +11,9 @@ income_account: Income
expenses_account: Expenses expenses_account: Expenses
oci_account: OCI # Other Comprehensive Income oci_account: OCI # Other Comprehensive Income
# These income and expense categories appear separately on the income statement
separate_pandl: []
# These accounts will automatically be populated on reports # These accounts will automatically be populated on reports
unrealized_gains: 'OCI:Unrealized Gains' unrealized_gains: 'OCI:Unrealized Gains'

View File

@ -11,6 +11,9 @@ income_account: Income
expenses_account: Expenses expenses_account: Expenses
oci_account: OCI # Other Comprehensive Income oci_account: OCI # Other Comprehensive Income
# These income and expense categories appear separately on the income statement
separate_pandl: ['Business']
# These accounts will automatically be populated on reports # These accounts will automatically be populated on reports
unrealized_gains: 'OCI:Unrealized Gains' unrealized_gains: 'OCI:Unrealized Gains'

View File

@ -17,6 +17,14 @@
Assets:Current:Inventory 50 Widgets {$7.00} Assets:Current:Inventory 50 Widgets {$7.00}
Assets:Current:Cash at Bank Assets:Current:Cash at Bank
2019-07-04 Sale
Assets:Current:Cash at Bank $100
Income:Business:Sales
2019-07-04 Sale
Expenses:Business:Cost of Goods Sold 10 Widgets {$5.00}
Assets:Current:Inventory
2019-08-01 Interest on business loan 2019-08-01 Interest on business loan
Expenses:Interest $100.00 Expenses:Interest $100.00
Liabilities:Non-current:Business Loan Liabilities:Non-current:Business Loan

View File

@ -136,13 +136,35 @@ def pandl():
l = accounting.ledger_to_cash(l, report_commodity) l = accounting.ledger_to_cash(l, report_commodity)
pandls = [accounting.trial_balance(l.clone(), de, db, report_commodity) for de, db in zip(dates_end, dates_beg)] pandls = [accounting.trial_balance(l.clone(), de, db, report_commodity) for de, db in zip(dates_end, dates_beg)]
# Process separate P&L accounts
separate_pandls = []
for separate_pandl_name in config['separate_pandl']:
acc_income = l.get_account(config['income_account'] + ':' + separate_pandl_name)
acc_expenses = l.get_account(config['expenses_account'] + ':' + separate_pandl_name)
separate_pandls.append((acc_income, acc_expenses))
# Unlink from parents so raw figures not counted in income/expense total
acc_income.parent.children.remove(acc_income)
acc_expenses.parent.children.remove(acc_expenses)
# Add summary account
for i, de, db in zip(range(compare + 1), dates_end, dates_beg):
balance = (pandls[i].get_total(acc_income) + pandls[i].get_total(acc_expenses)).exchange(report_commodity, True)
if balance <= 0: # Credit
summary_account = l.get_account(config['income_account'] + ':' + separate_pandl_name + ' Profit')
else:
summary_account = l.get_account(config['expenses_account'] + ':' + separate_pandl_name + ' Loss')
pandls[i].balances[summary_account.name] = pandls[i].get_balance(summary_account) + balance
# Delete accounts with always zero balances # Delete accounts with always zero balances
accounts = list(l.accounts.values()) accounts = list(l.accounts.values())
for account in accounts[:]: for account in accounts[:]:
if all(p.get_balance(account) == 0 and p.get_total(account) == 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)
return flask.render_template('pandl.html', period=describe_period(date_end, date_beg), ledger=l, pandls=pandls, accounts=accounts, config=config, report_commodity=report_commodity, cash=cash, scope=scope) return flask.render_template('pandl.html', period=describe_period(date_end, date_beg), ledger=l, pandls=pandls, accounts=accounts, separate_pandls=separate_pandls, config=config, report_commodity=report_commodity, cash=cash, scope=scope)
@app.route('/cashflow') @app.route('/cashflow')
def cashflow(): def cashflow():

View File

@ -68,7 +68,55 @@
<table class="ledger onedesc"> <table class="ledger onedesc">
{# Profit and loss #} {# Profit and loss #}
{% if scope != 'oci' %} {% if scope != 'oci' %}
{{ do_accounts(ledger.get_account(config['income_account']), 'Income', True, True) }} {# Separate P&L accounts #}
{% for acc_income, acc_expenses in separate_pandls %}
<tr><th class="h1" colspan="{{ pandls|length + 1 }}">{{ acc_income.bits[-1] }} Account</th></tr>
{# Income #}
<tr>
{% if loop.first %}
<th class="h2">{{ acc_income.bits[-1] }} Income</th>
{% for pandl in pandls %}<th class="h2">{{ pandl.date.strftime('%Y') }}&nbsp;</th>{% endfor %}
{% else %}
<th class="h2" colspan="{{ pandls|length + 1 }}">{{ acc_income.bits[-1] }} Income</th>
{% endif %}
</tr>
{% for account in acc_income.children|sort(attribute='name') if account in accounts %}
{{ print_rows(account, invert=True) }}
{% endfor %}
<tr class="total">
<td>Total {{ acc_income.bits[-1] }} Income</td>
{% for pandl in pandls %}<td>{{ -pandl.get_total(acc_income).exchange(report_commodity, True)|a }}</td>{% endfor %}
</tr>
<tr><td colspan="{{ pandls|length + 1}}">&nbsp;</td></tr>
{# Expenses #}
<tr><th class="h2" colspan="{{ pandls|length + 1 }}">{{ acc_income.bits[-1] }} Expenses</th></tr>
{% for account in acc_expenses.children|sort(attribute='name') if account in accounts %}
{{ print_rows(account) }}
{% endfor %}
<tr class="total">
<td>Total {{ acc_income.bits[-1] }} Expenses</td>
{% for pandl in pandls %}<td>{{ pandl.get_total(acc_expenses).exchange(report_commodity, True)|a }}</td>{% endfor %}
</tr>
<tr><td colspan="{{ pandls|length + 1}}">&nbsp;</td></tr>
{# Net Profit #}
<tr class="total">
<td>{{ acc_income.bits[-1] }} Profit (Loss)</td>
{% for pandl in pandls %}<td>{{ -(pandl.get_total(acc_income) + pandl.get_total(acc_expenses)).exchange(report_commodity, True)|a }}</td>{% endfor %}
</tr>
<tr><td colspan="{{ pandls|length + 1}}">&nbsp;</td></tr>
{% endfor %}
{{ do_accounts(ledger.get_account(config['income_account']), 'Income', True, separate_pandls|length == 0) }}
<tr><td colspan="{{ pandls|length + 1}}">&nbsp;</td></tr> <tr><td colspan="{{ pandls|length + 1}}">&nbsp;</td></tr>
{{ do_accounts(ledger.get_account(config['expenses_account']), 'Expenses', False, False) }} {{ do_accounts(ledger.get_account(config['expenses_account']), 'Expenses', False, False) }}