Fix settlement linking to original expense/receivable entries

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 <noreply@anthropic.com>
This commit is contained in:
padreug 2025-12-15 01:33:35 +01:00
parent 1ae5c8c927
commit 7362d6292e

View file

@ -1137,7 +1137,7 @@ window.app = Vue.createApp({
this.receivableDialog.reference = '' this.receivableDialog.reference = ''
this.receivableDialog.currency = null this.receivableDialog.currency = null
}, },
showSettleReceivableDialog(userBalance) { async showSettleReceivableDialog(userBalance) {
// Only show for users who owe castle (positive balance = receivable) // Only show for users who owe castle (positive balance = receivable)
if (userBalance.balance <= 0) return 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 fiatCurrency = Object.keys(fiatBalances)[0] || null // Get first fiat currency (e.g., 'EUR')
const fiatAmount = fiatCurrency ? Math.abs(fiatBalances[fiatCurrency]) : 0 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 = { this.settleReceivableDialog = {
show: true, show: true,
user_id: userBalance.user_id, user_id: userBalance.user_id,
@ -1168,7 +1181,9 @@ window.app = Vue.createApp({
checkWalletKey: null, checkWalletKey: null,
pollIntervalId: null, pollIntervalId: null,
exchangeRate: fiatAmount > 0 ? Math.abs(userBalance.balance) / fiatAmount : this.currentExchangeRate, // Calculate rate from actual amounts or use current rate 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() { async generateSettlementInvoice() {
@ -1304,6 +1319,11 @@ window.app = Vue.createApp({
payload.amount_sats = this.settleReceivableDialog.maxAmount 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( const response = await LNbits.api.request(
'POST', 'POST',
'/castle/api/v1/receivables/settle', '/castle/api/v1/receivables/settle',
@ -1329,7 +1349,7 @@ window.app = Vue.createApp({
this.settleReceivableDialog.loading = false this.settleReceivableDialog.loading = false
} }
}, },
showPayUserDialog(userBalance) { async showPayUserDialog(userBalance) {
// Only show for users castle owes (negative balance = payable) // Only show for users castle owes (negative balance = payable)
if (userBalance.balance >= 0) return if (userBalance.balance >= 0) return
@ -1342,6 +1362,19 @@ window.app = Vue.createApp({
const maxAmountSats = Math.abs(userBalance.balance) const maxAmountSats = Math.abs(userBalance.balance)
const maxAmountFiat = Math.abs(fiatAmount) 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 = { this.payUserDialog = {
show: true, show: true,
user_id: userBalance.user_id, user_id: userBalance.user_id,
@ -1356,7 +1389,9 @@ window.app = Vue.createApp({
loading: false, loading: false,
paymentSuccess: false, paymentSuccess: false,
exchangeRate: maxAmountFiat > 0 ? maxAmountSats / maxAmountFiat : this.currentExchangeRate, 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() { async sendLightningPayment() {
@ -1395,16 +1430,23 @@ window.app = Vue.createApp({
) )
// Record the payment in Castle accounting // 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( await LNbits.api.request(
'POST', 'POST',
'/castle/api/v1/payables/pay', '/castle/api/v1/payables/pay',
this.g.user.wallets[0].adminkey, this.g.user.wallets[0].adminkey,
{ payPayload
user_id: this.payUserDialog.user_id,
amount: this.payUserDialog.amount,
payment_method: 'lightning',
payment_hash: paymentResponse.data.payment_hash
}
) )
this.payUserDialog.paymentSuccess = true this.payUserDialog.paymentSuccess = true
@ -1461,6 +1503,11 @@ window.app = Vue.createApp({
payload.amount_sats = this.payUserDialog.maxAmount 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( const response = await LNbits.api.request(
'POST', 'POST',
'/castle/api/v1/payables/pay', '/castle/api/v1/payables/pay',