From 22ddfe82bf72735017048aa7f4636070fd784567 Mon Sep 17 00:00:00 2001 From: Mavis Date: Thu, 17 Sep 2026 23:17:58 +0200 Subject: [PATCH] fix: avoid PB SDK filter=undefined serialization (caused 400 errors) The PB JS SDK serializes `filter: undefined` as the literal string undefined in the URL, which PB rejects with 400 Bad Request. Switched to conditionally including filter only when non-empty. --- app/invoices/[id]/page.tsx | 1 - app/invoices/page.tsx | 22 ++++++++++++++-------- app/stock-codes/[code]/page.tsx | 7 +++++++ app/stock-codes/page.tsx | 8 ++++---- scripts/debug_invoices.mjs | 24 ++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 scripts/debug_invoices.mjs diff --git a/app/invoices/[id]/page.tsx b/app/invoices/[id]/page.tsx index 38d7bf6..ce7839d 100644 --- a/app/invoices/[id]/page.tsx +++ b/app/invoices/[id]/page.tsx @@ -15,7 +15,6 @@ export default async function InvoiceDetail({ params }: { params: { id: string } filter: `invoice="${params.id}"`, sort: "line_no", }); - lines = lr.items; } catch (e: any) { return (
diff --git a/app/invoices/page.tsx b/app/invoices/page.tsx index f732a1f..997e726 100644 --- a/app/invoices/page.tsx +++ b/app/invoices/page.tsx @@ -25,22 +25,28 @@ export default async function InvoicesPage({ } filter = parts.join(" && "); - const filterParam = filter ? `&filter=${encodeURIComponent(filter)}` : ""; let records: any[] = []; try { - const r = await pb.collection("JEI_invoices").getList(1, 200, { - sort: "-invoice_date", - filter: filter || undefined, - }); + // Don't pass filter when empty — the SDK serializes `undefined` as the + // literal string "undefined" which PB rejects with 400. + const opts: any = { sort: "-invoice_date", perPage: 200 }; + if (filter) opts.filter = filter; + const r = await pb.collection("JEI_invoices").getList(1, 200, opts); records = r.items; } catch (e: any) { + // Debug: log full error to server console + console.error("[/invoices] PB error:", e?.status, e?.message, JSON.stringify(e?.data || {})); + const msg = String(e?.message || e); + const status = e?.status ? `[${e.status}] ` : ""; return (

Invoices

-
Cannot reach PocketBase
-
{String(e?.message || e)}
-
Check PB_TOKEN in .env.local — it expires every ~24h.
+
Cannot reach PocketBase {status}
+
{msg}
+ {e?.data && ( +
{JSON.stringify(e.data, null, 2)}
+ )}
); diff --git a/app/stock-codes/[code]/page.tsx b/app/stock-codes/[code]/page.tsx index 9e0bed5..621366b 100644 --- a/app/stock-codes/[code]/page.tsx +++ b/app/stock-codes/[code]/page.tsx @@ -22,6 +22,13 @@ export default async function StockCodeDetail({ params }: { params: { code: stri filter: `stock_code="${sc.id}"`, expand: "invoice", }); + sc = found.items[0]; + if (!sc) return notFound(); + // All lines for this stock code, with their invoice (expand) + const lr = await pb.collection("JEI_invoice_lines").getList(1, 500, { + filter: `stock_code="${sc.id}"`, + expand: "invoice", + }); lines = lr.items; } catch (e: any) { return ( diff --git a/app/stock-codes/page.tsx b/app/stock-codes/page.tsx index 0eb9de0..d105be5 100644 --- a/app/stock-codes/page.tsx +++ b/app/stock-codes/page.tsx @@ -14,10 +14,10 @@ export default async function StockCodesPage({ // Fetch all stock codes let codes: any[] = []; try { - const r = await pb.collection("JEI_stock_codes").getList(1, 500, { - sort: "code", - filter: q ? `code~"${q.replace(/"/g, '\\"')}" || description_goods~"${q.replace(/"/g, '\\"')}"` : undefined, - }); + // Don't pass filter when empty — SDK serializes `undefined` as literal "undefined" + const opts: any = { sort: "code", perPage: 500 }; + if (q) opts.filter = `code~"${q.replace(/"/g, '\\"')}" || description_goods~"${q.replace(/"/g, '\\"')}"`; + const r = await pb.collection("JEI_stock_codes").getList(1, 500, opts); codes = r.items; } catch (e: any) { return ( diff --git a/scripts/debug_invoices.mjs b/scripts/debug_invoices.mjs new file mode 100644 index 0000000..08cda35 --- /dev/null +++ b/scripts/debug_invoices.mjs @@ -0,0 +1,24 @@ +// Debug: try to fetch invoices exactly like /invoices page does +import { default as PocketBase } from 'pocketbase'; +import fs from 'fs'; +const envFile = fs.readFileSync('.env.local', 'utf8'); +const token = envFile.match(/PB_TOKEN=(.+)/)[1].trim(); +const url = envFile.match(/PB_URL=(.+)/)[1].trim(); +process.env.PB_TOKEN = token; +process.env.NEXT_PUBLIC_PB_URL = url; + +const pb = new PocketBase(url); +pb.authStore.save(token); + +(async () => { + try { + const r = await pb.collection('JEI_invoices').getList(1, 200, { + sort: '-invoice_date', + }); + console.log('OK items=' + r.items.length + ' total=' + r.totalItems); + } catch (e) { + console.log('ERR ' + e.status + ': ' + e.message); + console.log('data: ' + JSON.stringify(e.data)); + console.log('url: ' + e.url); + } +})();