feat(investments): show exchanged amounts in movements and honor the display switch

Movements without an other-currency transfer side now show the exchanged
amount, and the converted value card follows show_exchanged_amounts.
This commit is contained in:
Herculino Trotta committed 2026-11-11 10:39:06 -03:00
1 parent 7aed2e8574
commit cd33b82b4e
3 files changed
+42 -3

No files matched your search

@@ -447,6 +447,34 @@ class InvestmentViewTests(TestCase):
response = self.client.get(reverse("investments_charts"), **self.HTMX)
self.assertContains(response, "investment-charts-0")
def test_detail_follows_show_exchanged_amounts(self):
brl = self.account.currency
usd = Currency.objects.create(
code="USD", name="Dollar", decimal_places=2, prefix="US$ ",
exchange_currency=brl,
)
ExchangeRate.objects.create(
from_currency=usd, to_currency=brl, rate=D("5"),
date=timezone.make_aware(datetime.datetime(2026, 1, 1)),
)
# Tracked by value in USD: movements and the "In Real" card come from USD's chain
account = Account.all_objects.create(
name="Broker", currency=usd, is_investment=True, owner=self.owner
)
tx(account, IN, "10", datetime.date(2026, 1, 2))
self.client.force_login(self.owner)
url = reverse("investment_detail", args=[account.id])
response = self.client.get(url, **self.HTMX)
self.assertContains(response, 'data-amount="50"', count=3) # 10 USD at 5: ≈ line, card, movement
self.assertContains(response, "In Real")
usd.show_exchanged_amounts = False
usd.save()
response = self.client.get(url, **self.HTMX)
self.assertNotContains(response, 'data-amount="50"')
self.assertNotContains(response, "In Real")
def test_stranger_cannot_see_detail(self):
self.client.force_login(self.stranger)
response = self.client.get(
+8 -2
View File
@@ -209,7 +209,12 @@ def investment_detail(request, account_id):
perf = get_holding_performance(account, with_history=True)
transactions = (
Transaction.objects.filter(account=account)
.select_related("linked_transaction__account__currency", "category")
.select_related(
"linked_transaction__account__currency",
"category",
"account__currency__exchange_currency",
"account__exchange_currency",
)
.order_by("-date", "-id")[:50]
)
income = []
@@ -246,7 +251,8 @@ def investment_detail(request, account_id):
{
"account": account,
"perf": perf,
"home": in_home_currency(perf),
# The valuation currency provides the next hop, so its switch decides
"home": in_home_currency(perf) if perf.currency.show_exchanged_amounts else None,
"transactions": transactions,
"income": income,
"income_entities": list(account.income_entities.all()),
@@ -286,11 +286,16 @@
</td>
<td class="text-end tabular-nums whitespace-nowrap">
<c-amount.display :amount="tx.amount" :prefix="a.prefix" :suffix="a.suffix" :decimal_places="a.decimal_places" color="{% if tx.type == 'IN' %}green{% else %}red{% endif %}" divless></c-amount.display>
{# What the other side paid beats a rate conversion, so it's shown first #}
{% if p and p.account.currency != a %}
<div class="text-xs text-base-content/50">
<c-amount.display :amount="p.amount" :prefix="p.account.currency.prefix" :suffix="p.account.currency.suffix" :decimal_places="p.account.currency.decimal_places" divless></c-amount.display>
</div>
{% endif %}
{% else %}{% with exchanged=tx.displayed_exchanged_amount %}{% if exchanged %}
<div class="text-xs text-base-content/50">
<c-amount.display :amount="exchanged.amount" :prefix="exchanged.prefix" :suffix="exchanged.suffix" :decimal_places="exchanged.decimal_places" divless></c-amount.display>
</div>
{% endif %}{% endwith %}{% endif %}
</td>
<td class="w-8 px-1">
<button class="btn btn-ghost btn-xs btn-square text-base-content/40 hover:text-base-content"