mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 10:53:13 +00:00
fix(work-orders): require vendor company not technician to complete (SH-183)
This commit is contained in:
parent
772d7f94df
commit
1554cf30d0
6 changed files with 67 additions and 8 deletions
|
|
@ -26,6 +26,7 @@ function rowClosabilityGaps(wo: WorkOrderTableRow) {
|
|||
dispatcherId: wo.dispatcherId,
|
||||
dispatcherName: wo.dispatcherName,
|
||||
company: wo.company,
|
||||
vendorId: wo.vendorId ?? "",
|
||||
tech: wo.tech,
|
||||
completedDate: wo.completedDate ?? "",
|
||||
woNumber: wo.woNumber,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ export type WoClosabilityInput = {
|
|||
dispatcherName: string;
|
||||
/** Company / vendor assignment — required for Completed (SH-183). */
|
||||
company: string;
|
||||
/** Board vendor id; assignment is valid with vendorId even when company label is empty. */
|
||||
vendorId: string;
|
||||
/** Technician is optional for closability; modeled separately from company. */
|
||||
tech: string;
|
||||
/** Work completed date (API CompletedDate). Empty until set. */
|
||||
|
|
@ -57,8 +59,8 @@ function hasDispatcher(row: Pick<WoClosabilityInput, "dispatcherId" | "dispatche
|
|||
return Boolean(row.dispatcherId.trim() || row.dispatcherName.trim());
|
||||
}
|
||||
|
||||
function hasCompany(company: string): boolean {
|
||||
return Boolean(company.trim());
|
||||
function hasAssignedCompany(row: Pick<WoClosabilityInput, "company" | "vendorId">): boolean {
|
||||
return Boolean((row.company ?? "").trim() || (row.vendorId ?? "").trim());
|
||||
}
|
||||
|
||||
function hasService(pm: string): boolean {
|
||||
|
|
@ -80,7 +82,7 @@ export function getClosabilityGaps(row: WoClosabilityInput): string[] {
|
|||
if (!hasDispatcher(row)) {
|
||||
gaps.push("Dispatcher");
|
||||
}
|
||||
if (!hasCompany(row.company)) {
|
||||
if (!hasAssignedCompany(row)) {
|
||||
gaps.push("Company");
|
||||
}
|
||||
if (!row.completedDate.trim()) {
|
||||
|
|
@ -143,6 +145,7 @@ export function wizardDraftToClosabilityInput(draft: WorkOrderWizardDraft): WoCl
|
|||
dispatcherId: draft.assigneeId,
|
||||
dispatcherName: draft.assigneeName,
|
||||
company: draft.vendorName,
|
||||
vendorId: draft.vendorId ?? "",
|
||||
tech: "",
|
||||
completedDate: "",
|
||||
woNumber: draft.woNumber,
|
||||
|
|
@ -159,13 +162,15 @@ export function wizardDraftToClosabilityInput(draft: WorkOrderWizardDraft): WoCl
|
|||
* as WorkOrderType. `problem` is the same service field the board maps into `pm`.
|
||||
*/
|
||||
export function detailToClosabilityInput(wo: WorkOrderDetail): WoClosabilityInput {
|
||||
const vendorName = wo.dispatches.find((dispatch) =>
|
||||
Boolean(dispatch.vendorName.trim()),
|
||||
)?.vendorName;
|
||||
const assigned = wo.dispatches.find((dispatch) => {
|
||||
const vendorId = String(dispatch.vendorId ?? "").trim();
|
||||
return Boolean(dispatch.vendorName.trim() || vendorId);
|
||||
});
|
||||
return {
|
||||
dispatcherId: "",
|
||||
dispatcherName: wo.assignedTo,
|
||||
company: vendorName ?? "",
|
||||
company: assigned?.vendorName ?? "",
|
||||
vendorId: assigned ? String(assigned.vendorId ?? "").trim() : "",
|
||||
tech: "",
|
||||
completedDate: wo.completedDate,
|
||||
woNumber: wo.workOrderNumber || wo.internalWONumber,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const incompleteRow = {
|
|||
dispatcherId: "",
|
||||
dispatcherName: "",
|
||||
company: "",
|
||||
vendorId: "",
|
||||
tech: "",
|
||||
completedDate: "",
|
||||
woNumber: "",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const pastDueRow = {
|
|||
dispatcherId: "",
|
||||
dispatcherName: "",
|
||||
company: "",
|
||||
vendorId: "",
|
||||
tech: "",
|
||||
completedDate: "",
|
||||
woNumber: "",
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ describe("StatusCell pending uplift closability", () => {
|
|||
|
||||
renderWithProviders(
|
||||
<StatusCell
|
||||
wo={makeRow({ company: "" })}
|
||||
wo={makeRow({ company: "", vendorId: "" })}
|
||||
onChangeStatus={onChangeStatus}
|
||||
onOpenSlideOver={vi.fn()}
|
||||
/>,
|
||||
|
|
@ -141,6 +141,24 @@ describe("StatusCell pending uplift closability", () => {
|
|||
expect(onChangeStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows Completed when vendorId is set even if company label is empty (SH-183)", () => {
|
||||
const onChangeStatus = vi.fn();
|
||||
|
||||
renderWithProviders(
|
||||
<StatusCell
|
||||
wo={makeRow({ company: "", vendorId: "7", tech: "" })}
|
||||
onChangeStatus={onChangeStatus}
|
||||
onOpenSlideOver={vi.fn()}
|
||||
/>,
|
||||
{ withAuth: false },
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: /completed/i }));
|
||||
|
||||
expect(onChangeStatus).toHaveBeenCalledWith("Completed");
|
||||
});
|
||||
|
||||
it("allows Completed when technician is empty if company is set (SH-183)", () => {
|
||||
const onChangeStatus = vi.fn();
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ function makeInput(overrides: Partial<WoClosabilityInput> = {}): WoClosabilityIn
|
|||
dispatcherId: "u1",
|
||||
dispatcherName: "Alex",
|
||||
company: "Acme HVAC",
|
||||
vendorId: "",
|
||||
tech: "Sam",
|
||||
completedDate: "2026-07-01",
|
||||
woNumber: "1001",
|
||||
|
|
@ -85,6 +86,16 @@ describe("getClosabilityGaps", () => {
|
|||
expect(getClosabilityGaps(makeInput({ company: " ", tech: "Sam" }))).toEqual(["Company"]);
|
||||
});
|
||||
|
||||
it("allows Completed when vendorId is set and company/technician are empty (SH-183)", () => {
|
||||
expect(getClosabilityGaps(makeInput({ company: "", vendorId: "7", tech: "" }))).toEqual([]);
|
||||
});
|
||||
|
||||
it("blocks Completed when both company and vendorId are empty even with a technician", () => {
|
||||
expect(getClosabilityGaps(makeInput({ company: "", vendorId: " ", tech: "Sam" }))).toEqual([
|
||||
"Company",
|
||||
]);
|
||||
});
|
||||
|
||||
it("lists field gaps before uplift and document gaps", () => {
|
||||
expect(
|
||||
getClosabilityGaps(
|
||||
|
|
@ -188,6 +199,7 @@ describe("wizardDraftToClosabilityInput", () => {
|
|||
dispatcherId: "u1",
|
||||
dispatcherName: "Alice",
|
||||
company: "ACME",
|
||||
vendorId: "",
|
||||
tech: "",
|
||||
completedDate: "",
|
||||
woNumber: "20260623001",
|
||||
|
|
@ -221,6 +233,7 @@ describe("detailToClosabilityInput", () => {
|
|||
const input = detailToClosabilityInput(detail);
|
||||
expect(input.dispatcherName).toBe("Alice");
|
||||
expect(input.company).toBe("ACME Tech");
|
||||
expect(input.vendorId).toBe("");
|
||||
expect(input.woNumber).toBe("20260623001");
|
||||
expect(input.pm).toBe("HVAC PM");
|
||||
expect(getClosabilityGaps(input)).toEqual([]);
|
||||
|
|
@ -277,6 +290,26 @@ describe("detailToClosabilityInput", () => {
|
|||
|
||||
expect(getClosabilityGaps(detailToClosabilityInput(detail))).toEqual(["At least 1 photo"]);
|
||||
});
|
||||
|
||||
it("treats vendorId without vendorName as assigned company (SH-183)", () => {
|
||||
const detail = {
|
||||
assignedTo: "Alice",
|
||||
completedDate: "2026-07-15",
|
||||
workOrderNumber: "20260623001",
|
||||
internalWONumber: "SH-1",
|
||||
problem: "HVAC PM",
|
||||
source: "procurement-integration",
|
||||
type: "",
|
||||
docStatus: "",
|
||||
mediaCount: 0,
|
||||
dispatches: [{ vendorName: "", vendorId: 45 }],
|
||||
} as WorkOrderDetail;
|
||||
|
||||
const input = detailToClosabilityInput(detail);
|
||||
expect(input.company).toBe("");
|
||||
expect(input.vendorId).toBe("45");
|
||||
expect(getClosabilityGaps(input)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readDetailWoType", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue