This repository has been archived on 2024-11-09. You can view files and clone it, but cannot push or open issues or pull requests.
RunasSudo 2bc6bb0e22
Rewrite backend again to process transactions fully ourselves
Reimplemented up to trial balance/account transactions
2020-03-20 21:43:05 +11:00

176 lines
7.6 KiB
Python

# 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 <https://www.gnu.org/licenses/>.
from . import accounting
from . import ledger
from .config import config
from .model import *
from datetime import datetime, timedelta
from decimal import Decimal
import flask
app = flask.Flask(__name__, template_folder='jinja2')
@app.route('/')
def index():
date = datetime.now()
pstart = ledger.financial_year(date)
return flask.render_template('index.html', date=date, pstart=pstart)
@app.route('/trial')
def trial():
date = datetime.strptime(flask.request.args['date'], '%Y-%m-%d')
pstart = datetime.strptime(flask.request.args['pstart'], '%Y-%m-%d')
compare = int(flask.request.args['compare'])
#cash = flask.request.args.get('cash', False)
if compare == 0:
# Get trial balance
l = ledger.raw_transactions_at_date(date)
trial_balance = accounting.trial_balance(l, date, pstart)
report_currency = Currency(*config['report_currency'])
trial_balance = accounting.add_unrealized_gains(trial_balance, report_currency)
total_dr = Amount(0, report_currency)
total_cr = Amount(0, report_currency)
for account in l.accounts.values():
# Display in "cost basis" as we have already accounted for unrealised gains
balance = trial_balance.get_balance(account).exchange(report_currency, True)
if balance > 0:
total_dr += balance
else:
total_cr -= balance
return flask.render_template('trial.html', date=date, pstart=pstart, trial_balance=trial_balance, accounts=sorted(l.accounts.values(), key=lambda a: a.name), total_dr=total_dr, total_cr=total_cr, report_currency=report_currency)
else:
# Get multiple trial balances for comparison
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)]
report_currency = Currency(*config['report_currency'])
l = ledger.raw_transactions_at_date(date)
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
accounts = list(trial_balances[0].ledger.accounts.values())
for account in accounts[:]:
if all(t.get_balance(account) == 0 for t in trial_balances):
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)
@app.route('/balance')
def balance():
raise Exception('NYI')
date = datetime.strptime(flask.request.args['date'], '%Y-%m-%d')
pstart = datetime.strptime(flask.request.args['pstart'], '%Y-%m-%d')
compare = int(flask.request.args['compare'])
#cash = flask.request.args.get('cash', False)
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)]
balance_sheets = [accounting.balance_sheet(d, p) for d, p in zip(dates, pstarts)]
# Delete accounts with always zero balances
accounts = list(balance_sheets[0].accounts.values())
for account in accounts[:]:
if all(b.get_balance(account.name) == 0 and b.get_total(account.name) == 0 for b in balance_sheets):
accounts.remove(account)
return flask.render_template('balance.html', balance_sheets=balance_sheets, accounts=accounts, config=ledger.config)
@app.route('/pandl')
def pandl():
raise Exception('NYI')
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'])
#cash = flask.request.args.get('cash', False)
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)]
pandls = [ledger.trial_balance(de, db) for de, db in zip(dates_end, dates_beg)]
# Delete accounts with always zero balances
accounts = list(pandls[0].accounts.values())
for account in accounts[:]:
if all(p.get_balance(account.name) == 0 and p.get_total(account.name) == 0 for p in pandls):
accounts.remove(account)
if date_end == (date_beg.replace(year=date_beg.year + 1) - timedelta(days=1)):
period = 'year ended {}'.format(date_end.strftime('%d %B %Y'))
elif date_beg == ledger.financial_year(date_end):
period = 'financial year to {}'.format(date_end.strftime('%d %B %Y'))
else:
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)
@app.route('/transactions')
def transactions():
date = datetime.strptime(flask.request.args['date'], '%Y-%m-%d')
pstart = datetime.strptime(flask.request.args['pstart'], '%Y-%m-%d')
account = flask.request.args.get('account', None)
# General ledger
l = ledger.raw_transactions_at_date(date)
# Unrealized gains
report_currency = Currency(*config['report_currency'])
l = accounting.add_unrealized_gains(accounting.trial_balance(l, date, pstart), report_currency).ledger
if not account:
transactions = [t for t in l.transactions if t.date <= date and t.date >= pstart]
total_dr = sum((p.amount for t in transactions for p in t.postings if p.amount > 0), Balance()).exchange(report_currency, True)
total_cr = sum((p.amount for t in transactions for p in t.postings if p.amount < 0), Balance()).exchange(report_currency, True)
return flask.render_template('transactions.html', date=date, pstart=pstart, account=None, ledger=l, transactions=transactions, total_dr=total_dr, total_cr=total_cr, report_currency=report_currency)
else:
account = l.get_account(account)
transactions = [t for t in l.transactions if t.date <= date and t.date >= pstart and any(p.account == account for p in t.postings)]
opening_balance = accounting.trial_balance(l, pstart, pstart).get_balance(account).exchange(report_currency, True)
closing_balance = accounting.trial_balance(l, date, pstart).get_balance(account).exchange(report_currency, True)
return flask.render_template('transactions.html', date=date, pstart=pstart, account=account, ledger=l, transactions=transactions, opening_balance=opening_balance, closing_balance=closing_balance, report_currency=report_currency)
@app.template_filter('a')
def filter_amount(amt):
if amt.amount < 0.005 and amt.amount >= -0.005:
return flask.Markup('0.00&nbsp;')
elif amt > 0:
return flask.Markup('{:,.2f}&nbsp;'.format(amt.amount).replace(',', '&#8239;')) # Narrow no-break space
else:
return flask.Markup('({:,.2f})'.format(-amt.amount).replace(',', '&#8239;'))
@app.template_filter('b')
def filter_amount_positive(amt):
return flask.Markup('{:,.2f}'.format(amt.amount).replace(',', '&#8239;'))
#return flask.Markup('{:,.2f} {}'.format(amt.amount, amt.currency.name).replace(',', '&#8239;'))
@app.template_filter('bb')
def filter_balance_positive(balance):
return flask.Markup('<br>'.join(filter_amount_positive(a) for a in balance.amounts))