From 7362d6292ec29c66add8e6bc974a2fe3010b174e Mon Sep 17 00:00:00 2001 From: padreug Date: Mon, 15 Dec 2025 01:33:35 +0100 Subject: [PATCH] Fix settlement linking to original expense/receivable entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend now: 1. Fetches unsettled entries when opening settlement dialogs 2. Includes entry links (exp-xxx/rcv-xxx) in settlement payloads 3. Passes settled_entry_links to backend for proper linking This enables the settlement transaction to include links back to the original entries it is settling, making it possible to track which expenses/receivables have been paid. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- static/js/index.js | 67 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index a0b38ba..bd9e61a 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -1137,7 +1137,7 @@ window.app = Vue.createApp({ this.receivableDialog.reference = '' this.receivableDialog.currency = null }, - showSettleReceivableDialog(userBalance) { + async showSettleReceivableDialog(userBalance) { // Only show for users who owe castle (positive balance = receivable) if (userBalance.balance <= 0) return @@ -1151,6 +1151,19 @@ window.app = Vue.createApp({ const fiatCurrency = Object.keys(fiatBalances)[0] || null // Get first fiat currency (e.g., 'EUR') const fiatAmount = fiatCurrency ? Math.abs(fiatBalances[fiatCurrency]) : 0 + // Fetch unsettled receivable entries for this user + let unsettledEntries = [] + try { + const response = await LNbits.api.request( + 'GET', + `/castle/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=receivable`, + this.g.user.wallets[0].adminkey + ) + unsettledEntries = response.data.unsettled_entries || [] + } catch (error) { + console.warn('Could not fetch unsettled entries:', error) + } + this.settleReceivableDialog = { show: true, user_id: userBalance.user_id, @@ -1168,7 +1181,9 @@ window.app = Vue.createApp({ checkWalletKey: null, pollIntervalId: null, exchangeRate: fiatAmount > 0 ? Math.abs(userBalance.balance) / fiatAmount : this.currentExchangeRate, // Calculate rate from actual amounts or use current rate - originalCurrency: fiatCurrency || 'BTC' + originalCurrency: fiatCurrency || 'BTC', + unsettledEntries: unsettledEntries, // Store for linking in settlement + entryLinks: unsettledEntries.map(e => e.link).filter(l => l) // Extract rcv-xxx links } }, async generateSettlementInvoice() { @@ -1304,6 +1319,11 @@ window.app = Vue.createApp({ payload.amount_sats = this.settleReceivableDialog.maxAmount } + // Include links to entries being settled + if (this.settleReceivableDialog.entryLinks && this.settleReceivableDialog.entryLinks.length > 0) { + payload.settled_entry_links = this.settleReceivableDialog.entryLinks + } + const response = await LNbits.api.request( 'POST', '/castle/api/v1/receivables/settle', @@ -1329,7 +1349,7 @@ window.app = Vue.createApp({ this.settleReceivableDialog.loading = false } }, - showPayUserDialog(userBalance) { + async showPayUserDialog(userBalance) { // Only show for users castle owes (negative balance = payable) if (userBalance.balance >= 0) return @@ -1342,6 +1362,19 @@ window.app = Vue.createApp({ const maxAmountSats = Math.abs(userBalance.balance) const maxAmountFiat = Math.abs(fiatAmount) + // Fetch unsettled expense entries for this user + let unsettledEntries = [] + try { + const response = await LNbits.api.request( + 'GET', + `/castle/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=expense`, + this.g.user.wallets[0].adminkey + ) + unsettledEntries = response.data.unsettled_entries || [] + } catch (error) { + console.warn('Could not fetch unsettled entries:', error) + } + this.payUserDialog = { show: true, user_id: userBalance.user_id, @@ -1356,7 +1389,9 @@ window.app = Vue.createApp({ loading: false, paymentSuccess: false, exchangeRate: maxAmountFiat > 0 ? maxAmountSats / maxAmountFiat : this.currentExchangeRate, - originalCurrency: fiatCurrency || 'BTC' + originalCurrency: fiatCurrency || 'BTC', + unsettledEntries: unsettledEntries, // Store for linking in settlement + entryLinks: unsettledEntries.map(e => e.link).filter(l => l) // Extract exp-xxx links } }, async sendLightningPayment() { @@ -1395,16 +1430,23 @@ window.app = Vue.createApp({ ) // Record the payment in Castle accounting + const payPayload = { + user_id: this.payUserDialog.user_id, + amount: this.payUserDialog.amount, + payment_method: 'lightning', + payment_hash: paymentResponse.data.payment_hash + } + + // Include links to entries being settled + if (this.payUserDialog.entryLinks && this.payUserDialog.entryLinks.length > 0) { + payPayload.settled_entry_links = this.payUserDialog.entryLinks + } + await LNbits.api.request( 'POST', '/castle/api/v1/payables/pay', this.g.user.wallets[0].adminkey, - { - user_id: this.payUserDialog.user_id, - amount: this.payUserDialog.amount, - payment_method: 'lightning', - payment_hash: paymentResponse.data.payment_hash - } + payPayload ) this.payUserDialog.paymentSuccess = true @@ -1461,6 +1503,11 @@ window.app = Vue.createApp({ payload.amount_sats = this.payUserDialog.maxAmount } + // Include links to entries being settled + if (this.payUserDialog.entryLinks && this.payUserDialog.entryLinks.length > 0) { + payload.settled_entry_links = this.payUserDialog.entryLinks + } + const response = await LNbits.api.request( 'POST', '/castle/api/v1/payables/pay',