94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import { authorizeOperation } from "@/lib/auth/operation";
|
|
import { NextResponse } from "next/server";
|
|
import { pbAdmin } from "@/lib/pb";
|
|
import type { UnitType } from "@/lib/pb-types";
|
|
|
|
/**
|
|
* POST /api/vehicles
|
|
*
|
|
* Creates a new vehicle row (used by the "Add plant" modal on the
|
|
* Plant dashboard). Required: `description`, `site` (relation id) — a
|
|
* vehicle's project attribution comes entirely from `site`, never a
|
|
* denormalized copy on this row. Everything else is optional and may
|
|
* be backfilled later via PATCH /api/vehicles/[id] or in PB admin.
|
|
*/
|
|
export async function POST(request: Request) {
|
|
const denied = await authorizeOperation(request);
|
|
if (denied) return denied;
|
|
let body: Record<string, unknown>;
|
|
try {
|
|
body = await request.json();
|
|
} catch {
|
|
return NextResponse.json({ error: "Body must be JSON" }, { status: 400 });
|
|
}
|
|
|
|
const description =
|
|
typeof body.description === "string" ? body.description.trim() : "";
|
|
if (!description) {
|
|
return NextResponse.json(
|
|
{ error: "description is required" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const site = typeof body.site === "string" ? body.site.trim() : "";
|
|
if (!site) {
|
|
return NextResponse.json(
|
|
{ error: "site is required" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// Optional fields — only include the key in the payload when the caller
|
|
// actually supplied a value, so PB doesn't reject "" for non-text fields
|
|
// (cost_per_hour / standard_work_hours are numeric in the schema).
|
|
const payload: Record<string, unknown> = {
|
|
description,
|
|
site,
|
|
};
|
|
|
|
if (typeof body.qr_code_id === "string") {
|
|
const qr = body.qr_code_id.trim();
|
|
if (qr) payload.qr_code_id = qr;
|
|
}
|
|
|
|
if (typeof body.fuel_limits === "string") {
|
|
const fl = body.fuel_limits.trim();
|
|
if (fl) payload.fuel_limits = fl;
|
|
}
|
|
|
|
if (body.unit_type === "fuel" || body.unit_type === "plant") {
|
|
payload.unit_type = body.unit_type as UnitType;
|
|
}
|
|
|
|
const numField = (v: unknown): number | undefined => {
|
|
if (v === undefined || v === null || v === "") return undefined;
|
|
const n = Number(v);
|
|
return Number.isFinite(n) ? n : undefined;
|
|
};
|
|
|
|
const standardWorkHours = numField(body.standard_work_hours);
|
|
if (standardWorkHours !== undefined) {
|
|
payload.standard_work_hours = standardWorkHours;
|
|
}
|
|
|
|
const costPerHour = numField(body.cost_per_hour);
|
|
if (costPerHour !== undefined) {
|
|
payload.cost_per_hour = costPerHour;
|
|
}
|
|
|
|
const pb = await pbAdmin();
|
|
try {
|
|
const created = await pb.collection("vehicles").create(payload);
|
|
return NextResponse.json({ vehicle: created }, { status: 201 });
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : "Insert failed";
|
|
if (/UNIQUE|duplicate|idx_/i.test(msg)) {
|
|
return NextResponse.json(
|
|
{ error: msg },
|
|
{ status: 409 },
|
|
);
|
|
}
|
|
return NextResponse.json({ error: msg }, { status: 500 });
|
|
}
|
|
}
|