fix(plant-hours): surface real PB field-level error in import response

formatPbError walked err.data but PB's ClientResponseError wraps the
field-level error object at err.data.data. The outer err.data has
just three keys (data, message, status), all 'invalid' on a failed
insert — useless to operators. Read err.data.data (and
err.data.data.fieldErrors if present) so the operator sees WHICH field
rejected the row.
This commit is contained in:
DEls 2026-08-28 14:43:29 +02:00
parent 0290ced51a
commit 0ea0257edb

View file

@ -47,22 +47,42 @@ const MAX_BYTES = 10 * 1024 * 1024; // 10 MB — same as fuel-logs import
export async function POST(request: Request) {
const start = Date.now();
// PB's ClientResponseError has `.data` with field-level validation
// details. Plain `err.message` is just "Failed to create record." -
// useless for diagnosis. Flatten field-level detail into a single
// human-readable string so the operator can see which field is
// rejecting the row.
// PB's ClientResponseError has `.data` whose keys are "data",
// "message", "status" — only the inner `.data.data` (or sometimes
// `.data.data.fieldErrors`) holds the field-level validation object.
// Plain `err.message` is just "Failed to create record." - useless
// for diagnosis. Flatten the inner field-level detail into a single
// human-readable string so the operator can see WHICH field rejected
// the row.
function formatPbError(err: unknown): string {
const e = err as {
message?: string;
data?: Record<string, { message?: string; code?: string }>;
data?: {
data?:
| Record<string, { message?: string; code?: string }>
| {
fieldErrors?: Record<string, { message?: string; code?: string }>;
};
message?: string;
};
};
const head = e?.message ?? "Insert failed";
if (e?.data && typeof e.data === "object") {
const parts = Object.entries(e.data)
.map(([field, info]) => `${field}: ${info?.message ?? "invalid"}`)
.join("; ");
if (parts) return `${head} (${parts})`;
const inner =
e?.data?.data && typeof e.data.data === "object" ? e.data.data : null;
if (inner) {
const fieldErrors =
"fieldErrors" in inner && inner.fieldErrors ? inner.fieldErrors : inner;
if (typeof fieldErrors === "object" && fieldErrors !== null) {
const parts = Object.entries(fieldErrors as Record<string, unknown>)
.map(([field, info]) => {
if (info && typeof info === "object" && "message" in info) {
return `${field}: ${(info as { message?: string }).message ?? "invalid"}`;
}
return `${field}: ${String(info)}`;
})
.join("; ");
if (parts) return `${head} (${parts})`;
}
}
return head;
}