JEI_Import_Documents-App/app/api/orders/create/route.ts
Mavis 5f73d2ea71 feat: Fedex import flow, orders + invoice APIs, payment bundles
- /fedex: batch create, document upload/replace/delete, save shipment, imported list
- /api/orders, /api/invoices, /api/pb-file, /api/pb/refresh-token, /api/stock-codes
- /api/payments/bundle: generate + download-all remittance bundles
- shared app/components/DocCell.tsx and app/lib/{invoice-docs,pb-fetch,pb-token}.ts
- recon: invoice reconciliation reports and PowerShell fix-plan scripts
- .gitignore: exclude one-off scripts/_* debugging artefacts, recon/pdfs+txts, data backups
2026-10-18 17:27:40 +02:00

105 lines
No EOL
2.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
import { getPb } from "../../../lib/pb";
export const dynamic = "force-dynamic";
const NAME_MAX = 100;
const NOTES_MAX = 1000;
const HEADER_MAX = 2000;
const FOOTNOTES_MAX = 4000;
function sanitizeLines(input: any): any[] | null {
if (!Array.isArray(input)) return null;
const out: any[] = [];
for (const raw of input) {
if (!raw || typeof raw !== "object") return null;
const code = String(raw.code || "").trim();
if (!code) return null; // every line must have a code
out.push({
stockId: String(raw.stockId || ""),
code,
description: String(raw.description || ""),
qty: Math.max(0, Number(raw.qty) || 0),
unitPrice: Math.max(0, Number(raw.unitPrice) || 0),
refInvoiceNo: String(raw.refInvoiceNo || ""),
refInvoiceDate: String(raw.refInvoiceDate || ""),
});
}
return out;
}
export async function POST(req: NextRequest) {
let body: any;
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Body must be JSON" }, { status: 400 });
}
const name = String(body.name || "").trim();
if (!name || name.length > NAME_MAX) {
return NextResponse.json(
{ error: `name is required (1-${NAME_MAX} chars)` },
{ status: 400 },
);
}
const notes = String(body.notes || "").trim();
if (notes.length > NOTES_MAX) {
return NextResponse.json(
{ error: `notes max ${NOTES_MAX} chars` },
{ status: 400 },
);
}
const headerText = String(body.header_text || "");
if (headerText.length > HEADER_MAX) {
return NextResponse.json(
{ error: `header text max ${HEADER_MAX} chars` },
{ status: 400 },
);
}
const footnotes = String(body.footnotes || "");
if (footnotes.length > FOOTNOTES_MAX) {
return NextResponse.json(
{ error: `footnotes max ${FOOTNOTES_MAX} chars` },
{ status: 400 },
);
}
const lines = sanitizeLines(body.lines);
if (lines === null) {
return NextResponse.json(
{ error: "lines must be a non-empty array of objects with a code field" },
{ status: 400 },
);
}
if (lines.length === 0) {
return NextResponse.json(
{ error: "lines must contain at least one item" },
{ status: 400 },
);
}
const pb = getPb();
try {
const created = await pb.collection("JEI_orders").create({
name,
notes,
// Header + footnotes are saved PER ORDER so loading an order restores its
// own wording instead of leaving whatever was last typed in the boxes.
header_text: headerText,
footnotes,
lines,
});
revalidatePath("/order");
return NextResponse.json({ id: created.id, name: created.name });
} catch (e: any) {
return NextResponse.json(
{ error: `Failed to create order: ${e?.message || e}` },
{ status: 500 },
);
}
}