feat(POS-FIX-8): completed feature
This commit is contained in:
@@ -1780,6 +1780,93 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
|
||||
},
|
||||
);
|
||||
|
||||
// ── POS-FIX-8: DELETE /pos/sales/:id — remove a pending parked sale ───────
|
||||
app.delete<{ Params: { id: string } }>(
|
||||
'/pos/sales/:id',
|
||||
{
|
||||
schema: {
|
||||
tags: ['POS Terminal'],
|
||||
summary: 'Remove a pending parked sale and restore inventory',
|
||||
params: { type: 'object', properties: { id: { type: 'string', format: 'uuid' } } },
|
||||
response: { 401: errorSchema, 403: 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 { id } = parseJson(idParamSchema, request.params);
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
const orderRows = await client.query<{
|
||||
id: string;
|
||||
state: string;
|
||||
store_id: string;
|
||||
}>(
|
||||
`SELECT id, state, store_id FROM orders_orders WHERE id = $1 FOR UPDATE`,
|
||||
[id],
|
||||
);
|
||||
const order = orderRows.rows[0];
|
||||
if (!order) throw new AppError(404, 'POS_SALE_NOT_FOUND', 'Venta no encontrada');
|
||||
if (order.state !== 'PENDING') {
|
||||
throw new AppError(409, 'POS_SALE_NOT_PENDING', 'Solo se pueden eliminar ventas pendientes');
|
||||
}
|
||||
|
||||
const paymentRows = await client.query<{ cnt: string }>(
|
||||
`SELECT COUNT(*) AS cnt FROM payments_transactions WHERE order_id = $1 AND status = 'succeeded'`,
|
||||
[id],
|
||||
);
|
||||
if (Number(paymentRows.rows[0]?.cnt ?? 0) > 0) {
|
||||
throw new AppError(409, 'POS_SALE_HAS_PAYMENTS', 'No se puede eliminar una venta con pagos registrados');
|
||||
}
|
||||
|
||||
const itemRows = await client.query<{ variant_id: string | null; quantity: number; is_free_item: boolean }>(
|
||||
`SELECT variant_id, quantity, is_free_item FROM orders_items WHERE order_id = $1`,
|
||||
[id],
|
||||
);
|
||||
for (const item of itemRows.rows) {
|
||||
if (!item.is_free_item && item.variant_id) {
|
||||
await client.query(
|
||||
`UPDATE inventory_stock
|
||||
SET available = available + $3,
|
||||
sold = GREATEST(sold - $3, 0),
|
||||
updated_at = now()
|
||||
WHERE variant_id = $1 AND store_id = $2`,
|
||||
[item.variant_id, order.store_id, item.quantity],
|
||||
);
|
||||
await client.query(
|
||||
`INSERT INTO inventory_movements (variant_id, store_id, operation, quantity)
|
||||
VALUES ($1, $2, 'cancel', $3)`,
|
||||
[item.variant_id, order.store_id, item.quantity],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`UPDATE orders_orders
|
||||
SET state = 'CANCELLED', state_changed_at = now(), updated_at = now()
|
||||
WHERE id = $1`,
|
||||
[id],
|
||||
);
|
||||
await client.query(
|
||||
`INSERT INTO orders_order_events (order_id, event, actor_id, metadata)
|
||||
VALUES ($1, 'CANCELLED', $2, $3)`,
|
||||
[id, user.id, JSON.stringify({ source: 'pos_delete' })],
|
||||
);
|
||||
|
||||
await client.query('COMMIT');
|
||||
return reply.send({ ok: true, deletedAt: new Date().toISOString() });
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.get<{ Params: { id: string } }>(
|
||||
'/pos/sales/:id/receipt',
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user