diff --git a/crud.py b/crud.py index 2f85952..74f04d2 100644 --- a/crud.py +++ b/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, ) ) diff --git a/models.py b/models.py index 4dd80b1..45777e3 100644 --- a/models.py +++ b/models.py @@ -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): diff --git a/static/js/index.js b/static/js/index.js index 01a88c8..6876513 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -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() }, diff --git a/templates/castle/index.html b/templates/castle/index.html index 71997d5..0ac63bc 100644 --- a/templates/castle/index.html +++ b/templates/castle/index.html @@ -79,6 +79,11 @@
{% raw %}{{ formatSats(Math.abs(balance.balance)) }} sats{% endraw %}
+
+ + {% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %} + +
{% raw %}{{ balance.balance > 0 ? 'Total you owe' : balance.balance < 0 ? 'Total owed to you' : 'No outstanding balances' }}{% endraw %}
@@ -126,6 +131,11 @@
{% raw %}{{ formatSats(Math.abs(props.row.balance)) }} sats{% endraw %}
+
+ + {% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %} + +
{% raw %}{{ props.row.balance > 0 ? 'You owe' : 'Owes you' }}{% endraw %}
@@ -316,7 +326,14 @@
Pay Balance
- Amount owed: {% raw %}{{ formatSats(Math.abs(balance.balance)) }}{% endraw %} sats +
+ Amount owed: {% raw %}{{ formatSats(Math.abs(balance.balance)) }}{% endraw %} sats +
+
+ + {% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %} + +
diff --git a/views_api.py b/views_api.py index 0316958..83db451 100644 --- a/views_api.py +++ b/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