shoc-frontend-new/src/domain/work-orders/utils/board-audit-action-rules.ts

172 lines
5.2 KiB
TypeScript
Raw Normal View History

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;
}
function tryTypeRule({ fieldKey, actionKey, newValue }: AuditActionRuleContext): string | null {
const matches =
fieldKey === "type" ||
fieldKey === "workordertype" ||
fieldKey === "wotype" ||
actionKey === "typechanged" ||
actionKey.includes("workordertype");
if (!matches || !newValue.text) return null;
return `Changed Type to ${wrapAuditValue(newValue)}`;
}
function trySeverityRule({ fieldKey, actionKey, newValue }: AuditActionRuleContext): string | null {
const matches = fieldKey.includes("severity") || actionKey.includes("severity");
if (!matches || !newValue.text) return null;
const labeled = { text: formatSeverityAuditLabel(newValue.text), isQuotedList: false };
return `Changed Severity to ${wrapAuditValue(labeled)}`;
}
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,
];