mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-01 14:23:11 +00:00
Some checks are pending
* fix(work-orders): add Unscheduled option to Advanced Filters Date range * fix(work-orders): page unscheduled results server-side --------- Co-authored-by: Codex Review Integration <codex-review@local.invalid> Co-authored-by: Alexandre Brandizzi <alex_brandizzi@hotmail.com>
435 lines
15 KiB
TypeScript
435 lines
15 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { ASSIGNEE_FILTER_UNASSIGNED } from "@/domain/work-orders/types/work-order-list";
|
|
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
|
|
import {
|
|
DOC_STATUS_API,
|
|
LIFECYCLE_STATUS_API,
|
|
WORK_ORDER_TYPE_API,
|
|
} from "@/domain/work-orders/types/work-order-board";
|
|
import type { WOType } from "@/domain/work-orders/types/work-order-wizard";
|
|
import { queryKeys } from "@/infra/query-key/query-key";
|
|
import {
|
|
BOARD_SEARCH_MAX_PAGE_SIZE,
|
|
buildBoardSearchParamsRecord,
|
|
toBoardQueryParams,
|
|
toBoardSearchQueryParams,
|
|
toUnassignedQueueQueryParams,
|
|
UNASSIGNED_QUEUE_DATE_FROM,
|
|
UNASSIGNED_QUEUE_DATE_TO,
|
|
} from "@/domain/work-orders/utils/board-query-params";
|
|
|
|
describe("toBoardQueryParams", () => {
|
|
const baseBar = {
|
|
weekMonday: "2026-07-13",
|
|
dispatcherIds: new Set<string>(),
|
|
types: new Set<WOType>(),
|
|
};
|
|
|
|
it("omits search when shorter than 2 characters", () => {
|
|
const params = toBoardQueryParams(baseBar, "B");
|
|
expect(params.search).toBeUndefined();
|
|
expect(params.weekStart).toBe("2026-07-13");
|
|
expect(params.weekEnd).toBe("2026-07-17");
|
|
});
|
|
|
|
it("includes trimmed search when length >= 2", () => {
|
|
const params = toBoardQueryParams(baseBar, " BK5 ");
|
|
expect(params.search).toBe("BK5");
|
|
});
|
|
|
|
it("maps unassigned dispatcher to __unassigned__", () => {
|
|
const params = toBoardQueryParams(
|
|
{
|
|
...baseBar,
|
|
dispatcherIds: new Set([ASSIGNEE_FILTER_UNASSIGNED, "user-guid"]),
|
|
},
|
|
"",
|
|
);
|
|
expect(params.dispatchers).toEqual(["__unassigned__", "user-guid"]);
|
|
});
|
|
|
|
it("maps PM type filter to API types", () => {
|
|
const params = toBoardQueryParams({ ...baseBar, types: new Set(["PM"]) }, "");
|
|
expect(params.types).toEqual([WORK_ORDER_TYPE_API.PM]);
|
|
expect(params.overdue).toBeUndefined();
|
|
});
|
|
|
|
it("maps multiple types as union query params", () => {
|
|
const params = toBoardQueryParams({ ...baseBar, types: new Set(["PM", "Emergency"]) }, "");
|
|
expect(params.types).toEqual([WORK_ORDER_TYPE_API.PM, WORK_ORDER_TYPE_API.Emergency]);
|
|
expect(params.overdue).toBeUndefined();
|
|
});
|
|
|
|
it("maps Overdue bar filter to overdue=true without types", () => {
|
|
const params = toBoardQueryParams({ ...baseBar, types: new Set(["Overdue"]) }, "");
|
|
expect(params.overdue).toBe(true);
|
|
expect(params.types).toBeUndefined();
|
|
});
|
|
|
|
it("maps Overdue with real types as OR (types + overdue)", () => {
|
|
const params = toBoardQueryParams({ ...baseBar, types: new Set(["PM", "Overdue"]) }, "");
|
|
expect(params.types).toEqual([WORK_ORDER_TYPE_API.PM]);
|
|
expect(params.overdue).toBe(true);
|
|
});
|
|
|
|
it("omits types when the set is empty", () => {
|
|
const params = toBoardQueryParams(baseBar, "");
|
|
expect(params.types).toBeUndefined();
|
|
expect(params.overdue).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("toBoardSearchQueryParams", () => {
|
|
it("defaults page to 0 (0-based pagination)", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS },
|
|
});
|
|
expect(params.page).toBe(0);
|
|
expect(params.pageSize).toBe(BOARD_SEARCH_MAX_PAGE_SIZE);
|
|
expect(params.datePreset).toBe("Custom");
|
|
expect(params.dateFrom).toBe("2000-01-01");
|
|
expect(params.dateTo).toBe("2099-12-31");
|
|
expect(params.unscheduledOnly).toBeUndefined();
|
|
});
|
|
|
|
it("forwards chrome pageSize 50 instead of the search max", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS },
|
|
page: 1,
|
|
pageSize: 50,
|
|
});
|
|
expect(params.page).toBe(1);
|
|
expect(params.pageSize).toBe(50);
|
|
});
|
|
|
|
it("maps date presets to PascalCase API values", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, dateRange: "last-3-months" },
|
|
});
|
|
expect(params.datePreset).toBe("Last3Months");
|
|
});
|
|
|
|
it("includes dateFrom/dateTo only for Custom preset", () => {
|
|
const custom = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
dateRange: "custom",
|
|
customFrom: "2026-06-01",
|
|
customTo: "2026-06-30",
|
|
},
|
|
});
|
|
expect(custom.datePreset).toBe("Custom");
|
|
expect(custom.dateFrom).toBe("2026-06-01");
|
|
expect(custom.dateTo).toBe("2026-06-30");
|
|
|
|
const thisWeek = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
dateRange: "this-week",
|
|
customFrom: "2026-06-01",
|
|
customTo: "2026-06-30",
|
|
},
|
|
});
|
|
expect(thisWeek.datePreset).toBe("Custom");
|
|
expect(thisWeek.dateFrom).toBe("2000-01-01");
|
|
expect(thisWeek.dateTo).toBe("2099-12-31");
|
|
});
|
|
|
|
it("sends unscheduledOnly=true with no faked Custom date window (SH-338)", () => {
|
|
const withDefaultRange = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, unscheduledOnly: true },
|
|
});
|
|
expect(withDefaultRange.unscheduledOnly).toBe(true);
|
|
expect(withDefaultRange.datePreset).toBeUndefined();
|
|
expect(withDefaultRange.dateFrom).toBeUndefined();
|
|
expect(withDefaultRange.dateTo).toBeUndefined();
|
|
|
|
const overridingLastWeek = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
dateRange: "last-week",
|
|
unscheduledOnly: true,
|
|
},
|
|
});
|
|
expect(overridingLastWeek.unscheduledOnly).toBe(true);
|
|
expect(overridingLastWeek.datePreset).toBeUndefined();
|
|
expect(overridingLastWeek.dateFrom).toBeUndefined();
|
|
expect(overridingLastWeek.dateTo).toBeUndefined();
|
|
|
|
const record = buildBoardSearchParamsRecord(overridingLastWeek);
|
|
expect(record.unscheduledOnly).toBe(true);
|
|
expect(record).not.toHaveProperty("datePreset");
|
|
expect(record).not.toHaveProperty("dateFrom");
|
|
expect(record).not.toHaveProperty("dateTo");
|
|
});
|
|
|
|
it("keeps applying every other facet alongside unscheduledOnly", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
unscheduledOnly: true,
|
|
statuses: new Set(["Scheduled"]),
|
|
severities: new Set([4]),
|
|
pmTypes: new Set(["HVAC"]),
|
|
},
|
|
});
|
|
expect(params.unscheduledOnly).toBe(true);
|
|
expect(params.statuses).toEqual([LIFECYCLE_STATUS_API.Scheduled]);
|
|
expect(params.severities).toEqual([4]);
|
|
expect(params.pmTypes).toEqual(["HVAC"]);
|
|
});
|
|
|
|
it("omits unscheduledOnly and keeps the wide Custom default when the toggle is off", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, dateRange: "last-week" },
|
|
});
|
|
expect(params.unscheduledOnly).toBeUndefined();
|
|
expect(params.datePreset).toBe("LastWeek");
|
|
expect(buildBoardSearchParamsRecord(params)).not.toHaveProperty("unscheduledOnly");
|
|
});
|
|
|
|
it("maps Overdue-only advanced filter to overdue=true without types", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
types: new Set(["Overdue"]),
|
|
},
|
|
});
|
|
expect(params.overdue).toBe(true);
|
|
expect(params.types).toBeUndefined();
|
|
});
|
|
|
|
it("maps Overdue with real types as OR (types + overdue)", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
types: new Set(["PM", "Overdue"]),
|
|
},
|
|
});
|
|
expect(params.types).toEqual([WORK_ORDER_TYPE_API.PM]);
|
|
expect(params.overdue).toBe(true);
|
|
});
|
|
|
|
it("resolves vendor company names to vendorIds", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
vendorTechs: new Set(["Acme HVAC", "Unknown"]),
|
|
},
|
|
vendorIdsByCompanyName: new Map([["Acme HVAC", 12]]),
|
|
});
|
|
expect(params.vendorIds).toEqual([12]);
|
|
});
|
|
|
|
it("omits empty filter arrays", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS },
|
|
search: "x",
|
|
});
|
|
expect(params.search).toBeUndefined();
|
|
expect(params.sites).toBeUndefined();
|
|
expect(params.types).toBeUndefined();
|
|
expect(params.overdue).toBeUndefined();
|
|
expect(params.dispatchers).toBeUndefined();
|
|
expect(params.statuses).toBeUndefined();
|
|
expect(params.pmTypes).toBeUndefined();
|
|
expect(params.vendorIds).toBeUndefined();
|
|
expect(params.docStatuses).toBeUndefined();
|
|
expect(params.severities).toBeUndefined();
|
|
expect(params.rescheduled).toBeUndefined();
|
|
expect(params.carriedOver).toBeUndefined();
|
|
expect(params.addOn).toBeUndefined();
|
|
expect(params.flagColors).toBeUndefined();
|
|
expect(params.internalOnly).toBeUndefined();
|
|
expect(params.hasUplift).toBeUndefined();
|
|
expect(params.upliftStatuses).toBeUndefined();
|
|
expect(params.unscheduledOnly).toBeUndefined();
|
|
});
|
|
|
|
it("maps service types and completion docs onto board search params", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
pmTypes: new Set(["HVAC", "Electrical"]),
|
|
docs: new Set(["No", "Yes", "NN"]),
|
|
},
|
|
});
|
|
expect(params.pmTypes).toEqual(["HVAC", "Electrical"]);
|
|
expect(params.docStatuses).toEqual([
|
|
DOC_STATUS_API.No,
|
|
DOC_STATUS_API.Yes,
|
|
DOC_STATUS_API.NotNeeded,
|
|
]);
|
|
});
|
|
|
|
it("maps each extra advanced-filter facet alone onto board search params", () => {
|
|
const severity = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, severities: new Set([4]) },
|
|
});
|
|
expect(severity.severities).toEqual([4]);
|
|
expect(severity.carriedOver).toBeUndefined();
|
|
expect(severity.addOn).toBeUndefined();
|
|
expect(severity.pmTypes).toBeUndefined();
|
|
expect(severity.docStatuses).toBeUndefined();
|
|
|
|
const carried = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, carriedOver: true },
|
|
});
|
|
expect(carried.carriedOver).toBe(true);
|
|
expect(carried.severities).toBeUndefined();
|
|
|
|
const addOn = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, addOn: true },
|
|
});
|
|
expect(addOn.addOn).toBe(true);
|
|
expect(addOn.carriedOver).toBeUndefined();
|
|
|
|
const aveta = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, avetaOnly: true },
|
|
});
|
|
expect(aveta.avetaOnly).toBe(true);
|
|
|
|
const service = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, pmTypes: new Set(["HVAC"]) },
|
|
});
|
|
expect(service.pmTypes).toEqual(["HVAC"]);
|
|
|
|
const docs = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, docs: new Set(["NN"]) },
|
|
});
|
|
expect(docs.docStatuses).toEqual([DOC_STATUS_API.NotNeeded]);
|
|
});
|
|
|
|
it("combines extra advanced-filter facets with a status facet on the same request", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
severities: new Set([1]),
|
|
carriedOver: true,
|
|
addOn: true,
|
|
pmTypes: new Set(["HVAC"]),
|
|
docs: new Set(["Yes"]),
|
|
statuses: new Set(["Scheduled"]),
|
|
},
|
|
});
|
|
expect(params.severities).toEqual([1]);
|
|
expect(params.carriedOver).toBe(true);
|
|
expect(params.addOn).toBe(true);
|
|
expect(params.pmTypes).toEqual(["HVAC"]);
|
|
expect(params.docStatuses).toEqual([DOC_STATUS_API.Yes]);
|
|
expect(params.statuses).toEqual([LIFECYCLE_STATUS_API.Scheduled]);
|
|
const record = buildBoardSearchParamsRecord(params);
|
|
expect(record.severities).toEqual([1]);
|
|
expect(record.carriedOver).toBe(true);
|
|
expect(record.addOn).toBe(true);
|
|
expect(record.pmTypes).toEqual(["HVAC"]);
|
|
expect(record.docStatuses).toEqual([DOC_STATUS_API.Yes]);
|
|
expect(record.statuses).toEqual([LIFECYCLE_STATUS_API.Scheduled]);
|
|
});
|
|
|
|
it("maps severity and uplift facets onto board search params", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
severities: new Set([1, 3, 5]),
|
|
rescheduled: true,
|
|
carriedOver: true,
|
|
addOn: true,
|
|
flagColors: new Set(["#EF4444", "#3B82F6"]),
|
|
internalOnly: true,
|
|
hasUplift: true,
|
|
upliftStatuses: new Set(["pending", "approved"]),
|
|
},
|
|
});
|
|
expect(params.severities).toEqual([1, 3, 5]);
|
|
expect(params.rescheduled).toBe(true);
|
|
expect(params.carriedOver).toBe(true);
|
|
expect(params.addOn).toBe(true);
|
|
expect(params.flagColors).toEqual(["#EF4444", "#3B82F6"]);
|
|
expect(params.internalOnly).toBe(true);
|
|
expect(params.hasUplift).toBe(true);
|
|
expect(params.upliftStatuses).toEqual(["pending", "approved"]);
|
|
});
|
|
|
|
it("omits upliftStatuses when hasUplift is false", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: {
|
|
...EMPTY_ADVANCED_FILTERS,
|
|
hasUplift: false,
|
|
upliftStatuses: new Set(["pending"]),
|
|
},
|
|
});
|
|
expect(params.hasUplift).toBeUndefined();
|
|
expect(params.upliftStatuses).toBeUndefined();
|
|
});
|
|
|
|
it("caps pageSize at BOARD_SEARCH_MAX_PAGE_SIZE", () => {
|
|
const params = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS },
|
|
pageSize: 500,
|
|
});
|
|
expect(params.pageSize).toBe(200);
|
|
});
|
|
});
|
|
|
|
describe("buildBoardSearchParamsRecord", () => {
|
|
it("keeps arrays as repeated-key friendly values (not types[])", () => {
|
|
const record = buildBoardSearchParamsRecord({
|
|
weekStart: "2026-07-13",
|
|
types: [2, 6],
|
|
overdue: true,
|
|
dispatchers: ["guid-a", "__unassigned__"],
|
|
search: "",
|
|
});
|
|
expect(record.types).toEqual([2, 6]);
|
|
expect(record.overdue).toBe(true);
|
|
expect(record.dispatchers).toEqual(["guid-a", "__unassigned__"]);
|
|
expect(record.search).toBeUndefined();
|
|
expect(Object.keys(record)).not.toContain("types[]");
|
|
});
|
|
});
|
|
|
|
describe("toUnassignedQueueQueryParams", () => {
|
|
it("requests unassigned dispatchers with a Custom range, not the weekly board window", () => {
|
|
const params = toUnassignedQueueQueryParams(2);
|
|
const record = buildBoardSearchParamsRecord(params);
|
|
|
|
expect(params.page).toBe(2);
|
|
expect(params.pageSize).toBe(BOARD_SEARCH_MAX_PAGE_SIZE);
|
|
expect(params.dispatchers).toEqual(["__unassigned__"]);
|
|
expect(params.statuses).not.toContain(7);
|
|
expect(params.statuses).not.toContain(9);
|
|
expect(params.datePreset).toBe("Custom");
|
|
expect(params.dateFrom).toBe(UNASSIGNED_QUEUE_DATE_FROM);
|
|
expect(params.dateTo).toBe(UNASSIGNED_QUEUE_DATE_TO);
|
|
expect(record).not.toHaveProperty("weekStart");
|
|
expect(record).not.toHaveProperty("weekEnd");
|
|
});
|
|
});
|
|
|
|
describe("boardSearch query-key isolation", () => {
|
|
it("separates unscheduledOnly=true from the same facets without it", () => {
|
|
const without = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, rescheduled: true },
|
|
});
|
|
const withUnscheduled = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, rescheduled: true, unscheduledOnly: true },
|
|
});
|
|
|
|
expect(queryKeys.workOrders.boardSearch(without)).not.toEqual(
|
|
queryKeys.workOrders.boardSearch(withUnscheduled),
|
|
);
|
|
});
|
|
|
|
it("separates pages under the same unscheduledOnly filter", () => {
|
|
const base = toBoardSearchQueryParams({
|
|
filters: { ...EMPTY_ADVANCED_FILTERS, unscheduledOnly: true },
|
|
page: 0,
|
|
});
|
|
const pageOne = { ...base, page: 1 };
|
|
|
|
expect(queryKeys.workOrders.boardSearch(base)).not.toEqual(
|
|
queryKeys.workOrders.boardSearch(pageOne),
|
|
);
|
|
});
|
|
});
|