diff --git a/ledger_pyreport/__init__.py b/ledger_pyreport/__init__.py index 3abf125..94d7a48 100644 --- a/ledger_pyreport/__init__.py +++ b/ledger_pyreport/__init__.py @@ -136,6 +136,43 @@ def pandl(): return flask.render_template('pandl.html', period=describe_period(date_end, date_beg), ledger=l, pandls=pandls, accounts=accounts, config=config, report_currency=report_currency, cash=cash, scope=scope) +@app.route('/cashflow') +def cashflow(): + date_beg = datetime.strptime(flask.request.args['date_beg'], '%Y-%m-%d') + date_end = datetime.strptime(flask.request.args['date_end'], '%Y-%m-%d') + compare = int(flask.request.args['compare']) + method = flask.request.args['method'] + + 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)] + + report_currency = Currency(*config['report_currency']) + l = ledger.raw_transactions_at_date(date_end) + + cash_accounts = [a for a in l.accounts.values() if a.is_cash] + + # Calculate opening and closing cash + opening_balances = [] + closing_balances = [] + cashflows = [] + for de, db in zip(dates_end, dates_beg): + tb = accounting.trial_balance(l.clone(), db - timedelta(days=1), db, report_currency) + opening_balances.append(sum((tb.get_balance(a) for a in cash_accounts), Balance()).exchange(report_currency, True)) + + tb = accounting.trial_balance(l.clone(), de, db, report_currency) + closing_balances.append(sum((tb.get_balance(a) for a in cash_accounts), Balance()).exchange(report_currency, True)) + + cashflows.append(accounting.account_flows(tb.ledger, de, db, cash_accounts)) + + if method == 'direct': + # Delete accounts with always zero balances + accounts = list(l.accounts.values()) + for account in accounts[:]: + if all(p.get_balance(account) == 0 and p.get_total(account) == 0 for p in cashflows): + accounts.remove(account) + + return flask.render_template('cashflow_direct.html', period=describe_period(date_end, date_beg), ledger=l, cashflows=cashflows, opening_balances=opening_balances, closing_balances=closing_balances, accounts=accounts, config=config, report_currency=report_currency) + @app.route('/transactions') def transactions(): date_beg = datetime.strptime(flask.request.args['date_beg'], '%Y-%m-%d') diff --git a/ledger_pyreport/accounting.py b/ledger_pyreport/accounting.py index fc2afe2..9a1bc33 100644 --- a/ledger_pyreport/accounting.py +++ b/ledger_pyreport/accounting.py @@ -166,3 +166,18 @@ def ledger_to_cash(ledger, currency): account_to_cash(account, currency) return ledger + +# Summarise related transactions +def account_flows(ledger, date, pstart, accounts): + transactions = [t for t in ledger.transactions if any(p.account in accounts for p in t.postings) and t.date <= date and t.date >= pstart] + + tb = TrialBalance(ledger, date, pstart) + + for transaction in transactions: + for posting in transaction.postings: + if posting.account in accounts: + continue + + tb.balances[posting.account.name] = tb.get_balance(posting.account) - posting.amount + + return tb diff --git a/ledger_pyreport/jinja2/cashflow_direct.html b/ledger_pyreport/jinja2/cashflow_direct.html new file mode 100644 index 0000000..110cf91 --- /dev/null +++ b/ledger_pyreport/jinja2/cashflow_direct.html @@ -0,0 +1,76 @@ +{# + ledger-pyreport + Copyright © 2020 Lee Yingtong Li (RunasSudo) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +#} + +{% extends 'base_report.html' %} + +{% macro print_rows(account, invert=False, level=0) %} + + + {% if cashflows|length == 1 %} + {{ account.bits[-1] }} + {% else %} + {{ account.bits[-1] }} + {% endif %} + + {% for cashflow in cashflows %} + {% set amount = (-cashflow.get_balance(account) if invert else cashflow.get_balance(account)).exchange(report_currency, True) %} + {% if not amount.near_zero %}{{ amount|a('/transactions?' + {'date': cashflow.date.strftime('%Y-%m-%d'), 'pstart': cashflow.pstart.strftime('%Y-%m-%d'), 'account': account.name, 'cash': 'on' if cash else ''}|urlencode) }}{% endif %} + {% endfor %} + + + {% for child in account.children|sort(attribute='name') if child in accounts %} + {{ print_rows(child, invert, level + 1) }} + {% endfor %} +{% endmacro %} + +{% block title %}Cash Flow Statement for the {{ period }}{% endblock %} + +{% block report %} +

Cash Flow Statement

+

For the {{ period }}

+ + + {# Cash flows #} + + + {% for cashflow in cashflows %}{% endfor %} + + {% for account in ledger.root_account.children|sort(attribute='name') if account in accounts %} + {{ print_rows(account, invert=invert) }} + {% endfor %} + + + {% for cashflow in cashflows %}{% endfor %} + + + + {# Summary #} + + + {% for opening_balance in opening_balances %}{% endfor %} + + + + {% for cashflow in cashflows %}{% endfor %} + + + + {% for closing_balance in closing_balances %}{% endfor %} + +
Cash Inflows (Outflows){{ cashflow.date.strftime('%Y') }} 
Net Cash Inflow (Outflow){{ cashflow.get_total(ledger.root_account).exchange(report_currency, True)|a }}
 
Opening Cash{{ opening_balance|a }}
Net Cash Inflow (Outflow){{ cashflow.get_total(ledger.root_account).exchange(report_currency, True)|a }}
Closing Cash{{ closing_balance|a }}
+{% endblock %} diff --git a/ledger_pyreport/jinja2/index.html b/ledger_pyreport/jinja2/index.html index c92543b..70388e6 100644 --- a/ledger_pyreport/jinja2/index.html +++ b/ledger_pyreport/jinja2/index.html @@ -56,6 +56,19 @@ +
+
+ + + + + +
+
+