status
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
|||||||
DELIVERY_STATUS_TABS,
|
DELIVERY_STATUS_TABS,
|
||||||
ORDER_STATUS_TABS,
|
ORDER_STATUS_TABS,
|
||||||
} from './orderStatus';
|
} from './orderStatus';
|
||||||
|
import { UNMIRRORED_STAGES } from './orderProgress';
|
||||||
|
|
||||||
/* Set after the imports on purpose, and it still takes: `billedAtMs` reads the
|
/* Set after the imports on purpose, and it still takes: `billedAtMs` reads the
|
||||||
zone when it is CALLED, not when this module loads, and assigning TZ calls
|
zone when it is CALLED, not when this module loads, and assigning TZ calls
|
||||||
@@ -247,3 +248,50 @@ test('the delivery strip offers the whole ladder', () => {
|
|||||||
['All', 'Pending', 'Accepted', 'Arrived', 'Picked', 'Active', 'Skipped', 'Rejected', 'Delivered', 'Cancelled'],
|
['All', 'Pending', 'Accepted', 'Arrived', 'Picked', 'Active', 'Skipped', 'Rejected', 'Delivered', 'Cancelled'],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
The orders table shows one vocabulary and used to filter by another.
|
||||||
|
|
||||||
|
`orderStage` resolves each order row to the stage its DELIVERY reached, because
|
||||||
|
`orders.orderstatus` sits on `pending` from the moment a rider is assigned until
|
||||||
|
the parcel is dropped. So the column read "Picked" while the tabs filtered on
|
||||||
|
"pending": the row sat under Pending, there was no Picked tab to find it in, and
|
||||||
|
the counts described a set nobody could see.
|
||||||
|
|
||||||
|
The strip now carries every word that column can display.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test('every stage the orders table can display has an order tab', () => {
|
||||||
|
// The six the order row is never told about, plus the ones it is. If the
|
||||||
|
// column can render it, an operator must be able to filter to it.
|
||||||
|
for (const status of [...UNMIRRORED_STAGES, ...LIVE_ORDER_STATUSES]) {
|
||||||
|
const hits = ORDER_STATUS_TABS.filter(
|
||||||
|
(tab) => tab.key !== 'all' && matchesStatus(tab.key, status),
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
hits.length,
|
||||||
|
1,
|
||||||
|
`"${status}" is shown on an order row but matched ${hits.length} tabs`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('an order mid-delivery is filed under the stage it shows', () => {
|
||||||
|
// The whole bug in one assertion: a row displaying "picked" must not answer
|
||||||
|
// to the Pending tab, or pressing Picked finds nothing and pressing Pending
|
||||||
|
// shows a row labelled Picked.
|
||||||
|
assert.equal(matchesStatus('picked', 'picked'), true);
|
||||||
|
assert.equal(matchesStatus('pending', 'picked'), false);
|
||||||
|
assert.equal(matchesStatus('arrived', 'arrived'), true);
|
||||||
|
assert.equal(matchesStatus('pending', 'arrived'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('created stays on the order strip and off the delivery one', () => {
|
||||||
|
// The one word that is genuinely order-only — a delivery is never created,
|
||||||
|
// it is requested. Losing it would drop every brand-new order into All alone.
|
||||||
|
assert.ok(ORDER_STATUS_TABS.some((tab) => tab.key === 'created'));
|
||||||
|
// Widened on purpose: the key union already proves `created` is not a
|
||||||
|
// delivery tab, and TypeScript rejects the comparison outright. The runtime
|
||||||
|
// check stays because the union is what a future edit would change first.
|
||||||
|
assert.ok(!DELIVERY_STATUS_TABS.some((tab) => (tab.key as string) === 'created'));
|
||||||
|
});
|
||||||
|
|||||||
@@ -114,10 +114,33 @@ export const statusColor = (map: Record<string, string>, status: string | undefi
|
|||||||
* else, so an operator working through the tabs never sees those rows at all.
|
* else, so an operator working through the tabs never sees those rows at all.
|
||||||
* That is the failure this list is checked against.
|
* That is the failure this list is checked against.
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
* ── Why the order strip carries the delivery stages too ─────────────────────
|
||||||
|
*
|
||||||
|
* It used to be the five words an order row writes about itself. But the orders
|
||||||
|
* table does not SHOW those words any more — `orderStage` resolves each row to
|
||||||
|
* the stage its delivery reached, because `orders.orderstatus` sits on
|
||||||
|
* `pending` from the moment a rider is assigned until the parcel is dropped,
|
||||||
|
* and an operator staring at "pending" cannot tell a job nobody has accepted
|
||||||
|
* from one already on a bike.
|
||||||
|
*
|
||||||
|
* So the table showed `Picked` and the tabs filtered on `pending`, and a row
|
||||||
|
* labelled Picked lived under the Pending tab with no Picked tab to find it in.
|
||||||
|
* The screen displayed one vocabulary and filtered by another.
|
||||||
|
*
|
||||||
|
* The strip now carries every word the column can display. `created` stays,
|
||||||
|
* because an order reaches it and a delivery never does.
|
||||||
|
*/
|
||||||
export const ORDER_STATUS_TABS = [
|
export const ORDER_STATUS_TABS = [
|
||||||
{ key: 'all', label: 'All' },
|
{ key: 'all', label: 'All' },
|
||||||
{ key: 'created', label: 'Created' },
|
{ key: 'created', label: 'Created' },
|
||||||
{ key: 'pending', label: 'Pending' },
|
{ key: 'pending', label: 'Pending' },
|
||||||
|
{ key: 'accepted', label: 'Accepted' },
|
||||||
|
{ key: 'arrived', label: 'Arrived' },
|
||||||
|
{ key: 'picked', label: 'Picked' },
|
||||||
|
{ key: 'active', label: 'Active' },
|
||||||
|
{ key: 'skipped', label: 'Skipped' },
|
||||||
|
{ key: 'rejected', label: 'Rejected' },
|
||||||
{ key: 'delivered', label: 'Delivered' },
|
{ key: 'delivered', label: 'Delivered' },
|
||||||
{ key: 'cancelled', label: 'Cancelled' },
|
{ key: 'cancelled', label: 'Cancelled' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -141,19 +141,28 @@ export function SalesPage() {
|
|||||||
const importedBills = useMemo(() => everyOrder.filter(isCounterSale), [everyOrder]);
|
const importedBills = useMemo(() => everyOrder.filter(isCounterSale), [everyOrder]);
|
||||||
const allDeliveries = deliveries.data ?? [];
|
const allDeliveries = deliveries.data ?? [];
|
||||||
|
|
||||||
|
/* How far each order has actually got. The order row is only ever told
|
||||||
|
pending/delivered/cancelled, so the six stages in between are read off the
|
||||||
|
deliveries list — same window, same query, no extra fetch. See gap D in
|
||||||
|
`orderProgress.ts`.
|
||||||
|
|
||||||
|
Built here, above the filter, because the filter and the tab counts now
|
||||||
|
read the same resolved word the table renders. */
|
||||||
|
const stages = useMemo(() => deliveryStageFrom(allDeliveries), [allDeliveries]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Filtered on the ORDER ladder, deliberately, even though the chip now shows
|
* Filtered on the word the row actually SHOWS.
|
||||||
* the delivery stage.
|
|
||||||
*
|
*
|
||||||
* `ORDER_STATUS_TABS` has no Picked or Arrived tab — an order never reaches
|
* This used to filter on `row.orderstatus` while the chip rendered
|
||||||
* those words — so filtering on the stage would drop every order in progress
|
* `orderStage(...)`, on the reasoning that the order strip had no Picked or
|
||||||
* out of every tab except All. Under Pending the rows are the outstanding
|
* Arrived tab to put those rows in. That reasoning held the wrong end: a row
|
||||||
* orders, which is what Pending means here, and the chip says how far along
|
* labelled Picked sat under the Pending tab, there was no Picked tab to look
|
||||||
* each one is. The tab counts stay on the same footing for the same reason.
|
* in, and the counts described a set the operator could not see. The strip
|
||||||
|
* carries those tabs now, so the filter follows the column.
|
||||||
*/
|
*/
|
||||||
const orderRows = useMemo(
|
const orderRows = useMemo(
|
||||||
() => allOrders.filter((row) => matchesStatus(status, row.orderstatus)),
|
() => allOrders.filter((row) => matchesStatus(status, orderStage(row, stages).status)),
|
||||||
[allOrders, status],
|
[allOrders, status, stages],
|
||||||
);
|
);
|
||||||
const deliveryRows = useMemo(
|
const deliveryRows = useMemo(
|
||||||
() => allDeliveries.filter((row) => matchesStatus(status, row.orderstatus)),
|
() => allDeliveries.filter((row) => matchesStatus(status, row.orderstatus)),
|
||||||
@@ -219,17 +228,31 @@ export function SalesPage() {
|
|||||||
*/
|
*/
|
||||||
const statusTabs = tab === 'deliveries' ? DELIVERY_STATUS_TABS : ORDER_STATUS_TABS;
|
const statusTabs = tab === 'deliveries' ? DELIVERY_STATUS_TABS : ORDER_STATUS_TABS;
|
||||||
|
|
||||||
/** Counts for the tab strip, from the unfiltered set. */
|
/**
|
||||||
|
* Counts for the tab strip, from the unfiltered set.
|
||||||
|
*
|
||||||
|
* Read through the same resolution the rows are filtered and rendered by. A
|
||||||
|
* count taken from `row.orderstatus` while the tab selects on the stage is a
|
||||||
|
* number describing a different set from the one that appears when it is
|
||||||
|
* pressed — Pending would say 41 and then show 6.
|
||||||
|
*/
|
||||||
const tabCounts = useMemo(() => {
|
const tabCounts = useMemo(() => {
|
||||||
const source = tab === 'deliveries' ? allDeliveries : allOrders;
|
const statusOf =
|
||||||
|
tab === 'deliveries'
|
||||||
|
? (row: DeliveryRow | OrderRow) => row.orderstatus
|
||||||
|
: (row: DeliveryRow | OrderRow) => orderStage(row as OrderRow, stages).status;
|
||||||
|
|
||||||
|
const source: (DeliveryRow | OrderRow)[] =
|
||||||
|
tab === 'deliveries' ? allDeliveries : allOrders;
|
||||||
const tabs = tab === 'deliveries' ? DELIVERY_STATUS_TABS : ORDER_STATUS_TABS;
|
const tabs = tab === 'deliveries' ? DELIVERY_STATUS_TABS : ORDER_STATUS_TABS;
|
||||||
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
tabs.map((entry) => [
|
tabs.map((entry) => [
|
||||||
entry.key,
|
entry.key,
|
||||||
source.filter((row) => matchesStatus(entry.key, row.orderstatus)).length,
|
source.filter((row) => matchesStatus(entry.key, statusOf(row))).length,
|
||||||
]),
|
]),
|
||||||
) as Record<StatusKey, number>;
|
) as Record<StatusKey, number>;
|
||||||
}, [tab, allOrders, allDeliveries]);
|
}, [tab, allOrders, allDeliveries, stages]);
|
||||||
|
|
||||||
const counterTotals = useMemo(() => {
|
const counterTotals = useMemo(() => {
|
||||||
const fromTills = posSummary.reduce(
|
const fromTills = posSummary.reduce(
|
||||||
@@ -285,12 +308,6 @@ export function SalesPage() {
|
|||||||
for a rider again, and without this the delivery row left behind keeps
|
for a rider again, and without this the delivery row left behind keeps
|
||||||
them out of every assign surface for good — see `releasedFrom`. */
|
them out of every assign surface for good — see `releasedFrom`. */
|
||||||
const released = useMemo(() => releasedFrom(allDeliveries), [allDeliveries]);
|
const released = useMemo(() => releasedFrom(allDeliveries), [allDeliveries]);
|
||||||
/* How far each order has actually got. The order row is only ever told
|
|
||||||
pending/delivered/cancelled, so the six stages in between are read off the
|
|
||||||
deliveries list — same window, same query, no extra fetch. See gap D in
|
|
||||||
`orderProgress.ts`. */
|
|
||||||
const stages = useMemo(() => deliveryStageFrom(allDeliveries), [allDeliveries]);
|
|
||||||
|
|
||||||
const branchOf = useMemo(
|
const branchOf = useMemo(
|
||||||
() => (row: OrderRow) => branches.find((branch) => branch.locationid === row.locationid),
|
() => (row: OrderRow) => branches.find((branch) => branch.locationid === row.locationid),
|
||||||
[branches],
|
[branches],
|
||||||
|
|||||||
Reference in New Issue
Block a user