diff --git a/src/__tests__/manual-create.test.ts b/src/__tests__/manual-create.test.ts index 35a7820..4aef2d3 100644 --- a/src/__tests__/manual-create.test.ts +++ b/src/__tests__/manual-create.test.ts @@ -97,3 +97,46 @@ describe("createProject (manual, standalone)", () => { } }); }); + +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(); + }); +}); diff --git a/src/routes/admin/orders.ts b/src/routes/admin/orders.ts index 2a35e7b..c029650 100644 --- a/src/routes/admin/orders.ts +++ b/src/routes/admin/orders.ts @@ -104,7 +104,6 @@ export default async function ordersRoutes( request.headers["content-type"]?.includes("multipart"); if (isMultipart) { - // === Order from quotation flow (multipart) === const fields: Record = {}; let attachmentBuffer: Buffer | null = null; let attachmentName: string | null = null; @@ -119,37 +118,82 @@ export default async function ordersRoutes( } } - const quotationId = parseInt(fields.quotationId, 10); - const customerOrderNumber = fields.customerOrderNumber || ""; - - if (!quotationId || isNaN(quotationId)) { - return error(reply, "Chybí ID nabídky", 400); + // From-quotation flow (multipart with attachment) + if (fields.quotationId) { + const quotationId = parseInt(fields.quotationId, 10); + const customerOrderNumber = fields.customerOrderNumber || ""; + if (!quotationId || isNaN(quotationId)) { + return error(reply, "Chybí ID nabídky", 400); + } + const result = await createOrderFromQuotation({ + quotationId, + customerOrderNumber, + attachmentBuffer, + attachmentName, + }); + if ("error" in result) + return error(reply, result.error!, result.status!); + await logAudit({ + request, + authData: request.authData, + action: "create", + entityType: "order", + entityId: result.data.order_id, + description: `Vytvořena objednávka ${result.data.order_number} z nabídky #${result.data.quotationId}`, + }); + return success( + reply, + { + order_id: result.data.order_id, + id: result.data.id, + order_number: result.data.order_number, + }, + 201, + "Objednávka byla vytvořena", + ); } - const result = await createOrderFromQuotation({ - quotationId, - customerOrderNumber, + // Manual order (multipart, with optional PO attachment) + const rawManual: Record = { ...fields }; + if (typeof fields.items === "string" && fields.items.length > 0) { + try { + rawManual.items = JSON.parse(fields.items); + } catch { + return error(reply, "Neplatná data položek", 400); + } + } + const manualParsed = parseBody(CreateOrderSchema, rawManual); + if ("error" in manualParsed) + return error(reply, manualParsed.error, 400); + + const result = await createOrder( + manualParsed.data, attachmentBuffer, attachmentName, - }); + ); if ("error" in result) return error(reply, result.error!, result.status!); - await logAudit({ request, authData: request.authData, action: "create", entityType: "order", - entityId: result.data.order_id, - description: `Vytvořena objednávka ${result.data.order_number} z nabídky #${result.data.quotationId}`, + entityId: result.id, + description: `Vytvořena objednávka ${result.order_number}`, }); + if (result.project_id) { + await logAudit({ + request, + authData: request.authData, + action: "create", + entityType: "project", + entityId: result.project_id, + description: `Vytvořen projekt k objednávce ${result.order_number}`, + }); + } return success( reply, - { - order_id: result.data.order_id, - id: result.data.id, - order_number: result.data.order_number, - }, + { id: result.id }, 201, "Objednávka byla vytvořena", ); diff --git a/src/services/orders.service.ts b/src/services/orders.service.ts index 8c07e9e..d5ab664 100644 --- a/src/services/orders.service.ts +++ b/src/services/orders.service.ts @@ -291,7 +291,11 @@ interface CreateOrderData { create_project?: boolean; } -export async function createOrder(body: CreateOrderData) { +export async function createOrder( + body: CreateOrderData, + attachmentBuffer?: Buffer | null, + attachmentName?: string | null, +) { try { return await prisma.$transaction(async (tx) => { const orderNumber = @@ -323,6 +327,10 @@ export async function createOrder(body: CreateOrderData) { scope_title: body.scope_title ?? null, scope_description: body.scope_description ?? null, notes: body.notes ?? null, + attachment_data: attachmentBuffer + ? new Uint8Array(attachmentBuffer) + : null, + attachment_name: attachmentName || null, }, });