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:
parent
519fb422ae
commit
0b6171b851
5 changed files with 120 additions and 9 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue