diff --git a/ledger_pyreport/__init__.py b/ledger_pyreport/__init__.py
index 519d3c9..1077540 100644
--- a/ledger_pyreport/__init__.py
+++ b/ledger_pyreport/__init__.py
@@ -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('
'.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
diff --git a/ledger_pyreport/jinja2/base_report.html b/ledger_pyreport/jinja2/base_report.html
index 3f65983..e60c8ba 100644
--- a/ledger_pyreport/jinja2/base_report.html
+++ b/ledger_pyreport/jinja2/base_report.html
@@ -19,7 +19,11 @@
{% extends 'base.html' %}
{% block body %}
- Home
+
{% block report %}
{% endblock %}
diff --git a/ledger_pyreport/jinja2/transactions.html b/ledger_pyreport/jinja2/transactions.html
index 858372e..01a991c 100644
--- a/ledger_pyreport/jinja2/transactions.html
+++ b/ledger_pyreport/jinja2/transactions.html
@@ -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() }}
+ Show commodity detail
+{% endblock %}
+
{% block report %}
{% if account %}
Account Transactions
diff --git a/ledger_pyreport/jinja2/transactions_commodity.html b/ledger_pyreport/jinja2/transactions_commodity.html
new file mode 100644
index 0000000..eb8c5fd
--- /dev/null
+++ b/ledger_pyreport/jinja2/transactions_commodity.html
@@ -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 .
+#}
+
+{% extends 'base_report.html' %}
+
+{% block title %}Account Transactions as at {{ date.strftime('%d %B %Y') }}{% endblock %}
+
+{% block report %}
+ Account Transactions
+ For {{ account.name }}
+ For the {{ period }}
+
+
+
+ Date |
+ Description |
+ |
+ Amount |
+ |
+ Balance |
+ |
+ |
+
+
+ {% 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 %}
+
+ {% if loop.first %}{{ pstart.strftime('%Y-%m-%d') }}{% endif %} |
+ {% if loop.first %}Opening Balance{% endif %} |
+ |
+ |
+ |
+ {{ amount|abs|bc }} |
+ {% if amount.currency.price %}{{ '{' + amount.currency.price|bc + '}' }}{% endif %} |
+ {% if amount >= 0 %}Dr{% else %}Cr{% endif %} |
+
+ {% 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) %}
+
+ {% if loop.first %}{{ transaction.date.strftime('%Y-%m-%d') }}{% endif %} |
+ {% if loop.first %}{{ transaction.description }}{% endif %} |
+ {% if posting %}
+ {% if posting.amount >= 0 %}Dr{% else %}Cr{% endif %} |
+ {{ posting.amount|abs|bc }} |
+ {% if posting.amount.currency.price %}{{ '{' + posting.amount.currency.price|bc + '}' }}{% endif %} |
+ {% else %}
+ |
+ {% endif %}
+ {{ amount|abs|bc }} |
+ {% if amount.currency.price %}{{ '{' + amount.currency.price|bc + '}' }}{% endif %} |
+ {% if amount >= 0 %}Dr{% else %}Cr{% endif %} |
+
+ {% endfor %}
+ {% set ns.balance = ns.balance.clean() %}
+ {% endfor %}
+
+ {% for amount in closing_balance.amounts %}
+
+ {% if loop.first %}{{ date.strftime('%Y-%m-%d') }}{% endif %} |
+ {% if loop.first %}Closing Balance{% endif %} |
+ |
+ |
+ |
+ {{ amount|abs|bc }} |
+ {% if amount.currency.price %}{{ '{' + amount.currency.price|bc + '}' }}{% endif %} |
+ {% if amount >= 0 %}Dr{% else %}Cr{% endif %} |
+
+ {% endfor %}
+
+{% endblock %}
diff --git a/ledger_pyreport/model.py b/ledger_pyreport/model.py
index 3c807ab..e6aad20 100644
--- a/ledger_pyreport/model.py
+++ b/ledger_pyreport/model.py
@@ -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):
diff --git a/ledger_pyreport/static/main.css b/ledger_pyreport/static/main.css
index d23e6d8..be6fcd8 100644
--- a/ledger_pyreport/static/main.css
+++ b/ledger_pyreport/static/main.css
@@ -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 {