feat(projects/orders): project status machine + bidirectional project-order sync; order reopen
- projects.service: VALID_TRANSITIONS (aktivni->dokonceny/zruseny, terminal->aktivni reopen), tolerant of legacy statuses; getProject returns valid_transitions; updateProject validates transitions (invalid_transition token -> Czech 400 in the route). - NEW project->order cascade, transactional with the project update: finishing/ cancelling a project completes/cancels its linked received order — only while the order is still open (never resurrects storno, never cancels completed); reopen never cascades. Direct tx write, no service recursion; cascaded order change gets its own audit row. - orders.service: reopen edges (dokoncena/stornovana -> v_realizaci); syncProjectStatus is reopen-aware (reopen skips project sync) and returns the cascaded projects for per-project audit rows in the route. - orders.schema: fix phantom 'zrusena' enum token -> canonical 'stornovana' (every storno PUT through the route 400ed with a raw English Zod message; latent until now because no UI sent it) + route-level regression test. - NEW project-order-status-sync.test.ts: 15 tests — both cascade directions, guards, reopen-no-cascade, legacy tolerance, forward-sync regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -196,6 +196,31 @@ describe("order attachment payload hygiene", () => {
|
||||
expect(withoutDetail!.attachment_name).toBeNull();
|
||||
});
|
||||
|
||||
it("PUT /:id accepts the canonical storno token through the Zod layer", async () => {
|
||||
// Regression: UpdateOrderSchema carried a phantom "zrusena" enum member
|
||||
// until 2026-07, so { status: "stornovana" } 400ed at parseBody with a
|
||||
// raw English Zod message. Service-level tests bypass the schema — this
|
||||
// must stay a route-level test.
|
||||
const customer = await makeCustomer();
|
||||
const res = await createOrder({
|
||||
...baseOrder,
|
||||
customer_id: customer.id,
|
||||
create_project: false,
|
||||
});
|
||||
if (!("id" in res)) failResult(res);
|
||||
createdOrderIds.push(res.id!);
|
||||
|
||||
const put = await app.inject({
|
||||
method: "PUT",
|
||||
url: `/api/admin/orders/${res.id}`,
|
||||
headers: { authorization: `Bearer ${adminToken}` },
|
||||
payload: { status: "stornovana" },
|
||||
});
|
||||
expect(put.statusCode).toBe(200);
|
||||
const row = await prisma.orders.findUnique({ where: { id: res.id! } });
|
||||
expect(row!.status).toBe("stornovana");
|
||||
});
|
||||
|
||||
it("HTTP list/detail JSON carries no attachment_data; /attachment still serves the binary", async () => {
|
||||
const { withId, withoutId } = await makeOrderPair();
|
||||
const headers = { authorization: `Bearer ${adminToken}` };
|
||||
|
||||
Reference in New Issue
Block a user