feat(orders): manual order optionally creates a linked project

Add `create_project` flag (default true) to `CreateOrderSchema` and
`CreateOrderData`. When true and `quotation_id` is absent, `createOrder`
creates a `projects` row sharing the order's shared-pool number and
`order_id` link — mirroring the from-quotation flow. The route audits
the new project creation when it occurs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-06 11:06:25 +02:00
parent bd9dc8d103
commit f6cdfb9801
4 changed files with 93 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
import { describe, it, expect, afterEach } from "vitest";
import prisma from "../config/database";
import { createProject } from "../services/projects.service";
import { createOrder } from "../services/orders.service";
const createdProjectIds: number[] = [];
const createdOrderIds: number[] = [];
@@ -15,6 +16,62 @@ afterEach(async () => {
await prisma.number_sequences.deleteMany({ where: { type: "shared" } });
});
const baseOrder = {
status: "prijata" as const,
currency: "CZK",
language: "cs",
vat_rate: 21,
apply_vat: true,
exchange_rate: 1,
};
describe("createOrder (manual, optional linked project)", () => {
it("creates an order AND a project sharing one number when create_project=true", async () => {
const res = await createOrder({
...baseOrder,
scope_title: "Ruční zakázka",
create_project: true,
});
expect("id" in res).toBe(true);
if (!("id" in res)) return;
createdOrderIds.push(res.id);
expect(res.project_id).toBeTruthy();
const project = await prisma.projects.findUnique({
where: { id: res.project_id! },
});
if (project) createdProjectIds.push(project.id);
expect(project?.project_number).toBe(res.order_number);
expect(project?.order_id).toBe(res.id);
});
it("creates only the order when create_project=false", async () => {
const res = await createOrder({ ...baseOrder, create_project: false });
expect("id" in res).toBe(true);
if (!("id" in res)) return;
createdOrderIds.push(res.id);
expect(res.project_id).toBeUndefined();
});
});
describe("shared pool coherence (tracking)", () => {
it("order → standalone project → order produce three distinct shared numbers", async () => {
const o1 = await createOrder({ ...baseOrder, create_project: true });
if ("id" in o1) {
createdOrderIds.push(o1.id);
if (o1.project_id) createdProjectIds.push(o1.project_id);
}
const p = await createProject({ name: "Mezi-projekt", status: "aktivni" });
if ("project_number" in p) createdProjectIds.push(p.id);
const o2 = await createOrder({ ...baseOrder, create_project: false });
if ("id" in o2) createdOrderIds.push(o2.id);
const n1 = "id" in o1 ? o1.order_number : null;
const np = "project_number" in p ? p.project_number : null;
const n2 = "id" in o2 ? o2.order_number : null;
expect(new Set([n1, np, n2]).size).toBe(3);
});
});
describe("createProject (manual, standalone)", () => {
it("assigns a shared number and is not linked to an order", async () => {
const result = await createProject({