Implement report showing transactions by commodity
This commit is contained in:
parent
91343abd3b
commit
c379a28fbd
@ -169,6 +169,34 @@ def transactions():
|
||||
|
||||
return flask.render_template('transactions.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)
|
||||
|
||||
@app.route('/transactions_commodity')
|
||||
def transactions_commodity():
|
||||
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)
|
||||
cash = flask.request.args.get('cash', False)
|
||||
|
||||
report_currency = Currency(*config['report_currency'])
|
||||
|
||||
# General ledger
|
||||
l = ledger.raw_transactions_at_date(date)
|
||||
if cash:
|
||||
l = accounting.ledger_to_cash(l, report_currency)
|
||||
|
||||
# Unrealized gains
|
||||
l = accounting.add_unrealized_gains(accounting.trial_balance(l, date, pstart), report_currency).ledger
|
||||
|
||||
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)
|
||||
closing_balance = accounting.trial_balance(l, date, pstart).get_balance(account)
|
||||
|
||||
def matching_posting(transaction, amount):
|
||||
return next((p for p in transaction.postings if p.account == account and p.amount.currency == amount.currency), None)
|
||||
|
||||
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)
|
||||
|
||||
# Template filters
|
||||
|
||||
@app.template_filter('a')
|
||||
@ -185,9 +213,12 @@ def filter_amount_positive(amt):
|
||||
return flask.Markup('{:,.2f}'.format(amt.amount).replace(',', ' '))
|
||||
#return flask.Markup('{:,.2f} {}'.format(amt.amount, amt.currency.name).replace(',', ' '))
|
||||
|
||||
@app.template_filter('bb')
|
||||
def filter_balance_positive(balance):
|
||||
return flask.Markup('<br>'.join(filter_amount_positive(a) for a in balance.amounts))
|
||||
@app.template_filter('bc')
|
||||
def filter_currency_positive(amt):
|
||||
if amt.currency.is_prefix:
|
||||
return flask.Markup('{}{:,.2f}'.format(amt.currency.name, amt.amount).replace(',', ' '))
|
||||
else:
|
||||
return flask.Markup('{:,.2f} {}'.format(amt.amount, amt.currency.name).replace(',', ' '))
|
||||
|
||||
# Debug views
|
||||
|
||||
|
@ -19,7 +19,11 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block body %}
|
||||
<a class="homelink" href="/">Home</a>
|
||||
<div class="nav-header">
|
||||
{% block links %}
|
||||
<a href="/">Home</a>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% block report %}
|
||||
{% endblock %}
|
||||
|
@ -20,6 +20,11 @@
|
||||
|
||||
{% block title %}{% if account %}Account Transactions{% else %}General Ledger{% endif %} as at {{ date.strftime('%d %B %Y') }}{% endblock %}
|
||||
|
||||
{% block links %}
|
||||
{{ super() }}
|
||||
<a href="/transactions_commodity?{{ {'date': date.strftime('%Y-%m-%d'), 'pstart': pstart.strftime('%Y-%m-%d'), 'account': account.name, 'cash': 'on' if cash else ''}|urlencode }}">Show commodity detail</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block report %}
|
||||
{% if account %}
|
||||
<h1>Account Transactions</h1>
|
||||
|
93
ledger_pyreport/jinja2/transactions_commodity.html
Normal file
93
ledger_pyreport/jinja2/transactions_commodity.html
Normal file
@ -0,0 +1,93 @@
|
||||
{#
|
||||
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 %}Account Transactions as at {{ date.strftime('%d %B %Y') }}{% endblock %}
|
||||
|
||||
{% block report %}
|
||||
<h1>Account Transactions</h1>
|
||||
<h2 style="margin-bottom: 0;">For {{ account.name }}</h2>
|
||||
<h2>For the {{ period }}</h2>
|
||||
|
||||
<table class="ledger">
|
||||
<tr>
|
||||
<th style="width: 5em;">Date</th>
|
||||
<th>Description</th>
|
||||
<th class="h1" style="width: 1em;"></th>
|
||||
<th class="h1" style="text-align: right; width: 5em;">Amount</th>
|
||||
<th class="h1" style="width: 4em;"></th>
|
||||
<th class="h1" style="text-align: right; width: 5em;">Balance</th>
|
||||
<th class="h1" style="width: 4em;"></th>
|
||||
<th class="h1" style="width: 1em;"></th>
|
||||
</tr>
|
||||
|
||||
{% set ns = namespace(balance=None) %}
|
||||
{% set prevlink = '/transactions_commodity?' + {'date': (pstart - timedelta(days=1)).strftime('%Y-%m-%d'), 'pstart': pstart.replace(year=pstart.year-1).strftime('%Y-%m-%d'), 'account': account.name, 'cash': 'on' if cash else ''}|urlencode %}
|
||||
{% for amount in opening_balance.amounts %}
|
||||
<tr class="total">
|
||||
<td>{% if loop.first %}<a href="{{ prevlink }}">{{ pstart.strftime('%Y-%m-%d') }}</a>{% endif %}</td>
|
||||
<td>{% if loop.first %}<a href="{{ prevlink }}">Opening Balance</a>{% endif %}</td>
|
||||
<td style="text-align: right;"></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td style="text-align: right;"><a href="{{ prevlink }}">{{ amount|abs|bc }}</a></td>
|
||||
<td><a href="{{ prevlink }}">{% if amount.currency.price %}{{ '{' + amount.currency.price|bc + '}' }}{% endif %}</a></td>
|
||||
<td>{% if amount >= 0 %}Dr{% else %}Cr{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% set ns.balance = opening_balance %}
|
||||
|
||||
{% for transaction in transactions %}
|
||||
{% 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) %}
|
||||
<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 %}{{ transaction.description }}{% endif %}</td>
|
||||
{% if posting %}
|
||||
<td>{% if posting.amount >= 0 %}Dr{% else %}Cr{% endif %}</td>
|
||||
<td style="text-align: right;">{{ posting.amount|abs|bc }}</td>
|
||||
<td>{% if posting.amount.currency.price %}{{ '{' + posting.amount.currency.price|bc + '}' }}{% endif %}</td>
|
||||
{% else %}
|
||||
<td colspan="3"></td>
|
||||
{% endif %}
|
||||
<td style="text-align: right;">{{ amount|abs|bc }}</td>
|
||||
<td>{% if amount.currency.price %}{{ '{' + amount.currency.price|bc + '}' }}{% endif %}</td>
|
||||
<td>{% if amount >= 0 %}Dr{% else %}Cr{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% set ns.balance = ns.balance.clean() %}
|
||||
{% endfor %}
|
||||
|
||||
{% for amount in closing_balance.amounts %}
|
||||
<tr class="total explicit-rules" style="{% if loop.first %}border-top: 1pt solid black;{% endif %}{% if loop.last %}border-bottom: 1pt solid black;{% endif %}">
|
||||
<td>{% if loop.first %}{{ date.strftime('%Y-%m-%d') }}{% endif %}</td>
|
||||
<td>{% if loop.first %}Closing Balance{% endif %}</td>
|
||||
<td style="text-align: right;"></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td style="text-align: right;">{{ amount|abs|bc }}</td>
|
||||
<td>{% if amount.currency.price %}{{ '{' + amount.currency.price|bc + '}' }}{% endif %}</td>
|
||||
<td>{% if amount >= 0 %}Dr{% else %}Cr{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
@ -176,10 +176,21 @@ class Amount:
|
||||
|
||||
def __neg__(self):
|
||||
return Amount(-self.amount, self.currency)
|
||||
def __abs__(self):
|
||||
return Amount(abs(self.amount), self.currency)
|
||||
|
||||
@compatible_currency
|
||||
def __eq__(self, other):
|
||||
return self.amount == other
|
||||
if isinstance(other, Amount):
|
||||
if self.amount == 0 and other.amount == 0:
|
||||
return True
|
||||
if other.currency != self.currency:
|
||||
return False
|
||||
return self.amount == other.amount
|
||||
|
||||
if other == 0:
|
||||
return self.amount == 0
|
||||
|
||||
raise TypeError('Cannot compare Amount with non-zero number')
|
||||
@compatible_currency
|
||||
def __ne__(self, other):
|
||||
return self.amount != other
|
||||
@ -237,6 +248,9 @@ class Balance:
|
||||
new_amount = next((a for a in new_amounts if a.currency == amount.currency), None)
|
||||
return Balance(new_amounts)
|
||||
|
||||
def clean(self):
|
||||
return Balance([a for a in self.amounts if a != 0])
|
||||
|
||||
def exchange(self, currency, is_cost, date=None, ledger=None):
|
||||
result = Amount(0, currency)
|
||||
for amount in self.amounts:
|
||||
@ -269,18 +283,27 @@ class Balance:
|
||||
new_amount = Amount(0, amount.currency)
|
||||
new_amounts.append(new_amount)
|
||||
new_amount.amount += amount.amount
|
||||
|
||||
#if new_amount == 0:
|
||||
# new_amounts.remove(new_amount)
|
||||
elif isinstance(other, Amount):
|
||||
new_amount = next((a for a in new_amounts if a.currency == other.currency), None)
|
||||
if new_amount is None:
|
||||
new_amount = Amount(0, other.currency)
|
||||
new_amounts.append(new_amount)
|
||||
new_amount.amount += other.amount
|
||||
|
||||
#if new_amount == 0:
|
||||
# new_amounts.remove(new_amount)
|
||||
elif other == 0:
|
||||
pass
|
||||
else:
|
||||
raise Exception('NYI')
|
||||
|
||||
return Balance(new_amounts)
|
||||
|
||||
def __sub__(self, other):
|
||||
return self + (-other)
|
||||
|
||||
class Currency:
|
||||
def __init__(self, name, is_prefix, price=None):
|
||||
|
@ -52,7 +52,8 @@ table.ledger th.h1 {
|
||||
|
||||
table.ledger tr.total td {
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
table.ledger tr.total:not(.explicit-rules) td {
|
||||
border-top: 1pt solid black;
|
||||
border-bottom: 1pt solid black;
|
||||
}
|
||||
@ -72,12 +73,18 @@ table.ledger a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.homelink {
|
||||
.nav-header {
|
||||
color: #888;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.nav-header a {
|
||||
color: #888;
|
||||
}
|
||||
.nav-header a:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media screen {
|
||||
body {
|
||||
|
Reference in New Issue
Block a user