WFDASH-WEBAPP/src/lib/item-status-ui.settled.test.ts
Dave b212d0fcd8 feat(manager): a mobile orders board for the managers at /manager
A hidden, installable PWA: one link, opened on a phone, showing where
every order is. New / Processing / Completed, with a tap-to-preview
popup and the invoice/line detail Gary needs. Read-only, and NOT in
the sidebar — the route lives outside `(app)`, so `<Sidebar />` never
renders there.

The preview popup is reused, not rebuilt: the service emits
`UniversalHit`s and the page hands them to the existing
`SearchPreviewDialog`, so the manager gets the desk's quick-overview
verbatim.

Also fixes the /orders Completed tab, which was matching ZERO orders.
`delivered_at` is stamped on only 2 of 12,957 delivered lines and the
business stopped writing `delivered` on 2026-08-14 (the live pipeline
now ends at `invoiced` / `checked_in`), so the old filter never
matched. "Settled" is now defined once — `SETTLED_STATUSES` +
`SETTLED_DATE_FIELDS` — and dated per status: `invoiced_date` for
invoiced (the only properly stamped field, 2,199 lines), `delivered_at`
for delivered, `updated` only where no dedicated column exists.

`delivery_note` and `checked_in` move into `completed` as instructed,
and the standalone `post_processing` tab is retired (a DN is not a
stage of its own). `invoiced` stays in `collected` as well, so
invoiced orders don't vanish from the workflow tabs.

Completed tab: 0 -> 341 orders over 30 days, 112 over 7.
2026-10-26 23:09:01 +02:00

168 lines
6.6 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
ITEM_STATUS_VISUAL,
PIPELINE_GROUP_ITEM_STATUSES,
PIPELINE_GROUP_ORDER,
SETTLED_DATE_FIELDS,
SETTLED_STATUSES,
TAB_GROUP_ITEM_STATUSES,
TAB_GROUP_ORDER,
TAB_GROUP_VISUAL,
itemStatusToPipelineGroup,
itemStatusToTabGroup,
} from "./item-status-ui";
import type { ItemStatus } from "./types";
/**
* The 2026-10-26 taxonomy change.
*
* Before: the `completed` tab matched only `delivered` + `completed`
* rows, dated on `delivered_at` / `updated`. On live PB that matched
* ZERO orders, because:
* - `delivered_at` is stamped on only 2 of 12,957 delivered lines
* (the rest are legacy rows whose status was set without a stamp)
* - the `delivered` status itself has not been written since
* 2026-08-14 — the live pipeline now ends at `invoiced` /
* `checked_in`
*
* So the tab was permanently empty while `delivery_note` (319 lines)
* and `checked_in` (1,166 lines) sat in tabs that did not describe
* what had actually finished.
*
* After: `delivery_note`, `checked_in` and `invoiced` join
* `delivered` + `completed` as SETTLED, each dated by the best
* available field, and the standalone `post_processing` tab is gone.
*/
describe("SETTLED_STATUSES", () => {
it("includes the two statuses the operator moved into completed", () => {
expect(SETTLED_STATUSES).toContain("delivery_note");
expect(SETTLED_STATUSES).toContain("checked_in");
});
it("keeps the original pair", () => {
expect(SETTLED_STATUSES).toContain("delivered");
expect(SETTLED_STATUSES).toContain("completed");
});
it("includes invoiced — the only status with a properly stamped date", () => {
// `invoiced_date` is populated on 2,199 lines; `delivered_at` on 2.
// Without `invoiced` in the settled set, a 7-day window matched
// 13 orders instead of 112.
expect(SETTLED_STATUSES).toContain("invoiced");
});
it("excludes the in-flight statuses", () => {
for (const status of ["pending", "collection_pool", "with_driver"] as ItemStatus[]) {
expect(SETTLED_STATUSES).not.toContain(status);
}
});
it("gives every settled status a date source", () => {
for (const status of SETTLED_STATUSES) {
const spec = SETTLED_DATE_FIELDS[status];
expect(Boolean(spec?.primary || spec?.fallback)).toBe(true);
}
});
it("dates `delivered` on delivered_at ONLY", () => {
// `delivered_at` is empty on all but 2 of the 12,957 delivered
// lines, and their `updated` is the migration timestamp. Falling
// back to `updated` would resurface archived work every time an
// old row was touched — an un-dated row is archived, not recent.
expect(SETTLED_DATE_FIELDS.delivered).toEqual({ primary: "delivered_at" });
});
it("dates `invoiced` on invoiced_date", () => {
expect(SETTLED_DATE_FIELDS.invoiced).toEqual({ primary: "invoiced_date" });
});
it("uses `updated` only for statuses with no dedicated stamp field", () => {
// No schema field exists for these three, so `updated` is the only
// signal available.
for (const status of ["completed", "checked_in", "delivery_note"] as const) {
expect(SETTLED_DATE_FIELDS[status]).toEqual({ fallback: "updated" });
}
});
});
describe("TAB_GROUP_ITEM_STATUSES — the post_processing tab is retired", () => {
it("no longer has a post_processing group", () => {
expect(TAB_GROUP_ORDER).not.toContain("post_processing");
expect("post_processing" in TAB_GROUP_VISUAL).toBe(false);
});
it("keeps every ItemStatus mapped to exactly one group", () => {
// `itemStatusToTabGroup` throws on an unmapped status, so this is
// the guard that catches a status added to the union without a
// home.
for (const status of Object.keys(ITEM_STATUS_VISUAL) as ItemStatus[]) {
expect(() => itemStatusToTabGroup(status)).not.toThrow();
}
});
it("moves delivery_note and checked_in out of their old groups", () => {
expect(TAB_GROUP_ITEM_STATUSES.collected).not.toContain("checked_in");
expect(TAB_GROUP_ITEM_STATUSES.completed).toContain("checked_in");
expect(TAB_GROUP_ITEM_STATUSES.completed).toContain("delivery_note");
});
it("keeps invoiced visible on the collected workflow tab", () => {
// `invoiced` is deliberately listed in BOTH `collected` and
// `completed`: `itemStatusToTabGroup` resolves it to `collected`
// (first match) so the office's workflow tab keeps its rows, while
// the completed tab picks invoiced rows up through its own
// windowed filter. Dropping it from `collected` would make
// invoiced orders vanish from /orders entirely.
expect(TAB_GROUP_ITEM_STATUSES.collected).toContain("invoiced");
expect(TAB_GROUP_ITEM_STATUSES.completed).toContain("invoiced");
expect(itemStatusToTabGroup("invoiced")).toBe("collected");
});
});
describe("PIPELINE_GROUP_ITEM_STATUSES — the manager's three buckets", () => {
it("has exactly new / processing / completed", () => {
expect(PIPELINE_GROUP_ORDER).toEqual(["new", "processing", "completed"]);
});
it("maps every ItemStatus to a pipeline group", () => {
for (const status of Object.keys(ITEM_STATUS_VISUAL) as ItemStatus[]) {
expect(() => itemStatusToPipelineGroup(status)).not.toThrow();
}
});
it("keeps cancelled and removed OUT of the pipeline", () => {
// A cancelled order is not work in progress — it belongs on
// /orders/archive, not in the manager's three buckets.
expect(itemStatusToPipelineGroup("cancelled")).toBeNull();
expect(itemStatusToPipelineGroup("removed")).toBeNull();
});
it("puts the brand-new statuses in new", () => {
expect(PIPELINE_GROUP_ITEM_STATUSES.new).toContain("pending");
expect(itemStatusToPipelineGroup("pending")).toBe("new");
});
it("puts the in-flight statuses in processing", () => {
expect(itemStatusToPipelineGroup("po_generated")).toBe("processing");
expect(itemStatusToPipelineGroup("collection_pool")).toBe("processing");
expect(itemStatusToPipelineGroup("with_driver")).toBe("processing");
});
it("puts every settled status in completed", () => {
for (const status of SETTLED_STATUSES) {
expect(itemStatusToPipelineGroup(status)).toBe("completed");
}
});
it("keeps a status out of two pipeline groups", () => {
// The pipeline groups are a strict partition — an order must not
// land in both "processing" and "completed" by status.
const seen = new Set<string>();
for (const group of PIPELINE_GROUP_ORDER) {
for (const status of PIPELINE_GROUP_ITEM_STATUSES[group]) {
expect(seen.has(status)).toBe(false);
seen.add(status);
}
}
});
});