- New app_config key-value table for system settings, with in-memory cache (mirrors ModuleService pattern) - GET/PATCH /v1/config endpoints for reading and updating config (settings.view/settings.edit permissions) - Runtime log level: PATCH /v1/config/log_level applies immediately, persists across restarts - Startup loads log level from DB in onReady hook (env var is default, DB overrides) - Add structured request.log.info() to POS routes: transaction create/complete/void, drawer open/close, discount create/update/delete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
4.7 KiB
TypeScript
96 lines
4.7 KiB
TypeScript
import type { FastifyPluginAsync } from 'fastify'
|
|
import {
|
|
PaginationSchema,
|
|
TransactionCreateSchema,
|
|
TransactionLineItemCreateSchema,
|
|
ApplyDiscountSchema,
|
|
CompleteTransactionSchema,
|
|
} from '@lunarfront/shared/schemas'
|
|
import { TransactionService } from '../../services/transaction.service.js'
|
|
|
|
export const transactionRoutes: FastifyPluginAsync = async (app) => {
|
|
app.post('/transactions', { preHandler: [app.authenticate, app.requirePermission('pos.edit')] }, async (request, reply) => {
|
|
const parsed = TransactionCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const txn = await TransactionService.create(app.db, parsed.data, request.user.id)
|
|
request.log.info({ transactionId: txn.id, type: parsed.data.transactionType, userId: request.user.id }, 'Transaction created')
|
|
return reply.status(201).send(txn)
|
|
})
|
|
|
|
app.get('/transactions', { preHandler: [app.authenticate, app.requirePermission('pos.view')] }, async (request, reply) => {
|
|
const query = request.query as Record<string, string | undefined>
|
|
const params = PaginationSchema.parse(query)
|
|
|
|
const filters = {
|
|
status: query.status,
|
|
transactionType: query.transactionType,
|
|
locationId: query.locationId,
|
|
}
|
|
|
|
const result = await TransactionService.list(app.db, params, filters)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/transactions/:id', { preHandler: [app.authenticate, app.requirePermission('pos.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const txn = await TransactionService.getById(app.db, id)
|
|
if (!txn) return reply.status(404).send({ error: { message: 'Transaction not found', statusCode: 404 } })
|
|
return reply.send(txn)
|
|
})
|
|
|
|
app.get('/transactions/:id/receipt', { preHandler: [app.authenticate, app.requirePermission('pos.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const receipt = await TransactionService.getReceipt(app.db, id)
|
|
return reply.send(receipt)
|
|
})
|
|
|
|
app.post('/transactions/:id/line-items', { preHandler: [app.authenticate, app.requirePermission('pos.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = TransactionLineItemCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const lineItem = await TransactionService.addLineItem(app.db, id, parsed.data)
|
|
return reply.status(201).send(lineItem)
|
|
})
|
|
|
|
app.delete('/transactions/:id/line-items/:lineItemId', { preHandler: [app.authenticate, app.requirePermission('pos.edit')] }, async (request, reply) => {
|
|
const { id, lineItemId } = request.params as { id: string; lineItemId: string }
|
|
const deleted = await TransactionService.removeLineItem(app.db, id, lineItemId)
|
|
return reply.send(deleted)
|
|
})
|
|
|
|
app.post('/transactions/:id/discounts', { preHandler: [app.authenticate, app.requirePermission('pos.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = ApplyDiscountSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
await TransactionService.applyDiscount(app.db, id, parsed.data, request.user.id)
|
|
const txn = await TransactionService.getById(app.db, id)
|
|
return reply.send(txn)
|
|
})
|
|
|
|
app.post('/transactions/:id/complete', { preHandler: [app.authenticate, app.requirePermission('pos.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = CompleteTransactionSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
await TransactionService.complete(app.db, id, parsed.data)
|
|
const txn = await TransactionService.getById(app.db, id)
|
|
request.log.info({ transactionId: id, paymentMethod: parsed.data.paymentMethod, userId: request.user.id }, 'Transaction completed')
|
|
return reply.send(txn)
|
|
})
|
|
|
|
app.post('/transactions/:id/void', { preHandler: [app.authenticate, app.requirePermission('pos.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
await TransactionService.void(app.db, id, request.user.id)
|
|
const txn = await TransactionService.getById(app.db, id)
|
|
request.log.info({ transactionId: id, voidedBy: request.user.id }, 'Transaction voided')
|
|
return reply.send(txn)
|
|
})
|
|
}
|