diff --git a/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-new.png b/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-new.png index e7ee92cb..9edaccd5 100644 Binary files a/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-new.png and b/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-new.png differ diff --git a/src/app/(protected)/workorders/_components/list/table/build-inline-draft-create-input.ts b/src/app/(protected)/workorders/_components/list/table/build-inline-draft-create-input.ts index 169f062e..5f911bd8 100644 --- a/src/app/(protected)/workorders/_components/list/table/build-inline-draft-create-input.ts +++ b/src/app/(protected)/workorders/_components/list/table/build-inline-draft-create-input.ts @@ -19,7 +19,6 @@ export function buildInlineDraftCreateInput( locationId: String(draft.locationId || ""), locationName, siteCode: location ? resolveSiteCode(location) : resolveSiteCode({ name: locationName }), - customer: draft.customer.trim(), woNumber: draft.woNumber, type: draft.type || "PM", severity: draft.severity, diff --git a/src/app/(protected)/workorders/_components/list/table/cells/build-site-dialog-patch.ts b/src/app/(protected)/workorders/_components/list/table/cells/build-site-dialog-patch.ts index 1b9e23c3..3fe930cd 100644 --- a/src/app/(protected)/workorders/_components/list/table/cells/build-site-dialog-patch.ts +++ b/src/app/(protected)/workorders/_components/list/table/cells/build-site-dialog-patch.ts @@ -8,7 +8,6 @@ type BuildSiteDialogPatchArgs = { code: string; locId: string; selected: LocationOption | undefined; - customer?: string; pocName: string; pocPhone: string; pocNotes: string; @@ -21,7 +20,6 @@ export function buildSiteDialogPatch({ code, locId, selected, - customer, pocName, pocPhone, pocNotes, @@ -33,7 +31,6 @@ export function buildSiteDialogPatch({ site: code || (selected ? resolveSiteCode(selected) : ""), location: selected ? extractLocationLabel(selected) : "", locationId: locId, - ...(customer !== undefined ? { customer } : {}), pocName, pocPhone, pocNotes, diff --git a/src/app/(protected)/workorders/_components/list/table/cells/site-cell.tsx b/src/app/(protected)/workorders/_components/list/table/cells/site-cell.tsx index 04077833..4c40ae06 100644 --- a/src/app/(protected)/workorders/_components/list/table/cells/site-cell.tsx +++ b/src/app/(protected)/workorders/_components/list/table/cells/site-cell.tsx @@ -11,7 +11,6 @@ type SiteCellProps = { value: string; location: string; locationId: string | number; - customer?: string; city?: string; state?: string; pocName?: string; @@ -30,7 +29,6 @@ export function SiteCell({ value, location, locationId, - customer, city, state, pocName, @@ -97,7 +95,6 @@ export function SiteCell({ onOpenChange={setOpen} value={value} locationId={locationId} - customer={customer} pocName={pocName} pocPhone={pocPhone} pocNotes={pocNotes} diff --git a/src/app/(protected)/workorders/_components/list/table/cells/site-dialog-types.ts b/src/app/(protected)/workorders/_components/list/table/cells/site-dialog-types.ts index 956d54ba..499af6aa 100644 --- a/src/app/(protected)/workorders/_components/list/table/cells/site-dialog-types.ts +++ b/src/app/(protected)/workorders/_components/list/table/cells/site-dialog-types.ts @@ -4,7 +4,6 @@ export type SitePatch = { site: string; location: string; locationId: string | number; - customer?: string; pocName: string; pocPhone: string; pocNotes: string; diff --git a/src/app/(protected)/workorders/_components/list/table/cells/site-dialog.tsx b/src/app/(protected)/workorders/_components/list/table/cells/site-dialog.tsx index e68dc5c7..0843e331 100644 --- a/src/app/(protected)/workorders/_components/list/table/cells/site-dialog.tsx +++ b/src/app/(protected)/workorders/_components/list/table/cells/site-dialog.tsx @@ -1,4 +1,4 @@ -import { useContext, type MouseEvent } from "react"; +import type { MouseEvent } from "react"; import { MapPin } from "lucide-react"; import { Dialog, DialogContent, DialogTitle } from "@mui/material"; import { WizardFieldSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-field-select"; @@ -9,12 +9,9 @@ import type { LocationOption } from "@/domain/work-orders/types/work-order"; import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label"; import { SiteDialogDetailPanel } from "@/app/(protected)/workorders/_components/list/table/cells/site-dialog-detail-panel"; import { SiteDialogFooter } from "@/app/(protected)/workorders/_components/list/table/cells/site-dialog-footer"; -import { WizardStepLocationAccountSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-account-select"; import { SiteDialogPocSection } from "@/app/(protected)/workorders/_components/list/table/cells/site-dialog-poc-section"; import type { SitePatch } from "@/app/(protected)/workorders/_components/list/table/cells/site-dialog-types"; import { useSiteDialogState } from "@/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-state"; -import { isOrgWideScope } from "@/lib/auth/org-scope"; -import { AuthContext } from "@/providers/auth-context"; export type { SitePatch }; @@ -23,7 +20,6 @@ type SiteDialogProps = { onOpenChange: (open: boolean) => void; value: string; locationId: string | number; - customer?: string; pocName?: string; pocPhone?: string; pocNotes?: string; @@ -39,7 +35,6 @@ export function SiteDialog({ onOpenChange, value, locationId, - customer = "", pocName = "", pocPhone = "", pocNotes = "", @@ -49,20 +44,17 @@ export function SiteDialog({ viewOnly = false, onSave, }: SiteDialogProps) { - const requireAccount = isOrgWideScope(useContext(AuthContext)?.user); const s = useSiteDialogState({ open, onOpenChange, value, locationId, - customer, pocName, pocPhone, pocNotes, additionalContacts, sites, createMode, - requireAccount, onSave, }); @@ -89,13 +81,6 @@ export function SiteDialog({
- {Boolean(createMode && requireAccount) && ( - - )}
Site void; -}; - -export function useSiteDialogAccountField({ - open, - customer, - createMode, - requireAccount = true, - dirty, - onDirty, -}: UseSiteDialogAccountFieldArgs) { - const [accountName, setAccountNameState] = useState(customer); - - useEffect(() => { - if (!open || dirty) { - return; - } - setAccountNameState(customer); - }, [open, dirty, customer]); - - return { - accountName, - accountMissing: createMode && requireAccount && !accountName.trim(), - customerPatch: createMode && requireAccount ? { customer: accountName } : {}, - setAccountName: (next: string) => { - onDirty(); - setAccountNameState(next); - }, - }; -} diff --git a/src/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-state.ts b/src/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-state.ts index ca321241..c9d16c1e 100644 --- a/src/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-state.ts +++ b/src/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-state.ts @@ -1,7 +1,6 @@ import { useEffect } from "react"; import { buildSiteDialogPatch } from "@/app/(protected)/workorders/_components/list/table/cells/build-site-dialog-patch"; import type { SitePatch } from "@/app/(protected)/workorders/_components/list/table/cells/site-dialog-types"; -import { useSiteDialogAccountField } from "@/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-account-field"; import { useSiteDialogFormFields } from "@/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-form-fields"; import { useLocationDetail } from "@/domain/locations/use-cases/use-location-detail"; import { formatLocationAddressPreview } from "@/domain/locations/mappers/location-mapper"; @@ -15,14 +14,12 @@ type UseSiteDialogStateArgs = { onOpenChange: (open: boolean) => void; value: string; locationId: string | number; - customer?: string; pocName: string; pocPhone: string; pocNotes: string; additionalContacts: WorkOrderAdditionalContact[]; sites: LocationOption[]; createMode: boolean; - requireAccount?: boolean; onSave: (patch: SitePatch) => void; }; @@ -31,14 +28,12 @@ export function useSiteDialogState({ onOpenChange, value, locationId, - customer = "", pocName, pocPhone, pocNotes, additionalContacts = [], sites, createMode, - requireAccount = true, onSave, }: UseSiteDialogStateArgs) { const fields = useSiteDialogFormFields({ @@ -52,16 +47,7 @@ export function useSiteDialogState({ sites, createMode, }); - const account = useSiteDialogAccountField({ - open, - customer, - createMode, - requireAccount, - dirty: fields.dirty, - onDirty: fields.markDirty, - }); const { locId, pocFilledFor, pn, pp, setPn, setPp, setPocFilledFor, siteMissing } = fields; - const { accountMissing } = account; const { data: locationDetail, isLoading: locationDetailLoading, @@ -70,7 +56,7 @@ export function useSiteDialogState({ const addressPreview = locationDetail ? formatLocationAddressPreview(locationDetail) : ""; const pocMissing = !pn.trim() || !pp.trim(); - const canConfirm = !siteMissing && (!createMode || (!pocMissing && !accountMissing)); + const canConfirm = !siteMissing && (!createMode || !pocMissing); useEffect(() => { if (!open || !locId || !locationDetail || pocFilledFor === locId) { @@ -91,7 +77,6 @@ export function useSiteDialogState({ code: fields.code, locId: fields.locId, selected: fields.selected, - ...account.customerPatch, pocName: fields.pn, pocPhone: fields.pp, pocNotes: fields.notes, @@ -106,8 +91,6 @@ export function useSiteDialogState({ return { locId: fields.locId, code: fields.code, - accountName: account.accountName, - accountMissing: account.accountMissing, selected: fields.selected, locationDetail, locationDetailLoading, @@ -120,7 +103,6 @@ export function useSiteDialogState({ showErrors: fields.showErrors, siteMissing: fields.siteMissing, handlePick: fields.handlePick, - setAccountName: account.setAccountName, attemptSave, setPn: fields.setPnDirty, setPp: fields.setPpDirty, diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-inline-identity-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-inline-identity-cells.tsx index f650a040..47ca7b46 100644 --- a/src/app/(protected)/workorders/_components/list/table/wo-table-inline-identity-cells.tsx +++ b/src/app/(protected)/workorders/_components/list/table/wo-table-inline-identity-cells.tsx @@ -50,7 +50,6 @@ export function WoTableInlineIdentityCells({ value={draft.site} location={draft.location} locationId={draft.locationId} - customer={draft.customer} city={sitePlace?.city} state={sitePlace?.state} pocName={draft.pocName} @@ -64,7 +63,6 @@ export function WoTableInlineIdentityCells({ site: p.site, location: p.location, locationId: p.locationId, - ...(p.customer !== undefined ? { customer: p.customer } : {}), pocName: p.pocName, pocPhone: p.pocPhone, pocNotes: p.pocNotes, diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-inline-row.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-inline-row.tsx index 9a0d3a8a..79432d66 100644 --- a/src/app/(protected)/workorders/_components/list/table/wo-table-inline-row.tsx +++ b/src/app/(protected)/workorders/_components/list/table/wo-table-inline-row.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef, useState, type RefObject } from "react"; +import { useRef, useState, type RefObject } from "react"; import { Flag } from "lucide-react"; import { WorkOrderCompleteDialog } from "@/app/(protected)/workorders/_components/list/work-order-complete-dialog"; @@ -21,9 +21,6 @@ import { inlineCreateSaveHint, inlineCreateSiteError, } from "@/domain/work-orders/utils/get-inline-create-missing"; -import { isOrgWideScope } from "@/lib/auth/org-scope"; -import { AuthContext } from "@/providers/auth-context"; - import { WO_TABLE_ROW_HEIGHT } from "./wo-table-cols"; import { WoTableInlineRowCells } from "./wo-table-inline-row-cells"; import { WoTableInlineRowActions } from "./wo-table-inline-row-actions"; @@ -73,18 +70,14 @@ export function WoTableInlineRow({ onDuplicateFound, }: WoTableInlineRowProps) { const weekDefault = draftDayKey === UNSCHEDULED_DAY_KEY || draftDayKey === UNASSIGNED_DAY_KEY; - const missing = getInlineCreateMissing(draft, { - requireAccount: isOrgWideScope(useContext(AuthContext)?.user), - }); + const missing = getInlineCreateMissing(draft); const canSave = !saving && missing.length === 0; const [showErrors, setShowErrors] = useState(false); const [confirmComplete, setConfirmComplete] = useState(false); const siteCellRef = useRef(null); const woCellRef = useRef(null); - const siteCellMissing = missing.some( - (f) => f === "customer" || f === "site" || f === "pocName" || f === "pocPhone", - ); + const siteCellMissing = missing.some((f) => f === "site" || f === "pocName" || f === "pocPhone"); const siteShowError = showErrors && siteCellMissing; const woShowError = showErrors && missing.includes("woNumber"); diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-footer.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-footer.tsx index 9d6a2207..12ea5609 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-footer.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-footer.tsx @@ -1,18 +1,13 @@ -import { useContext } from "react"; import { Check, ChevronLeft, ChevronRight, Lightbulb } from "lucide-react"; import { Button } from "@/components/ui/button"; import { WIZARD_STEPS, - canContinueWizardStep1, canCreateWorkOrderFromWizard, isWizardStepValid, - isWizardWoNumberValid, + wizardStep1ContinueHint, wizardStep2ContinueHint, - type WizardAccountGate, type WorkOrderWizardDraft, } from "@/domain/work-orders/types/work-order-wizard"; -import { isOrgWideScope } from "@/lib/auth/org-scope"; -import { AuthContext } from "@/providers/auth-context"; type WizardFooterProps = { step: number; @@ -25,19 +20,12 @@ type WizardFooterProps = { isCreating: boolean; }; -function stepHint( - step: number, - draft: WorkOrderWizardDraft, - gate: WizardAccountGate, -): string | null { - if (step === 1 && !canContinueWizardStep1(draft)) { - if (!isWizardWoNumberValid(draft.woNumber)) { - return "Enter an alphanumeric WO number, or leave blank for SH generation"; - } - return "Select severity to continue"; +function stepHint(step: number, draft: WorkOrderWizardDraft): string | null { + if (step === 1) { + return wizardStep1ContinueHint(draft); } if (step === 2) { - return wizardStep2ContinueHint(draft, gate); + return wizardStep2ContinueHint(draft); } return null; } @@ -52,10 +40,9 @@ export function WizardFooter({ onAttemptContinue, isCreating, }: WizardFooterProps) { - const accountGate = { requireAccount: isOrgWideScope(useContext(AuthContext)?.user) }; - const stepValid = isWizardStepValid(step, draft, accountGate); - const canCreate = canCreateWorkOrderFromWizard(draft, accountGate); - const hint = stepHint(step, draft, accountGate); + const stepValid = isWizardStepValid(step, draft); + const canCreate = canCreateWorkOrderFromWizard(draft); + const hint = stepHint(step, draft); return (
void; - showError?: boolean; -}; - -function accountOptions(names: string[], selected: string) { - const withSelected = selected && !names.includes(selected) ? [selected, ...names] : names; - return withSelected.map((name) => ({ key: name, label: name })); -} - -export function WizardStepLocationAccountSelect({ - customer, - onCustomerChange, - showError = false, -}: WizardStepLocationAccountSelectProps) { - const orgWide = isOrgWideScope(useContext(AuthContext)?.user); - const [search, setSearch] = useState(""); - const debouncedSearch = useDebounce(search, 300); - const { data } = useAccountsList({ page: 1, pageSize: 50, search: debouncedSearch }, orgWide); - const names = (data?.items ?? []).map((account) => account.name.trim()).filter(Boolean); - const selected = customer.trim(); - - if (!orgWide) { - return null; - } - - return ( -
- Account - - } - searchPlaceholder="Search account name…" - options={accountOptions(names, selected)} - onPick={onCustomerChange} - onSearchChange={setSearch} - /> - - Account is required to create a work order. - -
- ); -} diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx index 17425052..2ef9f971 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx @@ -1,8 +1,6 @@ -import { Text } from "@/components/ui/text"; import type { LocationOption } from "@/domain/work-orders/types/work-order"; import type { Location } from "@/domain/locations/types/location"; import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard"; -import { WizardStepLocationAccountSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-account-select"; import { WizardStepLocationPocFields } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-poc-fields"; import { WizardStepLocationServiceSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-service-select"; import { WizardStepLocationSiteDetails } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-site-details"; @@ -45,11 +43,6 @@ export function WizardStepLocationService({ return (
- onPatch({ customer })} - /> - - {!draft.locationId && !draft.pm && ( - - Both are optional — leave blank to fill in later from the dispatch board. - - )}
); } diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx index 2b8f36b7..47cd6ee3 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx @@ -4,6 +4,7 @@ import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order import { WO_TYPE_META, WO_TYPES, + WIZARD_WO_NUMBER_MAX_LENGTH, isWizardWoNumberValid, type WOType, } from "@/domain/work-orders/types/work-order-wizard"; @@ -231,28 +232,6 @@ function clearSchedulePatch(): Partial { }; } -function WizardAddOnOverrideField({ - checked, - onChange, -}: { - checked: boolean; - onChange: (value: boolean) => void; -}) { - return ( - - ); -} - export function WizardStepTypeSchedule({ draft, users, @@ -294,19 +273,25 @@ export function WizardStepTypeSchedule({ type="text" value={draft.woNumber} onChange={(e) => - onUpdate("woNumber", e.target.value.replace(/[^a-zA-Z0-9]/g, "").slice(0, 20)) + onUpdate( + "woNumber", + e.target.value.replace(/[^a-zA-Z0-9]/g, "").slice(0, WIZARD_WO_NUMBER_MAX_LENGTH), + ) } onBlur={() => onDuplicateWoNumber?.(draft.woNumber)} placeholder="Leave blank for SH provisional number" - maxLength={20} + maxLength={WIZARD_WO_NUMBER_MAX_LENGTH} className={DLG_INPUT_CLS} style={DLG_INPUT_STYLE} /> - {Boolean(woNumberError) && ( - - WO number must be alphanumeric. - - )} + + WO number must be alphanumeric. +
@@ -338,13 +323,6 @@ export function WizardStepTypeSchedule({
- - {Boolean(!hasWizardSchedule(draft)) && ( - onUpdate("isAddOnOverride", value)} - /> - )}
); } diff --git a/src/domain/accounts/use-cases/use-accounts-list.ts b/src/domain/accounts/use-cases/use-accounts-list.ts index 2a031a0c..9dcb347a 100644 --- a/src/domain/accounts/use-cases/use-accounts-list.ts +++ b/src/domain/accounts/use-cases/use-accounts-list.ts @@ -5,12 +5,10 @@ import { queryKeys } from "@/infra/query-key/query-key"; export function useAccountsList( params: AccountsListParams, - enabled = true, ): UseQueryResult { return useQuery({ queryKey: queryKeys.accounts.list(params as Record), queryFn: () => accountsApi.getList(params), placeholderData: keepPreviousData, - enabled, }); } diff --git a/src/domain/work-orders/mappers/work-order-board-mapper.ts b/src/domain/work-orders/mappers/work-order-board-mapper.ts index 96f09868..af00a55d 100644 --- a/src/domain/work-orders/mappers/work-order-board-mapper.ts +++ b/src/domain/work-orders/mappers/work-order-board-mapper.ts @@ -141,10 +141,5 @@ export function mapWizardDraftToBoardCreatePayload( payload.locationId = locationId; } - const customer = draft.customer.trim(); - if (customer) { - payload.customer = customer; - } - return payload; } diff --git a/src/domain/work-orders/types/work-order-board.ts b/src/domain/work-orders/types/work-order-board.ts index 4cb9a424..cbd48acd 100644 --- a/src/domain/work-orders/types/work-order-board.ts +++ b/src/domain/work-orders/types/work-order-board.ts @@ -107,8 +107,6 @@ export interface WorkOrderBoardCreatePayload { /** Per-WO Aveta compliance flag from Service & Notes. */ avetaRequired?: boolean; locationId?: number; - /** Exact `Accounts.Name` for org-wide create stamp. */ - customer?: string; pocContactId?: number; pocName?: string; pocPhone?: string; 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 2b0e0493..fa7b7a63 100644 --- a/src/domain/work-orders/types/work-order-table-row.ts +++ b/src/domain/work-orders/types/work-order-table-row.ts @@ -79,8 +79,6 @@ export interface WorkOrderTableRow { export interface WorkOrderTableDraft { site: string; - /** Exact CRM `Accounts.Name` sent as board create `customer`. */ - customer: string; woNumber: string; type: WOType | ""; severity: WorkOrderSeverity | null; @@ -113,7 +111,6 @@ export interface WorkOrderTableDraft { export const EMPTY_TABLE_DRAFT: WorkOrderTableDraft = { site: "", - customer: "", woNumber: "", type: "", severity: null, diff --git a/src/domain/work-orders/types/work-order-wizard.ts b/src/domain/work-orders/types/work-order-wizard.ts index 20a53a92..f13b387e 100644 --- a/src/domain/work-orders/types/work-order-wizard.ts +++ b/src/domain/work-orders/types/work-order-wizard.ts @@ -28,8 +28,6 @@ export interface WorkOrderWizardDraft { locationId: string; locationName: string; siteCode: string; - /** Exact CRM `Accounts.Name` sent as board create `customer`. */ - customer: string; woNumber: string; type: WOType | ""; /** Severity 1–5 for Emergency/Reactive; null otherwise. */ @@ -76,7 +74,6 @@ export const EMPTY_WIZARD_DRAFT: WorkOrderWizardDraft = { locationId: "", locationName: "", siteCode: "", - customer: "", woNumber: "", type: "", severity: null, @@ -106,13 +103,15 @@ export const EMPTY_WIZARD_DRAFT: WorkOrderWizardDraft = { isAddOnOverride: false, }; -/** Blank numbers receive an SH provisional number from the backend; nonblank values are alphanumeric. */ +export const WIZARD_WO_NUMBER_MAX_LENGTH = 20; + +/** Blank is valid (backend generates an SH provisional number). Otherwise alphanumeric, max 20. */ export function isWizardWoNumberValid(woNumber: string): boolean { const trimmed = woNumber.trim(); if (!trimmed) { return true; } - return trimmed.length <= 20 && /^[a-z0-9]+$/i.test(trimmed); + return trimmed.length <= WIZARD_WO_NUMBER_MAX_LENGTH && /^[a-z0-9]+$/i.test(trimmed); } /** Type is optional at creation; empty draft type defaults to PM. */ @@ -197,56 +196,41 @@ export function canContinueWizardStep1(draft: WorkOrderWizardDraft): boolean { return true; } -export type WizardAccountGate = { - requireAccount?: boolean; -}; - -function requiresAccount(options?: WizardAccountGate): boolean { - return options?.requireAccount !== false; +export function wizardStep1ContinueHint(draft: WorkOrderWizardDraft): string | null { + if (canContinueWizardStep1(draft)) { + return null; + } + if (!isWizardWoNumberValid(draft.woNumber)) { + return "WO number must be alphanumeric"; + } + return "Select severity to continue"; } -export function canContinueWizardStep2( - draft: WorkOrderWizardDraft, - options?: WizardAccountGate, -): boolean { +export function canContinueWizardStep2(draft: WorkOrderWizardDraft): boolean { const hasSite = Boolean(draft.locationId && (draft.siteCode.trim() || draft.locationName.trim())); - const hasAccount = !requiresAccount(options) || Boolean(draft.customer.trim()); - return Boolean(hasSite && hasAccount && draft.pocName.trim() && draft.pocPhone.trim()); + return Boolean(hasSite && draft.pocName.trim() && draft.pocPhone.trim()); } -export function wizardStep2ContinueHint( - draft: WorkOrderWizardDraft, - options?: WizardAccountGate, -): string | null { - if (canContinueWizardStep2(draft, options)) { +export function wizardStep2ContinueHint(draft: WorkOrderWizardDraft): string | null { + if (canContinueWizardStep2(draft)) { return null; } if (!draft.locationId) { return "Pick a site to continue"; } - if (requiresAccount(options) && !draft.customer.trim()) { - return "Select an account to continue"; - } return "Enter POC name and phone to continue"; } -export function canCreateWorkOrderFromWizard( - draft: WorkOrderWizardDraft, - options?: WizardAccountGate, -): boolean { - return canContinueWizardStep1(draft) && canContinueWizardStep2(draft, options); +export function canCreateWorkOrderFromWizard(draft: WorkOrderWizardDraft): boolean { + return canContinueWizardStep1(draft) && canContinueWizardStep2(draft); } -export function isWizardStepValid( - step: number, - draft: WorkOrderWizardDraft, - options?: WizardAccountGate, -): boolean { +export function isWizardStepValid(step: number, draft: WorkOrderWizardDraft): boolean { if (step === 1) { return canContinueWizardStep1(draft); } if (step === 2) { - return canContinueWizardStep2(draft, options); + return canContinueWizardStep2(draft); } return true; } diff --git a/src/domain/work-orders/utils/get-inline-create-missing.ts b/src/domain/work-orders/utils/get-inline-create-missing.ts index 6c2ea6e2..a732e656 100644 --- a/src/domain/work-orders/utils/get-inline-create-missing.ts +++ b/src/domain/work-orders/utils/get-inline-create-missing.ts @@ -1,8 +1,7 @@ /** Required fields for board inline create (Schedule On is optional — SH-109). */ -export type InlineCreateReqField = "customer" | "site" | "woNumber" | "pocName" | "pocPhone"; +export type InlineCreateReqField = "site" | "woNumber" | "pocName" | "pocPhone"; export const INLINE_CREATE_REQ_LABELS: Record = { - customer: "Account", site: "Site", woNumber: "WO #", pocName: "POC name", @@ -10,7 +9,6 @@ export const INLINE_CREATE_REQ_LABELS: Record = { }; export type InlineCreateDraftFields = { - customer?: string; site?: string; locationId?: string | number; woNumber?: string; @@ -18,18 +16,8 @@ export type InlineCreateDraftFields = { pocPhone?: string; }; -export type InlineCreateAccountGate = { - requireAccount?: boolean; -}; - -export function getInlineCreateMissing( - d: InlineCreateDraftFields, - options?: InlineCreateAccountGate, -): InlineCreateReqField[] { +export function getInlineCreateMissing(d: InlineCreateDraftFields): InlineCreateReqField[] { const miss: InlineCreateReqField[] = []; - if (options?.requireAccount !== false && !d.customer?.trim()) { - miss.push("customer"); - } const hasSite = Boolean(d.site?.trim()) || Boolean(String(d.locationId ?? "").trim()); if (!hasSite) { miss.push("site"); @@ -55,9 +43,6 @@ export function inlineCreateSaveHint(miss: InlineCreateReqField[]): string { } export function inlineCreateSiteError(miss: InlineCreateReqField[]): string { - if (miss.includes("customer")) { - return "Account is required"; - } if (miss.includes("site")) { return "Site is required"; } diff --git a/src/lib/auth/org-scope.ts b/src/lib/auth/org-scope.ts deleted file mode 100644 index 681a9e37..00000000 --- a/src/lib/auth/org-scope.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { AuthUser } from "@/domain/auth/types/auth-user"; - -const ORG_SCOPE_ALL = "all"; - -function readJwtClaims(token: string): Record | null { - const parts = token.split("."); - if (parts.length < 2) { - return null; - } - try { - const normalized = parts[1].replace(/-/g, "+").replace(/_/g, "/"); - const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="); - const parsed: unknown = JSON.parse(globalThis.atob(padded)); - if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { - return null; - } - return parsed as Record; - } catch { - return null; - } -} - -function readStringClaim(source: Record | null, keys: string[]): string { - if (!source) { - return ""; - } - for (const key of keys) { - const value = source[key]; - if (typeof value === "string" && value.trim()) { - return value.trim(); - } - } - return ""; -} - -/** Org-wide only when the JWT claim is explicitly `all`. Fail-closed otherwise. */ -export function isOrgWideScope(user: AuthUser | null | undefined): boolean { - if (!user || typeof user.token !== "string") { - return false; - } - const claims = readJwtClaims(user.token); - const scope = readStringClaim(claims, ["org_scope", "orgScope", "OrgScope"]); - return scope.toLowerCase() === ORG_SCOPE_ALL; -} diff --git a/src/test/app/(protected)/workorders/build-inline-draft-create-input.test.ts b/src/test/app/(protected)/workorders/build-inline-draft-create-input.test.ts index 16b30ee8..b8c48168 100644 --- a/src/test/app/(protected)/workorders/build-inline-draft-create-input.test.ts +++ b/src/test/app/(protected)/workorders/build-inline-draft-create-input.test.ts @@ -8,7 +8,6 @@ describe("buildInlineDraftCreateInput", () => { const draft = { ...EMPTY_TABLE_DRAFT, site: "BK5", - customer: "Amazon", locationId: "9", woNumber: "25001", pocName: "Jane", @@ -31,8 +30,8 @@ describe("buildInlineDraftCreateInput", () => { expect(payload.scheduleWeekOnly).toBeUndefined(); expect(payload.woNumber).toBe("25001"); expect(payload.pocName).toBe("Jane"); - expect(payload.customer).toBe("Amazon"); - expect(input.customer).toBe("Amazon"); + expect(payload.locationId).toBe(9); + expect(payload).not.toHaveProperty("customer"); }); it("keeps week-only schedule when targetWeek is set", () => { diff --git a/src/test/app/(protected)/workorders/wizard-sidebar-summary-rows.test.ts b/src/test/app/(protected)/workorders/wizard-sidebar-summary-rows.test.ts new file mode 100644 index 00000000..126d4d6a --- /dev/null +++ b/src/test/app/(protected)/workorders/wizard-sidebar-summary-rows.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from "vitest"; +import { buildWizardSidebarSummaryRows } from "@/app/(protected)/workorders/_components/wizard/wizard-sidebar-summary-rows"; +import { EMPTY_WIZARD_DRAFT } from "@/domain/work-orders/types/work-order-wizard"; + +describe("buildWizardSidebarSummaryRows", () => { + it("omits Account from the live summary", () => { + const labels = buildWizardSidebarSummaryRows(EMPTY_WIZARD_DRAFT).map(([label]) => label); + expect(labels).not.toContain("Account"); + expect(labels).toContain("Site"); + }); +}); diff --git a/src/test/app/(protected)/workorders/wizard-step-location-account-select.test.tsx b/src/test/app/(protected)/workorders/wizard-step-location-account-select.test.tsx deleted file mode 100644 index 9acafb21..00000000 --- a/src/test/app/(protected)/workorders/wizard-step-location-account-select.test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { fireEvent, screen } from "@testing-library/react"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { WizardStepLocationAccountSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-account-select"; -import type { AuthUser } from "@/domain/auth/types/auth-user"; -import { AuthContext, type AuthContextValue } from "@/providers/auth-context"; -import { renderWithProviders } from "@/test/test-utils"; - -const { useAccountsListMock } = vi.hoisted(() => ({ - useAccountsListMock: vi.fn(), -})); - -vi.mock("@/hooks/use-debounce", () => ({ - useDebounce: (value: string) => value, -})); - -vi.mock("@/domain/accounts/use-cases/use-accounts-list", () => ({ - useAccountsList: (params: unknown, enabled?: boolean) => useAccountsListMock(params, enabled), -})); - -const FOREIGN_ACCOUNT = "Contoso Other Tenant"; - -const ACCOUNTS = { - items: [ - { - id: 1, - name: "Amazon", - number: "", - industry: "", - website: "", - primaryContact: "", - phoneNumber: "", - owner: "", - }, - { - id: 7, - name: FOREIGN_ACCOUNT, - number: "", - industry: "", - website: "", - primaryContact: "", - phoneNumber: "", - owner: "", - }, - ], -}; - -function encodeJwt(payload: Record): string { - const json = JSON.stringify(payload); - const b64 = btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); - return `eyJhbGciOiJub25lIn0.${b64}.sig`; -} - -function authUser(token: string, orgScope?: string): AuthUser { - return { - token, - expiration: new Date(Date.now() + 60_000).toISOString(), - email: "user@seahavenind.com", - userRoles: "User", - phoneNumber: "", - fullname: "Test User", - id: 1, - orgScope, - }; -} - -function authValue(user: AuthUser): AuthContextValue { - return { - user, - isAuthenticated: true, - isLoading: false, - error: null, - login: vi.fn(), - logout: vi.fn(), - changePassword: vi.fn(), - isLoggingIn: false, - isLoggingOut: false, - isChangingPassword: false, - loginError: null, - }; -} - -function renderSelect(user: AuthUser) { - return renderWithProviders( - - - , - { withAuth: false }, - ); -} - -describe("WizardStepLocationAccountSelect", () => { - beforeEach(() => { - useAccountsListMock.mockReturnValue({ data: ACCOUNTS }); - }); - - it("sets draft.customer to the exact account name on pick for org-wide scope", () => { - const onCustomerChange = vi.fn(); - renderWithProviders( - - - , - { withAuth: false }, - ); - - fireEvent.click(screen.getByRole("button", { name: /select account/i })); - fireEvent.click(screen.getByRole("button", { name: "Amazon" })); - - expect(onCustomerChange).toHaveBeenCalledWith("Amazon"); - expect(useAccountsListMock).toHaveBeenCalledWith({ page: 1, pageSize: 50, search: "" }, true); - }); - - it("passes typed search to the accounts list query for org-wide scope", () => { - renderSelect(authUser(encodeJwt({ org_scope: "all" }))); - - expect(useAccountsListMock).toHaveBeenCalledWith({ page: 1, pageSize: 50, search: "" }, true); - - fireEvent.click(screen.getByRole("button", { name: /select account/i })); - fireEvent.change(screen.getByPlaceholderText("Search account name…"), { - target: { value: "Sea" }, - }); - - expect(useAccountsListMock).toHaveBeenCalledWith( - { page: 1, pageSize: 50, search: "Sea" }, - true, - ); - }); - - it("does not fetch or expose another account name for a scoped caller", () => { - renderSelect(authUser(encodeJwt({ org_scope: "account", account_id: 7 }))); - - expect(useAccountsListMock).toHaveBeenCalledWith({ page: 1, pageSize: 50, search: "" }, false); - expect(screen.queryByRole("button", { name: /select account/i })).not.toBeInTheDocument(); - expect(screen.queryByText(FOREIGN_ACCOUNT)).not.toBeInTheDocument(); - expect(screen.queryByText("Amazon")).not.toBeInTheDocument(); - }); - - it("keeps the list disabled when a scoped token has no org_scope and storage says all", () => { - renderSelect(authUser(encodeJwt({ account_id: 7 }), "all")); - - expect(useAccountsListMock).toHaveBeenCalledWith({ page: 1, pageSize: 50, search: "" }, false); - expect(screen.queryByRole("button", { name: /select account/i })).not.toBeInTheDocument(); - expect(screen.queryByText(FOREIGN_ACCOUNT)).not.toBeInTheDocument(); - }); -}); diff --git a/src/test/app/(protected)/workorders/wizard-step-location-service.test.tsx b/src/test/app/(protected)/workorders/wizard-step-location-service.test.tsx new file mode 100644 index 00000000..76dbd61e --- /dev/null +++ b/src/test/app/(protected)/workorders/wizard-step-location-service.test.tsx @@ -0,0 +1,28 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { WizardStepLocationService } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-service"; +import { EMPTY_WIZARD_DRAFT } from "@/domain/work-orders/types/work-order-wizard"; + +describe("WizardStepLocationService", () => { + it("requires Site, keeps Service optional, and does not render Account", () => { + render( + , + ); + + expect(screen.getByText("Site")).toBeInTheDocument(); + expect(screen.getByText("Service")).toBeInTheDocument(); + expect(screen.queryByText("Account")).not.toBeInTheDocument(); + expect(screen.queryByText(/Both are optional/i)).not.toBeInTheDocument(); + }); +}); diff --git a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx index 2dbdc94e..e273324c 100644 --- a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx +++ b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx @@ -230,9 +230,7 @@ describe("WorkOrdersListPage board-core public interface", () => { expect( screen.getByPlaceholderText(/leave blank for sh provisional number/i), ).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /continue/i })).toBeEnabled(); fireEvent.click(screen.getByRole("button", { name: /continue/i })); - expect(screen.queryByText(/wo number is required/i)).not.toBeInTheDocument(); expect(screen.getByRole("heading", { name: /location & service/i })).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: /^back$/i })); diff --git a/src/test/domain/work-orders/api/work-orders-api.test.ts b/src/test/domain/work-orders/api/work-orders-api.test.ts index fabf80af..a24381d0 100644 --- a/src/test/domain/work-orders/api/work-orders-api.test.ts +++ b/src/test/domain/work-orders/api/work-orders-api.test.ts @@ -114,7 +114,7 @@ describe("workOrdersApi.createFromBoard", () => { type: "PM", siteCode: "BK5", woNumber: "12345", - customer: "Amazon", + locationId: "9", }); expect(apiPost).toHaveBeenCalledWith(API_PATHS.workOrder.boardCreate, { @@ -123,7 +123,7 @@ describe("workOrdersApi.createFromBoard", () => { lifecycleStatus: 1, woNumber: "12345", isAddOn: false, - customer: "Amazon", + locationId: 9, }); }); }); diff --git a/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts b/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts index 7079dcb0..b1124749 100644 --- a/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts +++ b/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts @@ -7,6 +7,7 @@ import { canCreateWorkOrderFromWizard, isWizardStepValid, isWizardWoNumberValid, + wizardStep1ContinueHint, wizardStep2ContinueHint, } from "@/domain/work-orders/types/work-order-wizard"; @@ -66,18 +67,18 @@ describe("mapWizardDraftToFormValues", () => { }); describe("wizard step validation", () => { - it("allows step 1 continue without a woNumber for backend SH generation", () => { + it("allows step 1 with a blank woNumber for backend SH generation", () => { expect(canContinueWizardStep1(EMPTY_WIZARD_DRAFT)).toBe(true); expect(isWizardStepValid(1, EMPTY_WIZARD_DRAFT)).toBe(true); }); - it("allows step 1 without type when woNumber is set (type defaults to PM)", () => { + it("allows step 1 without type when woNumber is set", () => { const draft = { ...EMPTY_WIZARD_DRAFT, woNumber: "12345" }; expect(canContinueWizardStep1(draft)).toBe(true); expect(isWizardStepValid(1, draft)).toBe(true); }); - it("allows step 1 when woNumber is missing even with type", () => { + it("allows step 1 without woNumber when type is set", () => { const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const }; expect(canContinueWizardStep1(draft)).toBe(true); expect(isWizardStepValid(1, draft)).toBe(true); @@ -124,7 +125,7 @@ describe("wizard step validation", () => { expect(isWizardStepValid(2, draft)).toBe(false); }); - it("allows step 2 without customer when account is not required", () => { + it("allows step 2 without an account when site and POC are set", () => { const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const, @@ -134,49 +135,32 @@ describe("wizard step validation", () => { pocName: "Jordan", pocPhone: "555-0100", }; - expect(canContinueWizardStep2(draft, { requireAccount: false })).toBe(true); - expect(isWizardStepValid(2, draft, { requireAccount: false })).toBe(true); - expect(wizardStep2ContinueHint(draft, { requireAccount: false })).toBeNull(); - }); - - it("blocks step 2 without customer", () => { - const draft = { - ...EMPTY_WIZARD_DRAFT, - type: "PM" as const, - woNumber: "12345", - locationId: "99", - siteCode: "BK5", - pocName: "Jordan", - pocPhone: "555-0100", - }; - expect(canContinueWizardStep2(draft)).toBe(false); - expect(isWizardStepValid(2, draft)).toBe(false); - expect(wizardStep2ContinueHint(draft)).toBe("Select an account to continue"); + expect(canContinueWizardStep2(draft)).toBe(true); + expect(isWizardStepValid(2, draft)).toBe(true); + expect(wizardStep2ContinueHint(draft)).toBeNull(); }); it("hints pick a site when step 2 has no location", () => { expect(wizardStep2ContinueHint(EMPTY_WIZARD_DRAFT)).toBe("Pick a site to continue"); }); - it("hints POC when step 2 has site and account but no phone", () => { + it("hints POC when step 2 has site but no phone", () => { const draft = { ...EMPTY_WIZARD_DRAFT, locationId: "99", siteCode: "BK5", - customer: "Amazon", pocName: "Jordan", }; expect(wizardStep2ContinueHint(draft)).toBe("Enter POC name and phone to continue"); }); - it("allows step 2 when site, account, and POC are set", () => { + it("allows step 2 when site and POC are set", () => { const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const, woNumber: "12345", locationId: "99", siteCode: "BK5", - customer: "Amazon", pocName: "Jordan", pocPhone: "555-0100", }; @@ -196,14 +180,13 @@ describe("wizard step validation", () => { expect(isWizardStepValid(2, draft)).toBe(false); }); - it("allows create when type, woNumber, site, account, and POC are set", () => { + it("allows create when type, woNumber, site, and POC are set", () => { const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const, - woNumber: "12345678901234567890", + woNumber: "12345678901", locationId: "99", siteCode: "BK5", - customer: "Amazon", pocName: "Jordan", pocPhone: "555-0100", }; @@ -212,18 +195,10 @@ describe("wizard step validation", () => { }); it("allows provisional SH-prefixed woNumber", () => { - const draft = { - ...EMPTY_WIZARD_DRAFT, - woNumber: "SH12345", - }; - expect(canContinueWizardStep1(draft)).toBe(true); - }); - - it("accepts blank woNumber for backend SH generation", () => { const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const, - woNumber: "", + woNumber: "SH12345", }; expect(isWizardWoNumberValid(draft.woNumber)).toBe(true); expect(canContinueWizardStep1(draft)).toBe(true); @@ -241,10 +216,22 @@ describe("wizard step validation", () => { it("rejects non-alphanumeric woNumber", () => { expect(isWizardWoNumberValid("!!!")).toBe(false); - expect(canContinueWizardStep1({ ...EMPTY_WIZARD_DRAFT, woNumber: "!!!" })).toBe(false); + expect(canContinueWizardStep1({ ...EMPTY_WIZARD_DRAFT, type: "PM", woNumber: "!!!" })).toBe( + false, + ); }); it("accepts alphanumeric woNumber", () => { expect(isWizardWoNumberValid("ABC123")).toBe(true); }); + + it("hints alphanumeric WO number or severity when step 1 is incomplete", () => { + expect(wizardStep1ContinueHint(EMPTY_WIZARD_DRAFT)).toBeNull(); + expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "PM", woNumber: "!!!" })).toBe( + "WO number must be alphanumeric", + ); + expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "Emergency" })).toBe( + "Select severity to continue", + ); + }); }); diff --git a/src/test/domain/work-orders/mappers/work-order-board-mapper.test.ts b/src/test/domain/work-orders/mappers/work-order-board-mapper.test.ts index ee3d5c27..0f003070 100644 --- a/src/test/domain/work-orders/mappers/work-order-board-mapper.test.ts +++ b/src/test/domain/work-orders/mappers/work-order-board-mapper.test.ts @@ -60,26 +60,27 @@ describe("mapWizardDraftToBoardCreatePayload", () => { }); }); - it("includes customer when the draft has an account name", () => { + it("sends locationId and never customer on create", () => { const payload = mapWizardDraftToBoardCreatePayload({ ...EMPTY_WIZARD_DRAFT, type: "PM", siteCode: "BK5", - customer: " Amazon ", + locationId: "12", }); - expect(payload.customer).toBe("Amazon"); + expect(payload.locationId).toBe(12); + expect(payload).not.toHaveProperty("customer"); }); - it("omits customer when the draft account is blank", () => { + it("allows create mapping without a service", () => { const payload = mapWizardDraftToBoardCreatePayload({ ...EMPTY_WIZARD_DRAFT, type: "PM", siteCode: "BK5", - customer: " ", + locationId: "12", }); - expect(payload.customer).toBeUndefined(); + expect(payload.primaryService).toBeUndefined(); }); it("sets isAddOn from cutoff preview when scheduled (create hint; not frozen)", () => { diff --git a/src/test/domain/work-orders/utils/get-inline-create-missing.test.ts b/src/test/domain/work-orders/utils/get-inline-create-missing.test.ts index 250b4b1d..3ddd8dca 100644 --- a/src/test/domain/work-orders/utils/get-inline-create-missing.test.ts +++ b/src/test/domain/work-orders/utils/get-inline-create-missing.test.ts @@ -7,10 +7,9 @@ import { } from "@/domain/work-orders/utils/get-inline-create-missing"; describe("getInlineCreateMissing", () => { - it("returns empty when Account, Site, WO#, and POC are filled even without schedule", () => { + it("returns empty when Site, WO#, and POC are filled even without schedule", () => { expect( getInlineCreateMissing({ - customer: "Amazon", site: "BK5", locationId: "12", woNumber: "25001", @@ -23,7 +22,6 @@ describe("getInlineCreateMissing", () => { it("accepts locationId alone as site presence", () => { expect( getInlineCreateMissing({ - customer: "Amazon", site: "", locationId: 42, woNumber: "25001", @@ -36,7 +34,6 @@ describe("getInlineCreateMissing", () => { it("lists missing POC fields without requiring schedule", () => { expect( getInlineCreateMissing({ - customer: "Amazon", site: "BK5", woNumber: "25001", pocName: "", @@ -45,29 +42,8 @@ describe("getInlineCreateMissing", () => { ).toEqual(["pocName", "pocPhone"]); }); - it("skips account when the caller does not require it", () => { - expect( - getInlineCreateMissing( - { - site: "BK5", - locationId: "12", - woNumber: "25001", - pocName: "Jane", - pocPhone: "555-0100", - }, - { requireAccount: false }, - ), - ).toEqual([]); - }); - - it("lists account, site, and woNumber when empty", () => { - expect(getInlineCreateMissing({})).toEqual([ - "customer", - "site", - "woNumber", - "pocName", - "pocPhone", - ]); + it("lists site and woNumber when empty", () => { + expect(getInlineCreateMissing({})).toEqual(["site", "woNumber", "pocName", "pocPhone"]); }); }); @@ -82,10 +58,6 @@ describe("inlineCreateSaveHint", () => { }); describe("inlineCreateSiteError", () => { - it("prefers account-required message when account is missing", () => { - expect(inlineCreateSiteError(["customer", "site"])).toBe("Account is required"); - }); - it("prefers site-required message when site is missing", () => { expect(inlineCreateSiteError(["site", "pocName"])).toBe("Site is required"); }); diff --git a/src/test/lib/auth/org-scope.test.ts b/src/test/lib/auth/org-scope.test.ts deleted file mode 100644 index 51f074c7..00000000 --- a/src/test/lib/auth/org-scope.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { describe, expect, it } from "vitest"; -import type { AuthUser } from "@/domain/auth/types/auth-user"; -import { isOrgWideScope } from "@/lib/auth/org-scope"; - -function encodeJwt(payload: Record): string { - const json = JSON.stringify(payload); - const b64 = btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); - return `eyJhbGciOiJub25lIn0.${b64}.sig`; -} - -function user(token: string, orgScope?: string): AuthUser { - return { - token, - expiration: new Date(Date.now() + 60_000).toISOString(), - email: "user@seahavenind.com", - userRoles: "User", - phoneNumber: "", - fullname: "Test User", - id: 1, - orgScope, - }; -} - -describe("isOrgWideScope", () => { - it("is false without a session", () => { - expect(isOrgWideScope(null)).toBe(false); - expect(isOrgWideScope(undefined)).toBe(false); - }); - - it("is true only when the JWT org_scope is all", () => { - expect(isOrgWideScope(user(encodeJwt({ org_scope: "all" })))).toBe(true); - expect(isOrgWideScope(user(encodeJwt({ org_scope: "ALL" })))).toBe(true); - }); - - it("is false for a claim-scoped account", () => { - expect(isOrgWideScope(user(encodeJwt({ org_scope: "account", account_id: 7 })))).toBe(false); - }); - - it("ignores a stored orgScope when the token is not a JWT", () => { - expect(isOrgWideScope(user("session-token", "all"))).toBe(false); - }); - - it("ignores a stale stored orgScope when the JWT has no org_scope", () => { - expect(isOrgWideScope(user(encodeJwt({ account_id: 7 }), "all"))).toBe(false); - }); - - it("ignores a stale stored orgScope when the JWT is scoped", () => { - expect(isOrgWideScope(user(encodeJwt({ org_scope: "account" }), "all"))).toBe(false); - }); - - it("is false when the token has no org_scope claim", () => { - expect(isOrgWideScope(user("wo-visual-token"))).toBe(false); - }); -});