Adds fiat currency balances to user balances
Extends user balance information to include fiat currency balances, calculated based on entry line metadata and account types. This allows for a more comprehensive view of user balances, including both satoshi and fiat currency holdings. Updates the castle index template and API to display fiat balances.
This commit is contained in:
parent
be386f60ef
commit
b0705fc24a
5 changed files with 116 additions and 4 deletions
76
crud.py
76
crud.py
|
|
@ -298,10 +298,43 @@ async def get_user_balance(user_id: str) -> UserBalance:
|
|||
)
|
||||
|
||||
total_balance = 0
|
||||
fiat_balances = {} # Track fiat balances by currency
|
||||
|
||||
for account in user_accounts:
|
||||
balance = await get_account_balance(account.id)
|
||||
|
||||
# Get all entry lines for this account to calculate fiat balances
|
||||
entry_lines = await db.fetchall(
|
||||
"SELECT * FROM entry_lines WHERE account_id = :account_id",
|
||||
{"account_id": account.id},
|
||||
)
|
||||
|
||||
for line in entry_lines:
|
||||
# Parse metadata to get fiat amounts
|
||||
metadata = json.loads(line["metadata"]) if line.get("metadata") else {}
|
||||
fiat_currency = metadata.get("fiat_currency")
|
||||
fiat_amount = metadata.get("fiat_amount")
|
||||
|
||||
if fiat_currency and fiat_amount:
|
||||
# Initialize currency if not exists
|
||||
if fiat_currency not in fiat_balances:
|
||||
fiat_balances[fiat_currency] = 0.0
|
||||
|
||||
# Calculate fiat balance based on account type
|
||||
if account.account_type == AccountType.LIABILITY:
|
||||
# Liability: credit increases (castle owes more), debit decreases
|
||||
if line["credit"] > 0:
|
||||
fiat_balances[fiat_currency] += fiat_amount
|
||||
elif line["debit"] > 0:
|
||||
fiat_balances[fiat_currency] -= fiat_amount
|
||||
elif account.account_type == AccountType.ASSET:
|
||||
# Asset (receivable): debit increases (user owes more), credit decreases
|
||||
if line["debit"] > 0:
|
||||
fiat_balances[fiat_currency] -= fiat_amount
|
||||
elif line["credit"] > 0:
|
||||
fiat_balances[fiat_currency] += fiat_amount
|
||||
|
||||
# Calculate satoshi balance
|
||||
# If it's a liability account (castle owes user), it's positive
|
||||
# If it's an asset account (user owes castle), it's negative
|
||||
if account.account_type == AccountType.LIABILITY:
|
||||
|
|
@ -314,6 +347,7 @@ async def get_user_balance(user_id: str) -> UserBalance:
|
|||
user_id=user_id,
|
||||
balance=total_balance,
|
||||
accounts=user_accounts,
|
||||
fiat_balances=fiat_balances,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -337,17 +371,55 @@ async def get_all_user_balances() -> list[UserBalance]:
|
|||
user_balances = []
|
||||
for user_id, accounts in users_dict.items():
|
||||
total_balance = 0
|
||||
fiat_balances = {}
|
||||
|
||||
for account in accounts:
|
||||
balance = await get_account_balance(account.id)
|
||||
|
||||
# Get all entry lines for this account to calculate fiat balances
|
||||
entry_lines = await db.fetchall(
|
||||
"SELECT * FROM entry_lines WHERE account_id = :account_id",
|
||||
{"account_id": account.id},
|
||||
)
|
||||
|
||||
for line in entry_lines:
|
||||
# Parse metadata to get fiat amounts
|
||||
metadata = json.loads(line["metadata"]) if line.get("metadata") else {}
|
||||
fiat_currency = metadata.get("fiat_currency")
|
||||
fiat_amount = metadata.get("fiat_amount")
|
||||
|
||||
if fiat_currency and fiat_amount:
|
||||
# Initialize currency if not exists
|
||||
if fiat_currency not in fiat_balances:
|
||||
fiat_balances[fiat_currency] = 0.0
|
||||
|
||||
# Calculate fiat balance based on account type
|
||||
if account.account_type == AccountType.LIABILITY:
|
||||
# Liability: credit increases (castle owes more), debit decreases
|
||||
if line["credit"] > 0:
|
||||
fiat_balances[fiat_currency] += fiat_amount
|
||||
elif line["debit"] > 0:
|
||||
fiat_balances[fiat_currency] -= fiat_amount
|
||||
elif account.account_type == AccountType.ASSET:
|
||||
# Asset (receivable): debit increases (user owes more), credit decreases
|
||||
if line["debit"] > 0:
|
||||
fiat_balances[fiat_currency] -= fiat_amount
|
||||
elif line["credit"] > 0:
|
||||
fiat_balances[fiat_currency] += fiat_amount
|
||||
|
||||
# Calculate satoshi balance
|
||||
if account.account_type == AccountType.LIABILITY:
|
||||
total_balance += balance
|
||||
elif account.account_type == AccountType.ASSET:
|
||||
total_balance -= balance
|
||||
|
||||
if total_balance != 0: # Only include users with non-zero balance
|
||||
if total_balance != 0 or fiat_balances: # Include users with non-zero balance or fiat balances
|
||||
user_balances.append(
|
||||
UserBalance(
|
||||
user_id=user_id, balance=total_balance, accounts=accounts
|
||||
user_id=user_id,
|
||||
balance=total_balance,
|
||||
accounts=accounts,
|
||||
fiat_balances=fiat_balances,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ class UserBalance(BaseModel):
|
|||
user_id: str
|
||||
balance: int # positive = castle owes user, negative = user owes castle
|
||||
accounts: list[Account] = []
|
||||
fiat_balances: dict[str, float] = {} # e.g. {"EUR": 250.0, "USD": 100.0}
|
||||
|
||||
|
||||
class ExpenseEntry(BaseModel):
|
||||
|
|
|
|||
|
|
@ -506,6 +506,14 @@ window.app = Vue.createApp({
|
|||
formatSats(amount) {
|
||||
return new Intl.NumberFormat().format(amount)
|
||||
},
|
||||
formatFiat(amount, currency) {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(amount)
|
||||
},
|
||||
formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -79,6 +79,11 @@
|
|||
<div class="text-h4" :class="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">
|
||||
<span v-for="(amount, currency) in balance.fiat_balances" :key="currency" class="q-mr-md">
|
||||
{% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %}
|
||||
</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 %}
|
||||
</div>
|
||||
|
|
@ -126,6 +131,11 @@
|
|||
<div :class="props.row.balance > 0 ? 'text-negative' : 'text-positive'">
|
||||
{% 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">
|
||||
<span v-for="(amount, currency) in props.row.fiat_balances" :key="currency" class="q-mr-sm">
|
||||
{% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-caption text-grey">
|
||||
{% raw %}{{ props.row.balance > 0 ? 'You owe' : 'Owes you' }}{% endraw %}
|
||||
</div>
|
||||
|
|
@ -316,7 +326,14 @@
|
|||
<div class="text-h6 q-mb-md">Pay Balance</div>
|
||||
|
||||
<div v-if="balance" class="q-mb-md">
|
||||
Amount owed: <strong>{% raw %}{{ formatSats(Math.abs(balance.balance)) }}{% endraw %} sats</strong>
|
||||
<div>
|
||||
Amount owed: <strong>{% raw %}{{ formatSats(Math.abs(balance.balance)) }}{% endraw %} sats</strong>
|
||||
</div>
|
||||
<div v-if="balance.fiat_balances && Object.keys(balance.fiat_balances).length > 0" class="text-body2 q-mt-xs">
|
||||
<span v-for="(amount, currency) in balance.fiat_balances" :key="currency" class="q-mr-md">
|
||||
<strong>{% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %}</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!payDialog.paymentRequest">
|
||||
|
|
|
|||
16
views_api.py
16
views_api.py
|
|
@ -420,9 +420,23 @@ async def api_get_my_balance(
|
|||
if wallet.wallet.user == lnbits_settings.super_user:
|
||||
all_balances = await get_all_user_balances()
|
||||
total_owed = sum(b.balance for b in all_balances if b.balance > 0)
|
||||
|
||||
# Aggregate fiat balances from all users
|
||||
total_fiat_balances = {}
|
||||
for user_balance in all_balances:
|
||||
for currency, amount in user_balance.fiat_balances.items():
|
||||
if currency not in total_fiat_balances:
|
||||
total_fiat_balances[currency] = 0.0
|
||||
# Only add positive balances (what castle owes)
|
||||
if amount > 0:
|
||||
total_fiat_balances[currency] += amount
|
||||
|
||||
# Return as castle's "balance" - positive means castle owes money
|
||||
return UserBalance(
|
||||
user_id=wallet.wallet.user, balance=total_owed, accounts=[]
|
||||
user_id=wallet.wallet.user,
|
||||
balance=total_owed,
|
||||
accounts=[],
|
||||
fiat_balances=total_fiat_balances,
|
||||
)
|
||||
|
||||
# For regular users, show their individual balance
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue