diff --git a/prisma/migrations/20260610171823_issued_orders_drop_terms_notes_issued_by/migration.sql b/prisma/migrations/20260610171823_issued_orders_drop_terms_notes_issued_by/migration.sql new file mode 100644 index 0000000..05cc22c --- /dev/null +++ b/prisma/migrations/20260610171823_issued_orders_drop_terms_notes_issued_by/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE `issued_orders` DROP COLUMN `delivery_terms`, + DROP COLUMN `issued_by`, + DROP COLUMN `notes`, + DROP COLUMN `payment_terms`; + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 03b1c40..7fe4bc7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -374,11 +374,7 @@ model issued_orders { order_date DateTime? @db.Date delivery_date DateTime? @db.Date language String? @default("cs") @db.VarChar(5) - delivery_terms String? @db.VarChar(500) - payment_terms String? @db.VarChar(500) - issued_by String? @db.VarChar(255) order_text String? @db.VarChar(500) - notes String? @db.Text internal_notes String? @db.Text locked_by Int? locked_at DateTime? @db.DateTime(0) diff --git a/src/__tests__/issued-orders.test.ts b/src/__tests__/issued-orders.test.ts index 500b7ed..9255b38 100644 --- a/src/__tests__/issued-orders.test.ts +++ b/src/__tests__/issued-orders.test.ts @@ -481,6 +481,36 @@ describe("POST /api/admin/issued-orders supplier validation", () => { }); }); +describe("POST /api/admin/issued-orders legacy dropped fields", () => { + it("silently ignores the removed flat-text keys (old clients keep working)", async () => { + // delivery_terms/payment_terms/issued_by/notes were dropped from the + // schema AND the table — a stale client still sending them must save + // cleanly (non-strict Zod strips unknown keys) with the kept fields intact. + const res = await app!.inject({ + method: "POST", + url: "/api/admin/issued-orders", + headers: { Authorization: `Bearer ${adminToken}` }, + payload: { + notes: "

stará poznámka

", + delivery_terms: "DAP, sklad odběratele", + payment_terms: "splatnost 14 dní", + issued_by: "Starý klient", + order_text: "Objednáváme dle smlouvy:", + }, + }); + expect(res.statusCode).toBe(201); + const body = res.json(); + expect(body.success).toBe(true); + createdIds.push(body.data.id); + + const row = await prisma.issued_orders.findUnique({ + where: { id: body.data.id }, + }); + expect(row).not.toBeNull(); + expect(row!.order_text).toBe("Objednáváme dle smlouvy:"); + }); +}); + describe("PUT /api/admin/issued-orders/:id editable-state guard (HTTP)", () => { it("400s a field edit on a confirmed order with the explicit Czech message", async () => { const order = await mkIssued({}); @@ -491,7 +521,7 @@ describe("PUT /api/admin/issued-orders/:id editable-state guard (HTTP)", () => { method: "PUT", url: `/api/admin/issued-orders/${order.id}`, headers: { Authorization: `Bearer ${adminToken}` }, - payload: { notes: "pokus o úpravu" }, + payload: { order_text: "pokus o úpravu" }, }); expect(res.statusCode).toBe(400); expect(res.json().error).toBe("Objednávku v tomto stavu nelze upravovat"); @@ -518,12 +548,12 @@ describe("PUT /api/admin/issued-orders/:id editable-state guard (HTTP)", () => { describe("issued-order audit old/new values", () => { it("PUT writes an audit row carrying oldValues and newValues", async () => { - const order = await mkIssued({ notes: "před úpravou" }); + const order = await mkIssued({ internal_notes: "před úpravou" }); const res = await app!.inject({ method: "PUT", url: `/api/admin/issued-orders/${order.id}`, headers: { Authorization: `Bearer ${adminToken}` }, - payload: { notes: "po úpravě" }, + payload: { internal_notes: "po úpravě" }, }); expect(res.statusCode).toBe(200); @@ -538,15 +568,15 @@ describe("issued-order audit old/new values", () => { expect(audit).not.toBeNull(); expect(audit!.old_values).toBeTruthy(); expect(audit!.new_values).toBeTruthy(); - expect(JSON.parse(audit!.old_values!).notes).toBe("před úpravou"); - expect(JSON.parse(audit!.new_values!).notes).toBe("po úpravě"); + expect(JSON.parse(audit!.old_values!).internal_notes).toBe("před úpravou"); + expect(JSON.parse(audit!.new_values!).internal_notes).toBe("po úpravě"); // Unnumbered drafts read "koncept", never "null". expect(audit!.description).toContain("koncept"); expect(audit!.description).not.toContain("null"); }); it("DELETE writes an audit row carrying oldValues", async () => { - const order = await mkIssued({ notes: "ke smazání" }); + const order = await mkIssued({ internal_notes: "ke smazání" }); const res = await app!.inject({ method: "DELETE", url: `/api/admin/issued-orders/${order.id}`, @@ -563,7 +593,7 @@ describe("issued-order audit old/new values", () => { orderBy: { id: "desc" }, }); expect(audit).not.toBeNull(); - expect(JSON.parse(audit!.old_values!).notes).toBe("ke smazání"); + expect(JSON.parse(audit!.old_values!).internal_notes).toBe("ke smazání"); expect(audit!.description).toContain("koncept"); }); }); @@ -612,10 +642,6 @@ describe("renderIssuedOrderHtml", () => { order_date: new Date("2026-06-09T12:00:00"), delivery_date: null, currency: "CZK", - notes: "

Pozn

", - delivery_terms: null, - payment_terms: null, - issued_by: null, }; const items = [ { @@ -690,10 +716,17 @@ describe("renderIssuedOrderHtml", () => { expect(html).not.toContain("DIČ: "); }); - it("strips script tags from notes", () => { - const html = renderIssuedOrderHtml(order, items, null, null, "cs", issuer); + it("escapes HTML in the order_text heading (no raw tags reach the PDF)", () => { + const html = renderIssuedOrderHtml( + { ...order, order_text: 'Text "X"' }, + items, + null, + null, + "cs", + issuer, + ); expect(html).not.toContain("