Merged PR 2374: addeding domain layer extrraction
# Summary This PR introduces a **Case Lifecycle Domain Boundary** to centralize lifecycle decision logic and reduce coupling within the appeals application. The work is **behaviour-preserving** and introduces no intentional changes to business rules, CRM integrations, translations, dashboards, API routes, or user-facing functionality. ## What was added New lifecycle boundary: ```text lib/domain/case-lifecycle/ ``` Key responsibilities extracted: - Specialist process normalization - Appeal type mapping - Specialist process stage override mapping - Stage case-type resolution - Stage catalogue lookup - Closed-case status recognition - Lifecycle stage index resolution - Lifecycle stage status assignment ## Behaviour preserved Characterization tests were added before each extraction to preserve: - Appeal type mapping and aliases - Specialist process handling - Lifecycle stage progression - Closed-case handling - Status assignment (`complete`, `in-progress`, `not-started`) - Existing ROW behaviour - Existing `statuscode` lifecycle semantics Closed-case recognition remains unchanged for: ```text 1000 5 6 846040013 846040059 846040060 ``` ## Documentation Added: ```text lib/domain/case-lifecycle/README.md ``` Documenting: - Boundary ownership - Non-goals - Lifecycle invariants - Known architectural constraints - Future extraction roadmap ## Testing Added lifecycle characterization coverage for: - Stage wrapper behaviour - Specialist process normalization - Appeal type mapping - Specialist process stage mapping - Stage case-type resolution - Stage catalogue lookup - Progress behaviour - Closed-case status handling - Stage index resolution - Stage status assignment ## Validation - Lifecycle characterization tests passed - `npm run lint` passed with no errors ## Out of Scope No changes to: - Stage catalogue ownership - Representation eligibility - Dashboard calculations - CRM/OData queries - API routes - Redux state - EN/CY translations - Event visibility logic ## Risk **Low risk** The refactor was delivered through small, characterization-first slices with no functional changes intended. Related work items: #23527
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
import { getStageCaseTypeKey } from "../../../../lib/domain/case-lifecycle/getStageCaseTypeKey";
|
||||
import { getStagesForCaseTypeKey } from "../../../../lib/domain/case-lifecycle/getStagesForCaseTypeKey";
|
||||
import { getLifecycleStageIndex } from "../../../../lib/domain/case-lifecycle/getLifecycleStageIndex";
|
||||
import { getLifecycleStageStatus } from "../../../../lib/domain/case-lifecycle/getLifecycleStageStatus";
|
||||
|
||||
const slugify = (value) =>
|
||||
value
|
||||
.toString()
|
||||
@@ -771,106 +776,10 @@ export const stagesByCaseType = {
|
||||
])
|
||||
};
|
||||
|
||||
export const caseTypeAliases = {
|
||||
// Planning S78 group
|
||||
S78: "PLANNING_S78",
|
||||
PLANNING_78: "PLANNING_S78",
|
||||
PLANNING_S78: "PLANNING_S78",
|
||||
CONDITIONS_73_79: "CONDITIONS_73_79",
|
||||
LBCAC: "LBCAC",
|
||||
LDCS: "LDCS",
|
||||
PLANNING_OBLIGATIONS_S106: "PLANNING_OBLIGATIONS_S106",
|
||||
PRIOR_NOTIFICATION: "PRIOR_NOTIFICATION",
|
||||
|
||||
// Other types
|
||||
HAS: "HAS",
|
||||
CALL_INS: "CALL_INS",
|
||||
CALL_IN: "CALL_INS",
|
||||
ENFORCEMENT: "ENFORCEMENT",
|
||||
ENFORCEMENT_LISTED_BUILDING: "ENFORCEMENT_LISTED_BUILDING",
|
||||
MAINTENANCE_OF_LAND: "MAINTENANCE_OF_LAND",
|
||||
RIGHTS_OF_WAY_SCHEDULE_14: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
RIGHTS_OF_WAY_ORDERS: "RIGHTS_OF_WAY_ORDERS",
|
||||
REQUESTS_FOR_DIRECTION: "REQUESTS_FOR_DIRECTION",
|
||||
ADVERTS: "ADVERTS",
|
||||
HEDGEROW_TPO: "HEDGEROW_TPO",
|
||||
COMMON_LAND: "COMMON_LAND",
|
||||
COMPULSORY_PURCHASE_ORDERS: "COMPULSORY_PURCHASE_ORDERS",
|
||||
ELECTRICITY_ACT: "ELECTRICITY_ACT",
|
||||
HARBOUR_REVISION_ORDER: "HARBOUR_REVISION_ORDER",
|
||||
TRANSPORT_WORKS: "TRANSPORT_WORKS",
|
||||
WAYLEAVE: "WAYLEAVE",
|
||||
NON_VALIDATION: "NON_VALIDATION",
|
||||
DNS: "DNS",
|
||||
SIP: "SIP"
|
||||
};
|
||||
|
||||
const normaliseCaseType = (caseType) =>
|
||||
caseType
|
||||
?.toString()
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/&/g, "AND")
|
||||
.replace(/[^A-Z0-9]+/g, "_")
|
||||
.replace(/^_|_$/g, "");
|
||||
|
||||
const specialistProcessKeyById = {
|
||||
// TODO: replace these with the real CRM option-set values
|
||||
846040000: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040100: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040001: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040101: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040002: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040102: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040003: "REQUESTS_FOR_DIRECTION",
|
||||
846040103: "REQUESTS_FOR_DIRECTION"
|
||||
};
|
||||
|
||||
const appealTypeSpecialistProcessStageMap = {
|
||||
846040015: specialistProcessKeyById
|
||||
};
|
||||
|
||||
const resolveStages = (caseTypeOrAppealTypeId, specialistProcess) => {
|
||||
const appealTypeId = Number(caseTypeOrAppealTypeId);
|
||||
const key = getStageCaseTypeKey(caseTypeOrAppealTypeId, specialistProcess);
|
||||
|
||||
const specialistProcessKey =
|
||||
appealTypeSpecialistProcessStageMap[appealTypeId]?.[specialistProcess];
|
||||
|
||||
const appealTypeKey =
|
||||
specialistProcessKey || caseTypeKeyByAppealTypeId[appealTypeId];
|
||||
|
||||
const normalised = normaliseCaseType(caseTypeOrAppealTypeId);
|
||||
|
||||
const key = appealTypeKey || caseTypeAliases[normalised] || normalised;
|
||||
|
||||
const entry = stagesByCaseType[key];
|
||||
|
||||
if (!entry) return [];
|
||||
|
||||
if (typeof entry === "string") {
|
||||
return stagesByCaseType[entry] || [];
|
||||
}
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
const caseClosedStatusIds = [1000, 5, 6, 846040013, 846040060, 846040059];
|
||||
|
||||
const isCaseClosedStatus = (stageId) =>
|
||||
caseClosedStatusIds.includes(Number(stageId));
|
||||
|
||||
const getStageIndex = (stages, currentStageNumber) => {
|
||||
const exactIndex = stages.findIndex(
|
||||
(stage) => Number(stage.stageId) === currentStageNumber
|
||||
);
|
||||
|
||||
if (exactIndex !== -1) return exactIndex;
|
||||
|
||||
if (isCaseClosedStatus(currentStageNumber)) {
|
||||
return stages.findIndex((stage) => stage.titleKey === "case-closed");
|
||||
}
|
||||
|
||||
return -1;
|
||||
return getStagesForCaseTypeKey(key);
|
||||
};
|
||||
|
||||
export const getStagesForAppealType = (
|
||||
@@ -879,46 +788,10 @@ export const getStagesForAppealType = (
|
||||
specialistProcess
|
||||
) => {
|
||||
const stages = resolveStages(caseType, specialistProcess);
|
||||
const currentStageNumber = Number(currentStageId);
|
||||
|
||||
const currentIndex = getStageIndex(stages, currentStageNumber);
|
||||
const currentIndex = getLifecycleStageIndex(stages, currentStageId);
|
||||
|
||||
return stages.map((stage, index) => ({
|
||||
...stage,
|
||||
status:
|
||||
currentIndex === -1
|
||||
? "not-started"
|
||||
: index < currentIndex
|
||||
? "complete"
|
||||
: index === currentIndex
|
||||
? "in-progress"
|
||||
: "not-started"
|
||||
status: getLifecycleStageStatus(index, currentIndex)
|
||||
}));
|
||||
};
|
||||
|
||||
export const caseTypeKeyByAppealTypeId = {
|
||||
846040000: "PLANNING_S78",
|
||||
846040001: "CONDITIONS_73_79",
|
||||
846040002: "SIP",
|
||||
846040003: "PLANNING_OBLIGATIONS_S106",
|
||||
846040004: "HAS",
|
||||
846040005: "ENFORCEMENT",
|
||||
846040006: "ENFORCEMENT_LISTED_BUILDING",
|
||||
846040007: "MAINTENANCE_OF_LAND",
|
||||
846040008: "LDCS",
|
||||
846040009: "PLANNING_S78",
|
||||
846040010: "CALL_INS",
|
||||
846040011: "DNS",
|
||||
846040012: "ELECTRICITY_ACT",
|
||||
846040013: "TRANSPORT_WORKS",
|
||||
846040014: "HARBOUR_REVISION_ORDER",
|
||||
846040015: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040016: "COMMON_LAND",
|
||||
846040017: "HEDGEROW_TPO",
|
||||
846040018: "ADVERTS",
|
||||
846040019: "COMPULSORY_PURCHASE_ORDERS",
|
||||
846040020: "PLANNING_S78",
|
||||
846040021: "WAYLEAVE",
|
||||
846040024: "NON_VALIDATION",
|
||||
846040025: "LBCAC"
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user