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:
parent
deeec7e2c5
commit
0a7c39adcb
2 changed files with 44 additions and 8 deletions
|
|
@ -1645,6 +1645,31 @@ window.app = Vue.createApp({
|
||||||
isIncomeEntry(entry) {
|
isIncomeEntry(entry) {
|
||||||
return Array.isArray(entry.tags) && entry.tags.includes('income-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) {
|
formatFiat(amount, currency) {
|
||||||
return new Intl.NumberFormat('en-US', {
|
return new Intl.NumberFormat('en-US', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
|
|
|
||||||
|
|
@ -187,16 +187,27 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:body-cell-balance="props">
|
<template v-slot:body-cell-balance="props">
|
||||||
<q-td :props="props">
|
<q-td :props="props">
|
||||||
<div :class="props.row.balance > 0 ? 'text-positive' : 'text-negative'">
|
<!-- User owes you (org), per currency -->
|
||||||
{% raw %}{{ formatSats(Math.abs(props.row.balance)) }} sats{% endraw %}
|
<div v-if="hasOwesYouFiat(props.row.fiat_balances)" class="text-positive">
|
||||||
|
<div v-for="(amount, currency) in owesYouFiat(props.row.fiat_balances)" :key="'oy-' + currency">
|
||||||
|
Owes you {% raw %}{{ formatFiat(amount, currency) }}{% endraw %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.row.fiat_balances && Object.keys(props.row.fiat_balances).length > 0" class="text-caption">
|
<!-- You (org) owe user, per currency -->
|
||||||
<span v-for="(amount, currency) in props.row.fiat_balances" :key="currency" class="q-mr-sm">
|
<div v-if="hasYouOweFiat(props.row.fiat_balances)" class="text-negative">
|
||||||
{% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %}
|
<div v-for="(amount, currency) in youOweFiat(props.row.fiat_balances)" :key="'yo-' + currency">
|
||||||
</span>
|
You owe {% raw %}{{ formatFiat(amount, currency) }}{% endraw %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-caption text-grey">
|
<!-- Fallback when there are no fiat balances (sats-only entries) -->
|
||||||
{% raw %}{{ props.row.balance > 0 ? 'Owes you' : 'You owe' }}{% endraw %}
|
<div v-if="!hasOwesYouFiat(props.row.fiat_balances) && !hasYouOweFiat(props.row.fiat_balances)"
|
||||||
|
:class="props.row.balance > 0 ? 'text-positive' : 'text-negative'">
|
||||||
|
{% raw %}{{ props.row.balance > 0 ? 'Owes you' : 'You owe' }} {{ formatSats(Math.abs(props.row.balance)) }} sats{% endraw %}
|
||||||
|
</div>
|
||||||
|
<!-- Net sats footnote (current-rate-derived; can't be netted across currencies) -->
|
||||||
|
<div v-if="hasOwesYouFiat(props.row.fiat_balances) || hasYouOweFiat(props.row.fiat_balances)"
|
||||||
|
class="text-caption text-grey q-mt-xs">
|
||||||
|
Net (current rates): {% raw %}{{ formatSats(Math.abs(props.row.balance)) }} sats {{ props.row.balance > 0 ? '(receivable)' : '(payable)' }}{% endraw %}
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue