fix(boss-dashboard): route unresolved orders.rep to (unknown) + parallelise 6th fetch

This commit is contained in:
Dave 2026-08-27 23:55:42 +02:00
parent ebbcc73020
commit 9aaeee25ec
2 changed files with 82 additions and 16 deletions

View file

@ -301,6 +301,52 @@ describe("getBossDashboardData", () => {
expect(unknown?.quotesLoaded).toBe(1);
});
it("buckets unresolved orders.rep into (unknown) — not silently dropped", async () => {
// Regression: an order whose rep field is free-text that doesn't resolve
// to any user must surface in the (unknown) bucket, not vanish from
// ordersPlaced. Before this fix, the repOrders Map.set was skipped when
// resolveOrderRepId returned "" and the order disappeared entirely.
addRow("users", { id: "rep-1", name: "Gary Ndlovu" });
// rep "Old Rep Name" is set on the order but doesn't match any user id or name.
const orphanOrder = addRow("orders", {
rep: "Old Rep Name",
invoiced: false,
created: "2026-08-27T08:00:00.000Z",
});
addRow("order_items", { order: orphanOrder, item_status: "pending", created: "2026-08-27T08:00:00.000Z" });
const data = await getBossDashboardData({ window: "today", now: fixedNow });
const unknown = data.reps.find((r) => r.isUnknown);
expect(unknown).toBeDefined();
expect(unknown?.ordersPlaced).toBe(1);
expect(data.orderTotals.total).toBe(1);
});
it("matches orders.rep by case-insensitive user name (not id)", async () => {
// orders.rep has historically stored free-text names; resolveOrderRepId
// must fall back to a case-insensitive name match against users.name
// when no user with that id exists. This is the primary reason the
// helper exists.
addRow("users", { id: "rep-1", name: "Gary Ndlovu" });
// rep is a free-text name (with different casing) — no user with id="Gary Ndlovu".
addRow("orders", {
rep: "gary ndlovu",
invoiced: false,
created: "2026-08-27T08:00:00.000Z",
});
const data = await getBossDashboardData({ window: "today", now: fixedNow });
const gary = data.reps.find((r) => r.id === "rep-1");
expect(gary).toBeDefined();
expect(gary?.ordersPlaced).toBe(1);
// (unknown) must NOT be created when the name resolved.
expect(data.reps.find((r) => r.isUnknown)).toBeUndefined();
});
it("excludes quotes and orders outside the active window", async () => {
addRow("users", { id: "rep-1", name: "Gary" });
addRow("customers", { id: "cust-a", rep: "rep-1" });

View file

@ -99,7 +99,7 @@ export async function getBossDashboardData({
const rangeFilter = `created >= "${rangeStart}" && created < "${rangeEnd}"`;
const todayRangeFilter = `created >= "${todayRange.rangeStart}" && created < "${todayRange.rangeEnd}"`;
const [users, customers, orders, quotes, todayItems] = await Promise.all([
const [users, customers, orders, quotes, todayItems, orderItemsInWindow] = await Promise.all([
pb.collection("users").getFullList({ fields: "id,name" }),
pb.collection("customers").getFullList({ fields: "id,rep" }),
pb.collection("orders").getFullList({
@ -115,15 +115,15 @@ export async function getBossDashboardData({
filter: todayRangeFilter,
fields: "id,drv_id,item_status,drv_status,drv_collected_at,created",
}),
// Delivered + cancelled are derived from order_items.item_status (the orders
// collection doesn't carry item_status in PB). Active window only. No data
// dependency on the other five — safe to parallelise.
pb.collection("order_items").getFullList({
filter: rangeFilter,
fields: "order,item_status",
}),
]);
// Delivered + cancelled are derived from order_items.item_status (the orders
// collection doesn't carry item_status in PB). Active window only.
const orderItemsInWindow = await pb.collection("order_items").getFullList({
filter: rangeFilter,
fields: "order,item_status",
});
const userById = new Map<string, { id: string; name: string }>();
for (const u of users) {
userById.set(u.id, {
@ -166,8 +166,14 @@ export async function getBossDashboardData({
if (rec.invoiced === true) invoiced += 1;
if (isCancelled) continue;
const repId = resolveOrderRepId(rec, userById);
if (repId) repOrders.set(repId, (repOrders.get(repId) ?? 0) + 1);
const { id: repId, unresolved } = resolveOrderRepId(rec, userById);
// Unresolved orders.rep values bucket into (unknown), matching how
// claimed_by handles unresolvable values. Empty rep → silently skip.
if (unresolved) {
repOrders.set("", (repOrders.get("") ?? 0) + 1);
} else if (repId) {
repOrders.set(repId, (repOrders.get(repId) ?? 0) + 1);
}
}
// --- Per-rep quote stats --------------------------------------------------
@ -218,6 +224,11 @@ export async function getBossDashboardData({
}
for (const [repId, count] of repOrders) {
if (repId === "") {
// Unresolved orders.rep — bucket into (unknown).
ensureRep("", "(unknown)").ordersPlaced = count;
continue;
}
const u = userById.get(repId);
if (!u) continue;
ensureRep(u.id, u.name).ordersPlaced = count;
@ -233,7 +244,10 @@ export async function getBossDashboardData({
a.name.localeCompare(b.name),
);
const unknownRow = repRows.get("");
if (unknownRow && (unknownRow.quotesLoaded > 0 || unknownRow.quotesDoneBy > 0)) {
if (
unknownRow &&
(unknownRow.quotesLoaded > 0 || unknownRow.quotesDoneBy > 0 || unknownRow.ordersPlaced > 0)
) {
reps.push(unknownRow);
}
@ -274,16 +288,22 @@ export async function getBossDashboardData({
/**
* `orders.rep` is historically either a PB user id or a free-text name.
* Try the id lookup first, then a case-insensitive name match.
*
* Returns `{ id, unresolved }`:
* - `id !== ""` → matched a user; use `id`.
* - `id === ""` + `unresolved` → rep value present but didn't resolve any user;
* caller should bucket into `(unknown)`.
* - `id === ""` + !`unresolved` → rep value was empty; caller should skip.
*/
function resolveOrderRepId(
order: Record<string, unknown>,
userById: Map<string, { id: string; name: string }>,
): string {
): { id: string; unresolved: boolean } {
const rep = String(order.rep ?? "").trim();
if (!rep) return "";
if (userById.has(rep)) return rep;
if (!rep) return { id: "", unresolved: false };
if (userById.has(rep)) return { id: rep, unresolved: false };
for (const u of userById.values()) {
if (u.name.trim().toLowerCase() === rep.toLowerCase()) return u.id;
if (u.name.trim().toLowerCase() === rep.toLowerCase()) return { id: u.id, unresolved: false };
}
return "";
return { id: "", unresolved: true };
}