mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 17:53:12 +00:00
Align create, wizard, and board status patches with AAP maybeAutoSchedule. Include the previous status in audit copy when the API sends it.
195 lines
6 KiB
TypeScript
195 lines
6 KiB
TypeScript
import {
|
|
ALL_WIZARD_STATUSES,
|
|
type WizardWOStatus,
|
|
} from "@/domain/work-orders/types/work-order-wizard";
|
|
import type {
|
|
WorkOrderTablePatch,
|
|
WorkOrderTableRow,
|
|
} from "@/domain/work-orders/types/work-order-table-row";
|
|
import {
|
|
PENDING_UPLIFT_GAP,
|
|
UPLIFT_PENDING_COMPLETED_TITLE,
|
|
} from "@/domain/work-orders/utils/wo-closability";
|
|
|
|
/** AAP `ALL_STATUSES` — Canceled is kebab-only. */
|
|
export const BOARD_INLINE_STATUSES: readonly WizardWOStatus[] = ALL_WIZARD_STATUSES;
|
|
|
|
/** AAP `CREATION_STATUSES` — wizard Step 3 and inline create. */
|
|
export const CREATION_INLINE_STATUSES: readonly WizardWOStatus[] = [
|
|
"Incomplete",
|
|
"Pending",
|
|
"Scheduled",
|
|
];
|
|
|
|
export const SCHEDULED_NEEDS_DATE_MSG =
|
|
"Set a Schedule On date before marking this work order as Scheduled.";
|
|
|
|
export const PAST_DUE_BADGE_TOOLTIP =
|
|
"This WO missed its service window. Reschedule to unlock status updates.";
|
|
|
|
export const PAST_DUE_POPOVER_BODY =
|
|
"This work order is past due. A new date must be scheduled before the status can be updated.";
|
|
|
|
export const PAST_DUE_RESCHEDULE_CTA = "Reschedule →";
|
|
|
|
export const COMPLETED_DOC_BLOCK_MSG =
|
|
"Generate or mark the completion document as not required before completing this work order";
|
|
|
|
export const COMPLETED_MEDIA_BLOCK_MSG =
|
|
"Upload at least one completion photo before completing this work order.";
|
|
|
|
const COMPLETE_ACTION = "completing this work order";
|
|
|
|
const EMPTY_FIELD_TOKENS = new Set(["", "-", "—", "N/A", "n/a"]);
|
|
|
|
const PRE_SCHEDULE_STATUSES = new Set<string>(["Incomplete", "Pending", ""]);
|
|
|
|
const FIELD_GAP_TO_AAP_LABEL: [string, string][] = [
|
|
["Dispatcher", "Assigned To"],
|
|
["Company", "Vendor"],
|
|
["Completed Date", "Completed Date"],
|
|
["Service", "Service"],
|
|
];
|
|
|
|
export type ScheduleDateInput = {
|
|
scheduledOn?: string;
|
|
scheduleWeekOnly?: boolean;
|
|
schedMode?: string;
|
|
};
|
|
|
|
export type StatusOptionState = {
|
|
disabled: boolean;
|
|
title?: string;
|
|
};
|
|
|
|
export function isLifecycleFieldEmpty(value?: string): boolean {
|
|
if (value == null) {
|
|
return true;
|
|
}
|
|
const trimmed = value.trim();
|
|
return EMPTY_FIELD_TOKENS.has(trimmed);
|
|
}
|
|
|
|
/** AAP `hasScheduleDate`: concrete day/range only — week-only does not count. */
|
|
export function hasConcreteScheduleDate(row: ScheduleDateInput): boolean {
|
|
if (row.scheduleWeekOnly === true || row.schedMode === "week") {
|
|
return false;
|
|
}
|
|
return !isLifecycleFieldEmpty(row.scheduledOn);
|
|
}
|
|
|
|
export function isPreScheduleStatus(status: string): boolean {
|
|
return PRE_SCHEDULE_STATUSES.has(status);
|
|
}
|
|
|
|
export function formatAapCompletedBlockTitle(gaps: string[]): string | undefined {
|
|
if (gaps.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
const fields = FIELD_GAP_TO_AAP_LABEL.filter(([gap]) => gaps.includes(gap)).map(
|
|
([, label]) => label,
|
|
);
|
|
|
|
if (fields.length === 1 && fields[0] === "Service") {
|
|
return `Select a service before ${COMPLETE_ACTION}.`;
|
|
}
|
|
if (fields.length > 0) {
|
|
return `Fill in ${fields.join(", ")} before ${COMPLETE_ACTION}.`;
|
|
}
|
|
if (gaps.includes("WO number")) {
|
|
return `Assign a real APM number (not SH) before ${COMPLETE_ACTION}.`;
|
|
}
|
|
if (gaps.includes(PENDING_UPLIFT_GAP)) {
|
|
return UPLIFT_PENDING_COMPLETED_TITLE;
|
|
}
|
|
if (gaps.includes("Completion document")) {
|
|
return COMPLETED_DOC_BLOCK_MSG;
|
|
}
|
|
if (gaps.includes("At least 1 photo")) {
|
|
return COMPLETED_MEDIA_BLOCK_MSG;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getStatusOptionState(
|
|
row: ScheduleDateInput,
|
|
status: WizardWOStatus,
|
|
closabilityGaps: string[],
|
|
): StatusOptionState {
|
|
if (status === "Scheduled" && !hasConcreteScheduleDate(row)) {
|
|
return { disabled: true, title: SCHEDULED_NEEDS_DATE_MSG };
|
|
}
|
|
if (status === "Completed") {
|
|
const title = formatAapCompletedBlockTitle(closabilityGaps);
|
|
if (title) {
|
|
return { disabled: true, title };
|
|
}
|
|
}
|
|
return { disabled: false };
|
|
}
|
|
|
|
/** AAP `maybeAutoSchedule` — no dispatcher requirement. */
|
|
export function maybeAutoSchedulePatch(
|
|
row: ScheduleDateInput & { status?: string },
|
|
): WorkOrderTablePatch {
|
|
const hasDate = hasConcreteScheduleDate(row);
|
|
if (hasDate && isPreScheduleStatus(row.status ?? "")) {
|
|
return { status: "Scheduled" };
|
|
}
|
|
if (!hasDate && row.status === "Scheduled") {
|
|
return { status: "Pending" };
|
|
}
|
|
return {};
|
|
}
|
|
|
|
function patchTouchesScheduleOrStatus(patch: WorkOrderTablePatch): boolean {
|
|
return (
|
|
patch.status !== undefined ||
|
|
patch.scheduledOn !== undefined ||
|
|
patch.scheduleWeekOnly !== undefined ||
|
|
patch.targetWeek !== undefined
|
|
);
|
|
}
|
|
|
|
export function mergeAutoSchedulePatch(
|
|
row: WorkOrderTableRow | undefined,
|
|
patch: WorkOrderTablePatch,
|
|
): WorkOrderTablePatch {
|
|
if (!row || !patchTouchesScheduleOrStatus(patch)) {
|
|
return patch;
|
|
}
|
|
const merged = {
|
|
status: patch.status ?? row.status,
|
|
scheduledOn: patch.scheduledOn !== undefined ? patch.scheduledOn : row.scheduledOn,
|
|
scheduleWeekOnly:
|
|
patch.scheduleWeekOnly !== undefined ? patch.scheduleWeekOnly : row.scheduleWeekOnly,
|
|
};
|
|
return { ...patch, ...maybeAutoSchedulePatch(merged) };
|
|
}
|
|
|
|
/** Apply schedule fields and the AAP auto-status for that next schedule. */
|
|
export function withAutoScheduleStatus<T extends ScheduleDateInput & { status?: string }>(
|
|
current: T,
|
|
schedulePatch: Partial<T> & ScheduleDateInput,
|
|
): Partial<T> {
|
|
const next = {
|
|
status: schedulePatch.status ?? current.status ?? "",
|
|
scheduledOn:
|
|
schedulePatch.scheduledOn !== undefined ? schedulePatch.scheduledOn : current.scheduledOn,
|
|
scheduleWeekOnly:
|
|
schedulePatch.scheduleWeekOnly !== undefined
|
|
? schedulePatch.scheduleWeekOnly
|
|
: current.scheduleWeekOnly,
|
|
};
|
|
return { ...schedulePatch, ...maybeAutoSchedulePatch(next) };
|
|
}
|
|
|
|
/** AAP create: apply maybeAutoSchedule to the draft before POST. */
|
|
export function resolveCreationStatus(row: ScheduleDateInput & { status: string }): WizardWOStatus {
|
|
const autoStatus = maybeAutoSchedulePatch(row).status;
|
|
if (autoStatus === "Scheduled" || autoStatus === "Pending") {
|
|
return autoStatus;
|
|
}
|
|
return (row.status || "Incomplete") as WizardWOStatus;
|
|
}
|