Reverts balance perspective to castle's view
Changes the displayed balance perspective to reflect the castle's point of view instead of the user's. This involves: - Displaying balances as positive when the user owes the castle - Displaying balances as negative when the castle owes the user. This change affects how balances are calculated and displayed in both the backend logic and the frontend templates.
This commit is contained in:
parent
0f24833e02
commit
5c1c7b1b05
2 changed files with 39 additions and 41 deletions
|
|
@ -172,18 +172,18 @@ class FavaClient:
|
|||
|
||||
async def get_user_balance(self, user_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get user's total balance (what castle owes user).
|
||||
Get user's balance from castle's perspective.
|
||||
|
||||
Aggregates:
|
||||
- Liabilities:Payable:User-{user_id} (negative balance = castle owes)
|
||||
- Assets:Receivable:User-{user_id} (positive balance = user owes)
|
||||
- Liabilities:Payable:User-{user_id} (negative = castle owes user)
|
||||
- Assets:Receivable:User-{user_id} (positive = user owes castle)
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
|
||||
Returns:
|
||||
{
|
||||
"balance": int (sats, positive = castle owes user),
|
||||
"balance": int (sats, positive = user owes castle, negative = castle owes user),
|
||||
"fiat_balances": {"EUR": Decimal("100.50")},
|
||||
"accounts": [list of account dicts with balances]
|
||||
}
|
||||
|
|
@ -228,12 +228,11 @@ class FavaClient:
|
|||
for cost_str, amount in sats_positions.items():
|
||||
amount_int = int(amount)
|
||||
|
||||
# For user balance perspective, negate Beancount balance
|
||||
# - Payable (Liability): negative in Beancount → positive (castle owes user)
|
||||
# - Receivable (Asset): positive in Beancount → negative (user owes castle)
|
||||
adjusted_amount = -amount_int
|
||||
total_sats += adjusted_amount
|
||||
account_balance["sats"] += adjusted_amount
|
||||
# Use Beancount balance as-is (castle's perspective)
|
||||
# - Receivable (Asset): positive = user owes castle
|
||||
# - Payable (Liability): negative = castle owes user
|
||||
total_sats += amount_int
|
||||
account_balance["sats"] += amount_int
|
||||
|
||||
# Extract fiat amount from cost basis
|
||||
# Format: "100.00 EUR" or "{100.00 EUR}"
|
||||
|
|
@ -248,22 +247,19 @@ class FavaClient:
|
|||
if fiat_currency not in fiat_balances:
|
||||
fiat_balances[fiat_currency] = Decimal(0)
|
||||
|
||||
# Apply same sign adjustment to fiat
|
||||
# Cost basis is always positive, derive sign from amount
|
||||
# Apply same sign as sats amount
|
||||
if amount_int < 0:
|
||||
fiat_amount = -fiat_amount
|
||||
adjusted_fiat = -fiat_amount
|
||||
fiat_balances[fiat_currency] += adjusted_fiat
|
||||
fiat_balances[fiat_currency] += fiat_amount
|
||||
except (ValueError, IndexError):
|
||||
logger.warning(f"Could not parse cost basis: {cost_str}")
|
||||
|
||||
elif isinstance(sats_positions, (int, float)):
|
||||
# Simple number (no cost basis)
|
||||
amount_int = int(sats_positions)
|
||||
# Negate Beancount balance for user perspective
|
||||
adjusted_amount = -amount_int
|
||||
total_sats += adjusted_amount
|
||||
account_balance["sats"] += adjusted_amount
|
||||
# Use Beancount balance as-is
|
||||
total_sats += amount_int
|
||||
account_balance["sats"] += amount_int
|
||||
|
||||
accounts.append(account_balance)
|
||||
|
||||
|
|
@ -341,31 +337,33 @@ class FavaClient:
|
|||
continue
|
||||
|
||||
import re
|
||||
# Extract SATS amount
|
||||
# Extract SATS amount (with sign)
|
||||
sats_match = re.match(r'^(-?\d+)\s+SATS', amount_str)
|
||||
if sats_match:
|
||||
sats_amount = int(sats_match.group(1))
|
||||
|
||||
# Negate Beancount balance for user perspective
|
||||
# Payable (liability): negative in Beancount = castle owes user (positive for user)
|
||||
# Receivable (asset): positive in Beancount = user owes castle (negative for user)
|
||||
adjusted_amount = -sats_amount
|
||||
user_data[user_id]["balance"] += adjusted_amount
|
||||
# For admin/castle view, use Beancount amounts as-is:
|
||||
# Receivable (asset): positive in Beancount = user owes castle (positive)
|
||||
# Payable (liability): negative in Beancount = castle owes user (negative)
|
||||
user_data[user_id]["balance"] += sats_amount
|
||||
|
||||
# Extract fiat from cost syntax: {33.33 EUR, ...}
|
||||
cost_match = re.search(r'\{([\d.]+)\s+([A-Z]+)', amount_str)
|
||||
if cost_match:
|
||||
fiat_amount_raw = Decimal(cost_match.group(1))
|
||||
fiat_amount_unsigned = Decimal(cost_match.group(1))
|
||||
fiat_currency = cost_match.group(2)
|
||||
|
||||
if fiat_currency not in user_data[user_id]["fiat_balances"]:
|
||||
user_data[user_id]["fiat_balances"][fiat_currency] = Decimal(0)
|
||||
|
||||
# Apply same sign logic as sats
|
||||
if "-" in amount_str:
|
||||
fiat_amount_raw = -fiat_amount_raw
|
||||
adjusted_fiat = -fiat_amount_raw
|
||||
user_data[user_id]["fiat_balances"][fiat_currency] += adjusted_fiat
|
||||
# Apply the same sign as the SATS amount
|
||||
# If SATS is negative, fiat should be negative too
|
||||
if sats_match:
|
||||
sats_amount_for_sign = int(sats_match.group(1))
|
||||
if sats_amount_for_sign < 0:
|
||||
fiat_amount_unsigned = -fiat_amount_unsigned
|
||||
|
||||
user_data[user_id]["fiat_balances"][fiat_currency] += fiat_amount_unsigned
|
||||
|
||||
return list(user_data.values())
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@
|
|||
</template>
|
||||
<template v-slot:body-cell-balance="props">
|
||||
<q-td :props="props">
|
||||
<div :class="props.row.balance > 0 ? 'text-negative' : 'text-positive'">
|
||||
<div :class="props.row.balance > 0 ? 'text-positive' : 'text-negative'">
|
||||
{% raw %}{{ formatSats(Math.abs(props.row.balance)) }} sats{% endraw %}
|
||||
</div>
|
||||
<div v-if="props.row.fiat_balances && Object.keys(props.row.fiat_balances).length > 0" class="text-caption">
|
||||
|
|
@ -191,15 +191,15 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="text-caption text-grey">
|
||||
{% raw %}{{ props.row.balance > 0 ? 'You owe' : 'Owes you' }}{% endraw %}
|
||||
{% raw %}{{ props.row.balance > 0 ? 'Owes you' : 'You owe' }}{% endraw %}
|
||||
</div>
|
||||
</q-td>
|
||||
</template>
|
||||
<template v-slot:body-cell-actions="props">
|
||||
<q-td :props="props">
|
||||
<!-- User owes Castle (negative balance) - Castle receives payment -->
|
||||
<!-- User owes Castle (positive balance) - Castle receives payment -->
|
||||
<q-btn
|
||||
v-if="props.row.balance < 0"
|
||||
v-if="props.row.balance > 0"
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
|
|
@ -209,9 +209,9 @@
|
|||
>
|
||||
<q-tooltip>Settle receivable (user pays castle)</q-tooltip>
|
||||
</q-btn>
|
||||
<!-- Castle owes User (positive balance) - Castle pays user -->
|
||||
<!-- Castle owes User (negative balance) - Castle pays user -->
|
||||
<q-btn
|
||||
v-if="props.row.balance > 0"
|
||||
v-if="props.row.balance < 0"
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
|
|
@ -241,7 +241,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-if="balance !== null">
|
||||
<div class="text-h4" :class="isSuperUser ? (balance.balance >= 0 ? 'text-negative' : 'text-positive') : (balance.balance >= 0 ? 'text-positive' : 'text-negative')">
|
||||
<div class="text-h4" :class="isSuperUser ? (balance.balance >= 0 ? 'text-positive' : 'text-negative') : (balance.balance >= 0 ? 'text-negative' : 'text-positive')">
|
||||
{% raw %}{{ formatSats(Math.abs(balance.balance)) }} sats{% endraw %}
|
||||
</div>
|
||||
<div v-if="balance.fiat_balances && Object.keys(balance.fiat_balances).length > 0" class="text-h6 q-mt-sm">
|
||||
|
|
@ -250,21 +250,21 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="text-subtitle2" v-if="isSuperUser">
|
||||
{% raw %}{{ balance.balance > 0 ? 'Total you owe' : balance.balance < 0 ? 'Total owed to you' : 'No outstanding balances' }}{% endraw %}
|
||||
{% raw %}{{ balance.balance > 0 ? 'Total owed to you' : balance.balance < 0 ? 'Total you owe' : 'No outstanding balances' }}{% endraw %}
|
||||
</div>
|
||||
<div class="text-subtitle2" v-else>
|
||||
{% raw %}{{ balance.balance >= 0 ? 'Castle owes you' : 'You owe Castle' }}{% endraw %}
|
||||
{% raw %}{{ balance.balance >= 0 ? 'You owe Castle' : 'Castle owes you' }}{% endraw %}
|
||||
</div>
|
||||
<div class="q-mt-md q-gutter-sm">
|
||||
<q-btn
|
||||
v-if="balance.balance < 0 && !isSuperUser"
|
||||
v-if="balance.balance > 0 && !isSuperUser"
|
||||
color="primary"
|
||||
@click="showPayBalanceDialog"
|
||||
>
|
||||
Pay Balance
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-if="balance.balance > 0 && !isSuperUser"
|
||||
v-if="balance.balance < 0 && !isSuperUser"
|
||||
color="secondary"
|
||||
@click="showManualPaymentDialog"
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue