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:
@@ -114,6 +114,81 @@ suite('POS', { tags: ['pos'] }, (t) => {
|
||||
t.assert.ok(res.data.pagination)
|
||||
})
|
||||
|
||||
// ─── Drawer Adjustments ─────────────────────────────────────────────────────
|
||||
|
||||
t.test('adds cash out adjustment to open drawer', { tags: ['drawer', 'adjustments'] }, async () => {
|
||||
const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
|
||||
t.assert.status(drawer, 201)
|
||||
|
||||
const res = await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, {
|
||||
type: 'cash_out',
|
||||
amount: 50,
|
||||
reason: 'Bank deposit',
|
||||
})
|
||||
t.assert.status(res, 201)
|
||||
t.assert.equal(res.data.type, 'cash_out')
|
||||
t.assert.equal(parseFloat(res.data.amount), 50)
|
||||
|
||||
// Cleanup
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 150 })
|
||||
})
|
||||
|
||||
t.test('adds cash in adjustment to open drawer', { tags: ['drawer', 'adjustments'] }, async () => {
|
||||
const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
|
||||
t.assert.status(drawer, 201)
|
||||
|
||||
const res = await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, {
|
||||
type: 'cash_in',
|
||||
amount: 25,
|
||||
reason: 'Change from petty cash',
|
||||
})
|
||||
t.assert.status(res, 201)
|
||||
t.assert.equal(res.data.type, 'cash_in')
|
||||
|
||||
// Cleanup
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 125 })
|
||||
})
|
||||
|
||||
t.test('lists drawer adjustments', { tags: ['drawer', 'adjustments'] }, async () => {
|
||||
const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_out', amount: 30, reason: 'Test out' })
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_in', amount: 10, reason: 'Test in' })
|
||||
|
||||
const res = await t.api.get(`/v1/drawer/${drawer.data.id}/adjustments`)
|
||||
t.assert.status(res, 200)
|
||||
t.assert.equal(res.data.data.length, 2)
|
||||
|
||||
// Cleanup
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 80 })
|
||||
})
|
||||
|
||||
t.test('drawer close includes adjustments in expected balance', { tags: ['drawer', 'adjustments', 'close'] }, async () => {
|
||||
const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
|
||||
t.assert.status(drawer, 201)
|
||||
|
||||
// Cash out $50, cash in $20 → net adjustment = -$30
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_out', amount: 50, reason: 'Bank drop' })
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_in', amount: 20, reason: 'Extra change' })
|
||||
|
||||
// Close — expected = 200 (opening) + 0 (no sales) + 20 (in) - 50 (out) = 170
|
||||
const closed = await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 170 })
|
||||
t.assert.status(closed, 200)
|
||||
t.assert.equal(parseFloat(closed.data.expectedBalance), 170)
|
||||
t.assert.equal(parseFloat(closed.data.overShort), 0)
|
||||
})
|
||||
|
||||
t.test('rejects adjustment on closed drawer', { tags: ['drawer', 'adjustments', 'validation'] }, async () => {
|
||||
const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 100 })
|
||||
|
||||
const res = await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, {
|
||||
type: 'cash_out',
|
||||
amount: 10,
|
||||
reason: 'Should fail',
|
||||
})
|
||||
t.assert.status(res, 409)
|
||||
})
|
||||
|
||||
// ─── Transactions ──────────────────────────────────────────────────────────
|
||||
|
||||
t.test('creates a sale transaction', { tags: ['transactions', 'create'] }, async () => {
|
||||
|
||||
Reference in New Issue
Block a user