diff --git a/src/app/(app)/collection-pool/page.tsx b/src/app/(app)/collection-pool/page.tsx index 5de2ceb..55538e5 100644 --- a/src/app/(app)/collection-pool/page.tsx +++ b/src/app/(app)/collection-pool/page.tsx @@ -297,6 +297,7 @@ export default function CollectionPoolPage() { // Carry through the assignment so the "Assigned to ..." badge // renders correctly when the driver has been assigned. assigned_at: s.assigned_at ?? null, + drv_id: s.drv_id ?? "", // Stock rows don't have a customer-order relation. We // leave `expand.order` undefined so the display can omit // the customer/order cells. diff --git a/src/app/(app)/collection-pool/view-model.test.ts b/src/app/(app)/collection-pool/view-model.test.ts index a035717..47db608 100644 --- a/src/app/(app)/collection-pool/view-model.test.ts +++ b/src/app/(app)/collection-pool/view-model.test.ts @@ -4,7 +4,6 @@ import { buildProximityMarkerMap, buildSupplierGroups, buildVisibleRows, - type CollectionPoolGroup, type CollectionPoolSortMode, getAssignmentBadge, getAssignmentState, @@ -131,6 +130,32 @@ describe("buildVisibleRows", () => { expect(rows.map((row) => row.id)).toEqual(["assigned-ready"]); }); + it("keeps stock rows in the ready filter because entering the pool marks them ready", () => { + const stock = makeRow({ + id: "stock-assigned", + source: "stock", + item_status: "assigned_to_driver", + drv_id: "drv-1", + assigned_at: "2026-07-07T00:00:00.000Z", + ready_for_collection: null, + }); + + expect( + buildVisibleRows([stock], { + todayYmd: "2026-07-07", + searchText: "", + readyFilter: "ready", + }).map((row) => row.id), + ).toEqual(["stock-assigned"]); + expect( + buildVisibleRows([stock], { + todayYmd: "2026-07-07", + searchText: "", + readyFilter: "unready", + }), + ).toEqual([]); + }); + it("matches order numbers in the text search", () => { const rows = buildVisibleRows( [ diff --git a/src/app/(app)/collection-pool/view-model.ts b/src/app/(app)/collection-pool/view-model.ts index 5c2986c..f3d2956 100644 --- a/src/app/(app)/collection-pool/view-model.ts +++ b/src/app/(app)/collection-pool/view-model.ts @@ -43,6 +43,10 @@ const ASSIGNMENT_SORT_ORDER: Record = { const MARKER_TOKENS = ["marker-blue", "marker-emerald", "marker-violet", "marker-amber"] as const; +function isReadyForCollection(row: CollectionPoolViewRow): boolean { + return row.source === "stock" || row.ready_for_collection === true; +} + export function getAssignmentState( row: Pick, todayYmd: string, @@ -104,8 +108,9 @@ export function buildVisibleRows( return rows .filter((row) => { - if (options.readyFilter === "ready" && !row.ready_for_collection) return false; - if (options.readyFilter === "unready" && row.ready_for_collection) return false; + const ready = isReadyForCollection(row); + if (options.readyFilter === "ready" && !ready) return false; + if (options.readyFilter === "unready" && ready) return false; if (!query) return true; return [ diff --git a/src/app/api/v1/driver/stops/route.test.ts b/src/app/api/v1/driver/stops/route.test.ts index 2940c89..2fc337f 100644 --- a/src/app/api/v1/driver/stops/route.test.ts +++ b/src/app/api/v1/driver/stops/route.test.ts @@ -201,6 +201,71 @@ describe("GET /api/v1/driver/stops", () => { expect(pickup.supplier.coords).toEqual([-26.1, 28.1]); }); + it("handles PocketBase array coordinates in the combined feed", async () => { + orderGetList.mockResolvedValueOnce({ + items: [ + deliveryRow({ + expand: { + purchase_order: { + id: "po-1", + po_number: "PO-1", + expand: { + supplier: { + id: "sup-1", + suppliername: "Acme Tools", + coordinates: [-26.2041, 28.0473], + }, + }, + }, + order: { + id: "order-1", + order_number: "SO-1", + expand: { + customer: { id: "cust-1", customer: "North Pit" }, + company: { id: "co-1", company: "Anglo" }, + }, + }, + }, + }), + ], + totalItems: 1, + page: 1, + perPage: 50, + totalPages: 1, + }); + stockGetList.mockResolvedValueOnce({ + items: [ + pickupRow({ + expand: { + purchase: { + id: "purchase-1", + purchase_number: "STK-1", + expand: { + supplier: { + id: "sup-2", + suppliername: "Afriq Lube", + coordinates: [-26.1, 28.1], + }, + }, + }, + stock_item: { id: "si-1", sku: "BOLT-10", name: "10mm bolts" }, + }, + }), + ], + totalItems: 1, + page: 1, + perPage: 50, + totalPages: 1, + }); + + const res = await GET(authedRequest()); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.items).toHaveLength(2); + expect(body.items[0].supplier.coords).toEqual([-26.2041, 28.0473]); + expect(body.items[1].supplier.coords).toEqual([-26.1, 28.1]); + }); + it("scopes the query to drv_id when provided", async () => { orderGetList.mockResolvedValueOnce(emptyPage()); stockGetList.mockResolvedValueOnce(emptyPage()); diff --git a/src/lib/server/driver-stops.ts b/src/lib/server/driver-stops.ts index 87cf8bb..b2c08c3 100644 --- a/src/lib/server/driver-stops.ts +++ b/src/lib/server/driver-stops.ts @@ -137,13 +137,28 @@ export interface ListDriverStopsResult { totalPages: number; } -function parseCoords(raw: string | null | undefined): [number, number] | null { - if (!raw) return null; - const parts = raw.split(",").map((s) => Number(s.trim())); - if (parts.length !== 2 || !Number.isFinite(parts[0]) || !Number.isFinite(parts[1])) { - return null; +function parseCoords(raw: unknown): [number, number] | null { + if (Array.isArray(raw)) { + return finiteCoordinatePair(raw[0], raw[1]); } - return [parts[0], parts[1]]; + if (raw && typeof raw === "object") { + const point = raw as Record; + return finiteCoordinatePair( + point.lat ?? point.latitude, + point.lng ?? point.lon ?? point.longitude, + ); + } + if (typeof raw !== "string" || !raw.trim()) return null; + + const parts = raw.split(",").map((value) => Number(value.trim())); + return finiteCoordinatePair(parts[0], parts[1]); +} + +function finiteCoordinatePair(firstValue: unknown, secondValue: unknown): [number, number] | null { + const first = Number(firstValue); + const second = Number(secondValue); + if (!Number.isFinite(first) || !Number.isFinite(second)) return null; + return [first, second]; } /**