src/features/reviews/workflow.ts
Linked repository file from this build.
import { RuleError } from "@/lib/errors";
export const PEC_ROUTING_PENDING =
"PEC routing and memo classification need the management-approved Review-tab defect map.";
import type { DemoRole, ReviewOutcome, WorkflowState } from "./types";
export type WorkflowAction = "submit" | "request_changes" | "route_pec" | "approve";
export interface WorkflowContext {
state: WorkflowState;
outcome: ReviewOutcome;
role: DemoRole;
requiredInputCurrent: boolean;
computationCurrent: boolean;
openFindingsBlockApproval: boolean;
note: string;
}
export function transition(context: WorkflowContext, action: WorkflowAction): WorkflowState {
const { state, role, note } = context;
if (
action === "submit" &&
role === "examiner" &&
(state === "new_active" || state === "needs_changes")
) {
if (context.outcome !== "drop" && !context.computationCurrent)
throw new RuleError("Apply supported Final Results before submission.");
if (context.outcome !== "drop" && !context.requiredInputCurrent)
throw new RuleError("Complete required review fields before submission.");
if (!context.outcome) throw new RuleError("Choose a review outcome before submission.");
return "awaiting_review";
}
const senior = role === "supervisor" || role === "program_manager";
if (senior && state === "awaiting_review") {
if (action === "request_changes") {
if (!note.trim()) throw new RuleError("A note is required when requesting changes.");
return "needs_changes";
}
if (action === "approve" || action === "route_pec") {
if (
context.outcome !== "drop" &&
(!context.requiredInputCurrent || !context.computationCurrent)
)
throw new RuleError("Complete required fields and current Final Results before approval.");
if (context.outcome !== "drop" && context.openFindingsBlockApproval)
throw new RuleError(
"Resolve or refuse open edit-check findings before approval. Sign-off does not refuse findings; changed facts require a fresh check.",
);
if (context.outcome === "drop") {
if (action === "route_pec")
throw new RuleError("Drops skip PEC and require manager approval.");
return "awaiting_manager";
}
if (!context.outcome) throw new RuleError("Choose a review outcome before approval.");
throw new RuleError(PEC_ROUTING_PENDING);
}
}
if (role === "program_manager" && state === "awaiting_manager") {
if (action === "request_changes") {
if (!note.trim()) throw new RuleError("A note is required when requesting changes.");
return "needs_changes";
}
if (action === "approve" && context.outcome === "drop") return "dropped";
}
throw new RuleError("This action is not available for the current role and workflow state.");
}