Stock pickups live in stock_purchase_items (status="assigned_to_driver"), not order_items. Previously getMyAssignedItems() only queried order_items, so a driver with a stock pickup assigned to them saw nothing in the Assigned tab or Map. - getMyAssignedStockItems() fetches stock_purchase_items with the matching drv_id/status filter and expands purchase.supplier + stock_item. A toOrderItemFromStock() reshaper puts each row onto the OrderItem shape so the existing card/modal/map components render stock pickups unchanged; a kind:'pickup' discriminator tags them for the write side. - handleCollect / handleProblem / handleBulkCollect / handleBulkProblem branch on item.kind to PATCH stock_purchase_items instead of order_items (status="collected" + quantity_received on collect, problem_flag + problem_type + problem_notes on problem; mirrors the order_items quantity-mismatch auto-notes). - Same merge on app/map/page.tsx so the stock stop lands on the map. - scripts/check-pb-schema.mjs now covers the stock filter so a future PB rename is caught at prebuild.
121 lines
5.1 KiB
JavaScript
121 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
||
// scripts/check-pb-schema.mjs
|
||
//
|
||
// Pre-build sanity check: probes every PocketBase filter used by the driver
|
||
// app against the live server and reports any reference to an unknown
|
||
// field. Catches the exact "added a clause to a filter, forgot to add the
|
||
// field to PB" class of bug that produced the July-2026 4×400 incident.
|
||
//
|
||
// Usage:
|
||
// npm run check-pb-schema
|
||
//
|
||
// Env (optional — defaults match this project's dev PB + e2e creds):
|
||
// NEXT_PUBLIC_POCKETBASE_URL=https://your-pb.example.com
|
||
// PB_USER_EMAIL=...
|
||
// PB_USER_PASSWORD=...
|
||
//
|
||
// Exit codes:
|
||
// 0 all filters and identifiers parse against the live schema
|
||
// 1 one or more filters reference unknown fields
|
||
// 2 couldn't authenticate against PB (fix env / network first)
|
||
|
||
import PocketBase from 'pocketbase';
|
||
|
||
// --- config ---------------------------------------------------------
|
||
const PB = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||
|| 'https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io';
|
||
const EMAIL = process.env.PB_USER_EMAIL || 'david.els1@outlook.com';
|
||
const PASSWORD = process.env.PB_USER_PASSWORD || '1234567890';
|
||
|
||
// One row per `filter:` string in `lib/pocketbase.ts`. Literal values are
|
||
// placeholders — we don't want to MATCH data, we want the filter to PARSE.
|
||
// PB validates identifier names at the parser level and 400s the moment
|
||
// it hits one it doesn't know, regardless of the value.
|
||
const FILTERS = [
|
||
{ collection: 'order_items', filter: 'drv_id = "placeholder" && item_status = "assigned"' },
|
||
{ collection: 'order_items', filter: 'item_status = "collection_pool"' },
|
||
{ collection: 'order_items', filter: 'problem_flag = true || item_status = "problem"' },
|
||
// Stock pickups live in their own collection — see
|
||
// getMyAssignedStockItems in lib/pocketbase.ts. The driver app
|
||
// was silently dropping these rows before this filter was added.
|
||
{ collection: 'stock_purchase_items', filter: 'drv_id = "placeholder" && status = "assigned_to_driver"' },
|
||
{ collection: 'driver_locations', filter: 'relation = "placeholder" && timestamp >= "2026-01-01T00:00:00Z"' },
|
||
];
|
||
|
||
// --- helpers (declared up here so they're in scope for the loop) ----
|
||
function truncate(s, n) {
|
||
return s.length > n ? s.slice(0, n - 1) + '…' : s;
|
||
}
|
||
|
||
// PB operator tokens we want to skip when picking identifiers out of a
|
||
// filter string. Anything not in here AND not in a quoted string AND
|
||
// matching `/^[a-zA-Z_]…/` is treated as a candidate field name.
|
||
const OPS = new Set(['&&', '||', '!=', '=', '<', '>', '<=', '>=']);
|
||
|
||
function extractIdentifiers(filter) {
|
||
const out = new Set();
|
||
// Strip quoted strings so their contents aren't picked up as idents.
|
||
const stripped = filter.replace(/"[^"]*"/g, '');
|
||
for (const tok of stripped.split(/[\s()]+/)) {
|
||
if (!tok) continue;
|
||
if (OPS.has(tok)) continue;
|
||
if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(tok)) continue;
|
||
// Reduce `expand.path` to just `expand` for the bisect probe —
|
||
// the relation FIELD is what PB validates, not the remote column.
|
||
out.add(tok.split('.')[0]);
|
||
}
|
||
return [...out];
|
||
}
|
||
|
||
// --- run ------------------------------------------------------------
|
||
const pb = new PocketBase(PB);
|
||
pb.autoCancellation(false);
|
||
|
||
console.log(`[check-pb-schema] target: ${PB}`);
|
||
|
||
try {
|
||
await pb.collection('users').authWithPassword(EMAIL, PASSWORD);
|
||
} catch (e) {
|
||
console.error(`[check-pb-schema] FATAL: cannot authenticate — ${e.message}`);
|
||
console.error(` check NEXT_PUBLIC_POCKETBASE_URL / PB_USER_EMAIL / PB_USER_PASSWORD`);
|
||
process.exit(2);
|
||
}
|
||
console.log(`[check-pb-schema] auth: OK (as ${pb.authStore.record?.id ?? EMAIL})\n`);
|
||
|
||
let failed = 0;
|
||
|
||
// Pass 1 — probe each filter at face value. Most filters are happy
|
||
// here and the loop is fast.
|
||
console.log('Pass 1 — probe every filter at face value:');
|
||
for (const { collection, filter } of FILTERS) {
|
||
try {
|
||
await pb.collection(collection).getList(1, 1, { filter });
|
||
console.log(` [OK] ${collection.padEnd(24)} ${truncate(filter, 70)}`);
|
||
} catch (e) {
|
||
failed++;
|
||
console.log(` [FAIL] ${collection.padEnd(24)} ${truncate(filter, 70)}`);
|
||
console.log(` status=${e.status ?? '??'} msg="${e.message}"`);
|
||
|
||
// Pass 2 — bisect: probe each top-level identifier in the filter
|
||
// as its own one-clause filter. Whatever 400s is an unknown field.
|
||
// Unknown fields 400 regardless of the operator/value used.
|
||
const suspects = extractIdentifiers(filter);
|
||
for (const id of suspects) {
|
||
try {
|
||
await pb.collection(collection).getList(1, 1, { filter: `${id} = ""` });
|
||
console.log(` · ok : ${id}`);
|
||
} catch (probeErr) {
|
||
console.log(` ⚠ unknown : ${id} (PB: ${probeErr.message})`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('');
|
||
if (failed > 0) {
|
||
console.error(`[check-pb-schema] ${failed} filter(s) reference unknown fields.`);
|
||
console.error(`[check-pb-schema] Either add the field to PB, or remove the clause from the filter.`);
|
||
console.error(`[check-pb-schema] See lib/pocketbase.ts header for verified-live schema notes.`);
|
||
process.exit(1);
|
||
}
|
||
console.log(`[check-pb-schema] all filters valid.`);
|