feat: add cash rounding, POS test suite, and fix test harness port cleanup
All checks were successful
CI / ci (pull_request) Successful in 20s
CI / e2e (pull_request) Successful in 50s

- Add Swedish rounding (nearest nickel) for cash payments at locations with cash_rounding enabled
- Add rounding_adjustment column to transactions, cash_rounding to locations
- Add POS schema to database plugin for relational query support
- Complete/void routes now return full transaction with line items via getById
- Test harness killPort falls back to fuser when lsof unavailable (fixes stale process bug)
- Add 35-test POS API suite covering discounts, drawer, transactions, tax, rounding, e2e flow
- Add unit tests for tax service and POS Zod schemas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryan
2026-04-04 18:23:05 +00:00
parent 7b15f18e59
commit 8256380cd1
15 changed files with 1225 additions and 25 deletions

View File

@@ -231,10 +231,26 @@ export const TransactionService = {
if (!txn) throw new NotFoundError('Transaction')
if (txn.status !== 'pending') throw new ConflictError('Transaction is not pending')
// Validate cash payment
// Validate cash payment (with optional nickel rounding)
let changeGiven: string | undefined
let roundingAdjustment = 0
if (input.paymentMethod === 'cash') {
const total = parseFloat(txn.total)
let total = parseFloat(txn.total)
// Apply Swedish rounding if location has cash_rounding enabled
if (txn.locationId) {
const [loc] = await db
.select({ cashRounding: locations.cashRounding })
.from(locations)
.where(eq(locations.id, txn.locationId))
.limit(1)
if (loc?.cashRounding) {
const rounded = TaxService.roundToNickel(total)
roundingAdjustment = Math.round((rounded - total) * 100) / 100
total = rounded
}
}
if (!input.amountTendered || input.amountTendered < total) {
throw new ValidationError('Amount tendered must be >= transaction total for cash payments')
}
@@ -273,13 +289,13 @@ export const TransactionService = {
paymentMethod: input.paymentMethod,
amountTendered: input.amountTendered?.toString(),
changeGiven,
roundingAdjustment: roundingAdjustment.toString(),
checkNumber: input.checkNumber,
completedAt: new Date(),
updatedAt: new Date(),
})
.where(eq(transactions.id, transactionId))
.returning()
return completed
},