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
+
+
+
+ Date |
+ Code |
+ Description |
+
+
+ {{ transaction.date.strftime('%Y-%m-%d') }} |
+ {{ transaction.code }} |
+ {{ transaction.description }} |
+
+
+
+
+
+ {#Date | #}
+ Description |
+ Account |
+ Dr |
+ Cr |
+
+
+ {% 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 %}
+
+ {{ posting.comment }} |
+ {{ (posting.account.name|e).__str__().replace(':', ':')|safe }} |
+ {% if amount > 0 %}{{ amount|b }}{% endif %} |
+ {% if amount < 0 %}{{ -amount|b }}{% endif %} |
+
+ {% endfor %}
+
+
+ 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))