feat(POS-008): completed feature
This commit is contained in:
@@ -11,6 +11,7 @@ import { ListTerminalsUseCase } from '../application/list-terminals.js';
|
||||
import { GetPosConfigUseCase } from '../application/get-pos-config.js';
|
||||
import { OpenCashSessionUseCase } from '../application/open-cash-session.js';
|
||||
import { CloseCashSessionUseCase } from '../application/close-cash-session.js';
|
||||
import { CreatePosSaleUseCase } from '../application/create-pos-sale.js';
|
||||
import { PgStoreRepository } from '../infrastructure/pg-store-repository.js';
|
||||
import { PgTerminalRepository } from '../infrastructure/pg-terminal-repository.js';
|
||||
import { PgPaymentMethodRepository } from '../infrastructure/pg-payment-method-repository.js';
|
||||
@@ -30,6 +31,7 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
|
||||
const terminalRepo = new PgTerminalRepository(pool);
|
||||
const paymentMethodRepo = new PgPaymentMethodRepository(pool);
|
||||
const sessionRepo = new PgCashSessionRepository(pool);
|
||||
const createPosSale = new CreatePosSaleUseCase(pool);
|
||||
|
||||
const listStores = new ListStoresUseCase(storeRepo);
|
||||
const listTerminals = new ListTerminalsUseCase(terminalRepo);
|
||||
@@ -510,4 +512,81 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
|
||||
return reply.send(result.rows[0]);
|
||||
});
|
||||
|
||||
|
||||
// ── POS-008: POST /pos/sales idempotent ───────────────────────────────────
|
||||
|
||||
app.post('/pos/sales', {
|
||||
schema: {
|
||||
tags: ['POS Terminal'],
|
||||
summary: 'Record a POS sale (idempotent)',
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['idempotencyKey', 'cashSessionId', 'terminalId', 'items', 'payments'],
|
||||
properties: {
|
||||
idempotencyKey: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
cashSessionId: { type: 'string', format: 'uuid' },
|
||||
terminalId: { type: 'string', format: 'uuid' },
|
||||
items: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['variantId', 'productId', 'sku', 'name', 'unitPriceCents', 'discountCents', 'taxCents', 'quantity'],
|
||||
properties: {
|
||||
variantId: { type: 'string', format: 'uuid' },
|
||||
productId: { type: 'string', format: 'uuid' },
|
||||
sku: { type: 'string' },
|
||||
ean: { type: ['string', 'null'] },
|
||||
name: { type: 'string' },
|
||||
unitPriceCents: { type: 'integer', minimum: 0 },
|
||||
discountCents: { type: 'integer', minimum: 0 },
|
||||
taxCents: { type: 'integer', minimum: 0 },
|
||||
quantity: { type: 'integer', minimum: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
payments: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['kind', 'amountCents'],
|
||||
properties: {
|
||||
kind: { type: 'string', enum: ['cash', 'card', 'other'] },
|
||||
amountCents: { type: 'integer', minimum: 1 },
|
||||
tenderedCents: { type: 'integer', minimum: 0 },
|
||||
last4: { type: 'string', maxLength: 4 },
|
||||
},
|
||||
},
|
||||
},
|
||||
customerId: { type: 'string', format: 'uuid' },
|
||||
},
|
||||
},
|
||||
response: { 400: errorSchema, 401: errorSchema, 404: errorSchema, 409: errorSchema },
|
||||
} as FastifySchema,
|
||||
}, async (request, reply) => {
|
||||
const user = await authenticate(request);
|
||||
requireAnyRole(user, ['admin', 'pos_manager', 'pos_cashier'] as ReadonlyArray<Role>);
|
||||
const body = parseJson(
|
||||
z.object({
|
||||
idempotencyKey: z.string().min(1).max(128),
|
||||
cashSessionId: z.string().uuid(),
|
||||
terminalId: z.string().uuid(),
|
||||
items: z.array(z.object({
|
||||
variantId: z.string().uuid(), productId: z.string().uuid(), sku: z.string(),
|
||||
ean: z.string().nullable(), name: z.string(),
|
||||
unitPriceCents: z.number().int().min(0), discountCents: z.number().int().min(0),
|
||||
taxCents: z.number().int().min(0), quantity: z.number().int().min(1),
|
||||
})),
|
||||
payments: z.array(z.object({
|
||||
kind: z.enum(['cash', 'card', 'other']), amountCents: z.number().int().min(1),
|
||||
tenderedCents: z.number().int().min(0).optional(), last4: z.string().max(4).optional(),
|
||||
})),
|
||||
customerId: z.string().uuid().optional(),
|
||||
}),
|
||||
request.body ?? {},
|
||||
);
|
||||
const result = await createPosSale.execute({ ...body, userId: user.id });
|
||||
return reply.code(201).send(result);
|
||||
});
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user