feat(collection-confirm): add POST /api/v1/order-items/confirm route

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
SJCoetz 2026-08-03 01:03:52 +02:00
parent fada6f155f
commit 3f228808ea
2 changed files with 99 additions and 0 deletions

View file

@ -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();
});
});

View file

@ -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<NextResponse> {
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 });
}
}