Adds receivable entry functionality

Implements the ability to record receivables (user owes the castle).

Adds API endpoint for creating receivable entries, which includes currency conversion to satoshis if fiat currency is provided.

Integrates a UI component (receivable dialog) for superusers to record debts owed by users, enhancing financial tracking capabilities.
This commit is contained in:
padreug 2025-10-22 16:16:36 +02:00
parent b7e4e05469
commit 2a14dd2e62
4 changed files with 250 additions and 3 deletions

View file

@ -12,6 +12,7 @@ window.app = Vue.createApp({
transactions: [],
accounts: [],
currencies: [],
users: [],
settings: null,
userWalletSettings: null,
isAdmin: false,
@ -42,6 +43,16 @@ window.app = Vue.createApp({
show: false,
userWalletId: '',
loading: false
},
receivableDialog: {
show: false,
selectedUser: '',
description: '',
amount: null,
revenueAccount: '',
reference: '',
currency: null,
loading: false
}
}
},
@ -49,18 +60,37 @@ window.app = Vue.createApp({
expenseAccounts() {
return this.accounts.filter(a => a.account_type === 'expense')
},
revenueAccounts() {
return this.accounts.filter(a => a.account_type === 'revenue')
},
amountLabel() {
if (this.expenseDialog.currency) {
return `Amount (${this.expenseDialog.currency}) *`
}
return 'Amount (sats) *'
},
receivableAmountLabel() {
if (this.receivableDialog.currency) {
return `Amount (${this.receivableDialog.currency}) *`
}
return 'Amount (sats) *'
},
currencyOptions() {
const options = [{label: 'Satoshis (default)', value: null}]
this.currencies.forEach(curr => {
options.push({label: curr, value: curr})
})
return options
},
userOptions() {
const options = []
this.users.forEach(user => {
options.push({
label: user.username,
value: user.user_wallet_id
})
})
return options
}
},
methods: {
@ -129,6 +159,18 @@ window.app = Vue.createApp({
LNbits.utils.notifyApiError(error)
}
},
async loadUsers() {
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/users',
this.g.user.wallets[0].adminkey
)
this.users = response.data
} catch (error) {
console.error('Error loading users:', error)
}
},
async loadSettings() {
try {
// Try with admin key first to check settings
@ -304,6 +346,43 @@ window.app = Vue.createApp({
this.payDialog.amount = Math.abs(this.balance.balance)
this.payDialog.show = true
},
async showReceivableDialog() {
// Load users if not already loaded
if (this.users.length === 0 && this.isSuperUser) {
await this.loadUsers()
}
this.receivableDialog.show = true
},
async submitReceivable() {
this.receivableDialog.loading = true
try {
await LNbits.api.request(
'POST',
'/castle/api/v1/entries/receivable',
this.g.user.wallets[0].adminkey,
{
description: this.receivableDialog.description,
amount: this.receivableDialog.amount,
revenue_account: this.receivableDialog.revenueAccount,
user_wallet: this.receivableDialog.selectedUser,
reference: this.receivableDialog.reference || null,
currency: this.receivableDialog.currency || null
}
)
this.$q.notify({
type: 'positive',
message: 'Receivable added successfully'
})
this.receivableDialog.show = false
this.resetReceivableDialog()
await this.loadBalance()
await this.loadTransactions()
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.receivableDialog.loading = false
}
},
resetExpenseDialog() {
this.expenseDialog.description = ''
this.expenseDialog.amount = null
@ -312,6 +391,14 @@ window.app = Vue.createApp({
this.expenseDialog.reference = ''
this.expenseDialog.currency = null
},
resetReceivableDialog() {
this.receivableDialog.selectedUser = ''
this.receivableDialog.description = ''
this.receivableDialog.amount = null
this.receivableDialog.revenueAccount = ''
this.receivableDialog.reference = ''
this.receivableDialog.currency = null
},
formatSats(amount) {
return new Intl.NumberFormat().format(amount)
},
@ -330,5 +417,9 @@ window.app = Vue.createApp({
await this.loadCurrencies()
await this.loadSettings()
await this.loadUserWallet()
// Load users if super user (for receivable dialog)
if (this.isSuperUser) {
await this.loadUsers()
}
}
})