From 3f228808eacb0393a6df43a6ab6799f1f929296b Mon Sep 17 00:00:00 2001 From: SJCoetz Date: Mon, 3 Aug 2026 01:03:52 +0200 Subject: [PATCH] feat(collection-confirm): add POST /api/v1/order-items/confirm route Co-authored-by: Orca --- .../api/v1/order-items/confirm/route.test.ts | 72 +++++++++++++++++++ src/app/api/v1/order-items/confirm/route.ts | 27 +++++++ 2 files changed, 99 insertions(+) create mode 100644 src/app/api/v1/order-items/confirm/route.test.ts create mode 100644 src/app/api/v1/order-items/confirm/route.ts diff --git a/src/app/api/v1/order-items/confirm/route.test.ts b/src/app/api/v1/order-items/confirm/route.test.ts new file mode 100644 index 0000000..2e7e920 --- /dev/null +++ b/src/app/api/v1/order-items/confirm/route.test.ts @@ -0,0 +1,72 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { NextRequest } from "next/server"; + +vi.mock("@/lib/server/collection-confirm-service", () => ({ + confirmOrderItemsForInvoicing: vi.fn(), +})); + +import { POST } from "./route"; +import { confirmOrderItemsForInvoicing } from "@/lib/server/collection-confirm-service"; + +function makePostRequest(body: unknown) { + return new NextRequest("http://localhost/api/v1/order-items/confirm", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), + }); +} + +describe("POST /api/v1/order-items/confirm", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("calls confirmOrderItemsForInvoicing with the body and returns the updated ids", async () => { + const updatedIds = ["row-1", "row-2"]; + vi.mocked(confirmOrderItemsForInvoicing).mockResolvedValue(updatedIds); + + const requestBody = { + itemIds: ["row-1", "row-2"], + invoice: { date: "2026-08-03", number: "INV-1" }, + costOverrides: { "row-1": 50 }, + actor: { id: "u-1", name: "sj" }, + }; + const request = makePostRequest(requestBody); + const response = await POST(request); + const body = await response.json(); + + expect(confirmOrderItemsForInvoicing).toHaveBeenCalledWith( + ["row-1", "row-2"], + { + invoice: { date: "2026-08-03", number: "INV-1" }, + costOverrides: { "row-1": 50 }, + actor: { id: "u-1", name: "sj" }, + }, + ); + expect(response.status).toBe(200); + expect(body).toEqual(updatedIds); + }); + + it("returns a 500 with a generic error body and logs the original error", async () => { + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + vi.mocked(confirmOrderItemsForInvoicing).mockRejectedValue( + new Error("PB down"), + ); + + const request = makePostRequest({ + itemIds: [], + invoice: { date: "2026-08-03", number: "INV-1" }, + costOverrides: {}, + actor: { id: "u-1", name: "sj" }, + }); + const response = await POST(request); + expect(response.status).toBe(500); + const body = await response.json(); + expect(body).toEqual({ error: "internal error" }); + expect(errorSpy).toHaveBeenCalledWith( + "[POST /api/v1/order-items/confirm]", + expect.any(Error), + ); + errorSpy.mockRestore(); + }); +}); \ No newline at end of file diff --git a/src/app/api/v1/order-items/confirm/route.ts b/src/app/api/v1/order-items/confirm/route.ts new file mode 100644 index 0000000..3a31fad --- /dev/null +++ b/src/app/api/v1/order-items/confirm/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { confirmOrderItemsForInvoicing } from "@/lib/server/collection-confirm-service"; + +/** + * POST /api/v1/order-items/confirm + * + * Thin wrapper around `confirmOrderItemsForInvoicing(ids, { invoice, + * costOverrides, actor })` from the collection-confirm service layer. + * The service performs the batched PocketBase update with cost-override + * audit notes; this route just plumbs the HTTP body through and reports + * failures with a generic 500 (the real error is logged server-side). + */ +export async function POST(request: Request): Promise { + try { + const body = await request.json(); + const { itemIds, invoice, costOverrides, actor } = body; + const ids = await confirmOrderItemsForInvoicing(itemIds, { + invoice, + costOverrides, + actor, + }); + return NextResponse.json(ids); + } catch (error) { + console.error("[POST /api/v1/order-items/confirm]", error); + return NextResponse.json({ error: "internal error" }, { status: 500 }); + } +} \ No newline at end of file