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:
Padreug 2026-10-10 22:26:45 +02:00
commit cdb1fe71cc
2 changed files with 114 additions and 2 deletions

View file

@ -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>