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:
Padreug 2026-09-22 22:07:35 +02:00
commit 0d43c4e033
3 changed files with 92 additions and 15 deletions

View file

@ -874,9 +874,14 @@ export function getInventory(): Record<number, number> {
const rows = loadCassettes()
const inv: Record<number, number> = {}
for (const row of rows) {
if (row.count > 0) {
inv[row.denomination] = (inv[row.denomination] ?? 0) + row.count
}
// Zero-count bays are KEPT. Dropping them made a drained machine
// indistinguishable from an unconfigured one, and every caller reads an
// empty map as "I don't know, ask the hardware" — so the last non-empty
// snapshot stuck and the availability beacon went on advertising bills
// that had already been dispensed. An empty map now means exactly one
// thing: no cassettes are configured. Consumers already filter for
// `> 0` before offering a denomination (CashOutView, machine.ts).
inv[row.denomination] = (inv[row.denomination] ?? 0) + row.count
}
return inv
}