diff --git a/prisma/migrations/20260610121316_issued_orders_order_text/migration.sql b/prisma/migrations/20260610121316_issued_orders_order_text/migration.sql new file mode 100644 index 0000000..edbd12f --- /dev/null +++ b/prisma/migrations/20260610121316_issued_orders_order_text/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE `issued_orders` ADD COLUMN `order_text` VARCHAR(500) NULL; + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 834a989..b220032 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -381,6 +381,7 @@ model issued_orders { 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 created_at DateTime? @default(now()) @db.DateTime(0) diff --git a/src/__tests__/issued-orders.test.ts b/src/__tests__/issued-orders.test.ts index 78c75a2..718a598 100644 --- a/src/__tests__/issued-orders.test.ts +++ b/src/__tests__/issued-orders.test.ts @@ -170,6 +170,23 @@ describe("createIssuedOrder", () => { const res = await createIssuedOrder({ supplier_id: 99999999 }); expect("error" in res && res.error).toBe("supplier_not_found"); }); + + it("persists order_text on create, updates and clears it on update", async () => { + const order = await mkIssued({ order_text: "Objednáváme dle smlouvy:" }); + let row = await prisma.issued_orders.findUnique({ + where: { id: order.id }, + }); + expect(row!.order_text).toBe("Objednáváme dle smlouvy:"); + + await updateIssuedOrder(order.id, { order_text: "Jiný text:" }); + row = await prisma.issued_orders.findUnique({ where: { id: order.id } }); + expect(row!.order_text).toBe("Jiný text:"); + + // null clears back to the PDF default. + await updateIssuedOrder(order.id, { order_text: null }); + row = await prisma.issued_orders.findUnique({ where: { id: order.id } }); + expect(row!.order_text).toBeNull(); + }); }); describe("updateIssuedOrder status transitions", () => { @@ -507,6 +524,29 @@ describe("renderIssuedOrderHtml", () => { expect(html).not.toContain("alert(1)"); }); + it("renders the custom order_text heading when set, default when not", () => { + const custom = renderIssuedOrderHtml( + { ...order, order_text: "Objednáváme dle nabídky č. 123:" }, + items, + null, + null, + "cs", + issuer, + ); + expect(custom).toContain("Objednáváme dle nabídky č. 123:"); + expect(custom).not.toContain("Objednáváme si u Vás:"); + + const fallback = renderIssuedOrderHtml( + order, + items, + null, + null, + "cs", + issuer, + ); + expect(fallback).toContain("Objednáváme si u Vás:"); + }); + it("with VAT: keeps the VAT columns and the DPH cell holds ONLY the line VAT", () => { const html = renderIssuedOrderHtml(order, items, null, null, "cs", issuer); expect(html).toContain("%DPH"); diff --git a/src/admin/lib/queries/issued-orders.ts b/src/admin/lib/queries/issued-orders.ts index 755f72b..1b8ca1f 100644 --- a/src/admin/lib/queries/issued-orders.ts +++ b/src/admin/lib/queries/issued-orders.ts @@ -45,6 +45,7 @@ export interface IssuedOrderDetail extends IssuedOrder { delivery_terms: string | null; payment_terms: string | null; issued_by: string | null; + order_text: string | null; notes: string | null; internal_notes: string | null; items: IssuedOrderItem[]; diff --git a/src/admin/pages/IssuedOrderDetail.tsx b/src/admin/pages/IssuedOrderDetail.tsx index 0b28edd..dc9df6b 100644 --- a/src/admin/pages/IssuedOrderDetail.tsx +++ b/src/admin/pages/IssuedOrderDetail.tsx @@ -171,6 +171,7 @@ interface OrderForm { delivery_terms: string; payment_terms: string; issued_by: string; + order_text: string; notes: string; internal_notes: string; status: string; @@ -520,6 +521,7 @@ export default function IssuedOrderDetail() { delivery_terms: "", payment_terms: "", issued_by: user?.fullName || "", + order_text: "", notes: "", internal_notes: "", status: "draft", @@ -621,6 +623,7 @@ export default function IssuedOrderDetail() { delivery_terms: d.delivery_terms || "", payment_terms: d.payment_terms || "", issued_by: d.issued_by || "", + order_text: d.order_text || "", notes: d.notes || "", internal_notes: d.internal_notes || "", status: d.status, @@ -779,6 +782,7 @@ export default function IssuedOrderDetail() { delivery_terms: form.delivery_terms, payment_terms: form.payment_terms, issued_by: form.issued_by, + order_text: form.order_text || null, notes: form.notes, internal_notes: form.internal_notes, items: items @@ -1419,6 +1423,16 @@ export default function IssuedOrderDetail() { {/* Notes & terms */} + + + setForm((prev) => ({ ...prev, order_text: e.target.value })) + } + placeholder="Objednáváme si u Vás: (ponechte prázdné pro výchozí)" + /> + > = { buyer: "Odběratel", issue_date: "Datum vystavení:", delivery_date: "Požadované dodání:", - billing: "Objednáváme u Vás:", + billing: "Objednáváme si u Vás:", col_no: "Č.", col_desc: "Popis", col_qty: "Množství", @@ -257,6 +257,8 @@ interface IssuedOrderPdfData { delivery_terms: string | null; payment_terms: string | null; issued_by: string | null; + // Editable heading above the items table; null → t.billing default. + order_text?: string | null; } interface IssuedOrderPdfItem { @@ -771,7 +773,7 @@ ${indentCSS} -
${escapeHtml(t.billing)}
+
${escapeHtml(order.order_text || t.billing)}
diff --git a/src/schemas/issued-orders.schema.ts b/src/schemas/issued-orders.schema.ts index 1616bb8..959c5fe 100644 --- a/src/schemas/issued-orders.schema.ts +++ b/src/schemas/issued-orders.schema.ts @@ -40,6 +40,9 @@ export const CreateIssuedOrderSchema = z.object({ delivery_terms: z.string().max(500).nullish(), payment_terms: z.string().max(500).nullish(), issued_by: z.string().max(255).nullish(), + // Editable heading above the PDF items table; empty/null falls back to the + // default "Objednáváme si u Vás:" (issued-orders-pdf t.billing). + order_text: z.string().max(500).nullish(), notes: z.string().nullish(), internal_notes: z.string().nullish(), items: z.array(IssuedOrderItemSchema).optional(), diff --git a/src/services/issued-orders.service.ts b/src/services/issued-orders.service.ts index fbe35dc..18a65f0 100644 --- a/src/services/issued-orders.service.ts +++ b/src/services/issued-orders.service.ts @@ -32,6 +32,7 @@ export interface IssuedOrderInput { delivery_terms?: string | null; payment_terms?: string | null; issued_by?: string | null; + order_text?: string | null; notes?: string | null; internal_notes?: string | null; items?: IssuedOrderItemInput[]; @@ -304,6 +305,7 @@ export async function createIssuedOrder(body: IssuedOrderInput) { : null, payment_terms: body.payment_terms ? String(body.payment_terms) : null, issued_by: body.issued_by ? String(body.issued_by) : null, + order_text: body.order_text ? String(body.order_text) : null, notes: body.notes ? String(body.notes) : null, internal_notes: body.internal_notes ? String(body.internal_notes) @@ -354,6 +356,7 @@ export async function updateIssuedOrder(id: number, body: IssuedOrderInput) { "delivery_terms", "payment_terms", "issued_by", + "order_text", ]; for (const f of strFields) { if (body[f] !== undefined) data[f] = body[f] ? String(body[f]) : null;