From ec60d3f2016b57e36679f87d2491b6265017011f Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Wed, 1 Apr 2026 00:36:53 -0400 Subject: [PATCH] feat: add cassette position for physical cartridge ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add position column to cassettes table (migration v5→v6) so cassettes are ordered by physical cartridge number instead of denomination. Update BATM3 preset to $20/$1 denominations with 400-bill capacity. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/machine/electron/main.ts | 4 +-- apps/machine/electron/state-store.ts | 41 ++++++++++++++++++++++------ apps/machine/src/config/device.ts | 4 +-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/apps/machine/electron/main.ts b/apps/machine/electron/main.ts index 1ca4635..e4f244c 100644 --- a/apps/machine/electron/main.ts +++ b/apps/machine/electron/main.ts @@ -516,8 +516,8 @@ app.whenReady().then(() => { ], gaia: [{ denomination: 20, count: 50 }], batm3: [ - { denomination: 20, count: 500 }, - { denomination: 50, count: 500 }, + { denomination: 20, count: 400 }, + { denomination: 1, count: 400 }, ], } seedCassettes = presets[model] || [] diff --git a/apps/machine/electron/state-store.ts b/apps/machine/electron/state-store.ts index bddf1c4..a4d1b0f 100644 --- a/apps/machine/electron/state-store.ts +++ b/apps/machine/electron/state-store.ts @@ -15,7 +15,7 @@ import fs from 'node:fs' let db: Database.Database | null = null -const SCHEMA_VERSION = '5' +const SCHEMA_VERSION = '6' function getDbPath(): string { const prodDir = '/var/lib/lamassu-atm' @@ -53,7 +53,8 @@ export function initDatabase(dbPath?: string): void { CREATE TABLE IF NOT EXISTS cassettes ( denomination INTEGER PRIMARY KEY, - count INTEGER NOT NULL DEFAULT 0 + count INTEGER NOT NULL DEFAULT 0, + position INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS cashbox ( @@ -198,6 +199,22 @@ export function initDatabase(dbPath?: string): void { console.log( '[StateStore] Migrated schema v4 → v5 (added manual_dispense type, remediated_by, operator_commands)' ) + existing.value = '5' + } + + if (existing && existing.value === '5') { + // Migration v5 → v6: add position column to cassettes for physical cartridge ordering + db.exec(`ALTER TABLE cassettes ADD COLUMN position INTEGER NOT NULL DEFAULT 0`) + // Backfill position from existing row order (by denomination) + const rows = db.prepare('SELECT denomination FROM cassettes ORDER BY denomination').all() as { + denomination: number + }[] + const update = db.prepare('UPDATE cassettes SET position = ? WHERE denomination = ?') + for (let i = 0; i < rows.length; i++) { + update.run(i + 1, rows[i]!.denomination) + } + db.prepare('UPDATE meta SET value = ? WHERE key = ?').run('6', 'schema_version') + console.log('[StateStore] Migrated schema v5 → v6 (added cassette position)') } const cashboxRow = db.prepare('SELECT id FROM cashbox WHERE id = 1').get() @@ -215,30 +232,36 @@ export function initDatabase(dbPath?: string): void { export interface CassetteRow { denomination: number count: number + position: number } export function loadCassettes(): CassetteRow[] { if (!db) throw new Error('Database not initialized') return db - .prepare('SELECT denomination, count FROM cassettes ORDER BY denomination') + .prepare('SELECT denomination, count, position FROM cassettes ORDER BY position, denomination') .all() as CassetteRow[] } /** * Upsert cassette counts. Replaces all existing rows. */ -export function setCassettes(cassettes: CassetteRow[]): void { +export function setCassettes( + cassettes: { denomination: number; count: number; position?: number }[] +): void { if (!db) throw new Error('Database not initialized') const upsert = db.prepare( - 'INSERT INTO cassettes (denomination, count) VALUES (?, ?) ON CONFLICT(denomination) DO UPDATE SET count = excluded.count' + 'INSERT INTO cassettes (denomination, count, position) VALUES (?, ?, ?) ON CONFLICT(denomination) DO UPDATE SET count = excluded.count, position = excluded.position' ) - const run = db.transaction((rows: CassetteRow[]) => { - for (const row of rows) { - upsert.run(row.denomination, row.count) + const run = db.transaction( + (rows: { denomination: number; count: number; position?: number }[]) => { + for (let i = 0; i < rows.length; i++) { + const row = rows[i]! + upsert.run(row.denomination, row.count, row.position ?? i + 1) + } } - }) + ) run(cassettes) console.log('[StateStore] Cassettes updated:', cassettes) diff --git a/apps/machine/src/config/device.ts b/apps/machine/src/config/device.ts index 23e0953..9a22ab8 100644 --- a/apps/machine/src/config/device.ts +++ b/apps/machine/src/config/device.ts @@ -145,8 +145,8 @@ export const MACHINE_PRESETS: Record