shoc-frontend-new/src/domain/work-orders/mappers/work-order-board-mapper.ts
Arthur Bassi b4cb9d0288
Some checks failed
CI / ci (push) Has been cancelled
CI / governance (push) Has been cancelled
CI / vendor-visual-regression (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
feat(work-orders): severity gates and wizard type/WO# parity (SH-192) (#106)
* fix(work-orders): allow blank WO number in wizard create path

[recover] remove malicious eslint payload (was d0d08508)

* fix(work-orders): emit severity before type in board scalar PATCH ops

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(work-orders): drop vendor collateral from SH-192 lane

* fix(work-orders): add severity to unscheduled highlight fixture after dev merge

* refactor(work-orders): extract inline type/severity cell under function line cap

* fix(work-orders): add severity to comp-doc and media fixtures after dev merge

PR #45 introduced completion-doc, media and table-mutation fixtures that predate the required severity field, so the integrated head failed tsc. Fill severity on those row builders (null for PM, SEV 1 for the Emergency fixture) and type the mutation BASE_ROW instead of asserting it.

* test(work-orders): align merge fixes with severity field and WO# placeholder

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Arthur Bassi <bassi-arthurr@users.noreply.github.com>
Co-authored-by: Alexandre Brandizzi <alex_brandizzi@hotmail.com>
2026-08-14 11:59:52 -03:00

137 lines
3.3 KiB
TypeScript

import type {
WorkOrderBoardCreatePayload,
WorkOrderTypeApi,
} from "@/domain/work-orders/types/work-order-board";
import {
LIFECYCLE_STATUS_API,
WORK_ORDER_TYPE_API,
} from "@/domain/work-orders/types/work-order-board";
import type {
WizardWOStatus,
WOType,
WorkOrderWizardDraft,
} from "@/domain/work-orders/types/work-order-wizard";
import { fmtDateRange } from "@/domain/work-orders/utils/wizard-date-utils";
import { assignWizardScheduleFields } from "@/domain/work-orders/mappers/assign-wizard-schedule-fields";
import { assignWizardVendorApptFields } from "@/domain/work-orders/mappers/assign-wizard-vendor-appt-fields";
import { assignWizardServicePocFields } from "@/domain/work-orders/mappers/assign-wizard-service-poc-fields";
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");
}
export function mapWizardDraftToBoardCreatePayload(
draft: WorkOrderWizardDraft,
): WorkOrderBoardCreatePayload {
const workOrderType = mapWizardTypeToApi(draft.type || "PM");
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.status) {
payload.lifecycleStatus = LIFECYCLE_STATUS_API[draft.status as WizardWOStatus];
}
if (draft.woNumber.trim()) {
payload.woNumber = draft.woNumber.trim();
}
if (draft.assigneeId.trim()) {
payload.assignTo = draft.assigneeId.trim();
}
assignWizardScheduleFields(payload, draft);
assignWizardVendorApptFields(payload, draft);
assignWizardServicePocFields(payload, draft);
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;
}