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>
28 lines
1.2 KiB
TypeScript
28 lines
1.2 KiB
TypeScript
import type { FastifyPluginAsync } from 'fastify'
|
|
import { z } from 'zod'
|
|
import { ReportService } from '../../services/report.service.js'
|
|
|
|
const DailyReportQuerySchema = z.object({
|
|
locationId: z.string().uuid(),
|
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
})
|
|
|
|
export const reportRoutes: FastifyPluginAsync = async (app) => {
|
|
// X or Z report for a drawer session
|
|
app.get('/reports/drawer/:id', { preHandler: [app.authenticate, app.requirePermission('pos.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const report = await ReportService.getDrawerReport(app.db, id)
|
|
return reply.send(report)
|
|
})
|
|
|
|
// Daily rollup for a location
|
|
app.get('/reports/daily', { preHandler: [app.authenticate, app.requirePermission('pos.view')] }, async (request, reply) => {
|
|
const parsed = DailyReportQuerySchema.safeParse(request.query)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed — locationId and date (YYYY-MM-DD) are required', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const report = await ReportService.getDailyReport(app.db, parsed.data.locationId, parsed.data.date)
|
|
return reply.send(report)
|
|
})
|
|
}
|