feat: named registers, X/Z reports, daily rollup, fix drawerSessionId

Registers:
- New register table with location association
- CRUD service + API routes (POST/GET/PATCH/DELETE /registers)
- Drawer sessions now link to a register via registerId
- Register ID persisted in localStorage per device

X/Z Reports:
- ReportService with getDrawerReport() (X or Z depending on session state)
- Z report auto-displayed on drawer close in the drawer dialog
- X report (Current Shift Report) button on open drawer view
- Report shows: sales summary, payment breakdown, discounts, cash accountability, adjustments

Daily Rollup:
- ReportService.getDailyReport() aggregates all sessions at a location for a date
- New /reports/daily endpoint with locationId + date params
- Frontend daily report page with date picker, location selector, session breakdown

Critical Fix:
- drawerSessionId is now populated on transactions when completing (was never set before)
- This enables accurate per-drawer reporting and cash accountability

Migration 0044: register table, drawer_session.register_id column

Tests: 14 new (register CRUD, drawer report X/Z, drawerSessionId population, daily rollup, register-drawer link)
Full suite: 367 passed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryan
2026-04-05 02:21:55 +00:00
parent a29e924544
commit 663f00b099
17 changed files with 1062 additions and 6 deletions

View File

@@ -23,6 +23,8 @@ import { repairRoutes } from './routes/v1/repairs.js'
import { lessonRoutes } from './routes/v1/lessons.js'
import { transactionRoutes } from './routes/v1/transactions.js'
import { drawerRoutes } from './routes/v1/drawer.js'
import { registerRoutes } from './routes/v1/register.js'
import { reportRoutes } from './routes/v1/reports.js'
import { discountRoutes } from './routes/v1/discounts.js'
import { taxRoutes } from './routes/v1/tax.js'
import { storageRoutes } from './routes/v1/storage.js'
@@ -120,6 +122,8 @@ export async function buildApp() {
await app.register(withModule('lessons', lessonRoutes), { prefix: '/v1' })
await app.register(withModule('pos', transactionRoutes), { prefix: '/v1' })
await app.register(withModule('pos', drawerRoutes), { prefix: '/v1' })
await app.register(withModule('pos', registerRoutes), { prefix: '/v1' })
await app.register(withModule('pos', reportRoutes), { prefix: '/v1' })
await app.register(withModule('pos', discountRoutes), { prefix: '/v1' })
await app.register(withModule('pos', taxRoutes), { prefix: '/v1' })
await app.register(withModule('vault', vaultRoutes), { prefix: '/v1' })