diff --git a/lib/pocketbase.ts b/lib/pocketbase.ts index 3dfbae0..1bfe1d1 100644 --- a/lib/pocketbase.ts +++ b/lib/pocketbase.ts @@ -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 => { }; // ---------- 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 => { 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). diff --git a/lib/types.ts b/lib/types.ts index e26d88d..cdc1986 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -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;