fix(cassettes): say when the counts are unverified instead of reporting a guess
When the dispenser throws or the dispense times out there is no per-bay report, so nothing is debited — not the cassette rows, not HAL's bays. Bills may well have reached the customer, and both counters then read high with nothing to indicate it. The machine went on treating a number it had reason to doubt as measurement. A dispense that ends with no report now latches a countsUncertainSince flag, which rides along in the state document as counts_uncertain_since so the operator can see the numbers need a recount. The field is additive: a consumer reading positions ignores it, so this needs no coordinated release. An operator config apply clears the flag inside the same transaction, since asserting authoritative counts is precisely what a recount is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
54c59fadcc
commit
474903c38b
7 changed files with 130 additions and 2 deletions
|
|
@ -12,11 +12,14 @@
|
|||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
applyOperatorCassettesConfig,
|
||||
closeDatabase,
|
||||
getCashbox,
|
||||
getCountsUncertainSince,
|
||||
getInventory,
|
||||
initDatabase,
|
||||
loadCassettes,
|
||||
markCountsUncertain,
|
||||
recordTransaction,
|
||||
setCassettes,
|
||||
} from '../state-store.js'
|
||||
|
|
@ -322,3 +325,43 @@ describe('state-store: getInventory represents a drained machine', () => {
|
|||
expect(getInventory()).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('state-store: unverified counts after a silent dispense', () => {
|
||||
it('starts clear, latches the first time, and keeps the earliest time', () => {
|
||||
expect(getCountsUncertainSince()).toBeNull()
|
||||
markCountsUncertain(1000)
|
||||
expect(getCountsUncertainSince()).toBe(1000)
|
||||
// A second failure does not move the clock forward — the question is how
|
||||
// long the numbers have been untrustworthy, not when we last noticed.
|
||||
markCountsUncertain(2000)
|
||||
expect(getCountsUncertainSince()).toBe(1000)
|
||||
})
|
||||
|
||||
it('clears when an operator asserts real counts', () => {
|
||||
markCountsUncertain(1000)
|
||||
const applied = applyOperatorCassettesConfig(
|
||||
{
|
||||
positions: {
|
||||
'1': { denomination: 20, count: 40 },
|
||||
'2': { denomination: 20, count: 40 },
|
||||
'3': { denomination: 50, count: 25 },
|
||||
},
|
||||
},
|
||||
1_700_000_000
|
||||
)
|
||||
expect(applied.applied).toBe(true)
|
||||
// A recount is exactly the operator asserting authoritative counts.
|
||||
expect(getCountsUncertainSince()).toBeNull()
|
||||
})
|
||||
|
||||
it('leaves the flag alone when the operator config is rejected', () => {
|
||||
markCountsUncertain(1000)
|
||||
// Position key-set mismatch — the bay layout is hardware-determined.
|
||||
const applied = applyOperatorCassettesConfig(
|
||||
{ positions: { '1': { denomination: 20, count: 40 } } },
|
||||
1_700_000_001
|
||||
)
|
||||
expect(applied.applied).toBe(false)
|
||||
expect(getCountsUncertainSince()).toBe(1000)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue