mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 18:12:05 +00:00
fix: send customer on board work-order create
Org-wide Admin create needs exact Accounts.Name so SH-221 can stamp AccountId.
This commit is contained in:
parent
b9b250ba46
commit
1714da6e37
22 changed files with 237 additions and 10 deletions
|
|
@ -19,6 +19,7 @@ export function buildInlineDraftCreateInput(
|
||||||
locationId: String(draft.locationId || ""),
|
locationId: String(draft.locationId || ""),
|
||||||
locationName,
|
locationName,
|
||||||
siteCode: location ? resolveSiteCode(location) : resolveSiteCode({ name: locationName }),
|
siteCode: location ? resolveSiteCode(location) : resolveSiteCode({ name: locationName }),
|
||||||
|
customer: draft.customer.trim(),
|
||||||
woNumber: draft.woNumber,
|
woNumber: draft.woNumber,
|
||||||
type: draft.type || "PM",
|
type: draft.type || "PM",
|
||||||
severity: draft.severity,
|
severity: draft.severity,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ type BuildSiteDialogPatchArgs = {
|
||||||
code: string;
|
code: string;
|
||||||
locId: string;
|
locId: string;
|
||||||
selected: LocationOption | undefined;
|
selected: LocationOption | undefined;
|
||||||
|
customer?: string;
|
||||||
pocName: string;
|
pocName: string;
|
||||||
pocPhone: string;
|
pocPhone: string;
|
||||||
pocNotes: string;
|
pocNotes: string;
|
||||||
|
|
@ -20,6 +21,7 @@ export function buildSiteDialogPatch({
|
||||||
code,
|
code,
|
||||||
locId,
|
locId,
|
||||||
selected,
|
selected,
|
||||||
|
customer,
|
||||||
pocName,
|
pocName,
|
||||||
pocPhone,
|
pocPhone,
|
||||||
pocNotes,
|
pocNotes,
|
||||||
|
|
@ -31,6 +33,7 @@ export function buildSiteDialogPatch({
|
||||||
site: code || (selected ? resolveSiteCode(selected) : ""),
|
site: code || (selected ? resolveSiteCode(selected) : ""),
|
||||||
location: selected ? extractLocationLabel(selected) : "",
|
location: selected ? extractLocationLabel(selected) : "",
|
||||||
locationId: locId,
|
locationId: locId,
|
||||||
|
...(customer !== undefined ? { customer } : {}),
|
||||||
pocName,
|
pocName,
|
||||||
pocPhone,
|
pocPhone,
|
||||||
pocNotes,
|
pocNotes,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ type SiteCellProps = {
|
||||||
value: string;
|
value: string;
|
||||||
location: string;
|
location: string;
|
||||||
locationId: string | number;
|
locationId: string | number;
|
||||||
|
customer?: string;
|
||||||
city?: string;
|
city?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
pocName?: string;
|
pocName?: string;
|
||||||
|
|
@ -29,6 +30,7 @@ export function SiteCell({
|
||||||
value,
|
value,
|
||||||
location,
|
location,
|
||||||
locationId,
|
locationId,
|
||||||
|
customer,
|
||||||
city,
|
city,
|
||||||
state,
|
state,
|
||||||
pocName,
|
pocName,
|
||||||
|
|
@ -95,6 +97,7 @@ export function SiteCell({
|
||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
value={value}
|
value={value}
|
||||||
locationId={locationId}
|
locationId={locationId}
|
||||||
|
customer={customer}
|
||||||
pocName={pocName}
|
pocName={pocName}
|
||||||
pocPhone={pocPhone}
|
pocPhone={pocPhone}
|
||||||
pocNotes={pocNotes}
|
pocNotes={pocNotes}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export type SitePatch = {
|
||||||
site: string;
|
site: string;
|
||||||
location: string;
|
location: string;
|
||||||
locationId: string | number;
|
locationId: string | number;
|
||||||
|
customer?: string;
|
||||||
pocName: string;
|
pocName: string;
|
||||||
pocPhone: string;
|
pocPhone: string;
|
||||||
pocNotes: string;
|
pocNotes: string;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import type { LocationOption } from "@/domain/work-orders/types/work-order";
|
||||||
import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label";
|
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 { 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 { 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 { 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 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 { useSiteDialogState } from "@/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-state";
|
||||||
|
|
@ -20,6 +21,7 @@ type SiteDialogProps = {
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
value: string;
|
value: string;
|
||||||
locationId: string | number;
|
locationId: string | number;
|
||||||
|
customer?: string;
|
||||||
pocName?: string;
|
pocName?: string;
|
||||||
pocPhone?: string;
|
pocPhone?: string;
|
||||||
pocNotes?: string;
|
pocNotes?: string;
|
||||||
|
|
@ -35,6 +37,7 @@ export function SiteDialog({
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
value,
|
value,
|
||||||
locationId,
|
locationId,
|
||||||
|
customer = "",
|
||||||
pocName = "",
|
pocName = "",
|
||||||
pocPhone = "",
|
pocPhone = "",
|
||||||
pocNotes = "",
|
pocNotes = "",
|
||||||
|
|
@ -49,6 +52,7 @@ export function SiteDialog({
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
value,
|
value,
|
||||||
locationId,
|
locationId,
|
||||||
|
customer,
|
||||||
pocName,
|
pocName,
|
||||||
pocPhone,
|
pocPhone,
|
||||||
pocNotes,
|
pocNotes,
|
||||||
|
|
@ -81,6 +85,13 @@ export function SiteDialog({
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<fieldset disabled={viewOnly} className="mt-3 space-y-3 border-0 p-0">
|
<fieldset disabled={viewOnly} className="mt-3 space-y-3 border-0 p-0">
|
||||||
|
{createMode && (
|
||||||
|
<WizardStepLocationAccountSelect
|
||||||
|
customer={s.accountName}
|
||||||
|
showError={s.showErrors && s.accountMissing}
|
||||||
|
onCustomerChange={s.setAccountName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel>Site</FieldLabel>
|
<FieldLabel>Site</FieldLabel>
|
||||||
<WizardFieldSelect
|
<WizardFieldSelect
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ type UseSiteDialogStateArgs = {
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
value: string;
|
value: string;
|
||||||
locationId: string | number;
|
locationId: string | number;
|
||||||
|
customer?: string;
|
||||||
pocName: string;
|
pocName: string;
|
||||||
pocPhone: string;
|
pocPhone: string;
|
||||||
pocNotes: string;
|
pocNotes: string;
|
||||||
|
|
@ -29,6 +30,7 @@ export function useSiteDialogState({
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
value,
|
value,
|
||||||
locationId,
|
locationId,
|
||||||
|
customer = "",
|
||||||
pocName,
|
pocName,
|
||||||
pocPhone,
|
pocPhone,
|
||||||
pocNotes,
|
pocNotes,
|
||||||
|
|
@ -38,6 +40,7 @@ export function useSiteDialogState({
|
||||||
onSave,
|
onSave,
|
||||||
}: UseSiteDialogStateArgs) {
|
}: UseSiteDialogStateArgs) {
|
||||||
const [code, setCode] = useState(value);
|
const [code, setCode] = useState(value);
|
||||||
|
const [accountName, setAccountName] = useState(customer);
|
||||||
const [locId, setLocId] = useState(() => resolveLocationId(locationId, value, sites));
|
const [locId, setLocId] = useState(() => resolveLocationId(locationId, value, sites));
|
||||||
const [pn, setPn] = useState(pocName);
|
const [pn, setPn] = useState(pocName);
|
||||||
const [pp, setPp] = useState(pocPhone);
|
const [pp, setPp] = useState(pocPhone);
|
||||||
|
|
@ -60,8 +63,9 @@ export function useSiteDialogState({
|
||||||
const selected = sites.find((s) => String(s.id) === locId);
|
const selected = sites.find((s) => String(s.id) === locId);
|
||||||
const addressPreview = locationDetail ? formatLocationAddressPreview(locationDetail) : "";
|
const addressPreview = locationDetail ? formatLocationAddressPreview(locationDetail) : "";
|
||||||
const siteMissing = !locId;
|
const siteMissing = !locId;
|
||||||
|
const accountMissing = createMode && !accountName.trim();
|
||||||
const pocMissing = !pn.trim() || !pp.trim();
|
const pocMissing = !pn.trim() || !pp.trim();
|
||||||
const canConfirm = !siteMissing && (!createMode || !pocMissing);
|
const canConfirm = !siteMissing && (!createMode || (!pocMissing && !accountMissing));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
|
|
@ -77,6 +81,7 @@ export function useSiteDialogState({
|
||||||
}
|
}
|
||||||
const resolved = resolveLocationId(locationId, value, sites);
|
const resolved = resolveLocationId(locationId, value, sites);
|
||||||
setCode(value);
|
setCode(value);
|
||||||
|
setAccountName(customer);
|
||||||
setLocId(resolved);
|
setLocId(resolved);
|
||||||
setPn(pocName);
|
setPn(pocName);
|
||||||
setPp(pocPhone);
|
setPp(pocPhone);
|
||||||
|
|
@ -84,7 +89,18 @@ export function useSiteDialogState({
|
||||||
setExtraContacts(additionalContacts);
|
setExtraContacts(additionalContacts);
|
||||||
setBaselineHadContacts(additionalContacts.length > 0);
|
setBaselineHadContacts(additionalContacts.length > 0);
|
||||||
setPocFilledFor(pocName.trim() || pocPhone.trim() ? resolved : "");
|
setPocFilledFor(pocName.trim() || pocPhone.trim() ? resolved : "");
|
||||||
}, [open, dirty, value, locationId, pocName, pocPhone, pocNotes, additionalContacts, sites]);
|
}, [
|
||||||
|
open,
|
||||||
|
dirty,
|
||||||
|
value,
|
||||||
|
locationId,
|
||||||
|
customer,
|
||||||
|
pocName,
|
||||||
|
pocPhone,
|
||||||
|
pocNotes,
|
||||||
|
additionalContacts,
|
||||||
|
sites,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !locId || !locationDetail || pocFilledFor === locId) {
|
if (!open || !locId || !locationDetail || pocFilledFor === locId) {
|
||||||
|
|
@ -121,6 +137,7 @@ export function useSiteDialogState({
|
||||||
code,
|
code,
|
||||||
locId,
|
locId,
|
||||||
selected,
|
selected,
|
||||||
|
...(createMode ? { customer: accountName } : {}),
|
||||||
pocName: pn,
|
pocName: pn,
|
||||||
pocPhone: pp,
|
pocPhone: pp,
|
||||||
pocNotes: notes,
|
pocNotes: notes,
|
||||||
|
|
@ -152,6 +169,8 @@ export function useSiteDialogState({
|
||||||
return {
|
return {
|
||||||
locId,
|
locId,
|
||||||
code,
|
code,
|
||||||
|
accountName,
|
||||||
|
accountMissing,
|
||||||
selected,
|
selected,
|
||||||
locationDetail,
|
locationDetail,
|
||||||
locationDetailLoading,
|
locationDetailLoading,
|
||||||
|
|
@ -164,6 +183,10 @@ export function useSiteDialogState({
|
||||||
showErrors,
|
showErrors,
|
||||||
siteMissing,
|
siteMissing,
|
||||||
handlePick,
|
handlePick,
|
||||||
|
setAccountName: (next: string) => {
|
||||||
|
setDirty(true);
|
||||||
|
setAccountName(next);
|
||||||
|
},
|
||||||
attemptSave,
|
attemptSave,
|
||||||
setPn: (next: string) => markPocDirty(() => setPn(next)),
|
setPn: (next: string) => markPocDirty(() => setPn(next)),
|
||||||
setPp: (next: string) => markPocDirty(() => setPp(next)),
|
setPp: (next: string) => markPocDirty(() => setPp(next)),
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export function WoTableInlineIdentityCells({
|
||||||
value={draft.site}
|
value={draft.site}
|
||||||
location={draft.location}
|
location={draft.location}
|
||||||
locationId={draft.locationId}
|
locationId={draft.locationId}
|
||||||
|
customer={draft.customer}
|
||||||
city={sitePlace?.city}
|
city={sitePlace?.city}
|
||||||
state={sitePlace?.state}
|
state={sitePlace?.state}
|
||||||
pocName={draft.pocName}
|
pocName={draft.pocName}
|
||||||
|
|
@ -63,6 +64,7 @@ export function WoTableInlineIdentityCells({
|
||||||
site: p.site,
|
site: p.site,
|
||||||
location: p.location,
|
location: p.location,
|
||||||
locationId: p.locationId,
|
locationId: p.locationId,
|
||||||
|
...(p.customer !== undefined ? { customer: p.customer } : {}),
|
||||||
pocName: p.pocName,
|
pocName: p.pocName,
|
||||||
pocPhone: p.pocPhone,
|
pocPhone: p.pocPhone,
|
||||||
pocNotes: p.pocNotes,
|
pocNotes: p.pocNotes,
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,9 @@ export function WoTableInlineRow({
|
||||||
const siteCellRef = useRef<HTMLTableCellElement>(null);
|
const siteCellRef = useRef<HTMLTableCellElement>(null);
|
||||||
const woCellRef = useRef<HTMLTableCellElement>(null);
|
const woCellRef = useRef<HTMLTableCellElement>(null);
|
||||||
|
|
||||||
const siteCellMissing = missing.some((f) => f === "site" || f === "pocName" || f === "pocPhone");
|
const siteCellMissing = missing.some(
|
||||||
|
(f) => f === "customer" || f === "site" || f === "pocName" || f === "pocPhone",
|
||||||
|
);
|
||||||
const siteShowError = showErrors && siteCellMissing;
|
const siteShowError = showErrors && siteCellMissing;
|
||||||
const woShowError = showErrors && missing.includes("woNumber");
|
const woShowError = showErrors && missing.includes("woNumber");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ export function buildWizardSidebarSummaryRows(draft: WorkOrderWizardDraft): [str
|
||||||
["Schedule on", scheduleOnLabel(draft)],
|
["Schedule on", scheduleOnLabel(draft)],
|
||||||
["Due date", fmtDate(draft.dueDate) || "—"],
|
["Due date", fmtDate(draft.dueDate) || "—"],
|
||||||
["Assigned to", draft.assigneeName || "—"],
|
["Assigned to", draft.assigneeName || "—"],
|
||||||
|
["Account", draft.customer.trim() || "—"],
|
||||||
["Site", draft.siteCode || draft.locationName || "—"],
|
["Site", draft.siteCode || draft.locationName || "—"],
|
||||||
["POC", pocLabel(draft)],
|
["POC", pocLabel(draft)],
|
||||||
["Additional POCs", formatAdditionalPocSummary(draft)],
|
["Additional POCs", formatAdditionalPocSummary(draft)],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Building2, Info } from "lucide-react";
|
||||||
|
import { Text } from "@/components/ui/text";
|
||||||
|
import { WizardFieldSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-field-select";
|
||||||
|
import { WizLabel } from "@/app/(protected)/workorders/_components/wizard/wizard-labels";
|
||||||
|
import { useAccountsList } from "@/domain/accounts/use-cases/use-accounts-list";
|
||||||
|
|
||||||
|
type WizardStepLocationAccountSelectProps = {
|
||||||
|
customer: string;
|
||||||
|
onCustomerChange: (customer: string) => void;
|
||||||
|
showError?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function WizardStepLocationAccountSelect({
|
||||||
|
customer,
|
||||||
|
onCustomerChange,
|
||||||
|
showError = false,
|
||||||
|
}: WizardStepLocationAccountSelectProps) {
|
||||||
|
const { data } = useAccountsList({ page: 1, pageSize: 100, search: "" });
|
||||||
|
const accounts = data?.items ?? [];
|
||||||
|
const options = accounts
|
||||||
|
.map((account) => account.name.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((name) => ({ key: name, label: name }));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<WizLabel required>Account</WizLabel>
|
||||||
|
<WizardFieldSelect
|
||||||
|
value={customer}
|
||||||
|
placeholder="Select account…"
|
||||||
|
icon={
|
||||||
|
<Building2 size={16} className="shrink-0" style={{ color: "var(--muted-foreground)" }} />
|
||||||
|
}
|
||||||
|
searchPlaceholder="Search account name…"
|
||||||
|
options={options}
|
||||||
|
onPick={onCustomerChange}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
as="p"
|
||||||
|
variant="description"
|
||||||
|
className="mt-1.5 flex items-center gap-1"
|
||||||
|
sx={{ fontSize: 11, color: showError ? "var(--destructive)" : "var(--muted-foreground)" }}
|
||||||
|
when={!customer.trim()}
|
||||||
|
>
|
||||||
|
<Info size={12} className="shrink-0" /> Account is required to create a work order.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { Text } from "@/components/ui/text";
|
||||||
import type { LocationOption } from "@/domain/work-orders/types/work-order";
|
import type { LocationOption } from "@/domain/work-orders/types/work-order";
|
||||||
import type { Location } from "@/domain/locations/types/location";
|
import type { Location } from "@/domain/locations/types/location";
|
||||||
import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard";
|
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 { 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 { 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";
|
import { WizardStepLocationSiteDetails } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-site-details";
|
||||||
|
|
@ -44,6 +45,11 @@ export function WizardStepLocationService({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
|
<WizardStepLocationAccountSelect
|
||||||
|
customer={draft.customer}
|
||||||
|
showError={showErrors}
|
||||||
|
onCustomerChange={(customer) => onPatch({ customer })}
|
||||||
|
/>
|
||||||
<WizardStepLocationSiteSelect
|
<WizardStepLocationSiteSelect
|
||||||
locationId={draft.locationId}
|
locationId={draft.locationId}
|
||||||
locations={locations}
|
locations={locations}
|
||||||
|
|
|
||||||
|
|
@ -141,5 +141,10 @@ export function mapWizardDraftToBoardCreatePayload(
|
||||||
payload.locationId = locationId;
|
payload.locationId = locationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customer = draft.customer.trim();
|
||||||
|
if (customer) {
|
||||||
|
payload.customer = customer;
|
||||||
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,8 @@ export interface WorkOrderBoardCreatePayload {
|
||||||
/** Per-WO Aveta compliance flag from Service & Notes. */
|
/** Per-WO Aveta compliance flag from Service & Notes. */
|
||||||
avetaRequired?: boolean;
|
avetaRequired?: boolean;
|
||||||
locationId?: number;
|
locationId?: number;
|
||||||
|
/** Exact `Accounts.Name` for org-wide create stamp (SH-221). */
|
||||||
|
customer?: string;
|
||||||
pocContactId?: number;
|
pocContactId?: number;
|
||||||
pocName?: string;
|
pocName?: string;
|
||||||
pocPhone?: string;
|
pocPhone?: string;
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ export interface WorkOrderTableRow {
|
||||||
|
|
||||||
export interface WorkOrderTableDraft {
|
export interface WorkOrderTableDraft {
|
||||||
site: string;
|
site: string;
|
||||||
|
/** Exact CRM `Accounts.Name` sent as board create `customer`. */
|
||||||
|
customer: string;
|
||||||
woNumber: string;
|
woNumber: string;
|
||||||
type: WOType | "";
|
type: WOType | "";
|
||||||
severity: WorkOrderSeverity | null;
|
severity: WorkOrderSeverity | null;
|
||||||
|
|
@ -111,6 +113,7 @@ export interface WorkOrderTableDraft {
|
||||||
|
|
||||||
export const EMPTY_TABLE_DRAFT: WorkOrderTableDraft = {
|
export const EMPTY_TABLE_DRAFT: WorkOrderTableDraft = {
|
||||||
site: "",
|
site: "",
|
||||||
|
customer: "",
|
||||||
woNumber: "",
|
woNumber: "",
|
||||||
type: "",
|
type: "",
|
||||||
severity: null,
|
severity: null,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ export interface WorkOrderWizardDraft {
|
||||||
locationId: string;
|
locationId: string;
|
||||||
locationName: string;
|
locationName: string;
|
||||||
siteCode: string;
|
siteCode: string;
|
||||||
|
/** Exact CRM `Accounts.Name` sent as board create `customer`. */
|
||||||
|
customer: string;
|
||||||
woNumber: string;
|
woNumber: string;
|
||||||
type: WOType | "";
|
type: WOType | "";
|
||||||
/** Severity 1–5 for Emergency/Reactive; null otherwise. */
|
/** Severity 1–5 for Emergency/Reactive; null otherwise. */
|
||||||
|
|
@ -74,6 +76,7 @@ export const EMPTY_WIZARD_DRAFT: WorkOrderWizardDraft = {
|
||||||
locationId: "",
|
locationId: "",
|
||||||
locationName: "",
|
locationName: "",
|
||||||
siteCode: "",
|
siteCode: "",
|
||||||
|
customer: "",
|
||||||
woNumber: "",
|
woNumber: "",
|
||||||
type: "",
|
type: "",
|
||||||
severity: null,
|
severity: null,
|
||||||
|
|
@ -196,7 +199,7 @@ export function canContinueWizardStep1(draft: WorkOrderWizardDraft): boolean {
|
||||||
|
|
||||||
export function canContinueWizardStep2(draft: WorkOrderWizardDraft): boolean {
|
export function canContinueWizardStep2(draft: WorkOrderWizardDraft): boolean {
|
||||||
const hasSite = Boolean(draft.locationId && (draft.siteCode.trim() || draft.locationName.trim()));
|
const hasSite = Boolean(draft.locationId && (draft.siteCode.trim() || draft.locationName.trim()));
|
||||||
return Boolean(hasSite && draft.pocName.trim() && draft.pocPhone.trim());
|
return Boolean(hasSite && draft.customer.trim() && draft.pocName.trim() && draft.pocPhone.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canCreateWorkOrderFromWizard(draft: WorkOrderWizardDraft): boolean {
|
export function canCreateWorkOrderFromWizard(draft: WorkOrderWizardDraft): boolean {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
/** Required fields for board inline create (Schedule On is optional — SH-109). */
|
/** Required fields for board inline create (Schedule On is optional — SH-109). */
|
||||||
export type InlineCreateReqField = "site" | "woNumber" | "pocName" | "pocPhone";
|
export type InlineCreateReqField = "customer" | "site" | "woNumber" | "pocName" | "pocPhone";
|
||||||
|
|
||||||
export const INLINE_CREATE_REQ_LABELS: Record<InlineCreateReqField, string> = {
|
export const INLINE_CREATE_REQ_LABELS: Record<InlineCreateReqField, string> = {
|
||||||
|
customer: "Account",
|
||||||
site: "Site",
|
site: "Site",
|
||||||
woNumber: "WO #",
|
woNumber: "WO #",
|
||||||
pocName: "POC name",
|
pocName: "POC name",
|
||||||
|
|
@ -9,6 +10,7 @@ export const INLINE_CREATE_REQ_LABELS: Record<InlineCreateReqField, string> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InlineCreateDraftFields = {
|
export type InlineCreateDraftFields = {
|
||||||
|
customer?: string;
|
||||||
site?: string;
|
site?: string;
|
||||||
locationId?: string | number;
|
locationId?: string | number;
|
||||||
woNumber?: string;
|
woNumber?: string;
|
||||||
|
|
@ -18,6 +20,9 @@ export type InlineCreateDraftFields = {
|
||||||
|
|
||||||
export function getInlineCreateMissing(d: InlineCreateDraftFields): InlineCreateReqField[] {
|
export function getInlineCreateMissing(d: InlineCreateDraftFields): InlineCreateReqField[] {
|
||||||
const miss: InlineCreateReqField[] = [];
|
const miss: InlineCreateReqField[] = [];
|
||||||
|
if (!d.customer?.trim()) {
|
||||||
|
miss.push("customer");
|
||||||
|
}
|
||||||
const hasSite = Boolean(d.site?.trim()) || Boolean(String(d.locationId ?? "").trim());
|
const hasSite = Boolean(d.site?.trim()) || Boolean(String(d.locationId ?? "").trim());
|
||||||
if (!hasSite) {
|
if (!hasSite) {
|
||||||
miss.push("site");
|
miss.push("site");
|
||||||
|
|
@ -43,6 +48,9 @@ export function inlineCreateSaveHint(miss: InlineCreateReqField[]): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function inlineCreateSiteError(miss: InlineCreateReqField[]): string {
|
export function inlineCreateSiteError(miss: InlineCreateReqField[]): string {
|
||||||
|
if (miss.includes("customer")) {
|
||||||
|
return "Account is required";
|
||||||
|
}
|
||||||
if (miss.includes("site")) {
|
if (miss.includes("site")) {
|
||||||
return "Site is required";
|
return "Site is required";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ describe("buildInlineDraftCreateInput", () => {
|
||||||
const draft = {
|
const draft = {
|
||||||
...EMPTY_TABLE_DRAFT,
|
...EMPTY_TABLE_DRAFT,
|
||||||
site: "BK5",
|
site: "BK5",
|
||||||
|
customer: "Amazon",
|
||||||
locationId: "9",
|
locationId: "9",
|
||||||
woNumber: "25001",
|
woNumber: "25001",
|
||||||
pocName: "Jane",
|
pocName: "Jane",
|
||||||
|
|
@ -30,6 +31,8 @@ describe("buildInlineDraftCreateInput", () => {
|
||||||
expect(payload.scheduleWeekOnly).toBeUndefined();
|
expect(payload.scheduleWeekOnly).toBeUndefined();
|
||||||
expect(payload.woNumber).toBe("25001");
|
expect(payload.woNumber).toBe("25001");
|
||||||
expect(payload.pocName).toBe("Jane");
|
expect(payload.pocName).toBe("Jane");
|
||||||
|
expect(payload.customer).toBe("Amazon");
|
||||||
|
expect(input.customer).toBe("Amazon");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps week-only schedule when targetWeek is set", () => {
|
it("keeps week-only schedule when targetWeek is set", () => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { fireEvent, screen } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { WizardStepLocationAccountSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-account-select";
|
||||||
|
import { renderWithProviders } from "@/test/test-utils";
|
||||||
|
|
||||||
|
vi.mock("@/domain/accounts/use-cases/use-accounts-list", () => ({
|
||||||
|
useAccountsList: () => ({
|
||||||
|
data: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Amazon",
|
||||||
|
number: "",
|
||||||
|
industry: "",
|
||||||
|
website: "",
|
||||||
|
primaryContact: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
owner: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Sea Haven",
|
||||||
|
number: "",
|
||||||
|
industry: "",
|
||||||
|
website: "",
|
||||||
|
primaryContact: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
owner: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("WizardStepLocationAccountSelect", () => {
|
||||||
|
it("sets draft.customer to the exact account name on pick", () => {
|
||||||
|
const onCustomerChange = vi.fn();
|
||||||
|
renderWithProviders(
|
||||||
|
<WizardStepLocationAccountSelect customer="" onCustomerChange={onCustomerChange} />,
|
||||||
|
{ withAuth: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /select account/i }));
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Amazon" }));
|
||||||
|
|
||||||
|
expect(onCustomerChange).toHaveBeenCalledWith("Amazon");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -114,6 +114,7 @@ describe("workOrdersApi.createFromBoard", () => {
|
||||||
type: "PM",
|
type: "PM",
|
||||||
siteCode: "BK5",
|
siteCode: "BK5",
|
||||||
woNumber: "12345",
|
woNumber: "12345",
|
||||||
|
customer: "Amazon",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(apiPost).toHaveBeenCalledWith(API_PATHS.workOrder.boardCreate, {
|
expect(apiPost).toHaveBeenCalledWith(API_PATHS.workOrder.boardCreate, {
|
||||||
|
|
@ -122,6 +123,7 @@ describe("workOrdersApi.createFromBoard", () => {
|
||||||
lifecycleStatus: 1,
|
lifecycleStatus: 1,
|
||||||
woNumber: "12345",
|
woNumber: "12345",
|
||||||
isAddOn: false,
|
isAddOn: false,
|
||||||
|
customer: "Amazon",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ describe("wizard step validation", () => {
|
||||||
expect(isWizardStepValid(2, draft)).toBe(false);
|
expect(isWizardStepValid(2, draft)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows step 2 when site and POC are set", () => {
|
it("blocks step 2 without customer", () => {
|
||||||
const draft = {
|
const draft = {
|
||||||
...EMPTY_WIZARD_DRAFT,
|
...EMPTY_WIZARD_DRAFT,
|
||||||
type: "PM" as const,
|
type: "PM" as const,
|
||||||
|
|
@ -133,6 +133,21 @@ describe("wizard step validation", () => {
|
||||||
pocName: "Jordan",
|
pocName: "Jordan",
|
||||||
pocPhone: "555-0100",
|
pocPhone: "555-0100",
|
||||||
};
|
};
|
||||||
|
expect(canContinueWizardStep2(draft)).toBe(false);
|
||||||
|
expect(isWizardStepValid(2, draft)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows step 2 when site, account, 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",
|
||||||
|
};
|
||||||
expect(canContinueWizardStep2(draft)).toBe(true);
|
expect(canContinueWizardStep2(draft)).toBe(true);
|
||||||
expect(isWizardStepValid(2, draft)).toBe(true);
|
expect(isWizardStepValid(2, draft)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
@ -148,13 +163,14 @@ describe("wizard step validation", () => {
|
||||||
expect(isWizardStepValid(2, draft)).toBe(false);
|
expect(isWizardStepValid(2, draft)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows create when type, woNumber, site, and POC are set", () => {
|
it("allows create when type, woNumber, site, account, and POC are set", () => {
|
||||||
const draft = {
|
const draft = {
|
||||||
...EMPTY_WIZARD_DRAFT,
|
...EMPTY_WIZARD_DRAFT,
|
||||||
type: "PM" as const,
|
type: "PM" as const,
|
||||||
woNumber: "12345678901234567890",
|
woNumber: "12345678901234567890",
|
||||||
locationId: "99",
|
locationId: "99",
|
||||||
siteCode: "BK5",
|
siteCode: "BK5",
|
||||||
|
customer: "Amazon",
|
||||||
pocName: "Jordan",
|
pocName: "Jordan",
|
||||||
pocPhone: "555-0100",
|
pocPhone: "555-0100",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,28 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("includes customer when the draft has an account name", () => {
|
||||||
|
const payload = mapWizardDraftToBoardCreatePayload({
|
||||||
|
...EMPTY_WIZARD_DRAFT,
|
||||||
|
type: "PM",
|
||||||
|
siteCode: "BK5",
|
||||||
|
customer: " Amazon ",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(payload.customer).toBe("Amazon");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits customer when the draft account is blank", () => {
|
||||||
|
const payload = mapWizardDraftToBoardCreatePayload({
|
||||||
|
...EMPTY_WIZARD_DRAFT,
|
||||||
|
type: "PM",
|
||||||
|
siteCode: "BK5",
|
||||||
|
customer: " ",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(payload.customer).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("sets isAddOn from cutoff preview when scheduled (create hint; not frozen)", () => {
|
it("sets isAddOn from cutoff preview when scheduled (create hint; not frozen)", () => {
|
||||||
const payload = mapWizardDraftToBoardCreatePayload({
|
const payload = mapWizardDraftToBoardCreatePayload({
|
||||||
...EMPTY_WIZARD_DRAFT,
|
...EMPTY_WIZARD_DRAFT,
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ import {
|
||||||
} from "@/domain/work-orders/utils/get-inline-create-missing";
|
} from "@/domain/work-orders/utils/get-inline-create-missing";
|
||||||
|
|
||||||
describe("getInlineCreateMissing", () => {
|
describe("getInlineCreateMissing", () => {
|
||||||
it("returns empty when Site, WO#, and POC are filled even without schedule", () => {
|
it("returns empty when Account, Site, WO#, and POC are filled even without schedule", () => {
|
||||||
expect(
|
expect(
|
||||||
getInlineCreateMissing({
|
getInlineCreateMissing({
|
||||||
|
customer: "Amazon",
|
||||||
site: "BK5",
|
site: "BK5",
|
||||||
locationId: "12",
|
locationId: "12",
|
||||||
woNumber: "25001",
|
woNumber: "25001",
|
||||||
|
|
@ -22,6 +23,7 @@ describe("getInlineCreateMissing", () => {
|
||||||
it("accepts locationId alone as site presence", () => {
|
it("accepts locationId alone as site presence", () => {
|
||||||
expect(
|
expect(
|
||||||
getInlineCreateMissing({
|
getInlineCreateMissing({
|
||||||
|
customer: "Amazon",
|
||||||
site: "",
|
site: "",
|
||||||
locationId: 42,
|
locationId: 42,
|
||||||
woNumber: "25001",
|
woNumber: "25001",
|
||||||
|
|
@ -34,6 +36,7 @@ describe("getInlineCreateMissing", () => {
|
||||||
it("lists missing POC fields without requiring schedule", () => {
|
it("lists missing POC fields without requiring schedule", () => {
|
||||||
expect(
|
expect(
|
||||||
getInlineCreateMissing({
|
getInlineCreateMissing({
|
||||||
|
customer: "Amazon",
|
||||||
site: "BK5",
|
site: "BK5",
|
||||||
woNumber: "25001",
|
woNumber: "25001",
|
||||||
pocName: "",
|
pocName: "",
|
||||||
|
|
@ -42,8 +45,14 @@ describe("getInlineCreateMissing", () => {
|
||||||
).toEqual(["pocName", "pocPhone"]);
|
).toEqual(["pocName", "pocPhone"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lists site and woNumber when empty", () => {
|
it("lists account, site, and woNumber when empty", () => {
|
||||||
expect(getInlineCreateMissing({})).toEqual(["site", "woNumber", "pocName", "pocPhone"]);
|
expect(getInlineCreateMissing({})).toEqual([
|
||||||
|
"customer",
|
||||||
|
"site",
|
||||||
|
"woNumber",
|
||||||
|
"pocName",
|
||||||
|
"pocPhone",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -58,6 +67,10 @@ describe("inlineCreateSaveHint", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("inlineCreateSiteError", () => {
|
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", () => {
|
it("prefers site-required message when site is missing", () => {
|
||||||
expect(inlineCreateSiteError(["site", "pocName"])).toBe("Site is required");
|
expect(inlineCreateSiteError(["site", "pocName"])).toBe("Site is required");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue