mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 10:53:13 +00:00
Stop treating integration source as WorkOrderType. Enrich detail from board detail for SH-114/SH-183 gates. Co-authored-by: Cursor <cursoragent@cursor.com>
489 lines
16 KiB
TypeScript
489 lines
16 KiB
TypeScript
import { parseSiteCodeFromLocationName } from "@/domain/work-orders/mappers/work-order-board-mapper";
|
|
import {
|
|
readDetailDocStatus,
|
|
readDetailMediaCount,
|
|
readDetailWoType,
|
|
} from "@/domain/work-orders/mappers/read-detail-closability-fields";
|
|
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";
|
|
|
|
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);
|
|
|
|
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);
|
|
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")),
|
|
source: readString(wo, "source", "Source"),
|
|
type: readDetailWoType(wo),
|
|
docStatus: readDetailDocStatus(wo),
|
|
mediaCount: readDetailMediaCount(wo),
|
|
...(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,
|
|
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);
|
|
}
|