feat: operator command queue for local TUI dispense
Add operator_commands table and polling loop so the TUI (or other local tools) can trigger manual dispenses by inserting a command row into SQLite. The Electron main process polls every 2s, executes pending commands via HAL, records the transaction, and updates the command status with the result. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
01e71b0954
commit
89b0ceaa73
2 changed files with 164 additions and 1 deletions
|
|
@ -95,6 +95,15 @@ export function initDatabase(dbPath?: string): void {
|
|||
dispensed INTEGER NOT NULL DEFAULT 0,
|
||||
rejected INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS operator_commands (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
command TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
result TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
completed_at INTEGER
|
||||
);
|
||||
`)
|
||||
|
||||
// Seed meta + cashbox if first run, or run migrations
|
||||
|
|
@ -170,8 +179,20 @@ export function initDatabase(dbPath?: string): void {
|
|||
DROP TABLE transactions;
|
||||
ALTER TABLE transactions_new RENAME TO transactions;
|
||||
`)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS operator_commands (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
command TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
result TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
completed_at INTEGER
|
||||
);
|
||||
`)
|
||||
db.prepare('UPDATE meta SET value = ? WHERE key = ?').run('5', 'schema_version')
|
||||
console.log('[StateStore] Migrated schema v4 → v5 (added manual_dispense type, remediated_by)')
|
||||
console.log(
|
||||
'[StateStore] Migrated schema v4 → v5 (added manual_dispense type, remediated_by, operator_commands)'
|
||||
)
|
||||
}
|
||||
|
||||
const cashboxRow = db.prepare('SELECT id FROM cashbox WHERE id = 1').get()
|
||||
|
|
@ -401,6 +422,56 @@ export function recordTransaction(tx: TransactionInput): void {
|
|||
console.log('[StateStore] Recorded transaction:', tx.txid, tx.type, `(${tx.status})`)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Operator Command Queue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface OperatorCommand {
|
||||
id: number
|
||||
command: string
|
||||
status: string
|
||||
created_at: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next pending operator command (FIFO).
|
||||
*/
|
||||
export function getPendingCommand(): OperatorCommand | null {
|
||||
if (!db) throw new Error('Database not initialized')
|
||||
|
||||
const row = db
|
||||
.prepare(
|
||||
"SELECT id, command, status, created_at FROM operator_commands WHERE status = 'pending' ORDER BY id LIMIT 1"
|
||||
)
|
||||
.get() as { id: number; command: string; status: string; created_at: number } | undefined
|
||||
|
||||
return row ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a command as executing (prevents double-pickup).
|
||||
*/
|
||||
export function markCommandExecuting(id: number): void {
|
||||
if (!db) throw new Error('Database not initialized')
|
||||
db.prepare(
|
||||
"UPDATE operator_commands SET status = 'executing' WHERE id = ? AND status = 'pending'"
|
||||
).run(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a command with a result.
|
||||
*/
|
||||
export function completeCommand(id: number, result: string, error?: boolean): void {
|
||||
if (!db) throw new Error('Database not initialized')
|
||||
db.prepare(
|
||||
'UPDATE operator_commands SET status = ?, result = ?, completed_at = ? WHERE id = ?'
|
||||
).run(error ? 'error' : 'complete', result, Date.now(), id)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transaction Remediation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Mark a failed transaction as remediated by a manual dispense.
|
||||
* Only updates transactions with status 'dispense_error' or 'partial'.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue