From 2a3df155657436e27cd964f5eeb067058c1d75aa Mon Sep 17 00:00:00 2001 From: BOHA Date: Tue, 9 Jun 2026 11:23:45 +0200 Subject: [PATCH] fix(orders): release PO number against creation year; tighten sort-dir + enum typing Addresses review of the issued-orders service: deleteIssuedOrder now derives the release year from created_at (cross-year deletes reclaim the right sequence); list sort direction is normalized to asc/desc; status cast uses $Enums instead of as-never. Adds a cross-year delete test. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/__tests__/issued-orders.test.ts | 21 +++++++++++++++++++++ src/services/issued-orders.service.ts | 24 ++++++++++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/__tests__/issued-orders.test.ts b/src/__tests__/issued-orders.test.ts index 9e2fd66..9c97655 100644 --- a/src/__tests__/issued-orders.test.ts +++ b/src/__tests__/issued-orders.test.ts @@ -195,4 +195,25 @@ describe("deleteIssuedOrder", () => { ).toBe(0); expect((await previewIssuedOrderNumber()).number).toBe(before); }); + + it("releases the number against the order's creation year, not the current year", async () => { + const priorYear = new Date().getFullYear() - 1; + // Allocate a real prior-year number (consumes that year's sequence: 0 -> 1) + // so the PO number matches the prior year's current highest. + const { number: poNumber } = await generateIssuedOrderNumber(priorYear); + const order = await createIssuedOrder({ po_number: poNumber }); + createdIds.push(order.id); + // Backdate creation into the prior year so delete derives that year. + await prisma.issued_orders.update({ + where: { id: order.id }, + data: { created_at: new Date(priorYear, 5, 1, 12, 0, 0) }, + }); + await deleteIssuedOrder(order.id); + // The prior-year sequence must be decremented (1 -> 0). With the old + // current-year bug it would stay at 1 (no current-year row to match). + const seq = await prisma.number_sequences.findFirst({ + where: { type: "issued_order", year: priorYear }, + }); + expect(seq?.last_number).toBe(0); + }); }); diff --git a/src/services/issued-orders.service.ts b/src/services/issued-orders.service.ts index e32b118..6cc9962 100644 --- a/src/services/issued-orders.service.ts +++ b/src/services/issued-orders.service.ts @@ -1,3 +1,4 @@ +import type { $Enums } from "@prisma/client"; import prisma from "../config/database"; import { generateIssuedOrderNumber, @@ -106,10 +107,12 @@ export async function listIssuedOrders(params: ListIssuedOrdersParams) { ]; } + // Normalize the direction so an unexpected value can't reach Prisma at runtime. + const dir: "asc" | "desc" = order === "asc" ? "asc" : "desc"; // Tiebreak on id so same-day order_date rows sort deterministically. - const orderBy: Record[] = [ - { [sortField]: order }, - { id: order }, + const orderBy: Record[] = [ + { [sortField]: dir }, + { id: dir }, ]; const [rows, total] = await Promise.all([ @@ -176,7 +179,9 @@ export async function createIssuedOrder(body: IssuedOrderInput) { data: { po_number: poNumber, customer_id: body.customer_id ? Number(body.customer_id) : null, - status: (body.status ? String(body.status) : "draft") as never, + status: (body.status + ? String(body.status) + : "draft") as $Enums.issued_orders_status, currency: body.currency ? String(body.currency) : "CZK", vat_rate: body.vat_rate != null ? Number(body.vat_rate) : 21.0, apply_vat: body.apply_vat !== false, @@ -306,10 +311,13 @@ export async function deleteIssuedOrder(id: number) { const existing = await prisma.issued_orders.findUnique({ where: { id } }); if (!existing) return null; await prisma.issued_orders.delete({ where: { id } }); - await releaseIssuedOrderNumber( - new Date().getFullYear(), - existing.po_number ?? undefined, - ); + // Reclaim the number against the year it was allocated in (the creation year), + // not the current year — a cross-year delete must release the right sequence + // or the number is never freed. + const year = existing.created_at + ? new Date(existing.created_at).getFullYear() + : new Date().getFullYear(); + await releaseIssuedOrderNumber(year, existing.po_number ?? undefined); return existing; }