fix(cassettes): report a drained machine as drained
getInventory dropped zero-count bays, so a fully dispensed machine returned an empty map — identical to a machine with no cassettes configured. Every caller reads an empty map as "nothing known, ask the hardware": reloadPersistedInventory skipped the update entirely, so the last non-empty snapshot stuck and the public availability beacon went on advertising bills that had already gone out the slot. Zero-count bays are kept, so an empty map now means exactly one thing: no cassettes are configured. Consumers already filter for > 0 before offering a denomination. loadInventoryFromDb returns null when the DB could not be asked at all (browser dev, failed IPC) so callers can still tell "no answer" from an answer of "the bays are empty", and only the former defers to HAL. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
5b22447dae
commit
0d43c4e033
3 changed files with 92 additions and 15 deletions
|
|
@ -161,10 +161,14 @@ async function handleManagementCommand(
|
|||
}
|
||||
|
||||
/**
|
||||
* Load inventory from SQLite via IPC (Electron only).
|
||||
* Returns empty object in browser dev mode.
|
||||
* Load inventory from SQLite via IPC.
|
||||
*
|
||||
* Returns `null` when the DB could not be asked at all — browser dev mode, or
|
||||
* a failed IPC call — so callers can tell "no answer" from an answer of "the
|
||||
* bays are empty". An empty map is a real, actionable reading: cassettes are
|
||||
* configured and drained, or none are configured.
|
||||
*/
|
||||
async function loadInventoryFromDb(): Promise<Record<number, number>> {
|
||||
async function loadInventoryFromDb(): Promise<Record<number, number> | null> {
|
||||
if (isElectron && window.electronAPI) {
|
||||
try {
|
||||
const inv = await window.electronAPI.getInventory()
|
||||
|
|
@ -174,7 +178,7 @@ async function loadInventoryFromDb(): Promise<Record<number, number>> {
|
|||
console.warn('[ATM] Failed to load inventory from DB:', e)
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -448,10 +452,12 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
|
||||
async function reloadPersistedInventory() {
|
||||
const inv = await loadInventoryFromDb()
|
||||
if (Object.keys(inv).length > 0) {
|
||||
persistedInventory.value = inv
|
||||
console.log('[ATM] Persisted inventory updated:', inv)
|
||||
}
|
||||
// Only a failed read is ignored. An empty map used to be skipped too,
|
||||
// which meant the last bill out of the machine never updated anything and
|
||||
// the availability beacon kept advertising a full cassette.
|
||||
if (inv === null) return
|
||||
persistedInventory.value = inv
|
||||
console.log('[ATM] Persisted inventory updated:', inv)
|
||||
}
|
||||
|
||||
/** Detect Bitcoin network from a BOLT-11 invoice prefix (called once, persisted) */
|
||||
|
|
@ -1064,7 +1070,8 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
}),
|
||||
getInventory: async () => {
|
||||
const fresh = await loadInventoryFromDb()
|
||||
return Object.keys(fresh).length > 0 ? fresh : services.atmServices.getInventory()
|
||||
// null == the DB could not be asked; an empty map is a real reading.
|
||||
return fresh ?? services.atmServices.getInventory()
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -1327,7 +1334,8 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
// If DB has inventory, use it; otherwise fall back to HAL
|
||||
getInventory: async () => {
|
||||
const fresh = await loadInventoryFromDb()
|
||||
return Object.keys(fresh).length > 0 ? fresh : hal.atmServices.getInventory()
|
||||
// null == the DB could not be asked; an empty map is a real reading.
|
||||
return fresh ?? hal.atmServices.getInventory()
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -1611,9 +1619,11 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
return await api.halDispense(amounts)
|
||||
},
|
||||
getInventory: async () => {
|
||||
// Priority: DB inventory > HAL hardware inventory > empty
|
||||
// Priority: DB inventory > HAL hardware inventory > empty. Only a
|
||||
// null (unreadable) DB defers to HAL — a drained machine reports
|
||||
// drained rather than borrowing the hardware's view.
|
||||
const fresh = await loadInventoryFromDb()
|
||||
if (Object.keys(fresh).length > 0) return fresh
|
||||
if (fresh !== null) return fresh
|
||||
// Fall back to HAL's cassette-based inventory
|
||||
try {
|
||||
const halInv = await api.halGetInventory()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue