Investment Dashboard: multi-currency amounts displayed inconsistently #36
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_biz#36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The Investment Dashboard mixes currency symbols across its sections, making the same underlying amounts appear with different symbols depending on where you look.
Current behaviour
The same £948K investment is shown as €948K in the Invested summary card, £948K in By Company, and £948K in the recent transactions list — three representations of the same value with two different currency symbols.
How "Total Invested" is calculated today
Investedsums allInvestment-type transaction amounts and formats the total using abase_currencyheuristic (most common transaction currency across all transactions). Because the heuristic samples all three transactions it should resolve toGBP, but the summary card still renders as€948K. This means either the heuristic is not resolving correctly for this data or the running service has not been restarted after the last fix attempt.Root cause
The dashboard has three independent data sources each carrying their own currency field:
Transaction.currency(e.g.gbp,eur)Instrument.currency(e.g.usd,eur)No normalisation happens between these sources, so a fund with GBP transactions and USD instruments shows different symbols in every section of the same page.
Proposed solutions
Option A — Per-currency breakdown (preferred)
Show each currency group separately in summary cards and tables:
Honest, no exchange-rate data needed, works naturally for multi-currency funds.
Option B — Single base currency with conversion
Choose a base currency per space (configurable, default EUR) and convert all amounts using stored or live exchange rates.
Option C — Single currency enforcement
Validate at create-time that all transactions and instruments in a space use the same currency.
Acceptance criteria
InvestedandNet Positionshow the same symbol(s) as the transactions they aggregate@marionrvrn can you look at which I should implement? either display currencies separately or sum them using conversion. conversion will add extra dependencies.
Let's go with option A and display currencies separately. No need to add exchange rate dependencies at this stage and it's more accurate to show amounts in their real currencies anyway
Fix the Investment Dashboard so that the four summary metric cards (Invested, Repaid, Income, Net Position) display amounts grouped by currency rather than collapsing all amounts into a single number under a `base_currency` heuristic.
Implementation Spec: Issue #36 — Multi-Currency Fix (Option A)
Objective
Fix the Investment Dashboard so that the four summary metric cards (Invested, Repaid, Income, Net Position) display amounts grouped by currency rather than collapsing all amounts into a single number under a
base_currencyheuristic.Root Cause
All broken behaviour lives in
InvestmentDashboardTemplate::render()incrates/hero_biz_ui/src/web/templates/mod.rs.base_currencyheuristic — picks the most frequent currency across all transactions and uses it as a single label, producing wrong symbols when currencies are mixed.total_invested,total_repaid, etc. sum amounts across all currencies as if they were the same.by_companyandby_type— useor_insert((0.0, first_currency_seen)), so a company's second currency's amounts get the wrong symbol.No handler changes are needed.
Files to Modify
crates/hero_biz_ui/src/web/templates/mod.rs(only theInvestmentDashboardTemplate::render()method, ~lines 6034–6366)Implementation Plan
Step 1 — Replace scalar totals with per-currency maps
Remove
base_currency,total_invested,total_repaid,total_interest,total_dividends,net_position. Replace withHashMap<String, f64>maps:invested_by_currency,repaid_by_currency,income_by_currency,net_by_currency. Build them by iteratingself.transactionsonce, grouping bytx.currency.to_uppercase()and transaction type.Dependencies: none
Step 2 — Add
render_currency_breakdownhelper closureAdd a local closure just before the HTML format string that takes a
&HashMap<String, f64>and returns a sorted multi-line HTML string offormat_amount_compactcalls. Falls back to"—"for empty maps.Dependencies: Step 1
Step 3 — Fix the four summary cards in the HTML format string
Replace the four
format_amount_compact(total_X, &base_currency)calls in the format! arguments withrender_currency_breakdown(&X_by_currency). Change<span class="display-6">wrappers to<div class="mt-2 text-end fs-5 fw-bold">so that multiple currency lines stack correctly.Dependencies: Steps 1, 2
Step 4 — Fix
by_companyto group by(company, currency)Replace
HashMap<String, (f64, String)>withHashMap<String, HashMap<String, f64>>. Render each company row with stacked currency amounts usingrender_currency_breakdown.Dependencies: Step 2
Step 5 — Fix
by_typeequivalentlySame as Step 4 but for instrument type grouping.
Dependencies: Step 2
Step 6 — Handle the ROI card
Compute
all_currencies(HashSet). Show ROI only when a single currency is present; otherwise render "N/A (multiple currencies)".Dependencies: Step 1
Step 7 — Remove dead variables and verify compile
Remove all now-unused bindings to fix compiler warnings. Run
cargo check.Dependencies: Steps 1–6
Acceptance Criteria
£948K(not€948K) for a GBP-only portfolioNotes
format_amount_compactandcurrency_symbolare correct as-is and handle GBP, USD, CHF, JPY, CNY, EUR.to_uppercase()when building currency map keysImplementation complete
Changes made
File modified:
crates/hero_biz_ui/src/web/templates/mod.rsAll changes are confined to
InvestmentDashboardTemplate::render().base_currencyheuristic (majority-vote currency picker) and all cross-currency scalar sums (total_invested,total_repaid,total_interest,total_dividends,net_position).HashMap<String, f64>maps:invested_by_currency,repaid_by_currency,income_by_currency,net_by_currency. Built in a single pass overself.transactionsgrouped bytx.currency.to_uppercase()and transaction type.render_currency_breakdownclosure that renders a per-currency map as sorted, stacked<div class="fw-bold">lines — one line per currency, alphabetical order for deterministic output.render_currency_breakdowninstead of a singleformat_amount_compactcall. Changed<span class="display-6">to<div class="text-end">so multiple lines stack correctly.by_company: replacedHashMap<String, (f64, String)>(which silently used the first-seen currency for a company) withHashMap<String, HashMap<String, f64>>(company → currency → sum). Rows now render stacked amounts per currency using the same helper.by_typeidentically for instrument type groupings.Test results
cargo checkandcargo testboth pass with no warnings.Acceptance criteria