PHASE 2: Implements balance assertions for reconciliation
Adds balance assertion functionality to enable admins to verify accounting accuracy. This includes: - A new `balance_assertions` table in the database - CRUD operations for balance assertions (create, get, list, check, delete) - API endpoints for managing balance assertions (admin only) - UI elements for creating, viewing, and re-checking assertions Also, reorders the implementation roadmap in the documentation to reflect better the dependencies between phases.
This commit is contained in:
parent
1a9c91d042
commit
0257b7807c
7 changed files with 890 additions and 17 deletions
|
|
@ -65,7 +65,18 @@ window.app = Vue.createApp({
|
|||
loading: false
|
||||
},
|
||||
manualPaymentRequests: [],
|
||||
pendingExpenses: []
|
||||
pendingExpenses: [],
|
||||
balanceAssertions: [],
|
||||
assertionDialog: {
|
||||
show: false,
|
||||
account_id: '',
|
||||
expected_balance_sats: null,
|
||||
expected_balance_fiat: null,
|
||||
fiat_currency: null,
|
||||
tolerance_sats: 0,
|
||||
tolerance_fiat: 0,
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -115,6 +126,15 @@ window.app = Vue.createApp({
|
|||
},
|
||||
pendingManualPaymentRequests() {
|
||||
return this.manualPaymentRequests.filter(r => r.status === 'pending')
|
||||
},
|
||||
failedAssertions() {
|
||||
return this.balanceAssertions.filter(a => a.status === 'failed')
|
||||
},
|
||||
passedAssertions() {
|
||||
return this.balanceAssertions.filter(a => a.status === 'passed')
|
||||
},
|
||||
allAccounts() {
|
||||
return this.accounts
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -579,6 +599,145 @@ window.app = Vue.createApp({
|
|||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
async loadBalanceAssertions() {
|
||||
if (!this.isSuperUser) return
|
||||
|
||||
try {
|
||||
const response = await LNbits.api.request(
|
||||
'GET',
|
||||
'/castle/api/v1/assertions',
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
this.balanceAssertions = response.data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
async submitAssertion() {
|
||||
this.assertionDialog.loading = true
|
||||
try {
|
||||
const payload = {
|
||||
account_id: this.assertionDialog.account_id,
|
||||
expected_balance_sats: this.assertionDialog.expected_balance_sats,
|
||||
tolerance_sats: this.assertionDialog.tolerance_sats || 0
|
||||
}
|
||||
|
||||
// Add fiat balance check if currency selected
|
||||
if (this.assertionDialog.fiat_currency) {
|
||||
payload.fiat_currency = this.assertionDialog.fiat_currency
|
||||
payload.expected_balance_fiat = this.assertionDialog.expected_balance_fiat
|
||||
payload.tolerance_fiat = this.assertionDialog.tolerance_fiat || 0
|
||||
}
|
||||
|
||||
await LNbits.api.request(
|
||||
'POST',
|
||||
'/castle/api/v1/assertions',
|
||||
this.g.user.wallets[0].adminkey,
|
||||
payload
|
||||
)
|
||||
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Balance assertion passed!',
|
||||
timeout: 3000
|
||||
})
|
||||
|
||||
// Reset dialog
|
||||
this.assertionDialog = {
|
||||
show: false,
|
||||
account_id: '',
|
||||
expected_balance_sats: null,
|
||||
expected_balance_fiat: null,
|
||||
fiat_currency: null,
|
||||
tolerance_sats: 0,
|
||||
tolerance_fiat: 0,
|
||||
loading: false
|
||||
}
|
||||
|
||||
// Reload assertions
|
||||
await this.loadBalanceAssertions()
|
||||
} catch (error) {
|
||||
// Check if it's a 409 Conflict (assertion failed)
|
||||
if (error.response && error.response.status === 409) {
|
||||
const detail = error.response.data.detail
|
||||
this.$q.notify({
|
||||
type: 'negative',
|
||||
message: `Assertion Failed! Expected: ${this.formatSats(detail.expected_sats)} sats, Got: ${this.formatSats(detail.actual_sats)} sats (diff: ${this.formatSats(detail.difference_sats)} sats)`,
|
||||
timeout: 5000,
|
||||
html: true
|
||||
})
|
||||
// Still reload to show the failed assertion
|
||||
await this.loadBalanceAssertions()
|
||||
this.assertionDialog.show = false
|
||||
} else {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
} finally {
|
||||
this.assertionDialog.loading = false
|
||||
}
|
||||
},
|
||||
async recheckAssertion(assertionId) {
|
||||
// Set loading state
|
||||
const assertion = this.balanceAssertions.find(a => a.id === assertionId)
|
||||
if (assertion) {
|
||||
this.$set(assertion, 'rechecking', true)
|
||||
}
|
||||
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'POST',
|
||||
`/castle/api/v1/assertions/${assertionId}/check`,
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Assertion re-checked',
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
await this.loadBalanceAssertions()
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
} finally {
|
||||
if (assertion) {
|
||||
this.$set(assertion, 'rechecking', false)
|
||||
}
|
||||
}
|
||||
},
|
||||
async deleteAssertion(assertionId) {
|
||||
// Set loading state
|
||||
const assertion = this.balanceAssertions.find(a => a.id === assertionId)
|
||||
if (assertion) {
|
||||
this.$set(assertion, 'deleting', true)
|
||||
}
|
||||
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'DELETE',
|
||||
`/castle/api/v1/assertions/${assertionId}`,
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Assertion deleted',
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
await this.loadBalanceAssertions()
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
} finally {
|
||||
if (assertion) {
|
||||
this.$set(assertion, 'deleting', false)
|
||||
}
|
||||
}
|
||||
},
|
||||
getAccountName(accountId) {
|
||||
const account = this.accounts.find(a => a.id === accountId)
|
||||
return account ? account.name : accountId
|
||||
},
|
||||
copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text)
|
||||
this.$q.notify({
|
||||
|
|
@ -730,6 +889,7 @@ window.app = Vue.createApp({
|
|||
if (this.isSuperUser) {
|
||||
await this.loadUsers()
|
||||
await this.loadPendingExpenses()
|
||||
await this.loadBalanceAssertions()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue