diff --git a/ledger_pyreport/__init__.py b/ledger_pyreport/__init__.py index 5d04126..090497a 100644 --- a/ledger_pyreport/__init__.py +++ b/ledger_pyreport/__init__.py @@ -197,6 +197,25 @@ def transactions_commodity(): return flask.render_template('transactions_commodity.html', date=date, pstart=pstart, period=describe_period(date, pstart), account=account, ledger=l, transactions=transactions, opening_balance=opening_balance, closing_balance=closing_balance, report_currency=report_currency, cash=cash, timedelta=timedelta, matching_posting=matching_posting) +@app.route('/transaction') +def transaction(): + tid = flask.request.args['tid'] + cash = flask.request.args.get('cash', False) + + report_currency = Currency(*config['report_currency']) + + # General ledger + l = ledger.raw_transactions_at_date(None) + if cash: + l = accounting.ledger_to_cash(l, report_currency) + + transaction = next((t for t in l.transactions if str(t.id) == tid)) + + total_dr = sum((p.amount for p in transaction.postings if p.amount > 0), Balance()).exchange(report_currency, True) + total_cr = sum((p.amount for p in transaction.postings if p.amount < 0), Balance()).exchange(report_currency, True) + + return flask.render_template('transaction.html', ledger=l, transaction=transaction, total_dr=total_dr, total_cr=total_cr, report_currency=report_currency, cash=cash) + # Template filters @app.template_filter('a') diff --git a/ledger_pyreport/jinja2/transaction.html b/ledger_pyreport/jinja2/transaction.html new file mode 100644 index 0000000..3ffcca8 --- /dev/null +++ b/ledger_pyreport/jinja2/transaction.html @@ -0,0 +1,66 @@ +{# + 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' %} + +{% block title %}{{ transaction.description }}{% endblock %} + +{% block report %} +

Transaction

+ + + + + + + + + + + + +
DateCodeDescription
{{ transaction.date.strftime('%Y-%m-%d') }}{{ transaction.code }}{{ transaction.description }}
+ + + + {##} + + + + + + + {% for posting in transaction.postings %} + {% set amount = posting.exchange(report_currency, transaction.date) %} + {% set trn_url = '/transaction?' + {'tid': transaction.id, 'cash': 'on' if cash else ''}|urlencode %} + + + + + + + {% endfor %} + + + + + + + +
DateDescriptionAccountDrCr
{{ posting.comment }}{{ (posting.account.name|e).__str__().replace(':', ':')|safe }}{% if amount > 0 %}{{ amount|b }}{% endif %}{% if amount < 0 %}{{ -amount|b }}{% endif %}
Total{{ total_dr|b }}{{ -total_cr|b }}
+{% endblock %} diff --git a/ledger_pyreport/jinja2/transactions.html b/ledger_pyreport/jinja2/transactions.html index 92126b2..dff7a0b 100644 --- a/ledger_pyreport/jinja2/transactions.html +++ b/ledger_pyreport/jinja2/transactions.html @@ -38,7 +38,7 @@ Date Description - Account + {% if account %}Related {% endif %}Account Dr Cr {% if account %}Balance{% endif %} @@ -69,9 +69,10 @@ {% for transaction in transactions %} {% for posting in transaction.postings if posting.account != account %} {% set amount = posting.exchange(report_currency, transaction.date) %} + {% set trn_url = '/transaction?' + {'tid': transaction.id, 'cash': 'on' if cash else ''}|urlencode %} - {% if loop.first %}{{ transaction.date.strftime('%Y-%m-%d') }}{% endif %} - {% if loop.first %}{{ transaction.description }}{% endif %} + {% if loop.first %}{% if transaction.id %}{% endif %}{{ transaction.date.strftime('%Y-%m-%d') }}{% if transaction.id %}{% endif %}{% endif %} + {% if loop.first %}{% if transaction.id %}{% endif %}{{ transaction.description }}{% if transaction.id %}{% endif %}{% endif %} {{ (posting.account.name|e).__str__().replace(':', ':')|safe }} {% if account %} {# Reverse Dr/Cr so it's from the "perspective" of this account #} diff --git a/ledger_pyreport/jinja2/transactions_commodity.html b/ledger_pyreport/jinja2/transactions_commodity.html index 4e6074a..06bbddf 100644 --- a/ledger_pyreport/jinja2/transactions_commodity.html +++ b/ledger_pyreport/jinja2/transactions_commodity.html @@ -54,14 +54,15 @@ {% set ns.balance = opening_balance %} {% for transaction in transactions %} + {% set trn_url = '/transaction?' + {'tid': transaction.id, 'cash': 'on' if cash else ''}|urlencode %} {% for posting in transaction.postings if posting.account == account %} {% set ns.balance = ns.balance + posting.amount %} {% endfor %} {% for amount in ns.balance.amounts %} {% set posting = matching_posting(transaction, amount) %} - {% if loop.first %}{{ transaction.date.strftime('%Y-%m-%d') }}{% endif %} - {% if loop.first %}{{ transaction.description }}{% endif %} + {% if loop.first %}{% if transaction.id %}{% endif %}{{ transaction.date.strftime('%Y-%m-%d') }}{% if transaction.id %}{% endif %}{% endif %} + {% if loop.first %}{% if transaction.id %}{% endif %}{{ transaction.description }}{% if transaction.id %}{% endif %}{% endif %} {% if posting %} {% if posting.amount >= 0 %}Dr{% else %}Cr{% endif %} {{ posting.amount|abs|bc }} diff --git a/ledger_pyreport/ledger.py b/ledger_pyreport/ledger.py index bbc9801..7395148 100644 --- a/ledger_pyreport/ledger.py +++ b/ledger_pyreport/ledger.py @@ -36,6 +36,8 @@ def run_ledger(*args): return stdout def run_ledger_date(date, *args): + if date is None: + return run_ledger(*args) return run_ledger('--end', (date + timedelta(days=1)).strftime('%Y-%m-%d'), *args) # General financial logic @@ -48,6 +50,8 @@ def financial_year(date): # Ledger logic +csv.register_dialect('ledger', doublequote=False, escapechar='\\') + def parse_amount(amount): if '{' in amount: amount_str = amount[:amount.index('{')].strip() @@ -60,7 +64,7 @@ def parse_amount(amount): # Currency follows number bits = amount_str.split() amount_num = Decimal(bits[0]) - currency = Currency(bits[1], False) + currency = Currency(bits[1].strip('"'), False) else: # Currency precedes number currency = Currency(amount_str[0], True) @@ -76,9 +80,9 @@ def get_pricedb(): prices = [] - reader = csv.reader(output.splitlines()) + reader = csv.reader(output.splitlines(), dialect='ledger') for date_str, currency, price_str in reader: - prices.append((datetime.strptime(date_str, '%Y-%m-%d'), currency, parse_amount(price_str))) + prices.append((datetime.strptime(date_str, '%Y-%m-%d'), currency.strip('"'), parse_amount(price_str))) return prices @@ -86,18 +90,21 @@ def raw_transactions_at_date(date): ledger = Ledger(date) ledger.prices = get_pricedb() - output = run_ledger_date(date, 'csv', '--csv-format', '%(quoted(parent.id)),%(quoted(format_date(date))),%(quoted(payee)),%(quoted(account)),%(quoted(display_amount))\n') + output = run_ledger_date(date, 'csv', '--csv-format', '%(quoted(parent.id)),%(quoted(format_date(date))),%(quoted(parent.code)),%(quoted(payee)),%(quoted(account)),%(quoted(display_amount)),%(quoted(comment))\n') - reader = csv.reader(output.splitlines()) - for trn_id, date_str, payee, account_str, amount_str in reader: + reader = csv.reader(output.splitlines(), dialect='ledger') + for trn_id, date_str, code, payee, account_str, amount_str, comment in reader: if not ledger.transactions or trn_id != ledger.transactions[-1].id: - transaction = Transaction(ledger, trn_id, datetime.strptime(date_str, '%Y-%m-%d'), payee) + transaction = Transaction(ledger, trn_id, datetime.strptime(date_str, '%Y-%m-%d'), payee, code=code) ledger.transactions.append(transaction) else: # Transaction ID matches: continuation of previous transaction transaction = ledger.transactions[-1] - posting = Posting(transaction, ledger.get_account(account_str), parse_amount(amount_str)) + if ';' in comment: + comment = comment[comment.index(';')+1:].strip() + + posting = Posting(transaction, ledger.get_account(account_str), parse_amount(amount_str), comment=comment) transaction.postings.append(posting) return ledger diff --git a/ledger_pyreport/model.py b/ledger_pyreport/model.py index 10104cd..fe46063 100644 --- a/ledger_pyreport/model.py +++ b/ledger_pyreport/model.py @@ -52,11 +52,13 @@ class Ledger: return max(prices, key=lambda p: p[0])[2] class Transaction: - def __init__(self, ledger, id, date, description): + def __init__(self, ledger, id, date, description, code=None): self.ledger = ledger self.id = id self.date = date self.description = description + self.code = code + self.postings = [] def __repr__(self): @@ -69,10 +71,11 @@ class Transaction: return '\n'.join(result) class Posting: - def __init__(self, transaction, account, amount): + def __init__(self, transaction, account, amount, comment=None): self.transaction = transaction self.account = account self.amount = Amount(amount) + self.comment = comment def __repr__(self): return ''.format(self.account.name, self.amount.tostr(False))