Show Outstanding Balances split per direction per currency

When a user has entries in multiple currencies that go in opposite
directions — e.g. an income entry in EUR (user owes the org) and an
expense entry in CAD (org owes user) — the previous row collapsed
both into a single "Owes you" / "You owe" label driven by the net
sats balance. The fiat amounts were displayed via Math.abs(), hiding
the per-currency signs the backend already returns, so the row was
actively misleading: it showed €200 and CA$300 under one direction
when in reality they point in opposite directions.

Render up to two grouped lines instead — "Owes you €200.00" and
"You owe CA$300.00" — using new owesYouFiat / youOweFiat helpers
that filter the signed fiat_balances dict by sign. Net sats stays
as a small caption with an explicit "(receivable)"/"(payable)"
qualifier, since sats can be netted but distinct fiat currencies
can't without a spot rate. Falls back to the old single-line render
when there are no fiat balances (sats-only entries).
This commit is contained in:
Padreug 2026-05-17 20:12:51 +02:00
commit 0a7c39adcb
2 changed files with 44 additions and 8 deletions

View file

@ -1645,6 +1645,31 @@ window.app = Vue.createApp({
isIncomeEntry(entry) {
return Array.isArray(entry.tags) && entry.tags.includes('income-entry')
},
// Per-currency split for multi-currency balances. Sign convention from the
// super-user perspective: positive fiat = user owes Libra (Receivable),
// negative fiat = Libra owes user (Payable). Distinct currencies can't be
// netted across each other (no spot rate), so we render them grouped by
// direction instead of one collapsed label.
owesYouFiat(fiatBalances) {
if (!fiatBalances) return {}
return Object.fromEntries(
Object.entries(fiatBalances).filter(([_, amount]) => Number(amount) > 0.005)
)
},
youOweFiat(fiatBalances) {
if (!fiatBalances) return {}
return Object.fromEntries(
Object.entries(fiatBalances)
.filter(([_, amount]) => Number(amount) < -0.005)
.map(([cur, amount]) => [cur, Math.abs(Number(amount))])
)
},
hasOwesYouFiat(fiatBalances) {
return Object.keys(this.owesYouFiat(fiatBalances)).length > 0
},
hasYouOweFiat(fiatBalances) {
return Object.keys(this.youOweFiat(fiatBalances)).length > 0
},
formatFiat(amount, currency) {
return new Intl.NumberFormat('en-US', {
style: 'currency',