mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-01 12:03:11 +00:00
200 lines
5.9 KiB
TypeScript
200 lines
5.9 KiB
TypeScript
import { SEVERITY_LEVELS } from "@/domain/work-orders/types/work-order-severity";
|
|
import type { WorkOrderAuditEntry } from "@/domain/work-orders/types/work-order-detail";
|
|
|
|
export type AuditActionSource = Pick<
|
|
WorkOrderAuditEntry,
|
|
"action" | "fieldName" | "oldValue" | "newValue"
|
|
>;
|
|
|
|
export type FormattedAuditValue = {
|
|
text: string;
|
|
/** Per-item quotes already applied; omit outer quotes in sentence templates. */
|
|
isQuotedList: boolean;
|
|
};
|
|
|
|
export function wrapAuditValue(value: FormattedAuditValue): string {
|
|
if (!value.text) return value.text;
|
|
return value.isQuotedList ? value.text : `"${value.text}"`;
|
|
}
|
|
|
|
export type AuditActionRuleContext = {
|
|
entry: AuditActionSource;
|
|
fieldKey: string;
|
|
actionKey: string;
|
|
oldValue: FormattedAuditValue;
|
|
newValue: FormattedAuditValue;
|
|
};
|
|
|
|
export type AuditActionRule = (ctx: AuditActionRuleContext) => string | null;
|
|
|
|
function formatSeverityAuditLabel(raw: string): string {
|
|
const n = Number(raw.trim());
|
|
if (n === 1 || n === 2 || n === 3 || n === 4 || n === 5) {
|
|
return SEVERITY_LEVELS[n].label;
|
|
}
|
|
const sevMatch = raw.trim().match(/^sev\s*([1-5])$/i);
|
|
if (sevMatch) {
|
|
return `SEV ${sevMatch[1]}`;
|
|
}
|
|
return raw;
|
|
}
|
|
|
|
const CLEARED_AUDIT_VALUE: FormattedAuditValue = { text: "—", isQuotedList: false };
|
|
|
|
/** `from X to Y` whenever the previous value is known; a cleared value reads as "—". */
|
|
function formatFieldChangeSentence(
|
|
label: string,
|
|
oldValue: FormattedAuditValue,
|
|
newValue: FormattedAuditValue,
|
|
): string | null {
|
|
if (!oldValue.text && !newValue.text) return null;
|
|
const next = wrapAuditValue(newValue.text ? newValue : CLEARED_AUDIT_VALUE);
|
|
if (oldValue.text) {
|
|
return `Changed ${label} from ${wrapAuditValue(oldValue)} to ${next}`;
|
|
}
|
|
return `Changed ${label} to ${next}`;
|
|
}
|
|
|
|
function tryTypeRule({
|
|
fieldKey,
|
|
actionKey,
|
|
oldValue,
|
|
newValue,
|
|
}: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey === "type" ||
|
|
fieldKey === "workordertype" ||
|
|
fieldKey === "wotype" ||
|
|
actionKey === "typechanged" ||
|
|
actionKey.includes("workordertype");
|
|
if (!matches) return null;
|
|
|
|
return formatFieldChangeSentence("Type", oldValue, newValue);
|
|
}
|
|
|
|
function severityValue(value: FormattedAuditValue): FormattedAuditValue {
|
|
return value.text ? { text: formatSeverityAuditLabel(value.text), isQuotedList: false } : value;
|
|
}
|
|
|
|
function trySeverityRule({
|
|
fieldKey,
|
|
actionKey,
|
|
oldValue,
|
|
newValue,
|
|
}: AuditActionRuleContext): string | null {
|
|
const matches = fieldKey.includes("severity") || actionKey.includes("severity");
|
|
if (!matches) return null;
|
|
|
|
return formatFieldChangeSentence("Severity", severityValue(oldValue), severityValue(newValue));
|
|
}
|
|
|
|
function tryStatusRule({
|
|
fieldKey,
|
|
actionKey,
|
|
oldValue,
|
|
newValue,
|
|
}: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey.includes("status") || fieldKey === "lifecyclestatus" || actionKey.includes("status");
|
|
if (!matches || !newValue.text) return null;
|
|
|
|
if (oldValue.text) {
|
|
return `Changed status from ${wrapAuditValue(oldValue)} to ${wrapAuditValue(newValue)}`;
|
|
}
|
|
return `Changed status to ${wrapAuditValue(newValue)}`;
|
|
}
|
|
|
|
function tryApptRule({
|
|
fieldKey,
|
|
actionKey,
|
|
oldValue,
|
|
newValue,
|
|
}: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey.includes("appt") || fieldKey.includes("appointment") || actionKey.includes("appt");
|
|
if (!matches) return null;
|
|
|
|
if (oldValue.text && newValue.text) {
|
|
return `Changed appt time from ${wrapAuditValue(oldValue)} to ${wrapAuditValue(newValue)}`;
|
|
}
|
|
if (newValue.text) return `Changed appt time to ${wrapAuditValue(newValue)}`;
|
|
|
|
return null;
|
|
}
|
|
|
|
function tryScheduleRule({ fieldKey, actionKey, newValue }: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey.includes("scheduled") ||
|
|
fieldKey === "schedule" ||
|
|
fieldKey.includes("scheduledate") ||
|
|
fieldKey.includes("duedate") ||
|
|
actionKey === "rescheduled" ||
|
|
actionKey.includes("reschedule");
|
|
if (!matches) return null;
|
|
|
|
if (newValue.text) return `Changed schedule to ${wrapAuditValue(newValue)}`;
|
|
|
|
return "Rescheduled work order";
|
|
}
|
|
|
|
function tryCarriedOverRule({ fieldKey, actionKey, entry }: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey.includes("carriedover") ||
|
|
actionKey === "weekrolled" ||
|
|
actionKey.includes("carryover");
|
|
if (!matches) return null;
|
|
|
|
const increment = Number(entry.newValue?.trim() ?? "");
|
|
if (Number.isFinite(increment) && increment > 0) {
|
|
return `Carried over to the next week (increment #${increment})`;
|
|
}
|
|
|
|
return "Carried over to the next week";
|
|
}
|
|
|
|
function tryPastDueRule({ fieldKey, actionKey }: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey.includes("pastdue") || actionKey.includes("pastdue") || actionKey === "markedpastdue";
|
|
if (!matches) return null;
|
|
|
|
return "WO marked as Past Due. Schedule On has passed without completion.";
|
|
}
|
|
|
|
function tryCreatedRule({ fieldKey, actionKey }: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey === "created" || actionKey === "workordercreated" || actionKey === "created";
|
|
if (!matches) return null;
|
|
|
|
return "Work order created in the system.";
|
|
}
|
|
|
|
function tryAssignedToRule({
|
|
fieldKey,
|
|
actionKey,
|
|
newValue,
|
|
}: AuditActionRuleContext): string | null {
|
|
const matches =
|
|
fieldKey === "assignto" ||
|
|
fieldKey === "assignedto" ||
|
|
fieldKey === "assigneeid" ||
|
|
fieldKey === "dispatcherid" ||
|
|
actionKey === "assignmentchanged" ||
|
|
actionKey.includes("changeassignment");
|
|
if (!matches) return null;
|
|
|
|
const value = newValue.text ? wrapAuditValue(newValue) : '"—"';
|
|
return `Changed Assigned To to ${value}`;
|
|
}
|
|
|
|
/** Applied in order; the first non-null result wins. */
|
|
export const BOARD_AUDIT_ACTION_RULES: AuditActionRule[] = [
|
|
tryStatusRule,
|
|
tryTypeRule,
|
|
trySeverityRule,
|
|
tryApptRule,
|
|
tryScheduleRule,
|
|
tryCarriedOverRule,
|
|
tryPastDueRule,
|
|
tryAssignedToRule,
|
|
tryCreatedRule,
|
|
];
|