feat(collection-pool): enhance stock row handling and filtering logic

- Added `drv_id` to the stock row data structure to ensure proper assignment tracking.
- Implemented a new `isReadyForCollection` function to streamline the logic for determining if stock rows are ready for collection.
- Updated the `buildVisibleRows` function to utilize the new readiness check, improving the accuracy of row filtering based on the ready status.
- Added a test case to verify that stock rows are correctly included in the ready filter, ensuring expected behavior in the collection pool view.
- Enhanced coordinate parsing logic to handle various input formats, improving robustness in the driver stops API.
This commit is contained in:
Dave 2026-08-17 22:33:47 +02:00
parent 519fb422ae
commit 0b6171b851
5 changed files with 120 additions and 9 deletions

View file

@ -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.

View file

@ -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(
[

View file

@ -43,6 +43,10 @@ const ASSIGNMENT_SORT_ORDER: Record<AssignmentState, number> = {
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<CollectionPoolViewRow, "item_status" | "drv_id" | "assigned_at">,
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 [

View file

@ -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());

View file

@ -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<string, unknown>;
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];
}
/**