mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 21:42:06 +00:00
250 lines
5.3 KiB
TypeScript
250 lines
5.3 KiB
TypeScript
|
|
import type {
|
||
|
|
WorkOrderBoardCreatePayload,
|
||
|
|
WorkOrderTypeApi,
|
||
|
|
} from "@/domain/work-orders/types/work-order-board";
|
||
|
|
|
||
|
|
import { WORK_ORDER_TYPE_API } from "@/domain/work-orders/types/work-order-board";
|
||
|
|
|
||
|
|
import type { WOType, WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard";
|
||
|
|
|
||
|
|
import {
|
||
|
|
fmtDateRange,
|
||
|
|
formatApptTimeValue,
|
||
|
|
mondayOf,
|
||
|
|
} from "@/domain/work-orders/utils/wizard-date-utils";
|
||
|
|
|
||
|
|
const WO_TYPE_TO_API: Record<WOType, WorkOrderTypeApi> = {
|
||
|
|
PM: WORK_ORDER_TYPE_API.PM,
|
||
|
|
|
||
|
|
Emergency: WORK_ORDER_TYPE_API.Emergency,
|
||
|
|
|
||
|
|
Reactive: WORK_ORDER_TYPE_API.Reactive,
|
||
|
|
|
||
|
|
"Add-On": WORK_ORDER_TYPE_API.AddOn,
|
||
|
|
|
||
|
|
Overdue: WORK_ORDER_TYPE_API.Other,
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Leading token of a location label, e.g. `BK5` from `BK5 · Dallas` or `CHI-1042` from `CHI-1042 — Chicago`. */
|
||
|
|
|
||
|
|
export function parseSiteCodeFromLocationName(name: string): string {
|
||
|
|
const trimmed = name.trim();
|
||
|
|
|
||
|
|
if (!trimmed) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
const match = trimmed.match(/^([A-Za-z0-9-]+)/);
|
||
|
|
|
||
|
|
return match?.[1] ?? "";
|
||
|
|
}
|
||
|
|
|
||
|
|
export function resolveSiteCode(option: {
|
||
|
|
siteCode?: string;
|
||
|
|
|
||
|
|
name?: string;
|
||
|
|
}): string {
|
||
|
|
if (option.siteCode?.trim()) {
|
||
|
|
return option.siteCode.trim();
|
||
|
|
}
|
||
|
|
|
||
|
|
return parseSiteCodeFromLocationName(option.name ?? "");
|
||
|
|
}
|
||
|
|
|
||
|
|
export function mapWizardTypeToApi(type: WOType | ""): WorkOrderTypeApi | null {
|
||
|
|
if (!type) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return WO_TYPE_TO_API[type];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
|
||
|
|
* Legacy/general description only.
|
||
|
|
|
||
|
|
* Service notes, extras, POC, and vendor notes are sent as structured fields.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
export function buildBoardDescription(draft: WorkOrderWizardDraft): string {
|
||
|
|
const parts: string[] = [];
|
||
|
|
|
||
|
|
if (draft.schedMode === "range" && draft.scheduledOn && draft.scheduleEnd) {
|
||
|
|
parts.push(`Schedule range: ${fmtDateRange(draft.scheduledOn, draft.scheduleEnd)}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return parts.join("\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
function trimOrUndefined(value: string | undefined): string | undefined {
|
||
|
|
const trimmed = value?.trim();
|
||
|
|
|
||
|
|
return trimmed ? trimmed : undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
function normalizeExtraServices(
|
||
|
|
primary: string | undefined,
|
||
|
|
extras: string[],
|
||
|
|
): string[] | undefined {
|
||
|
|
if (extras.length === 0) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
const seen = new Set<string>();
|
||
|
|
|
||
|
|
const primaryKey = primary?.toLowerCase() ?? "";
|
||
|
|
|
||
|
|
const out: string[] = [];
|
||
|
|
|
||
|
|
for (const raw of extras) {
|
||
|
|
const trimmed = raw.trim();
|
||
|
|
|
||
|
|
if (!trimmed) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const key = trimmed.toLowerCase();
|
||
|
|
|
||
|
|
if (key === primaryKey || seen.has(key)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
seen.add(key);
|
||
|
|
|
||
|
|
out.push(trimmed);
|
||
|
|
}
|
||
|
|
|
||
|
|
return out.length > 0 ? out : undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function mapWizardDraftToBoardCreatePayload(
|
||
|
|
draft: WorkOrderWizardDraft,
|
||
|
|
): WorkOrderBoardCreatePayload {
|
||
|
|
const workOrderType = mapWizardTypeToApi(draft.type);
|
||
|
|
|
||
|
|
const siteCode = draft.siteCode.trim() || resolveSiteCode({ name: draft.locationName });
|
||
|
|
|
||
|
|
if (workOrderType === null) {
|
||
|
|
throw new Error("Work order type is required");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!siteCode) {
|
||
|
|
throw new Error("Site code is required");
|
||
|
|
}
|
||
|
|
|
||
|
|
const payload: WorkOrderBoardCreatePayload = {
|
||
|
|
workOrderType,
|
||
|
|
|
||
|
|
siteCode,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (draft.woNumber.trim()) {
|
||
|
|
payload.woNumber = draft.woNumber.trim();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (draft.assigneeId.trim()) {
|
||
|
|
payload.assignTo = draft.assigneeId.trim();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (draft.dueDate) {
|
||
|
|
payload.dueDate = draft.dueDate;
|
||
|
|
}
|
||
|
|
|
||
|
|
const isRange = draft.schedMode === "range";
|
||
|
|
|
||
|
|
if (!isRange && draft.scheduleWeekOnly) {
|
||
|
|
payload.scheduleWeekOnly = true;
|
||
|
|
|
||
|
|
const week = draft.targetWeek?.trim() || (draft.scheduledOn ? mondayOf(draft.scheduledOn) : "");
|
||
|
|
|
||
|
|
if (week) {
|
||
|
|
payload.targetWeek = week;
|
||
|
|
}
|
||
|
|
} else if (!isRange && draft.scheduledOn) {
|
||
|
|
payload.scheduledDate = draft.scheduledOn;
|
||
|
|
}
|
||
|
|
|
||
|
|
const vendorId = draft.vendorId.trim() ? Number(draft.vendorId) : NaN;
|
||
|
|
|
||
|
|
const hasVendor = Number.isFinite(vendorId);
|
||
|
|
|
||
|
|
if (hasVendor) {
|
||
|
|
payload.vendorId = vendorId;
|
||
|
|
}
|
||
|
|
|
||
|
|
const apptTime = formatApptTimeValue(draft.apptStart, draft.apptEnd);
|
||
|
|
|
||
|
|
if (hasVendor && (draft.scheduledOn || apptTime) && !draft.scheduleWeekOnly && !isRange) {
|
||
|
|
if (draft.scheduledOn) {
|
||
|
|
payload.apptDate = draft.scheduledOn;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (apptTime) {
|
||
|
|
payload.apptTime = apptTime;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const primaryService = trimOrUndefined(draft.pm);
|
||
|
|
|
||
|
|
if (primaryService) {
|
||
|
|
payload.primaryService = primaryService;
|
||
|
|
}
|
||
|
|
|
||
|
|
const extraServices = normalizeExtraServices(primaryService, draft.extraServices);
|
||
|
|
|
||
|
|
if (extraServices) {
|
||
|
|
payload.extraServices = extraServices;
|
||
|
|
}
|
||
|
|
|
||
|
|
const serviceNotes = trimOrUndefined(draft.pmNote);
|
||
|
|
|
||
|
|
if (serviceNotes) {
|
||
|
|
payload.serviceNotes = serviceNotes;
|
||
|
|
}
|
||
|
|
|
||
|
|
const pocName = trimOrUndefined(draft.pocName);
|
||
|
|
|
||
|
|
if (pocName) {
|
||
|
|
payload.pocName = pocName;
|
||
|
|
}
|
||
|
|
|
||
|
|
const pocPhone = trimOrUndefined(draft.pocPhone);
|
||
|
|
|
||
|
|
if (pocPhone) {
|
||
|
|
payload.pocPhone = pocPhone;
|
||
|
|
}
|
||
|
|
|
||
|
|
const pocNotes = trimOrUndefined(draft.pocNotes);
|
||
|
|
|
||
|
|
if (pocNotes) {
|
||
|
|
payload.pocNotes = pocNotes;
|
||
|
|
}
|
||
|
|
|
||
|
|
const techPhone = trimOrUndefined(draft.techPhone);
|
||
|
|
|
||
|
|
if (techPhone) {
|
||
|
|
payload.techPhone = techPhone;
|
||
|
|
}
|
||
|
|
|
||
|
|
const vendorNotes = trimOrUndefined(draft.vendorNotes);
|
||
|
|
|
||
|
|
if (vendorNotes) {
|
||
|
|
payload.vendorNotes = vendorNotes;
|
||
|
|
}
|
||
|
|
|
||
|
|
const description = buildBoardDescription(draft);
|
||
|
|
|
||
|
|
if (description) {
|
||
|
|
payload.description = description;
|
||
|
|
}
|
||
|
|
|
||
|
|
const locationId = draft.locationId.trim() ? Number(draft.locationId) : NaN;
|
||
|
|
|
||
|
|
if (Number.isFinite(locationId)) {
|
||
|
|
payload.locationId = locationId;
|
||
|
|
}
|
||
|
|
|
||
|
|
return payload;
|
||
|
|
}
|