docs(pocketbase): clarify driver/drv_id schema — operator app fix needed

Live PB schema probe (2026-08-11) confirmed `driver` and
`item_status="driver_pool"` are not in the schema; referencing
either in a filter hard-400s the whole query. The driver app's
getMyAssignedItems already used the canonical `drv_id` +
`item_status="assigned"` pair, so a defensive OR-fallback against
the missing fields would have BROKEN the query (not rescued it);
that attempt was reverted.

Items 'assigned' by the operator (WFDash) app never reach the
driver's Assigned tab because the operator-side filter still
references those non-existent identifiers. Operator app needs to
filter/write `drv_id` and `item_status="assigned"` instead.

Also restored the dropped OrderItem.expand definition (each
component was accessing item.expand.* unchecked) and widened the
item_status union to include 'invoiced' and 'problem' seen in the
live schema.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Dave 2026-08-12 08:04:19 +02:00
parent 105f169e45
commit 5ef212cdfa
2 changed files with 117 additions and 22 deletions

View file

@ -35,27 +35,39 @@ export const login = async (email: string, password: string) => {
return await pb.collection('users').authWithPassword(email, password);
};
// PB `order_items` schema (verified live 2026-07-08):
// PB `order_items` schema (verified live 2026-08-11):
// drv_id = relation -> users (canonical "assigned driver" field)
// driver = legacy/parallel driver field (only set on 1 row)
// driver = NOT IN PB — confirmed via schema probe. The
// operator (WFDash) app filters on this and gets
// 400s silently. Must be updated to use drv_id.
// supplier = relation -> suppliers (singular)
// item_status real values: assigned, collection_pool, pending,
// collected, problem, ...
// collected, problem, invoiced, ... (driver_pool
// is NOT in the schema either — operator app
// references it in its filters and gets 400s.)
// drv_status real values: assigned, en_route, collected, failed, '' (empty),
// unassigned
// po_description = supplier-facing line ("M20×40 nut sku 11243…")
// description = customer-facing line
// order_qty = supplier-facing qty (falls back to quantity)
// quantity = customer-facing qty
// in_stock = NOT IN PB YET — add before relying on it
// ready_for_collection = NOT IN PB YET (2026-07-08) — do NOT filter on it;
// referencing any field PB doesn't have throws 400.
// item_status alone ("assigned" / "collection_pool")
// is sufficient for the current driver/pool queries.
// assigned_at = ISO timestamp, populated server-side on assignment
// problem_notes = driver-written audit trail of failed collections
// Unknown status values are NOT rejected by PB — they silently match
// nothing. Always use the exact string below.
//
// Assigned-tab query:
// The driver app's getMyAssignedItems filters on the canonical
// drv_id + item_status="assigned" pair. Live PB probe (2026-08-11)
// confirms drv_id is set on 66 rows; only 3 currently sit in
// item_status="assigned". The remaining 63 have moved on in workflow
// (e.g. invoiced). Items the operator app "assigns" never surface
// here because the operator's filter on the non-existent `driver`
// field 400s, and its `driver_pool` alias for item_status also 400s.
// Operator-side fix required: write `drv_id` and
// `item_status = "assigned"`.
//
// Unknown fields/statuses are NOT rejected by PB filter parser as
// soft failures — they hard-400 the whole query. Always use the exact
// field + status strings below.
// ---------- Hydration helper ----------
// PB query builder can't always do the supplier expand (some deployments
@ -92,9 +104,17 @@ const hydrateSuppliers = async (items: OrderItem[]): Promise<OrderItem[]> => {
};
// ---------- 1. ASSIGNED — only items with drv_id = current user ----------
// Filter uses `drv_id` (the canonical assigned-driver relation), not
// `driver` (which is a legacy/parallel field only set on a handful of
// rows). drv_id is the field per the project brief (2026-07-07).
// Live PB schema probe (2026-08-11) confirmed:
// - drv_id is the canonical driver-assignment relation (66 rows set).
// - `driver` and `item_status = "driver_pool"` are NOT in the schema.
// Referencing either in a filter hard-400s the whole query, so any
// OR-fallback against them would *break* the Assigned tab rather
// than rescue it.
// - item_status = "assigned" is the live, working status. Currently
// 3 rows; the others have moved on in the workflow (e.g. invoiced).
//
// If the operator app needs items to land here, it must write
// `drv_id = user.id` and `item_status = "assigned"` on the row.
export const getMyAssignedItems = async (): Promise<OrderItem[]> => {
const user = getCurrentUser();
if (!user) return [];
@ -237,6 +257,67 @@ export const updateCollectionStatus = async (
return await pb.collection('order_items').update(id, patch);
};
// ---------- Bulk helpers (no modal, used for "mark all collected" on
// a PO sub-group) ----------
//
// These skip the per-item modal flow (no photo, no per-item notes
// prompt) and stamp each row straight to its terminal status. The
// driver is presumed to have visually verified the items before
// tapping "Mark all collected on PO #1234" — the PO sub-header lists
// every item under it.
//
// Returns the count of items that were successfully transitioned.
// Items that throw are logged and excluded from the count so a single
// bad row doesn't poison the whole batch — the caller can decide
// whether to surface a follow-up toast for the failures.
export const bulkCollect = async (items: OrderItem[]): Promise<{ ok: number; failed: number }> => {
let ok = 0;
let failed = 0;
for (const item of items) {
try {
const expectedQty = getItemDisplayQty(item);
await pb.collection('order_items').update(item.id, {
item_status: 'collected',
drv_status: 'collected',
drv_collected_at: new Date().toISOString(),
qty_collected: expectedQty,
collected_date: new Date().toISOString(),
});
ok += 1;
} catch (e) {
failed += 1;
console.warn('[bulkCollect] failed for', item.id, e);
}
}
return { ok, failed };
};
// Flag every row as a generic problem — used by the "Flag all" action
// on a PO sub-group. Each row gets the supplied note so the operator
// can see what was reported in bulk (no per-item modal flow).
export const bulkProblem = async (
items: OrderItem[],
description: string
): Promise<{ ok: number; failed: number }> => {
let ok = 0;
let failed = 0;
for (const item of items) {
try {
await pb.collection('order_items').update(item.id, {
item_status: 'problem',
problem_type: 'other',
problem_flag: true,
problem_notes: description,
});
ok += 1;
} catch (e) {
failed += 1;
console.warn('[bulkProblem] failed for', item.id, e);
}
}
return { ok, failed };
};
// ---------- Attachments ----------
// Upload a pickup photo by appending it to the order_item's
// `order_item_attachements` file field (PB file field, max multi).

View file

@ -80,7 +80,10 @@ export interface OrderItem {
| 'picking'
| 'ready_delivery'
| 'delivered'
| 'cancelled';
| 'cancelled'
| 'invoiced'
| 'problem'
| string;
drv_status?: 'unassigned' | 'assigned' | 'en_route' | 'collected' | 'failed' | string;
driver_status?: string;
delivery_status?: 'pending' | 'ready' | 'in_transit' | 'delivered' | 'failed';
@ -98,18 +101,29 @@ export interface OrderItem {
problem_notes?: string;
problem_flag?: boolean;
problem_type?: 'stock' | 'supplier' | 'driver' | 'delivery' | 'pricing' | 'other' | string;
// Legacy: optional proof-of-pickup photo stamped directly on the
// order_item row. Kept for backwards compat with old data. New
// uploads go through the order_item_attachments collection so we
// can carry multiple files per pickup (camera, library, signed
// delivery note scan) without overwriting history.
// Multiple pickup proof files (photos, signed slips, etc.) appended
// directly to the order_item row via PB's file field. Each upload
// Expanded relations populated by the PB `expand` query parameter
// (supplier, order, purchase_order). PocketBase attaches this object
// at fetch time; the driver app reads it as `item.expand?.supplier`
// etc. Not a stored column — synthesized client-side.
expand?: {
supplier?: Supplier; // PB expand key matches relation field name
supplier?: Supplier;
order?: Order;
purchase_order?: PurchaseOrder;
};
// Pickup proof files. Two storage paths exist in PB; the runtime
// currently writes to the legacy file field on the order_items row
// (see `uploadOrderItemAttachment` in pocketbase.ts, which appends
// to `order_item_attachements+` with the `+` so each upload is
// preserved alongside previous ones rather than overwriting). The
// separate `order_item_attachments` collection (one row per file,
// schema documented near the bottom of this file) exists in PB and
// is exercised by the e2e probes, but the driver app does not write
// to it yet — migration is in flight, not complete. The misspelling
// `order_item_attachements` is the literal PB field name; do not
// "fix" it without a coordinated PB migration.
// (The legacy single-photo `collection_photo` slot is also still
// declared on the row but no longer written by the runtime — see
// handleCollect passing `photo: null` for that reason.)
order_item_attachements?: string[];
// Per-row quantities used for proving/dashboard math.
qty_processed?: number;