shoc-frontend-new/src/domain/work-orders/mappers/work-order-mapper.ts

489 lines
16 KiB
TypeScript

import { readDocStatusFromRecord } from "@/domain/work-orders/mappers/doc-status-mapper";
import { parseSiteCodeFromLocationName } from "@/domain/work-orders/mappers/work-order-board-mapper";
import type {
EmployeeOption,
LocationOption,
TaskListTemplate,
WorkOrderAuditEntry,
WorkOrderComment,
WorkOrderDetail,
WorkOrderDispatchSummary,
WorkOrderFormValues,
WorkOrderListItem,
} from "@/domain/work-orders/types/work-order";
import type { UpliftRequest } from "@/domain/uplifts/types/uplift";
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 { toDateInputValue } from "@/lib/time-utils";
export { mapDocStatusToApi } from "@/domain/work-orders/mappers/doc-status-mapper";
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;
}
const fromEditEnum: Record<string, WorkOrderStatus> = {
Open: "Open",
InProgress: "In Progress",
OnHold: "On Hold",
Completed: "Done",
Cancelled: "Cancelled",
Canceled: "Cancelled",
};
return fromEditEnum[value] ?? "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 readIdList(raw: unknown): number[] | undefined {
if (!Array.isArray(raw)) {
return undefined;
}
const ids: number[] = [];
for (const entry of raw) {
if (typeof entry === "number" && !Number.isNaN(entry)) {
ids.push(entry);
continue;
}
if (typeof entry === "string" && entry !== "") {
const parsed = Number(entry);
if (!Number.isNaN(parsed)) {
ids.push(parsed);
}
continue;
}
if (typeof entry === "object" && entry !== null) {
const id = readNumber(
asRecord(entry),
"id",
"Id",
"contactId",
"ContactId",
"categoryId",
"CategoryId",
);
if (id != null) {
ids.push(id);
}
}
}
return ids;
}
function readOptionalIdList(
record: Record<string, unknown>,
...keys: string[]
): number[] | undefined {
for (const key of keys) {
if (!(key in record)) {
continue;
}
const list = readIdList(record[key]);
if (list !== undefined) {
return list;
}
}
return undefined;
}
const FORM_STATUS_TO_EDIT_ENUM: Record<string, string> = {
Open: "Open",
"In Progress": "InProgress",
InProgress: "InProgress",
"On Hold": "OnHold",
OnHold: "OnHold",
Done: "Completed",
Completed: "Completed",
Cancelled: "Cancelled",
Canceled: "Cancelled",
};
const FORM_PRIORITY_TO_EDIT_ENUM: Record<string, string> = {
"Sev 1": "Critical",
"Sev 2": "High",
"Sev 3": "Medium",
"Sev 4": "Low",
"Sev 5": "Low",
Low: "Low",
Medium: "Medium",
High: "High",
Critical: "Critical",
};
export function mapFormStatusToEditEnum(status: string): string {
return FORM_STATUS_TO_EDIT_ENUM[status] ?? status;
}
export function mapFormPriorityToEditEnum(priority: string): string {
return FORM_PRIORITY_TO_EDIT_ENUM[priority] ?? priority;
}
function appendIfPresent(
formData: FormData,
key: string,
value: string | number | null | undefined,
): void {
if (value === null || value === undefined) {
return;
}
const asString = String(value).trim();
if (asString === "") {
return;
}
formData.append(key, asString);
}
function appendIdList(formData: FormData, key: string, ids: number[] | undefined): void {
if (ids === undefined) {
return;
}
for (const id of ids) {
formData.append(key, String(id));
}
}
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);
}
export { mapWorkOrderTableRow, mapWorkOrderTableRows } from "./work-order-table-row-mapper";
export function mapWorkOrderComment(raw: unknown): WorkOrderComment {
const item = asRecord(raw);
const authorId = readString(item, "authorId", "AuthorId") || null;
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", "authorName", "AuthorName"),
authorId,
commenter: readString(item, "commenter", "Commenter") || null,
recordType: readString(item, "recordType", "RecordType") || null,
};
}
export function mapWorkOrderAuditEntry(raw: unknown): WorkOrderAuditEntry {
const item = asRecord(raw);
const typeRaw = readString(item, "type", "Type").toLowerCase();
const type =
typeRaw === "system"
? ("system" as const)
: typeRaw === "manual"
? ("manual" as const)
: undefined;
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", "time", "Time"),
userName: readString(item, "userName", "UserName"),
type,
dispatcherId: readString(item, "dispatcherId", "DispatcherId") || undefined,
};
}
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;
const contactIds = readOptionalIdList(wo, "contactIds", "ContactIds", "contacts", "Contacts");
const categoryIds = readOptionalIdList(
wo,
"categoryIds",
"CategoryIds",
"categories",
"Categories",
);
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 is a board field — persist via PATCH …/board, not EditWorkorder.
docStatus: readDocStatusFromRecord(wo),
source: readString(wo, "source", "Source"),
...(contactIds !== undefined ? { contactIds } : {}),
...(categoryIds !== undefined ? { categoryIds } : {}),
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,
...(detail.contactIds !== undefined ? { contactIds: detail.contactIds } : {}),
...(detail.categoryIds !== undefined ? { categoryIds: detail.categoryIds } : {}),
};
}
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,
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 mapWorkOrderFormToUpdateFormData(
id: string | number,
values: WorkOrderFormValues,
): FormData {
const formData = new FormData();
formData.append("Id", String(id));
appendIfPresent(formData, "WorkerOrderTitle", values.title);
appendIfPresent(formData, "Description", values.description);
appendIfPresent(formData, "Status", mapFormStatusToEditEnum(values.status));
appendIfPresent(formData, "Priority", mapFormPriorityToEditEnum(values.priority));
const locationId = values.locationId ? parseInt(values.locationId, 10) : NaN;
appendIfPresent(formData, "LocationId", Number.isNaN(locationId) ? undefined : locationId);
appendIfPresent(formData, "AssignTo", values.assignedTo);
appendIfPresent(formData, "DueDate", values.dueDate);
appendIfPresent(formData, "CompletedDate", values.completedDate);
appendIdList(formData, "ContactIds", values.contactIds);
appendIdList(formData, "CategoryIds", values.categoryIds);
return formData;
}
export function mapLocationOption(raw: unknown): LocationOption {
const item = asRecord(raw);
const name = readString(item, "name", "Name", "locationName", "LocationName");
const siteCode =
readString(item, "siteCode", "SiteCode", "code", "Code") || parseSiteCodeFromLocationName(name);
return {
id: readId(item),
name,
...(siteCode ? { siteCode } : {}),
};
}
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 {
mapDispatchChecklistItem,
mapDispatchComment,
mapDispatchDetail,
mapDispatchSignoff,
mapDispatchToVendorPayload,
mapDispatchWorkOrderRef,
mapUpdateDispatchPayload,
} from "./work-order-dispatch-mapper";
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);
}