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.
168 lines
6.6 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
});
|
|
});
|