fix: require open drawer to complete transactions, fix product price field
- Backend enforces open drawer at location before completing any transaction - Frontend disables payment buttons when drawer is closed with warning message - Fix product price field name (price, not sellingPrice) in POS API types - Fix seed UUIDs to use valid UUID v4 format (version nibble must be 1-8) - Fix Vite allowedHosts for dev.lunarfront.tech access - Add e2e test for drawer enforcement (39 POS tests now pass) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -273,6 +273,36 @@ suite('POS', { tags: ['pos'] }, (t) => {
|
||||
|
||||
// ─── Complete Transaction ──────────────────────────────────────────────────
|
||||
|
||||
t.test('rejects completing transaction without open drawer', { tags: ['transactions', 'complete', 'validation', 'drawer'] }, async () => {
|
||||
// Ensure no drawer is open at LOCATION_ID
|
||||
const current = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
|
||||
if (current.status === 200 && current.data.id) {
|
||||
await t.api.post(`/v1/drawer/${current.data.id}/close`, { closingBalance: 0 })
|
||||
}
|
||||
|
||||
const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
|
||||
await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
|
||||
description: 'No Drawer Item',
|
||||
qty: 1,
|
||||
unitPrice: 10,
|
||||
})
|
||||
|
||||
const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
|
||||
paymentMethod: 'cash',
|
||||
amountTendered: 20,
|
||||
})
|
||||
t.assert.status(res, 400)
|
||||
|
||||
// Void to clean up
|
||||
await t.api.post(`/v1/transactions/${txn.data.id}/void`)
|
||||
})
|
||||
|
||||
// Open a drawer for the remaining complete tests
|
||||
t.test('opens drawer for complete tests', { tags: ['transactions', 'complete', 'setup'] }, async () => {
|
||||
const res = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
|
||||
t.assert.status(res, 201)
|
||||
})
|
||||
|
||||
t.test('completes a cash transaction with change', { tags: ['transactions', 'complete', 'cash'] }, async () => {
|
||||
const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
|
||||
await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
|
||||
@@ -427,6 +457,18 @@ suite('POS', { tags: ['pos'] }, (t) => {
|
||||
|
||||
// ─── Cash Rounding ─────────────────────────────────────────────────────────
|
||||
|
||||
// Close the LOCATION_ID drawer and open one at ROUNDING_LOCATION_ID
|
||||
t.test('setup drawer for rounding tests', { tags: ['transactions', 'rounding', 'setup'] }, async () => {
|
||||
// Close drawer at LOCATION_ID
|
||||
const current = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
|
||||
if (current.status === 200 && current.data.id) {
|
||||
await t.api.post(`/v1/drawer/${current.data.id}/close`, { closingBalance: 200 })
|
||||
}
|
||||
// Open drawer at ROUNDING_LOCATION_ID
|
||||
const res = await t.api.post('/v1/drawer/open', { locationId: ROUNDING_LOCATION_ID, openingBalance: 200 })
|
||||
t.assert.status(res, 201)
|
||||
})
|
||||
|
||||
t.test('cash rounding adjusts total to nearest nickel', { tags: ['transactions', 'rounding'] }, async () => {
|
||||
// Create transaction at the rounding-enabled location
|
||||
const txn = await t.api.post('/v1/transactions', {
|
||||
@@ -484,6 +526,10 @@ suite('POS', { tags: ['pos'] }, (t) => {
|
||||
})
|
||||
|
||||
t.test('no rounding at non-rounding location', { tags: ['transactions', 'rounding'] }, async () => {
|
||||
// Open drawer at LOCATION_ID for this test
|
||||
const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
|
||||
t.assert.status(drawer, 201)
|
||||
|
||||
const txn = await t.api.post('/v1/transactions', {
|
||||
transactionType: 'sale',
|
||||
locationId: LOCATION_ID,
|
||||
@@ -500,6 +546,17 @@ suite('POS', { tags: ['pos'] }, (t) => {
|
||||
})
|
||||
t.assert.status(res, 200)
|
||||
t.assert.equal(parseFloat(res.data.roundingAdjustment), 0)
|
||||
|
||||
// Cleanup
|
||||
await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 100 })
|
||||
})
|
||||
|
||||
// Close rounding location drawer
|
||||
t.test('cleanup rounding drawer', { tags: ['transactions', 'rounding', 'setup'] }, async () => {
|
||||
const current = await t.api.get('/v1/drawer/current', { locationId: ROUNDING_LOCATION_ID })
|
||||
if (current.status === 200 && current.data.id) {
|
||||
await t.api.post(`/v1/drawer/${current.data.id}/close`, { closingBalance: 200 })
|
||||
}
|
||||
})
|
||||
|
||||
// ─── Full POS Flow ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user