From c7aec49f0535a462b9a81883ced51d02de5dc497 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Thu, 10 Sep 2026 18:47:32 -0300 Subject: [PATCH] fix(work-orders): keep completion freeze on live registry edits --- .../comp-doc/comp-doc-details-section.tsx | 2 +- .../comp-doc/comp-doc-preview-document.tsx | 2 +- .../comp-doc-preview-service-info.tsx | 4 +- .../comp-doc/comp-doc-preview-summary.tsx | 8 +-- .../comp-doc-preview-vendor-safety.tsx | 10 +-- .../comp-doc/use-comp-doc-dialog.tsx | 6 +- .../tabs/slide-over-info-tab-display-view.tsx | 9 ++- .../list/table/cells/vendor-dialog-body.tsx | 40 +++++++----- .../list/table/cells/vendor-dialog.tsx | 4 +- .../list/table/wo-table-row-cells.tsx | 6 +- .../workorders/_hooks/use-comp-doc-dialog.ts | 6 +- .../mappers/work-order-table-row-mapper.ts | 1 + .../work-orders/types/completion-doc.ts | 6 ++ .../work-orders/types/work-order-table-row.ts | 2 +- .../work-orders/utils/completion-doc-html.ts | 6 +- .../work-orders/utils/completion-doc-site.ts | 64 +++++++++++++++---- .../utils/effective-completion-snapshot.ts | 13 ++++ .../vendor-dialog-frozen-company.test.tsx | 39 +++++++++++ .../utils/completion-doc-site.test.ts | 7 ++ .../effective-completion-snapshot.test.ts | 32 +++++++++- 20 files changed, 208 insertions(+), 59 deletions(-) diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx index 3a38f2e3..26ba67fd 100644 --- a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx @@ -46,7 +46,7 @@ export function CompDocDetailsSection({ - +
- Site ID — {row.site} + Site ID — {site.code} - Amazon.com Services LLC {row.site} + Amazon.com Services LLC {site.code} {site.street}, diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-summary.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-summary.tsx index c9716b8d..e07e3726 100644 --- a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-summary.tsx +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-summary.tsx @@ -73,10 +73,10 @@ export function CompDocPreviewSummary({ margin: "2px 0", }} > - Site ID — {row.site} + Site ID — {site.code} - Amazon.com Services LLC {row.site} + Amazon.com Services LLC {site.code} {site.street}, @@ -88,8 +88,8 @@ export function CompDocPreviewSummary({ Vendor / Technician:  - {row.tech || "—"} - {row.company ? ` · ${row.company}` : ""} + {site.tech || "—"} + {site.company ? ` · ${site.company}` : ""} ); diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-vendor-safety.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-vendor-safety.tsx index 807eca19..dec94e83 100644 --- a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-vendor-safety.tsx +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-vendor-safety.tsx @@ -1,18 +1,18 @@ import { Text } from "@/components/ui/text"; -import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import type { CompDocSiteInfo } from "@/domain/work-orders/types/completion-doc"; import { SAFETY_BULLETS } from "@/domain/work-orders/utils/completion-doc-procedures"; type CompDocPreviewVendorSafetyProps = { - row: WorkOrderTableRow; + site: CompDocSiteInfo; }; -export function CompDocPreviewVendorSafety({ row }: CompDocPreviewVendorSafetyProps) { +export function CompDocPreviewVendorSafety({ site }: CompDocPreviewVendorSafetyProps) { return ( <> Vendor / Technician:  - {row.tech || "—"} - {row.company ? ` · ${row.company}` : ""} + {site.tech || "—"} + {site.company ? ` · ${site.company}` : ""}
    diff --git a/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx index b6ff6147..b11f6464 100644 --- a/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx +++ b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx @@ -100,8 +100,9 @@ export function useCompDocDialog({ setEditDateCompleted, }); + const company = effectiveCompany(info); const amazonRep = effectivePoc(info).contacts[0]?.name || ""; - const seaRep = effectiveCompany(info).contactName || ""; + const seaRep = company.contactName || ""; const cfg = DOC_STATE_CFG[docState]; const isNotRequired = docState === "not-required"; const isGenerated = docState === "generated" || Boolean(signOffAttachment); @@ -114,6 +115,9 @@ export function useCompDocDialog({ dateCompleted: editDateCompleted, amazonRep, seaRep, + siteCode: site.code, + tech: company.contactName || "", + company: company.name || "", }; const { persist, syncDateIfChanged, refreshAuthoritativeState, handleSaveDraft, handleGenerate } = diff --git a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-display-view.tsx b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-display-view.tsx index 1109be91..f74bcf78 100644 --- a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-display-view.tsx +++ b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-display-view.tsx @@ -16,6 +16,7 @@ import { formatApptRange, parseApptTime } from "@/domain/work-orders/utils/wizar import { effectiveCompany, effectivePoc, + effectiveSite, } from "@/domain/work-orders/utils/effective-completion-snapshot"; import { DocBadge } from "./slide-over-doc-badge"; import { SlideOverInfoTabAssignedField } from "./slide-over-info-tab-assigned-field"; @@ -71,10 +72,11 @@ function SlideOverCompletedSite({ sites: LocationOption[]; }) { const primaryPoc = poc.contacts[0]; + const frozenSite = effectiveSite(source); return ( ({ name: contact.name ?? "", phone: contact.phone ?? "", + notes: contact.notes ?? "", }))} sites={sites} viewOnly - frozenSite={source.frozenSite} + frozenSite={frozenSite} onSave={() => undefined} /> ); diff --git a/src/app/(protected)/workorders/_components/list/table/cells/vendor-dialog-body.tsx b/src/app/(protected)/workorders/_components/list/table/cells/vendor-dialog-body.tsx index 4195702c..62e69494 100644 --- a/src/app/(protected)/workorders/_components/list/table/cells/vendor-dialog-body.tsx +++ b/src/app/(protected)/workorders/_components/list/table/cells/vendor-dialog-body.tsx @@ -98,21 +98,31 @@ export function VendorDialogBody({
    Technician (optional) - - } - searchPlaceholder="Search technician…" - options={technicianOptions.map((option) => ({ - key: option.key, - label: option.name, - }))} - onPick={onPick} - /> - {companyVendorForAdd != null && ( + {frozenCompany != null ? ( +
    + {frozenCompany.contactName?.trim() || tech || "—"} +
    + ) : ( + + } + searchPlaceholder="Search technician…" + options={technicianOptions.map((option) => ({ + key: option.key, + label: option.name, + }))} + onPick={onPick} + /> + )} + {frozenCompany == null && companyVendorForAdd != null && (
    ({ - name: contact.name ?? "", - phone: contact.phone ?? "", - }))} + additionalContacts={effectiveAdditionalContacts(row)} sites={sites} q={search} viewOnly={viewOnly} diff --git a/src/app/(protected)/workorders/_hooks/use-comp-doc-dialog.ts b/src/app/(protected)/workorders/_hooks/use-comp-doc-dialog.ts index 07328e0c..01025099 100644 --- a/src/app/(protected)/workorders/_hooks/use-comp-doc-dialog.ts +++ b/src/app/(protected)/workorders/_hooks/use-comp-doc-dialog.ts @@ -105,8 +105,9 @@ export function useCompDocDialog({ setEditAddress(value); }; + const company = effectiveCompany(row); const amazonRep = effectivePoc(row).contacts[0]?.name || ""; - const seaRep = effectiveCompany(row).contactName || ""; + const seaRep = company.contactName || ""; const cfg = DOC_STATE_CFG[docState]; const isNotRequired = docState === "not-required"; const isGenerated = docState === "generated"; @@ -120,6 +121,9 @@ export function useCompDocDialog({ dateCompleted: editDateCompleted, amazonRep, seaRep, + siteCode: site.code, + tech: company.contactName || "", + company: company.name || "", }; const previewSite = useMemo( diff --git a/src/domain/work-orders/mappers/work-order-table-row-mapper.ts b/src/domain/work-orders/mappers/work-order-table-row-mapper.ts index d7203502..d1d4afc3 100644 --- a/src/domain/work-orders/mappers/work-order-table-row-mapper.ts +++ b/src/domain/work-orders/mappers/work-order-table-row-mapper.ts @@ -314,6 +314,7 @@ function readFrozenPoc(item: Record): WorkOrderFrozenPoc | unde ? rawContacts.map(asRecord).map((contact) => ({ name: readString(contact, "name", "Name"), phone: readString(contact, "phone", "Phone"), + notes: readString(contact, "notes", "Notes"), })) : []; return { diff --git a/src/domain/work-orders/types/completion-doc.ts b/src/domain/work-orders/types/completion-doc.ts index 0c6377f5..e68685a4 100644 --- a/src/domain/work-orders/types/completion-doc.ts +++ b/src/domain/work-orders/types/completion-doc.ts @@ -14,6 +14,9 @@ export interface DocFields { dateCompleted: string; amazonRep: string; seaRep: string; + siteCode: string; + tech: string; + company: string; } export type DocState = "draft" | "generated" | "not-required"; @@ -31,12 +34,15 @@ export interface CompletionDocStateConfig { } export interface CompDocSiteInfo { + code: string; street: string; city: string; state: string; zip: string; label: string; fullAddress: string; + tech: string; + company: string; } export type CompDocSaveStatus = Exclude; diff --git a/src/domain/work-orders/types/work-order-table-row.ts b/src/domain/work-orders/types/work-order-table-row.ts index ad6bb06e..d362f931 100644 --- a/src/domain/work-orders/types/work-order-table-row.ts +++ b/src/domain/work-orders/types/work-order-table-row.ts @@ -38,7 +38,7 @@ export type WorkOrderFrozenCompany = { }; export type WorkOrderFrozenPoc = { - contacts: { name?: string; phone?: string }[]; + contacts: { name?: string; phone?: string; notes?: string }[]; notes?: string; }; diff --git a/src/domain/work-orders/utils/completion-doc-html.ts b/src/domain/work-orders/utils/completion-doc-html.ts index 87d3b30c..a1114090 100644 --- a/src/domain/work-orders/utils/completion-doc-html.ts +++ b/src/domain/work-orders/utils/completion-doc-html.ts @@ -75,11 +75,11 @@ export function buildDocHtml(

    ${esc(serviceTitle(row).toUpperCase())}

    WO# ${esc(row.woNumber)}

    -

    Site ID — ${esc(row.site)}

    -

    Amazon.com Services LLC ${esc(row.site)}

    +

    Site ID — ${esc(fields.siteCode)}

    +

    Amazon.com Services LLC ${esc(fields.siteCode)}

    ${esc(fields.street)},

    ${esc(cityStateZip)}

    -

    Vendor / Technician: ${esc(row.tech || "—")}${row.company ? ` · ${esc(row.company)}` : ""}

    +

    Vendor / Technician: ${esc(fields.tech || "—")}${fields.company ? ` · ${esc(fields.company)}` : ""}

      ${safety}
    ${completedBlock}

    ${esc(proc.heading)}:

    diff --git a/src/domain/work-orders/utils/completion-doc-site.ts b/src/domain/work-orders/utils/completion-doc-site.ts index 5ff4d52b..1546fdcc 100644 --- a/src/domain/work-orders/utils/completion-doc-site.ts +++ b/src/domain/work-orders/utils/completion-doc-site.ts @@ -1,20 +1,53 @@ import type { Location } from "@/domain/locations/types/location"; import type { CompDocSiteInfo } from "@/domain/work-orders/types/completion-doc"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; -import { effectiveSite } from "@/domain/work-orders/utils/effective-completion-snapshot"; +import { + effectiveCompany, + effectiveSite, +} from "@/domain/work-orders/utils/effective-completion-snapshot"; + +type CompDocAddress = Pick< + CompDocSiteInfo, + "street" | "city" | "state" | "zip" | "label" | "fullAddress" +>; + +type CompDocIdentityRow = Pick & + Partial>; function buildLabel(city?: string, state?: string): string { return [city, state].filter(Boolean).join(", "); } -function toCompDocSite(street: string, city: string, state: string, zip: string): CompDocSiteInfo { +function toCompDocAddress( + street: string, + city: string, + state: string, + zip: string, +): CompDocAddress { const label = buildLabel(city, state); const fullAddress = [street, label, zip].filter(Boolean).join(", ") || "—"; return { street, city, state, zip, label, fullAddress }; } -function resolveLiveLocationSite(location: Location): CompDocSiteInfo { - return toCompDocSite( +function withCompDocIdentity(address: CompDocAddress, row: CompDocIdentityRow): CompDocSiteInfo { + const frozen = effectiveSite(row); + const company = effectiveCompany({ + status: row.status, + company: row.company ?? "", + tech: row.tech ?? "", + techPhone: row.techPhone ?? "", + frozenCompany: row.frozenCompany, + }); + return { + ...address, + code: frozen?.code ?? row.site, + tech: company.contactName ?? "", + company: company.name ?? "", + }; +} + +function resolveLiveLocationSite(location: Location): CompDocAddress { + return toCompDocAddress( location.address ?? "", location.city ?? "", location.state ?? "", @@ -22,30 +55,33 @@ function resolveLiveLocationSite(location: Location): CompDocSiteInfo { ); } -function resolveRowLocationSite(row: Pick): CompDocSiteInfo { +function resolveRowLocationSite(row: Pick): CompDocAddress { const locationParts = row.location.split(",").map((part) => part.trim()); const city = locationParts[0] ?? ""; const state = locationParts[1] ?? ""; const label = buildLabel(city, state); const street = row.location && !label ? row.location : ""; - return toCompDocSite(street, city, state, ""); + return toCompDocAddress(street, city, state, ""); } export function resolveCompDocSite( - row: Pick, + row: CompDocIdentityRow, location?: Location | null, ): CompDocSiteInfo { const frozen = effectiveSite(row); if (frozen != null) { - return toCompDocSite( - frozen.address ?? "", - frozen.city ?? "", - frozen.state ?? "", - frozen.zip ?? "", + return withCompDocIdentity( + toCompDocAddress( + frozen.address ?? "", + frozen.city ?? "", + frozen.state ?? "", + frozen.zip ?? "", + ), + row, ); } if (location?.address || location?.city) { - return resolveLiveLocationSite(location); + return withCompDocIdentity(resolveLiveLocationSite(location), row); } - return resolveRowLocationSite(row); + return withCompDocIdentity(resolveRowLocationSite(row), row); } diff --git a/src/domain/work-orders/utils/effective-completion-snapshot.ts b/src/domain/work-orders/utils/effective-completion-snapshot.ts index 59dc9827..b552b39c 100644 --- a/src/domain/work-orders/utils/effective-completion-snapshot.ts +++ b/src/domain/work-orders/utils/effective-completion-snapshot.ts @@ -1,3 +1,4 @@ +import type { WorkOrderAdditionalContact } from "@/domain/work-orders/types/work-order-additional-contact"; import type { WorkOrderFrozenCompany, WorkOrderFrozenPoc, @@ -48,8 +49,20 @@ export function effectivePoc(row: PocSnapshotRow): WorkOrderFrozenPoc { ...(row.additionalContacts ?? []).map((contact) => ({ name: contact.name, phone: contact.phone, + notes: contact.notes, })), ], notes: row.pocNotes, }; } + +export function effectiveAdditionalContacts(row: PocSnapshotRow): WorkOrderAdditionalContact[] { + if (row.status === "Completed" && row.frozenPoc != null) { + return row.frozenPoc.contacts.slice(1).map((contact) => ({ + name: contact.name ?? "", + phone: contact.phone ?? "", + notes: contact.notes ?? "", + })); + } + return row.additionalContacts ?? []; +} diff --git a/src/test/app/(protected)/workorders/vendor-dialog-frozen-company.test.tsx b/src/test/app/(protected)/workorders/vendor-dialog-frozen-company.test.tsx index afe6f231..413eb2f1 100644 --- a/src/test/app/(protected)/workorders/vendor-dialog-frozen-company.test.tsx +++ b/src/test/app/(protected)/workorders/vendor-dialog-frozen-company.test.tsx @@ -1,5 +1,6 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; +import { VendorDialogBody } from "@/app/(protected)/workorders/_components/list/table/cells/vendor-dialog-body"; import { VendorDialogCompanyCard } from "@/app/(protected)/workorders/_components/list/table/cells/vendor-dialog-company-card"; import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order"; @@ -48,4 +49,42 @@ describe("VendorDialogCompanyCard completion snapshot", () => { expect(screen.getByText("200 Vendor Ave, Chicago, IL, 60602")).toBeInTheDocument(); expect(screen.queryByText("900 Live St")).not.toBeInTheDocument(); }); + + it("shows the frozen technician instead of a renamed live roster contact", () => { + render( + undefined} + onPick={() => undefined} + onPhoneChange={() => undefined} + onNotesChange={() => undefined} + onTechnicianCreated={() => undefined} + />, + ); + + expect(screen.getAllByText("Frozen Tech").length).toBeGreaterThan(0); + expect(screen.queryByText("Live Contact")).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /\+ add technician/i })).not.toBeInTheDocument(); + }); }); diff --git a/src/test/domain/work-orders/utils/completion-doc-site.test.ts b/src/test/domain/work-orders/utils/completion-doc-site.test.ts index 8abeae5d..4a2cb2f9 100644 --- a/src/test/domain/work-orders/utils/completion-doc-site.test.ts +++ b/src/test/domain/work-orders/utils/completion-doc-site.test.ts @@ -8,12 +8,16 @@ describe("resolveCompDocSite", () => { site: "BK5", location: "Live City, WI", status: "Completed", + company: "Live Co", + tech: "Live Tech", frozenSite: { + code: "FROZEN", address: "100 Frozen Ave", city: "Chicago", state: "IL", zip: "60601", }, + frozenCompany: { name: "Frozen Co", contactName: "Frozen Tech" }, }, { address: "900 Live St", @@ -24,12 +28,15 @@ describe("resolveCompDocSite", () => { ); expect(result).toEqual({ + code: "FROZEN", street: "100 Frozen Ave", city: "Chicago", state: "IL", zip: "60601", label: "Chicago, IL", fullAddress: "100 Frozen Ave, Chicago, IL, 60601", + tech: "Frozen Tech", + company: "Frozen Co", }); }); diff --git a/src/test/domain/work-orders/utils/effective-completion-snapshot.test.ts b/src/test/domain/work-orders/utils/effective-completion-snapshot.test.ts index 688e886f..5044a09a 100644 --- a/src/test/domain/work-orders/utils/effective-completion-snapshot.test.ts +++ b/src/test/domain/work-orders/utils/effective-completion-snapshot.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { + effectiveAdditionalContacts, effectiveCompany, effectivePoc, effectiveSite, @@ -61,15 +62,42 @@ describe("effective completion snapshot", () => { pocName: "Live POC", pocPhone: "222", pocNotes: "live", - additionalContacts: [{ name: "Backup", phone: "333" }], + additionalContacts: [{ name: "Backup", phone: "333", notes: "gate hours" }], }), ).toEqual({ contacts: [ { name: "Live POC", phone: "222" }, - { name: "Backup", phone: "333" }, + { name: "Backup", phone: "333", notes: "gate hours" }, ], notes: "live", }); + expect( + effectiveAdditionalContacts({ + status: "Scheduled", + pocName: "Live POC", + pocPhone: "222", + pocNotes: "live", + additionalContacts: [{ name: "Backup", phone: "333", notes: "gate hours" }], + }), + ).toEqual([{ name: "Backup", phone: "333", notes: "gate hours" }]); + }); + + it("keeps extra-contact notes on the open work-order path", () => { + expect( + effectiveAdditionalContacts({ + status: "Scheduled", + pocName: "Primary", + pocPhone: "111", + pocNotes: "", + additionalContacts: [{ name: "Backup", phone: "333", notes: "gate hours" }], + frozenPoc: { + contacts: [ + { name: "Frozen", phone: "000" }, + { name: "Frozen Extra", phone: "999", notes: "ignored while open" }, + ], + }, + }), + ).toEqual([{ name: "Backup", phone: "333", notes: "gate hours" }]); }); it("treats missing additional contacts as an empty live contact list", () => {