From 63806da8dc94918ee21ee57f89cb20e413f7a52e Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Fri, 31 Jan 2020 17:57:27 +1100 Subject: [PATCH] Implement cash basis accounting --- ledger_pyreport/__init__.py | 11 +++++--- ledger_pyreport/jinja2/balance.html | 38 +++++++++++++------------ ledger_pyreport/jinja2/index.html | 15 ++++++---- ledger_pyreport/ledger.py | 44 +++++++++++++++++++++++++---- 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/ledger_pyreport/__init__.py b/ledger_pyreport/__init__.py index b008203..e4a5b8d 100644 --- a/ledger_pyreport/__init__.py +++ b/ledger_pyreport/__init__.py @@ -32,9 +32,10 @@ def index(): def trial(): date = datetime.strptime(flask.request.args['date'], '%Y-%m-%d') pstart = datetime.strptime(flask.request.args['pstart'], '%Y-%m-%d') + cash = flask.request.args.get('cash', False) # Get trial balance - accounts = ledger.trial_balance(date, pstart) + accounts = ledger.trial_balance(date, pstart, cash) total_dr = sum(a.balance for a in accounts if a.balance > 0) total_cr = -sum(a.balance for a in accounts if a.balance < 0) @@ -45,9 +46,10 @@ def trial(): def balance(): date = datetime.strptime(flask.request.args['date'], '%Y-%m-%d') pstart = datetime.strptime(flask.request.args['pstart'], '%Y-%m-%d') + cash = flask.request.args.get('cash', False) # Get trial balance - accounts = ledger.trial_balance(date, pstart) + accounts = ledger.trial_balance(date, pstart, cash) accounts_map = ledger.make_account_tree(accounts) # Calculate Profit/Loss @@ -57,15 +59,16 @@ def balance(): accounts.append(ledger.Account(ledger.config['current_year_earnings'], total_pandl)) accounts_map = ledger.make_account_tree(accounts) - return flask.render_template('balance.html', date=date, assets=accounts_map[ledger.config['assets_account']], liabilities=accounts_map[ledger.config['liabilities_account']], equity=accounts_map[ledger.config['equity_account']]) + return flask.render_template('balance.html', date=date, cash=cash, assets=accounts_map.get(ledger.config['assets_account'], ledger.Account('Assets')), liabilities=accounts_map.get(ledger.config['liabilities_account'], ledger.Account('Liabilities')), equity=accounts_map.get(ledger.config['equity_account'], ledger.Account('Equity'))) @app.route('/pandl') def pandl(): date_beg = datetime.strptime(flask.request.args['date_beg'], '%Y-%m-%d') date_end = datetime.strptime(flask.request.args['date_end'], '%Y-%m-%d') + cash = flask.request.args.get('cash', False) # Get P&L - accounts = ledger.parse_balance(ledger.run_ledger('--begin', date_beg.strftime('%Y-%m-%d'), '--end', (date_end + timedelta(days=1)).strftime('%Y-%m-%d'), 'balance', '--balance-format', ledger.BALANCE_FORMAT, '--no-total', '--flat', '--cost', ledger.aregex(ledger.config['income_account']), ledger.aregex(ledger.config['expenses_account']))) + accounts = ledger.pandl(date_beg, date_end, cash) accounts_map = ledger.make_account_tree(accounts) if date_end == (date_beg.replace(year=date_beg.year + 1) - timedelta(days=1)): diff --git a/ledger_pyreport/jinja2/balance.html b/ledger_pyreport/jinja2/balance.html index 74e1ad3..e6e6e2f 100644 --- a/ledger_pyreport/jinja2/balance.html +++ b/ledger_pyreport/jinja2/balance.html @@ -61,27 +61,29 @@   - Liabilities - - {% for liability_class in liabilities.children %} - {{ liability_class.name_parts[-1] }} Liabilities - {{ walk_children(liability_class.children, acc_level=1, invert=True) }} + {% if not cash %} + Liabilities + + {% for liability_class in liabilities.children %} + {{ liability_class.name_parts[-1] }} Liabilities + {{ walk_children(liability_class.children, acc_level=1, invert=True) }} + + Total {{ liability_class.name_parts[-1] }} Liabilities + {{ -liability_class.total()|a }} + +   + {% endfor %} + - Total {{ liability_class.name_parts[-1] }} Liabilities - {{ -liability_class.total()|a }} + Total Liabilities + {{ -liabilities.total()|a }} + + + Net Assets + {{ (assets.total() + liabilities.total())|a }}   - {% endfor %} - - - Total Liabilities - {{ -liabilities.total()|a }} - - - Net Assets - {{ (assets.total() + liabilities.total())|a }} - -   + {% endif %} Equity diff --git a/ledger_pyreport/jinja2/index.html b/ledger_pyreport/jinja2/index.html index 12f3997..ea9cd63 100644 --- a/ledger_pyreport/jinja2/index.html +++ b/ledger_pyreport/jinja2/index.html @@ -26,20 +26,23 @@ diff --git a/ledger_pyreport/ledger.py b/ledger_pyreport/ledger.py index feed2c1..39cda7a 100644 --- a/ledger_pyreport/ledger.py +++ b/ledger_pyreport/ledger.py @@ -25,7 +25,10 @@ with open('config.yml', 'r') as f: config = yaml.safe_load(f) class Account: - def __init__(self, name, balance): + def __init__(self, name, balance=None): + if balance is None: + balance = Decimal(0) + self.name = name self.balance = balance @@ -92,6 +95,11 @@ def make_account_tree(accounts): def aregex(account): return '^{0}:|^{0}$'.format(account) +def amatch(needle, haystack): + if haystack == needle or haystack.startswith(needle + ':'): + return True + return False + def financial_year(date): pstart = date.replace(day=1, month=7) if pstart > date: @@ -109,7 +117,7 @@ def unrealized_gains(date): return unrealized_gains # Get account balances at date -def get_accounts(date): +def get_accounts(date, cash=False): # Calculate Unrealized Gains unrealized_gains_amt = unrealized_gains(date) @@ -121,16 +129,30 @@ def get_accounts(date): accounts.append(Account(config['unrealized_gains'], -unrealized_gains_amt)) accounts.sort(key=lambda a: a.name) + # Convert to cash basis + if cash: + accounts_map = make_account_tree(accounts) + + for account in accounts[:]: + if amatch(config['liabilities_account'], account.name) or (amatch(config['assets_account'], account.name) and not any(amatch(x, account.name) for x in config['cash_asset_accounts'])): + drcr = parse_balance(run_ledger_date(date, 'balance', '--related', '--balance-format', BALANCE_FORMAT, '--no-total', '--flat', '--cost' if amatch(config['income_account'], account.name) or amatch(config['expenses_account'], account.name) else '--market', aregex(account.name))) + + for drcr_account in drcr: + accounts_map[drcr_account.name].balance -= drcr_account.balance + + accounts.remove(account) + del accounts_map[account.name] + return accounts # Calculate trial balance -def trial_balance(date, pstart): +def trial_balance(date, pstart, cash=False): # Get balances at period start - accounts_pstart = get_accounts(pstart - timedelta(days=1)) + accounts_pstart = get_accounts(pstart - timedelta(days=1), cash) accounts_map_pstart = make_account_tree(accounts_pstart) # Get balances at date - accounts = get_accounts(date) + accounts = get_accounts(date, cash) # Adjust Retained Earnings total_pandl = Decimal(0) @@ -143,8 +165,18 @@ def trial_balance(date, pstart): # Adjust income/expense accounts for account in accounts: - if account.name == config['income_account'] or account.name.startswith(config['income_account'] + ':') or account.name == config['expenses_account'] or account.name.startswith(config['expenses_account'] + ':'): + if amatch(config['income_account'], account.name) or amatch(config['expenses_account'], account.name): if account.name in accounts_map_pstart: account.balance -= accounts_map_pstart[account.name].balance return accounts + +# Calculate profit and loss +def pandl(date_beg, date_end, cash=False): + accounts = trial_balance(date_end, date_beg, cash) + + for account in accounts[:]: + if not (amatch(config['income_account'], account.name) or amatch(config['expenses_account'], account.name)): + accounts.remove(account) + + return accounts