Implement transaction detail page
This commit is contained in:
parent
c21b8c08f4
commit
63a4b6bdb2
@ -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)
|
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
|
# Template filters
|
||||||
|
|
||||||
@app.template_filter('a')
|
@app.template_filter('a')
|
||||||
|
66
ledger_pyreport/jinja2/transaction.html
Normal file
66
ledger_pyreport/jinja2/transaction.html
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% extends 'base_report.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ transaction.description }}{% endblock %}
|
||||||
|
|
||||||
|
{% block report %}
|
||||||
|
<h1 style="margin-bottom: 1em;">Transaction</h1>
|
||||||
|
|
||||||
|
<table class="ledger" style="margin-bottom: 1em;">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5em;">Date</th>
|
||||||
|
<th style="width: 5em;">Code</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ transaction.date.strftime('%Y-%m-%d') }}</td>
|
||||||
|
<td>{{ transaction.code }}</td>
|
||||||
|
<td>{{ transaction.description }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="ledger">
|
||||||
|
<tr>
|
||||||
|
{#<th style="width: 5em;">Date</th>#}
|
||||||
|
<th>Description</th>
|
||||||
|
<th style="max-width: 8em;">Account</th>
|
||||||
|
<th class="h1" style="text-align: right; width: 5em;">Dr</th>
|
||||||
|
<th class="h1" style="text-align: right; width: 5em;">Cr</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ posting.comment }}</td>
|
||||||
|
<td>{{ (posting.account.name|e).__str__().replace(':', ':<wbr>')|safe }}</td>
|
||||||
|
<td style="text-align: right;">{% if amount > 0 %}<span title="{{ posting.amount.tostr(False) }}">{{ amount|b }}</span>{% endif %}</td>
|
||||||
|
<td style="text-align: right;">{% if amount < 0 %}<span title="{{ (-posting.amount).tostr(False) }}">{{ -amount|b }}</span>{% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<tr class="total">
|
||||||
|
<td>Total</td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right;">{{ total_dr|b }}</td>
|
||||||
|
<td style="text-align: right;">{{ -total_cr|b }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
@ -38,7 +38,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th style="width: 5em;">Date</th>
|
<th style="width: 5em;">Date</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th style="max-width: 8em;">Account</th>
|
<th style="max-width: 8em;">{% if account %}Related {% endif %}Account</th>
|
||||||
<th class="h1" style="text-align: right; width: 5em;">Dr</th>
|
<th class="h1" style="text-align: right; width: 5em;">Dr</th>
|
||||||
<th class="h1" style="text-align: right; width: 5em;">Cr</th>
|
<th class="h1" style="text-align: right; width: 5em;">Cr</th>
|
||||||
{% if account %}<th class="h1" style="text-align: right; width: 6em;">Balance</th>{% endif %}
|
{% if account %}<th class="h1" style="text-align: right; width: 6em;">Balance</th>{% endif %}
|
||||||
@ -69,9 +69,10 @@
|
|||||||
{% for transaction in transactions %}
|
{% for transaction in transactions %}
|
||||||
{% for posting in transaction.postings if posting.account != account %}
|
{% for posting in transaction.postings if posting.account != account %}
|
||||||
{% set amount = posting.exchange(report_currency, transaction.date) %}
|
{% set amount = posting.exchange(report_currency, transaction.date) %}
|
||||||
|
{% set trn_url = '/transaction?' + {'tid': transaction.id, 'cash': 'on' if cash else ''}|urlencode %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% if loop.first %}{{ transaction.date.strftime('%Y-%m-%d') }}{% endif %}</td>
|
<td>{% if loop.first %}{% if transaction.id %}<a href="{{ trn_url }}">{% endif %}{{ transaction.date.strftime('%Y-%m-%d') }}{% if transaction.id %}</a>{% endif %}{% endif %}</td>
|
||||||
<td>{% if loop.first %}{{ transaction.description }}{% endif %}</td>
|
<td>{% if loop.first %}{% if transaction.id %}<a href="{{ trn_url }}">{% endif %}{{ transaction.description }}{% if transaction.id %}</a>{% endif %}{% endif %}</td>
|
||||||
<td><a href="/transactions?{{ {'date': date.strftime('%Y-%m-%d'), 'pstart': pstart.strftime('%Y-%m-%d'), 'account': posting.account.name, 'cash': 'on' if cash else ''}|urlencode }}">{{ (posting.account.name|e).__str__().replace(':', ':<wbr>')|safe }}</a></td>
|
<td><a href="/transactions?{{ {'date': date.strftime('%Y-%m-%d'), 'pstart': pstart.strftime('%Y-%m-%d'), 'account': posting.account.name, 'cash': 'on' if cash else ''}|urlencode }}">{{ (posting.account.name|e).__str__().replace(':', ':<wbr>')|safe }}</a></td>
|
||||||
{% if account %}
|
{% if account %}
|
||||||
{# Reverse Dr/Cr so it's from the "perspective" of this account #}
|
{# Reverse Dr/Cr so it's from the "perspective" of this account #}
|
||||||
|
@ -54,14 +54,15 @@
|
|||||||
{% set ns.balance = opening_balance %}
|
{% set ns.balance = opening_balance %}
|
||||||
|
|
||||||
{% for transaction in transactions %}
|
{% 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 %}
|
{% for posting in transaction.postings if posting.account == account %}
|
||||||
{% set ns.balance = ns.balance + posting.amount %}
|
{% set ns.balance = ns.balance + posting.amount %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for amount in ns.balance.amounts %}
|
{% for amount in ns.balance.amounts %}
|
||||||
{% set posting = matching_posting(transaction, amount) %}
|
{% set posting = matching_posting(transaction, amount) %}
|
||||||
<tr{% if loop.first %} style="border-top: 1px solid black;"{% endif %}>
|
<tr{% if loop.first %} style="border-top: 1px solid black;"{% endif %}>
|
||||||
<td>{% if loop.first %}{{ transaction.date.strftime('%Y-%m-%d') }}{% endif %}</td>
|
<td>{% if loop.first %}{% if transaction.id %}<a href="{{ trn_url }}">{% endif %}{{ transaction.date.strftime('%Y-%m-%d') }}{% if transaction.id %}</a>{% endif %}{% endif %}</td>
|
||||||
<td>{% if loop.first %}{{ transaction.description }}{% endif %}</td>
|
<td>{% if loop.first %}{% if transaction.id %}<a href="{{ trn_url }}">{% endif %}{{ transaction.description }}{% if transaction.id %}</a>{% endif %}{% endif %}</td>
|
||||||
{% if posting %}
|
{% if posting %}
|
||||||
<td>{% if posting.amount >= 0 %}Dr{% else %}Cr{% endif %}</td>
|
<td>{% if posting.amount >= 0 %}Dr{% else %}Cr{% endif %}</td>
|
||||||
<td style="text-align: right;"><span title="{{ posting.amount.tostr(False) }}">{{ posting.amount|abs|bc }}</span></td>
|
<td style="text-align: right;"><span title="{{ posting.amount.tostr(False) }}">{{ posting.amount|abs|bc }}</span></td>
|
||||||
|
@ -36,6 +36,8 @@ def run_ledger(*args):
|
|||||||
return stdout
|
return stdout
|
||||||
|
|
||||||
def run_ledger_date(date, *args):
|
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)
|
return run_ledger('--end', (date + timedelta(days=1)).strftime('%Y-%m-%d'), *args)
|
||||||
|
|
||||||
# General financial logic
|
# General financial logic
|
||||||
@ -48,6 +50,8 @@ def financial_year(date):
|
|||||||
|
|
||||||
# Ledger logic
|
# Ledger logic
|
||||||
|
|
||||||
|
csv.register_dialect('ledger', doublequote=False, escapechar='\\')
|
||||||
|
|
||||||
def parse_amount(amount):
|
def parse_amount(amount):
|
||||||
if '{' in amount:
|
if '{' in amount:
|
||||||
amount_str = amount[:amount.index('{')].strip()
|
amount_str = amount[:amount.index('{')].strip()
|
||||||
@ -60,7 +64,7 @@ def parse_amount(amount):
|
|||||||
# Currency follows number
|
# Currency follows number
|
||||||
bits = amount_str.split()
|
bits = amount_str.split()
|
||||||
amount_num = Decimal(bits[0])
|
amount_num = Decimal(bits[0])
|
||||||
currency = Currency(bits[1], False)
|
currency = Currency(bits[1].strip('"'), False)
|
||||||
else:
|
else:
|
||||||
# Currency precedes number
|
# Currency precedes number
|
||||||
currency = Currency(amount_str[0], True)
|
currency = Currency(amount_str[0], True)
|
||||||
@ -76,9 +80,9 @@ def get_pricedb():
|
|||||||
|
|
||||||
prices = []
|
prices = []
|
||||||
|
|
||||||
reader = csv.reader(output.splitlines())
|
reader = csv.reader(output.splitlines(), dialect='ledger')
|
||||||
for date_str, currency, price_str in reader:
|
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
|
return prices
|
||||||
|
|
||||||
@ -86,18 +90,21 @@ def raw_transactions_at_date(date):
|
|||||||
ledger = Ledger(date)
|
ledger = Ledger(date)
|
||||||
ledger.prices = get_pricedb()
|
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())
|
reader = csv.reader(output.splitlines(), dialect='ledger')
|
||||||
for trn_id, date_str, payee, account_str, amount_str in reader:
|
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:
|
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)
|
ledger.transactions.append(transaction)
|
||||||
else:
|
else:
|
||||||
# Transaction ID matches: continuation of previous transaction
|
# Transaction ID matches: continuation of previous transaction
|
||||||
transaction = ledger.transactions[-1]
|
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)
|
transaction.postings.append(posting)
|
||||||
|
|
||||||
return ledger
|
return ledger
|
||||||
|
@ -52,11 +52,13 @@ class Ledger:
|
|||||||
return max(prices, key=lambda p: p[0])[2]
|
return max(prices, key=lambda p: p[0])[2]
|
||||||
|
|
||||||
class Transaction:
|
class Transaction:
|
||||||
def __init__(self, ledger, id, date, description):
|
def __init__(self, ledger, id, date, description, code=None):
|
||||||
self.ledger = ledger
|
self.ledger = ledger
|
||||||
self.id = id
|
self.id = id
|
||||||
self.date = date
|
self.date = date
|
||||||
self.description = description
|
self.description = description
|
||||||
|
self.code = code
|
||||||
|
|
||||||
self.postings = []
|
self.postings = []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -69,10 +71,11 @@ class Transaction:
|
|||||||
return '\n'.join(result)
|
return '\n'.join(result)
|
||||||
|
|
||||||
class Posting:
|
class Posting:
|
||||||
def __init__(self, transaction, account, amount):
|
def __init__(self, transaction, account, amount, comment=None):
|
||||||
self.transaction = transaction
|
self.transaction = transaction
|
||||||
self.account = account
|
self.account = account
|
||||||
self.amount = Amount(amount)
|
self.amount = Amount(amount)
|
||||||
|
self.comment = comment
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Posting "{}" {}>'.format(self.account.name, self.amount.tostr(False))
|
return '<Posting "{}" {}>'.format(self.account.name, self.amount.tostr(False))
|
||||||
|
Reference in New Issue
Block a user