diff --git a/drcr/models.py b/drcr/models.py
index df47ce2..0206d2e 100644
--- a/drcr/models.py
+++ b/drcr/models.py
@@ -111,6 +111,9 @@ class Amount:
def __sub__(self, other):
return self + (-other)
+ def clone(self):
+ return Amount(self.quantity, self.commodity)
+
def format(self, commodity='non_reporting'):
if commodity not in ('non_reporting', 'force', 'hide'):
raise ValueError('Invalid commodity reporting option')
@@ -168,6 +171,11 @@ class Balance:
def __init__(self):
self.amounts = []
+ def clone(self):
+ balance = Balance()
+ balance.amounts = [a.clone() for a in self.amounts]
+ return balance
+
def add(self, rhs):
amount = next((a for a in self.amounts if a.commodity == rhs.commodity), None)
if amount is None:
diff --git a/drcr/templates/transactions.html b/drcr/templates/transactions.html
index f817a59..82bd7c2 100644
--- a/drcr/templates/transactions.html
+++ b/drcr/templates/transactions.html
@@ -1,5 +1,5 @@
{# DrCr: Web-based double-entry bookkeeping framework
- Copyright (C) 2022 Lee Yingtong Li (RunasSudo)
+ Copyright (C) 2022–2024 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
@@ -42,7 +42,6 @@
{% for transaction in transactions %}
{% if transaction.postings|length == 2 %}
{% for posting in transaction.postings if posting.account == account %}
- {% set _ = running_total.__setattr__('quantity', running_total.quantity + posting.amount().as_cost().quantity) %}
{{ transaction.dt.strftime('%Y-%m-%d') }} |
@@ -52,8 +51,8 @@
| {% for p in transaction.postings if p.account != account %}{{ p.account }}{% endfor %} |
{{ posting.amount().as_cost().format() if posting.quantity >= 0 else '' }} |
{{ (posting.amount()|abs).as_cost().format() if posting.quantity < 0 else '' }} |
- {{ (running_total|abs).format() }} |
- {{ 'Dr' if running_total.quantity >= 0 else 'Cr' }} |
+ {{ (running_totals[posting]|abs).format() }} |
+ {{ 'Dr' if running_totals[posting].quantity >= 0 else 'Cr' }} |
{% endfor %}
{% else %}
@@ -69,15 +68,14 @@
|
{% for posting in transaction.postings if posting.account == account %}
- {% set _ = running_total.__setattr__('quantity', running_total.quantity + posting.amount().as_cost().quantity) %}
|
{{ 'Dr' if posting.quantity >= 0 else 'Cr' }} |
{{ account }} |
{{ posting.amount().as_cost().format() if posting.quantity >= 0 else '' }} |
{{ (posting.amount()|abs).as_cost().format() if posting.quantity < 0 else '' }} |
- {{ (running_total|abs).format() }} |
- {{ 'Dr' if running_total.quantity >= 0 else 'Cr' }} |
+ {{ (running_totals[posting]|abs).format() }} |
+ {{ 'Dr' if running_totals[posting].quantity >= 0 else 'Cr' }} |
{% endfor %}
{% for posting in transaction.postings if posting.account != account %}
diff --git a/drcr/templates/transactions_commodity_detail.html b/drcr/templates/transactions_commodity_detail.html
index 424954f..65eeb12 100644
--- a/drcr/templates/transactions_commodity_detail.html
+++ b/drcr/templates/transactions_commodity_detail.html
@@ -1,5 +1,5 @@
{# DrCr: Web-based double-entry bookkeeping framework
- Copyright (C) 2022–2023 Lee Yingtong Li (RunasSudo)
+ Copyright (C) 2022–2024 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
@@ -49,10 +49,7 @@
|
|
- {% for posting in transaction.postings if posting.account == account %}
- {% set _ = running_total.add(posting.amount()) %}
- {% endfor %}
- {% for amount in running_total.amounts %}
+ {% for amount in running_totals[transaction].amounts %}
{# FIXME: Assumes at most one posting per commodity #}
{% for posting in transaction.postings if posting.commodity == amount.commodity and posting.account == account %}
@@ -74,7 +71,6 @@
{% endfor %}
{% endfor %}
- {% set _ = running_total.clean() %}
{% endfor %}
diff --git a/drcr/views.py b/drcr/views.py
index 3e94685..eea17a6 100644
--- a/drcr/views.py
+++ b/drcr/views.py
@@ -83,17 +83,39 @@ def account_transactions():
transactions = [t for t in all_transactions() if any(p.account == request.args['account'] for p in t.postings)]
if request.args.get('commodity_detail', '0') == '1':
+ # Pre-compute running totals
+ # At the level of individual transactions
+ running_totals = {}
+ running_total = Balance()
+ for transaction in sorted(transactions, key=lambda t: t.dt):
+ for posting in transaction.postings:
+ if posting.account == request.args['account']:
+ running_total.add(posting.amount())
+
+ running_totals[transaction] = running_total.clone()
+ running_total.clean()
+
return render_template(
'transactions_commodity_detail.html',
account=request.args['account'],
- running_total=Balance(),
+ running_totals=running_totals,
transactions=reversed(sorted(transactions, key=lambda t: t.dt))
)
else:
+ # Pre-compute running totals
+ # There can be more than one posting per account per transaction, so track the running total at the level of individual postings
+ running_totals = {}
+ running_total = Amount(0, '$')
+ for transaction in sorted(transactions, key=lambda t: t.dt):
+ for posting in transaction.postings:
+ if posting.account == request.args['account']:
+ running_total += posting.amount().as_cost()
+ running_totals[posting] = running_total
+
return render_template(
'transactions.html',
account=request.args['account'],
- running_total=Amount(0, '$'),
+ running_totals=running_totals,
transactions=reversed(sorted(transactions, key=lambda t: t.dt))
)