Extend createOrder service to accept optional attachmentBuffer/attachmentName parameters and persist them. Restructure the POST / multipart branch in the orders route to dispatch on quotationId presence: from-quotation path is unchanged, new manual-multipart path validates with CreateOrderSchema and forwards the attachment. Add two TDD tests covering price line items and PO file attachment storage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
143 lines
4.9 KiB
TypeScript
143 lines
4.9 KiB
TypeScript
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[] = [];
|
|
|
|
afterEach(async () => {
|
|
for (const id of createdProjectIds)
|
|
await prisma.projects.deleteMany({ where: { id } });
|
|
for (const id of createdOrderIds)
|
|
await prisma.orders.deleteMany({ where: { id } });
|
|
createdProjectIds.length = 0;
|
|
createdOrderIds.length = 0;
|
|
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({
|
|
name: "Test projekt",
|
|
status: "aktivni",
|
|
});
|
|
expect("project_number" in result).toBe(true);
|
|
if (!("project_number" in result)) return;
|
|
createdProjectIds.push(result.id);
|
|
expect(typeof result.project_number).toBe("string");
|
|
expect(result.project_number!.length).toBeGreaterThan(0);
|
|
expect(result.order_id).toBeNull();
|
|
});
|
|
|
|
it("gives consecutive standalone projects distinct numbers", async () => {
|
|
const a = await createProject({ name: "Projekt A", status: "aktivni" });
|
|
const b = await createProject({ name: "Projekt B", status: "aktivni" });
|
|
if ("project_number" in a) createdProjectIds.push(a.id);
|
|
if ("project_number" in b) createdProjectIds.push(b.id);
|
|
expect("project_number" in a && "project_number" in b).toBe(true);
|
|
if ("project_number" in a && "project_number" in b) {
|
|
expect(a.project_number).not.toBe(b.project_number);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("createOrder with price line item + attachment", () => {
|
|
it("stores a single line item with the entered price", async () => {
|
|
const res = await createOrder({
|
|
...baseOrder,
|
|
create_project: false,
|
|
items: [
|
|
{
|
|
description: "Práce",
|
|
quantity: 1,
|
|
unit_price: 12345,
|
|
is_included_in_total: true,
|
|
},
|
|
],
|
|
});
|
|
expect("id" in res).toBe(true);
|
|
if (!("id" in res)) return;
|
|
createdOrderIds.push(res.id);
|
|
const items = await prisma.order_items.findMany({
|
|
where: { order_id: res.id },
|
|
});
|
|
expect(items.length).toBe(1);
|
|
expect(Number(items[0].unit_price)).toBe(12345);
|
|
});
|
|
|
|
it("stores an uploaded PO attachment", async () => {
|
|
const buf = Buffer.from("%PDF-1.4 fake");
|
|
const res = await createOrder(
|
|
{ ...baseOrder, create_project: false },
|
|
buf,
|
|
"po.pdf",
|
|
);
|
|
expect("id" in res).toBe(true);
|
|
if (!("id" in res)) return;
|
|
createdOrderIds.push(res.id);
|
|
const order = await prisma.orders.findUnique({
|
|
where: { id: res.id },
|
|
select: { attachment_name: true, attachment_data: true },
|
|
});
|
|
expect(order?.attachment_name).toBe("po.pdf");
|
|
expect(order?.attachment_data).toBeTruthy();
|
|
});
|
|
});
|