fix(boss-dashboard): route unresolved orders.rep to (unknown) + parallelise 6th fetch
This commit is contained in:
parent
ebbcc73020
commit
9aaeee25ec
2 changed files with 82 additions and 16 deletions
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue