feat(ui): settle off-machine, glossary links, alerts pubkey
Settle action on the cash_owed/partial_pending worklist buckets and the settlements table; the worklist's error cell links the raw code to the glossary entry; the platform settings dialog gains the alerts pubkey.
This commit is contained in:
parent
7375112638
commit
cdb1fe71cc
2 changed files with 114 additions and 2 deletions
|
|
@ -108,7 +108,8 @@ window.app = Vue.createApp({
|
|||
super_cash_in_fee_fraction: 0,
|
||||
super_cash_out_fee_fraction: 0,
|
||||
super_fee_wallet_id: '',
|
||||
max_cash_in_sats: null
|
||||
max_cash_in_sats: null,
|
||||
alerts_pubkey: ''
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -261,6 +262,13 @@ window.app = Vue.createApp({
|
|||
dispensed_sats: null,
|
||||
notes: ''
|
||||
},
|
||||
// ADR-005 §6 — settle an owed-cash settlement off-machine
|
||||
settleDialog: {
|
||||
show: false,
|
||||
settlement: null,
|
||||
note: '',
|
||||
saving: false
|
||||
},
|
||||
noteDialog: {
|
||||
show: false,
|
||||
saving: false,
|
||||
|
|
@ -659,7 +667,8 @@ window.app = Vue.createApp({
|
|||
super_cash_out_fee_fraction:
|
||||
this.superConfig?.super_cash_out_fee_fraction ?? 0,
|
||||
super_fee_wallet_id: this.superConfig?.super_fee_wallet_id || '',
|
||||
max_cash_in_sats: this.superConfig?.max_cash_in_sats ?? null
|
||||
max_cash_in_sats: this.superConfig?.max_cash_in_sats ?? null,
|
||||
alerts_pubkey: this.superConfig?.alerts_pubkey || ''
|
||||
}
|
||||
this.superFeeDialog.show = true
|
||||
},
|
||||
|
|
@ -1263,6 +1272,45 @@ window.app = Vue.createApp({
|
|||
this.partialDispenseDialog.show = true
|
||||
},
|
||||
|
||||
// ADR-005 §6 — the operator paid the customer by hand; close both ledgers.
|
||||
openSettleCashOwed(settlement) {
|
||||
this.settleDialog.settlement = settlement
|
||||
this.settleDialog.note = ''
|
||||
this.settleDialog.show = true
|
||||
},
|
||||
|
||||
async submitSettleCashOwed() {
|
||||
const d = this.settleDialog
|
||||
d.saving = true
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'POST',
|
||||
`${SETTLEMENTS_PATH}/${d.settlement.id}/settle-cash-owed`,
|
||||
null,
|
||||
{note: d.note}
|
||||
)
|
||||
this._replaceSettlement(data)
|
||||
d.show = false
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message:
|
||||
'Settled — distributing the full amount; the machine will mark its row remediated'
|
||||
})
|
||||
if (this.worklist.totalCount) await this.loadWorklist()
|
||||
} catch (e) {
|
||||
this._notifyError(e, 'Settle failed')
|
||||
} finally {
|
||||
d.saving = false
|
||||
}
|
||||
},
|
||||
|
||||
// Deep link into the error glossary for a settlement's dispenser error.
|
||||
errorGlossaryHref(settlement) {
|
||||
const code = settlement.dispense_raw_code || settlement.dispense_error_code || ''
|
||||
const anchor = code.toLowerCase().replace(/[^a-z0-9]+/g, '-')
|
||||
return `/spirekeeper/static/docs/errors.html${anchor ? '#' + anchor : ''}`
|
||||
},
|
||||
|
||||
// ADR-005 §5 — release a machine's cash-out hold after a terminal
|
||||
// dispenser fault, when the jam was cleared without a recount.
|
||||
confirmResumeCashOut(machine) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
<h4 class="q-my-none">Satoshi Machine — Operator</h4>
|
||||
<p class="text-caption q-my-none" :style="{opacity: 0.7}">
|
||||
Manage your bitSpire fleet, liquidity providers, and commission distribution.
|
||||
<a href="/spirekeeper/static/docs/operator-guide.html" target="_blank" rel="noopener"
|
||||
:style="{marginLeft: '8px'}">Operator guide</a> ·
|
||||
<a href="/spirekeeper/static/docs/errors.html" target="_blank" rel="noopener">Error glossary</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
|
|
@ -654,6 +657,13 @@
|
|||
<q-td key="error_message">
|
||||
<span :style="{fontSize: '0.85em', opacity: 0.8}"
|
||||
v-text="props.row.error_message || '—'"></span>
|
||||
<a v-if="props.row.dispense_error_code"
|
||||
:href="errorGlossaryHref(props.row)" target="_blank" rel="noopener"
|
||||
:style="{fontSize: '0.8em', marginLeft: '6px', whiteSpace: 'nowrap'}">
|
||||
<code v-text="props.row.dispense_raw_code || props.row.dispense_error_code"></code>
|
||||
<q-icon name="help_outline" size="xs"></q-icon>
|
||||
<q-tooltip>What this code means and what to do</q-tooltip>
|
||||
</a>
|
||||
</q-td>
|
||||
<q-td key="actions" auto-width>
|
||||
<q-btn flat dense size="sm" icon="open_in_new"
|
||||
|
|
@ -661,6 +671,12 @@
|
|||
@click="viewMachineFromWorklist(props.row)">
|
||||
<q-tooltip>Open machine detail</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn v-if="bucket.key === 'cash_owed' || bucket.key === 'partial_pending'"
|
||||
flat dense size="sm" icon="handshake"
|
||||
color="positive"
|
||||
@click="openSettleCashOwed(props.row)">
|
||||
<q-tooltip>Settle off-machine — you paid the customer by hand</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn v-if="bucket.key === 'partial_pending'"
|
||||
flat dense size="sm" icon="call_split"
|
||||
color="deep-orange"
|
||||
|
|
@ -1119,6 +1135,15 @@
|
|||
</q-item-section>
|
||||
<q-item-section>Retry distribution</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-if="['cash_owed','partial_pending'].includes(props.row.status)"
|
||||
clickable v-close-popup
|
||||
@click="openSettleCashOwed(props.row)">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="handshake" color="positive"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>Settle off-machine…</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-if="['pending','errored'].includes(props.row.status)"
|
||||
clickable v-close-popup
|
||||
|
|
@ -1454,6 +1479,41 @@
|
|||
<!-- =============================================================== -->
|
||||
<!-- ADD-NOTE DIALOG -->
|
||||
<!-- =============================================================== -->
|
||||
<!-- ADR-005 §6: settle an owed-cash settlement off-machine -->
|
||||
<q-dialog v-model="settleDialog.show" persistent>
|
||||
<q-card :style="{width: '460px', maxWidth: '95vw'}">
|
||||
<q-card-section class="row items-center q-pb-none">
|
||||
<div class="text-h6">Settle off-machine</div>
|
||||
<q-space></q-space>
|
||||
<q-btn icon="close" flat round dense v-close-popup></q-btn>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="settleDialog.settlement">
|
||||
<p class="q-mb-sm">
|
||||
The customer paid
|
||||
<strong v-text="formatFiat(settleDialog.settlement.fiat_amount, settleDialog.settlement.fiat_code)"></strong>
|
||||
and the machine reports
|
||||
<strong v-text="settleDialog.settlement.status === 'cash_owed' ? 'nothing dispensed' : 'a partial dispense'"></strong>.
|
||||
</p>
|
||||
<p class="text-caption q-mb-md" :style="{opacity: 0.8}">
|
||||
Use this when you have made the customer whole <em>outside</em> the machine.
|
||||
The settlement distributes at the full amount and the machine marks its own
|
||||
record remediated. Nothing is dispensed. If you dispensed the shortfall at the
|
||||
machine instead, do nothing — it reports that itself.
|
||||
</p>
|
||||
<q-input v-model="settleDialog.note" type="textarea" autogrow outlined dense
|
||||
label="How was the customer paid? (required)"
|
||||
hint="e.g. handed 40 EUR to the customer in person, 2026-10-09"
|
||||
maxlength="500" counter></q-input>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="Cancel" v-close-popup></q-btn>
|
||||
<q-btn color="positive" label="Settle at full amount"
|
||||
:disable="!settleDialog.note.trim()" :loading="settleDialog.saving"
|
||||
@click="submitSettleCashOwed"></q-btn>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="noteDialog.show" persistent>
|
||||
<q-card :style="{width: '420px', maxWidth: '95vw'}">
|
||||
<q-card-section class="row items-center q-pb-none">
|
||||
|
|
@ -1518,6 +1578,10 @@
|
|||
hint="Server-side ceiling on a single cash-in's principal. The ATM attests the amount; this bounds a compromised/buggy machine to one capped tx."
|
||||
type="number" step="1" min="0"
|
||||
class="q-mb-md" dense outlined></q-input>
|
||||
<q-input v-model="superFeeDialog.data.alerts_pubkey"
|
||||
label="Alerts pubkey (hex or npub — blank = each operator's own account key)"
|
||||
hint="Where owed-cash alerts are delivered as Nostr DMs (NIP-17). Blank sends each operator a note to self on their LNbits account identity."
|
||||
class="q-mb-md" dense outlined></q-input>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="Cancel" v-close-popup></q-btn>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue