shoc-frontend-new/src/domain/work-orders/mappers/work-order-mapper.ts
Arthur Bassi 2d64c34898 fix(work-orders): extract docStatus mapper for governance ratchet
Keep work-order-mapper under the frozen line cap and cap addDispatchSignoff params.
2026-07-29 09:31:15 -03:00

570 lines
20 KiB
TypeScript

import { mapApiStatusToWizard } from "@/domain/work-orders/mappers/wizard-draft-mapper";
import {
mapDocStatusToApi,
readDocStatusFromRecord,
} from "@/domain/work-orders/mappers/doc-status-mapper";
import type { UpliftRequest } from "@/domain/uplifts/types/uplift";
import type {
DispatchChecklistItem,
DispatchComment,
DispatchDetail,
DispatchSignoff,
DispatchToVendorPayload,
DispatchWorkOrderRef,
EmployeeOption,
LocationOption,
TaskListTemplate,
UpdateDispatchPayload,
WorkOrderAuditEntry,
WorkOrderComment,
WorkOrderDetail,
WorkOrderDispatchSummary,
WorkOrderFormValues,
WorkOrderListItem,
} from "@/domain/work-orders/types/work-order";
import type { WorkOrderStatus } from "@/domain/work-orders/types/work-order-common";
import { WORK_ORDER_STATUS_OPTIONS } from "@/domain/work-orders/types/work-order-common";
import type {
CompletionDocStatus,
WorkOrderTableRow,
} from "@/domain/work-orders/types/work-order-table-row";
import type { WOType } from "@/domain/work-orders/types/work-order-wizard";
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
import { formatApptRange } from "@/domain/work-orders/utils/wizard-date-utils";
import { toDateInputValue } from "@/lib/time-utils";
export { mapDocStatusToApi };
function asRecord(raw: unknown): Record<string, unknown> {
return typeof raw === "object" && raw !== null ? (raw as Record<string, unknown>) : {};
}
function toWorkOrderStatus(value: string): WorkOrderStatus {
if (WORK_ORDER_STATUS_OPTIONS.includes(value as WorkOrderStatus)) {
return value as WorkOrderStatus;
}
return "Open";
}
function readString(record: Record<string, unknown>, ...keys: string[]): string {
for (const key of keys) {
const value = record[key];
if (typeof value === "string") {
return value;
}
if (typeof value === "number") {
return String(value);
}
}
return "";
}
function readId(record: Record<string, unknown>): string | number {
const value = record.id ?? record.Id;
if (typeof value === "string" || typeof value === "number") {
return value;
}
return "";
}
function readNumber(record: Record<string, unknown>, ...keys: string[]): number | null {
for (const key of keys) {
const value = record[key];
if (typeof value === "number") {
return value;
}
if (typeof value === "string" && value !== "") {
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
return parsed;
}
}
}
return null;
}
function readBool(record: Record<string, unknown>, ...keys: string[]): boolean {
for (const key of keys) {
const value = record[key];
if (typeof value === "boolean") {
return value;
}
}
return false;
}
export function mapWorkOrderListItem(raw: unknown): WorkOrderListItem {
const item = asRecord(raw);
return {
id: readId(item),
number: readString(item, "internalWONumber", "number", "workOrderNumber"),
title: readString(item, "title", "Title"),
location: readString(item, "location", "Location", "locationName", "LocationName"),
priority: readString(item, "priority", "Priority"),
status: readString(item, "status", "Status"),
createdDate: readString(item, "createdDate", "CreatedDate"),
dueDate: readString(item, "dueDate", "DueDate"),
assignedTo: readString(item, "assignedTo", "AssignedTo", "employeeName", "EmployeeName"),
lastUpdated: readString(item, "lastUpdated", "LastUpdated"),
};
}
export function mapWorkOrderListItems(rawItems: unknown[]): WorkOrderListItem[] {
return rawItems.map(mapWorkOrderListItem);
}
const WO_TYPES_SET = new Set(["PM", "Reactive", "Emergency", "Add-On", "Overdue"]);
function readWoType(record: Record<string, unknown>): WOType | "" {
const raw = readString(record, "type", "Type", "source", "Source", "woType", "WoType");
if (WO_TYPES_SET.has(raw)) {
return raw as WOType;
}
return "";
}
function readDocStatus(record: Record<string, unknown>): CompletionDocStatus {
return readDocStatusFromRecord(record);
}
export function mapWorkOrderTableRow(raw: unknown): WorkOrderTableRow {
const item = asRecord(raw);
const scheduledOn = toDateInputValue(
readString(item, "scheduledOn", "scheduledDate", "ScheduledDate", "ScheduledOn"),
);
const { dayGroup, dayLabel } = deriveDayFields(scheduledOn);
const woNumber = readString(
item,
"woNumber",
"internalWONumber",
"InternalWONumber",
"number",
"workOrderNumber",
"WorkerOrderNumber",
);
const site = readString(item, "site", "Site", "locationCode", "LocationCode");
const location = readString(item, "location", "Location", "locationName", "LocationName");
const dispatcherId = readString(
item,
"dispatcherId",
"DispatcherId",
"assigneeId",
"AssigneeId",
"employeeId",
"EmployeeId",
);
const dispatcherName = readString(
item,
"dispatcherName",
"DispatcherName",
"assignedTo",
"AssignedTo",
"employeeName",
"EmployeeName",
"assignee",
"Assignee",
);
const apptStart = readString(item, "apptStart", "ApptStart", "appointmentStart");
const apptEnd = readString(item, "apptEnd", "ApptEnd", "appointmentEnd");
const apptTime =
readString(item, "apptTime", "ApptTime") ||
(apptStart ? formatApptRange(apptStart, apptEnd) : "");
const apiStatus = readString(item, "status", "Status");
const locationIdRaw = item.locationId ?? item.LocationId;
const row: WorkOrderTableRow = {
id: readId(item),
woNumber,
site: site || location.slice(0, 8),
type: readWoType(item),
dispatcherId,
dispatcherName,
scheduledOn,
completedDate: toDateInputValue(readString(item, "completedDate", "CompletedDate")),
targetWeek: readString(item, "targetWeek", "TargetWeek"),
dayGroup,
dayLabel,
location,
locationId:
typeof locationIdRaw === "string" || typeof locationIdRaw === "number" ? locationIdRaw : "",
pm: readString(item, "pm", "PM", "problem", "Problem", "service", "Service"),
company: readString(item, "company", "Company", "vendorName", "VendorName"),
tech: readString(item, "tech", "Tech", "technician", "Technician"),
techPhone: readString(item, "techPhone", "TechPhone"),
apptStart,
apptEnd,
apptTime,
status: mapApiStatusToWizard(apiStatus),
docStatus: readDocStatus(item),
pocName: readString(item, "pocName", "PocName", "POCName"),
pocPhone: readString(item, "pocPhone", "PocPhone", "POCPhone"),
pocNotes: readString(item, "pocNotes", "PocNotes", "POCNotes"),
dueDate: toDateInputValue(readString(item, "dueDate", "DueDate")),
rescheduleCount: readNumber(item, "rescheduleCount", "RescheduleCount") ?? 0,
carriedOver: readNumber(item, "carriedOver", "CarriedOver") ?? 0,
originalWeek: readString(item, "originalWeek", "OriginalWeek"),
originalDate: toDateInputValue(readString(item, "originalDate", "OriginalDate")),
isPastDue: readBool(item, "isPastDue", "IsPastDue"),
scheduleWeekOnly: readBool(item, "scheduleWeekOnly", "ScheduleWeekOnly"),
mediaCount: readNumber(item, "mediaCount", "MediaCount") ?? 0,
title: readString(item, "title", "Title", "workerOrderTitle", "WorkerOrderTitle"),
priority: readString(item, "priority", "Priority"),
};
return row;
}
export function mapWorkOrderTableRows(rawItems: unknown[]): WorkOrderTableRow[] {
return rawItems.map(mapWorkOrderTableRow);
}
export function mapWorkOrderComment(raw: unknown): WorkOrderComment {
const item = asRecord(raw);
return {
id: readId(item),
createdDate: readString(item, "createdDate", "CreatedDate"),
commenttext: readString(item, "commenttext", "Commenttext", "text", "Text"),
commentType: readString(item, "commentType", "CommentType"),
userName: readString(item, "userName", "UserName"),
commenter: readString(item, "commenter", "Commenter") || null,
recordType: readString(item, "recordType", "RecordType") || null,
};
}
export function mapWorkOrderAuditEntry(raw: unknown): WorkOrderAuditEntry {
const item = asRecord(raw);
return {
id: readId(item),
fieldName: readString(item, "fieldName", "FieldName"),
oldValue: readString(item, "oldValue", "OldValue"),
newValue: readString(item, "newValue", "NewValue"),
action: readString(item, "action", "Action"),
createdAt: readString(item, "createdAt", "CreatedAt"),
userName: readString(item, "userName", "UserName"),
};
}
export function mapWorkOrderDispatchSummary(raw: unknown): WorkOrderDispatchSummary {
const item = asRecord(raw);
return {
id: readId(item),
vendorId: readId({ id: item.vendorId ?? item.VendorId }),
dispatchNumber: readString(item, "dispatchNumber", "DispatchNumber"),
poNumber: readString(item, "poNumber", "PoNumber", "PONumber"),
vendorName: readString(item, "vendorName", "VendorName"),
vendorEmail: readString(item, "vendorEmail", "VendorEmail"),
nteAmount: readNumber(item, "nteAmount", "NTEAmount"),
status: readString(item, "status", "Status"),
dispatchedAt: readString(item, "dispatchedAt", "DispatchedAt"),
estimatedArrivalAt: readString(item, "estimatedArrivalAt", "EstimatedArrivalAt"),
etaManualOverride: readBool(item, "etaManualOverride", "EtaManualOverride"),
};
}
export function mapWorkOrderDetail(raw: unknown): WorkOrderDetail {
const wo = asRecord(raw);
const locationId = wo.locationId ?? wo.LocationId;
return {
id: readId(wo),
internalWONumber: readString(wo, "internalWONumber", "InternalWONumber"),
workOrderNumber: readString(wo, "workerOrderNumber", "workOrderNumber", "WorkerOrderNumber"),
title: readString(wo, "title", "workerOrderTitle", "WorkerOrderTitle"),
location: readString(wo, "location", "Location"),
locationId: locationId != null ? String(locationId) : "",
locationZip: readString(wo, "locationZip", "LocationZip"),
assignedTo: readString(wo, "assignee", "assignedTo", "Assignee", "AssignedTo"),
dueDate: toDateInputValue(readString(wo, "dueDate", "DueDate")),
createdDate: toDateInputValue(readString(wo, "createdDate", "CreatedDate")),
createdDateFull: readString(wo, "createdDate", "CreatedDate"),
description: readString(wo, "description", "Description"),
priority: readString(wo, "priority", "Priority"),
status: readString(wo, "status", "Status") || "Open",
po: readString(wo, "po", "PO"),
tt: readString(wo, "tt", "TT"),
problem: readString(wo, "problem", "Problem"),
trade: readString(wo, "trade", "Trade"),
subTrade: readString(wo, "subTrade", "SubTrade"),
vendorNTE: wo.vendorNTE != null ? String(wo.vendorNTE) : "",
scheduledDate: toDateInputValue(readString(wo, "scheduledDate", "ScheduledDate")),
completedDate: toDateInputValue(readString(wo, "completedDate", "CompletedDate")),
docStatus: readDocStatus(wo),
source: readString(wo, "source", "Source"),
comments: Array.isArray(wo.comments)
? wo.comments.map(mapWorkOrderComment)
: Array.isArray(wo.Comments)
? wo.Comments.map(mapWorkOrderComment)
: [],
auditLog: Array.isArray(wo.auditLog)
? wo.auditLog.map(mapWorkOrderAuditEntry)
: Array.isArray(wo.AuditLog)
? wo.AuditLog.map(mapWorkOrderAuditEntry)
: [],
dispatches: Array.isArray(wo.dispatches)
? wo.dispatches.map(mapWorkOrderDispatchSummary)
: Array.isArray(wo.Dispatches)
? wo.Dispatches.map(mapWorkOrderDispatchSummary)
: [],
};
}
export function mapWorkOrderDetailToFormValues(detail: WorkOrderDetail): WorkOrderFormValues {
return {
internalWONumber: detail.internalWONumber,
workOrderNumber: detail.workOrderNumber,
title: detail.title,
locationId: detail.locationId,
priority: detail.priority,
status: toWorkOrderStatus(detail.status),
createdDate: detail.createdDate,
dueDate: detail.dueDate,
scheduledDate: detail.scheduledDate,
completedDate: detail.completedDate,
docStatus: detail.docStatus,
assignedTo: detail.assignedTo,
description: detail.description,
po: detail.po,
tt: detail.tt,
problem: detail.problem,
trade: detail.trade,
subTrade: detail.subTrade,
vendorNTE: detail.vendorNTE,
source: detail.source,
};
}
export function mapWorkOrderFormToCreatePayload(
values: WorkOrderFormValues,
): Record<string, unknown> {
return {
WorkerOrderNumber: values.workOrderNumber,
WorkerOrderTitle: values.title,
LocationId: values.locationId ? parseInt(values.locationId, 10) : null,
Priority: values.priority || null,
Status: values.status,
CreatedDate: values.createdDate || null,
DueDate: values.dueDate || null,
ScheduledDate: values.scheduledDate || null,
CompletedDate: values.completedDate || null,
// DocStatus is a board field — persist via PATCH …/board, not EditWorkorder.
AssignTo: values.assignedTo || null,
Description: values.description,
PO: values.po || null,
TT: values.tt || null,
Problem: values.problem || null,
Trade: values.trade || null,
SubTrade: values.subTrade || null,
VendorNTE: values.vendorNTE ? parseFloat(values.vendorNTE) : null,
Source: values.source || null,
};
}
export function mapWorkOrderFormToUpdatePayload(
id: string | number,
values: WorkOrderFormValues,
): Record<string, unknown> {
return {
Id: id,
WorkerOrderNumber: values.workOrderNumber,
WorkerOrderTitle: values.title,
Description: values.description,
PO: values.po || null,
TT: values.tt || null,
AssignTo: values.assignedTo || null,
DueDate: values.dueDate || null,
Priority: values.priority || null,
Status: values.status,
LocationId: values.locationId ? parseInt(values.locationId, 10) : null,
Problem: values.problem || null,
Trade: values.trade || null,
SubTrade: values.subTrade || null,
VendorNTE: values.vendorNTE ? parseFloat(values.vendorNTE) : null,
ScheduledDate: values.scheduledDate || null,
CompletedDate: values.completedDate || null,
// DocStatus is a board field — persist via PATCH …/board, not EditWorkorder.
CreatedDate: values.createdDate || null,
Source: values.source || null,
};
}
export function mapLocationOption(raw: unknown): LocationOption {
const item = asRecord(raw);
return {
id: readId(item),
name: readString(item, "name", "Name", "locationName", "LocationName"),
};
}
export function mapLocationOptions(rawItems: unknown[]): LocationOption[] {
return rawItems.map(mapLocationOption);
}
export function mapEmployeeOption(raw: unknown): EmployeeOption {
const item = asRecord(raw);
return {
id: readId(item),
name:
readString(item, "name", "Name", "fullName", "FullName", "employeeName", "EmployeeName") ||
readString(item, "userName", "UserName"),
};
}
export function mapEmployeeOptions(rawItems: unknown[]): EmployeeOption[] {
return rawItems.map(mapEmployeeOption);
}
export { mapVendorDropdownItem, mapVendorDropdownItems } from "./work-order-vendor-mapper";
export function mapTaskListTemplate(raw: unknown): TaskListTemplate {
const item = asRecord(raw);
const items = item.items ?? item.Items;
return {
id: readId(item),
name: readString(item, "name", "Name"),
items: Array.isArray(items) ? items : [],
};
}
export function mapTaskListTemplates(rawItems: unknown[]): TaskListTemplate[] {
return rawItems.map(mapTaskListTemplate);
}
export function mapDispatchChecklistItem(raw: unknown): DispatchChecklistItem {
const item = asRecord(raw);
return {
id: readId(item),
itemText: readString(item, "itemText", "ItemText"),
isCompleted: readBool(item, "isCompleted", "IsCompleted"),
completedBy: readString(item, "completedBy", "CompletedBy") || null,
completedAt: readString(item, "completedAt", "CompletedAt") || null,
};
}
export function mapDispatchSignoff(raw: unknown): DispatchSignoff {
const item = asRecord(raw);
return {
id: readId(item),
signoffType: readString(item, "signoffType", "SignoffType"),
name: readString(item, "name", "Name"),
signature: readString(item, "signature", "Signature"),
signatureMethod: readString(item, "signatureMethod", "SignatureMethod"),
signedAt: readString(item, "signedAt", "SignedAt"),
};
}
export function mapDispatchComment(raw: unknown): DispatchComment {
const item = asRecord(raw);
return {
id: readId(item),
createdDate: readString(item, "createdDate", "CreatedDate"),
commenttext: readString(item, "commenttext", "Commenttext", "text", "Text"),
userName: readString(item, "userName", "UserName"),
commenter: readString(item, "commenter", "Commenter") || null,
};
}
export function mapDispatchWorkOrderRef(raw: unknown): DispatchWorkOrderRef {
const item = asRecord(raw);
return {
id: readId(item),
woNumber: readString(item, "woNumber", "WoNumber", "WONumber", "number", "Number"),
};
}
export function mapDispatchDetail(raw: unknown): DispatchDetail {
const item = asRecord(raw);
const checklistItems = item.checklistItems ?? item.ChecklistItems;
const signoffs = item.signoffs ?? item.Signoffs;
const comments = item.comments ?? item.Comments;
const workOrders = item.workOrders ?? item.WorkOrders;
return {
id: readId(item),
dispatchNumber: readString(item, "dispatchNumber", "DispatchNumber"),
poNumber: readString(item, "poNumber", "PoNumber", "PONumber"),
vendorName: readString(item, "vendorName", "VendorName"),
vendorEmail: readString(item, "vendorEmail", "VendorEmail"),
woNumber: readString(item, "woNumber", "WoNumber"),
status: readString(item, "status", "Status"),
nteAmount: readNumber(item, "nteAmount", "NTEAmount"),
scheduledDate: readString(item, "scheduledDate", "ScheduledDate"),
estimatedArrivalAt: readString(item, "estimatedArrivalAt", "EstimatedArrivalAt"),
etaManualOverride: readBool(item, "etaManualOverride", "EtaManualOverride"),
completedDate: readString(item, "completedDate", "CompletedDate"),
description: readString(item, "description", "Description"),
dispatchedAt: readString(item, "dispatchedAt", "DispatchedAt"),
acknowledgedAt: readString(item, "acknowledgedAt", "AcknowledgedAt"),
verifiedAt: readString(item, "verifiedAt", "VerifiedAt"),
verifiedBy: readString(item, "verifiedBy", "VerifiedBy"),
checklistItems: Array.isArray(checklistItems)
? checklistItems.map(mapDispatchChecklistItem)
: [],
signoffs: Array.isArray(signoffs) ? signoffs.map(mapDispatchSignoff) : [],
comments: Array.isArray(comments) ? comments.map(mapDispatchComment) : [],
workOrders: Array.isArray(workOrders) ? workOrders.map(mapDispatchWorkOrderRef) : [],
};
}
export function mapDispatchToVendorPayload(
payload: DispatchToVendorPayload,
): Record<string, unknown> {
return {
WorkOrderIds: payload.workOrderIds,
VendorIds: payload.vendorIds,
NTEAmount: payload.nteAmount,
ScheduledDate: payload.scheduledDate,
EstimatedArrivalAt: payload.estimatedArrivalAt,
EtaManualOverride: payload.etaManualOverride ?? false,
Description: payload.description,
TaskListTemplateId: payload.taskListTemplateId,
CustomChecklistItems: payload.customChecklistItems,
};
}
export function mapUpdateDispatchPayload(payload: UpdateDispatchPayload): Record<string, unknown> {
return {
Id: payload.id,
Status: payload.status,
NTEAmount: payload.nteAmount,
ScheduledDate: payload.scheduledDate,
EstimatedArrivalAt: payload.estimatedArrivalAt,
EtaManualOverride: payload.etaManualOverride ?? false,
CompletedDate: payload.completedDate,
Description: payload.description,
};
}
export function mapUpliftRequest(raw: unknown): UpliftRequest {
const item = asRecord(raw);
return {
id: readId(item),
status: readString(item, "status", "Status"),
currentNTE: readNumber(item, "currentNTE", "CurrentNTE"),
requestedNTE: readNumber(item, "requestedNTE", "RequestedNTE") ?? 0,
delta: readNumber(item, "delta", "Delta") ?? 0,
vendorReason: readString(item, "vendorReason", "VendorReason"),
requestedAt: readString(item, "requestedAt", "RequestedAt"),
requestedByVendorName: readString(item, "requestedByVendorName", "RequestedByVendorName"),
decidedAt: readString(item, "decidedAt", "DecidedAt"),
decidedByName: readString(item, "decidedByName", "DecidedByName"),
decisionNote: readString(item, "decisionNote", "DecisionNote"),
requiredTier: readNumber(item, "requiredTier", "RequiredTier") ?? 1,
canDecide: readBool(item, "canDecide", "CanDecide"),
};
}
export function mapUpliftRequests(rawItems: unknown[]): UpliftRequest[] {
return rawItems.map(mapUpliftRequest);
}