Adds super user balance overview
Implements functionality for super users to view a breakdown of outstanding balances for all users. This includes: - Adding an API endpoint to fetch all user balances. - Updating the frontend to display these balances in a table, accessible only to super users. - Modifying the balance calculation for the current user to reflect the total owed by or to the castle for super users. This provides super users with a comprehensive view of the castle's financial position.
This commit is contained in:
parent
cb7e4ee555
commit
b7e4e05469
4 changed files with 117 additions and 4 deletions
37
crud.py
37
crud.py
|
|
@ -317,6 +317,43 @@ async def get_user_balance(user_id: str) -> UserBalance:
|
|||
)
|
||||
|
||||
|
||||
async def get_all_user_balances() -> list[UserBalance]:
|
||||
"""Get balances for all users (used by castle to see who they owe)"""
|
||||
# Get all user-specific accounts
|
||||
all_accounts = await db.fetchall(
|
||||
"SELECT * FROM accounts WHERE user_id IS NOT NULL",
|
||||
{},
|
||||
Account,
|
||||
)
|
||||
|
||||
# Group by user_id
|
||||
users_dict = {}
|
||||
for account in all_accounts:
|
||||
if account.user_id not in users_dict:
|
||||
users_dict[account.user_id] = []
|
||||
users_dict[account.user_id].append(account)
|
||||
|
||||
# Calculate balance for each user
|
||||
user_balances = []
|
||||
for user_id, accounts in users_dict.items():
|
||||
total_balance = 0
|
||||
for account in accounts:
|
||||
balance = await get_account_balance(account.id)
|
||||
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
|
||||
user_balances.append(
|
||||
UserBalance(
|
||||
user_id=user_id, balance=total_balance, accounts=accounts
|
||||
)
|
||||
)
|
||||
|
||||
return user_balances
|
||||
|
||||
|
||||
async def get_account_transactions(
|
||||
account_id: str, limit: int = 100
|
||||
) -> list[tuple[JournalEntry, EntryLine]]:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ window.app = Vue.createApp({
|
|||
data() {
|
||||
return {
|
||||
balance: null,
|
||||
allUserBalances: [],
|
||||
transactions: [],
|
||||
accounts: [],
|
||||
currencies: [],
|
||||
|
|
@ -71,10 +72,27 @@ window.app = Vue.createApp({
|
|||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
this.balance = response.data
|
||||
|
||||
// If super user, also load all user balances
|
||||
if (this.isSuperUser) {
|
||||
await this.loadAllUserBalances()
|
||||
}
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
async loadAllUserBalances() {
|
||||
try {
|
||||
const response = await LNbits.api.request(
|
||||
'GET',
|
||||
'/castle/api/v1/balances/all',
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
this.allUserBalances = response.data
|
||||
} catch (error) {
|
||||
console.error('Error loading all user balances:', error)
|
||||
}
|
||||
},
|
||||
async loadTransactions() {
|
||||
try {
|
||||
const response = await LNbits.api.request(
|
||||
|
|
|
|||
|
|
@ -76,14 +76,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-if="balance !== null">
|
||||
<div class="text-h4" :class="balance.balance >= 0 ? 'text-positive' : 'text-negative'">
|
||||
<div class="text-h4" :class="balance.balance >= 0 ? 'text-negative' : 'text-positive'">
|
||||
{% raw %}{{ formatSats(Math.abs(balance.balance)) }} sats{% endraw %}
|
||||
</div>
|
||||
<div class="text-subtitle2">
|
||||
<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>
|
||||
<div class="text-subtitle2" v-else>
|
||||
{% raw %}{{ balance.balance >= 0 ? 'Castle owes you' : 'You owe Castle' }}{% endraw %}
|
||||
</div>
|
||||
<q-btn
|
||||
v-if="balance.balance < 0"
|
||||
v-if="balance.balance < 0 && !isSuperUser"
|
||||
color="primary"
|
||||
class="q-mt-md"
|
||||
@click="showPayBalanceDialog"
|
||||
|
|
@ -98,6 +101,40 @@
|
|||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<!-- User Balances Breakdown (Super User Only) -->
|
||||
<q-card v-if="isSuperUser && allUserBalances.length > 0">
|
||||
<q-card-section>
|
||||
<h6 class="q-my-none q-mb-md">Outstanding Balances by User</h6>
|
||||
<q-table
|
||||
flat
|
||||
:rows="allUserBalances"
|
||||
:columns="[
|
||||
{name: 'user', label: 'User ID', field: 'user_id', align: 'left'},
|
||||
{name: 'balance', label: 'Amount Owed', field: 'balance', align: 'right'}
|
||||
]"
|
||||
row-key="user_id"
|
||||
hide-pagination
|
||||
:rows-per-page-options="[0]"
|
||||
>
|
||||
<template v-slot:body-cell-user="props">
|
||||
<q-td :props="props">
|
||||
<div class="text-caption">{% raw %}{{ props.row.user_id.substring(0, 16) }}...{% endraw %}</div>
|
||||
</q-td>
|
||||
</template>
|
||||
<template v-slot:body-cell-balance="props">
|
||||
<q-td :props="props">
|
||||
<div :class="props.row.balance > 0 ? 'text-negative' : 'text-positive'">
|
||||
{% raw %}{{ formatSats(Math.abs(props.row.balance)) }} sats{% endraw %}
|
||||
</div>
|
||||
<div class="text-caption text-grey">
|
||||
{% raw %}{{ props.row.balance > 0 ? 'You owe' : 'Owes you' }}{% endraw %}
|
||||
</div>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
|
|
|
|||
23
views_api.py
23
views_api.py
|
|
@ -19,6 +19,7 @@ from .crud import (
|
|||
get_account_transactions,
|
||||
get_all_accounts,
|
||||
get_all_journal_entries,
|
||||
get_all_user_balances,
|
||||
get_journal_entries_by_user,
|
||||
get_journal_entry,
|
||||
get_or_create_user_account,
|
||||
|
|
@ -382,7 +383,19 @@ async def api_get_my_balance(
|
|||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||
) -> UserBalance:
|
||||
"""Get current user's balance with the Castle"""
|
||||
return await get_user_balance(wallet.wallet.id)
|
||||
from lnbits.settings import settings as lnbits_settings
|
||||
|
||||
# If super user, show total castle liabilities (what castle owes to all users)
|
||||
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)
|
||||
# Return as castle's "balance" - positive means castle owes money
|
||||
return UserBalance(
|
||||
user_id=wallet.wallet.user, balance=total_owed, accounts=[]
|
||||
)
|
||||
|
||||
# For regular users, show their individual balance
|
||||
return await get_user_balance(wallet.wallet.user)
|
||||
|
||||
|
||||
@castle_api_router.get("/api/v1/balance/{user_id}")
|
||||
|
|
@ -391,6 +404,14 @@ async def api_get_user_balance(user_id: str) -> UserBalance:
|
|||
return await get_user_balance(user_id)
|
||||
|
||||
|
||||
@castle_api_router.get("/api/v1/balances/all")
|
||||
async def api_get_all_balances(
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> list[UserBalance]:
|
||||
"""Get all user balances (admin/super user only)"""
|
||||
return await get_all_user_balances()
|
||||
|
||||
|
||||
# ===== PAYMENT ENDPOINTS =====
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue