feat: add drawer cash in/out adjustments with balance reconciliation

- New drawer_adjustment table (type: cash_in/cash_out, amount, reason)
- POST/GET /drawer/:id/adjustments endpoints
- Drawer close calculation now includes adjustments: expected = opening + sales + cash_in - cash_out
- DrawerAdjustmentSchema for input validation
- 5 new tests (44 total POS tests passing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryan
2026-04-04 20:24:55 +00:00
parent 24ddb17ca8
commit 3ed2707a66
8 changed files with 180 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
import type { FastifyPluginAsync } from 'fastify'
import { PaginationSchema, DrawerOpenSchema, DrawerCloseSchema } from '@lunarfront/shared/schemas'
import { PaginationSchema, DrawerOpenSchema, DrawerCloseSchema, DrawerAdjustmentSchema } from '@lunarfront/shared/schemas'
import { DrawerService } from '../../services/drawer.service.js'
export const drawerRoutes: FastifyPluginAsync = async (app) => {
@@ -35,6 +35,23 @@ export const drawerRoutes: FastifyPluginAsync = async (app) => {
return reply.send(session)
})
app.post('/drawer/:id/adjustments', { preHandler: [app.authenticate, app.requirePermission('pos.edit')] }, async (request, reply) => {
const { id } = request.params as { id: string }
const parsed = DrawerAdjustmentSchema.safeParse(request.body)
if (!parsed.success) {
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
}
const adjustment = await DrawerService.addAdjustment(app.db, id, parsed.data, request.user.id)
request.log.info({ drawerSessionId: id, type: parsed.data.type, amount: parsed.data.amount, userId: request.user.id }, 'Drawer adjustment')
return reply.status(201).send(adjustment)
})
app.get('/drawer/:id/adjustments', { preHandler: [app.authenticate, app.requirePermission('pos.view')] }, async (request, reply) => {
const { id } = request.params as { id: string }
const adjustments = await DrawerService.getAdjustments(app.db, id)
return reply.send({ data: adjustments })
})
app.get('/drawer/:id', { preHandler: [app.authenticate, app.requirePermission('pos.view')] }, async (request, reply) => {
const { id } = request.params as { id: string }
const session = await DrawerService.getById(app.db, id)