Merged PR 2242: Refactor: New Appeal flow structural cleanup (Slices 1–8, no behaviour change)

## Summary

This PR merges the completed new-appeal refactor stream (Slices 1–8) into SIPS-Development.

This refactor was executed in controlled slices with regression validation at each step.

## Scope

Structural and readability improvements only:
- XML/form derivation extraction
- payload and file helper extraction
- side-effect facade introduction
- BuildSection and BuildCheckSection decomposition
- BuildCheckRow formatter map refactor
- nested prop boundary cleanup
- start-flow cleanup (CreateCase / AboutYou)

## Behaviour

No intended behavioural changes.

The following were explicitly preserved:
- S78 journey behaviour
- payload shapes and field names/ids
- HASCAS mapping logic
- appellant/agent branching
- validation rules and messages
- redirect/query parameters (`lpa`, `apt`, `id`)
- navigation and side-effect sequencing
- EN/CY output parity

## Validation

Full regression has been performed on:
- start flow (CreateCase / AboutYou)
- save and resume flows
- file upload handling
- check answers rendering
- submit/finalisation sequence
- confirmation flow
- CRM insertion path
- EN/CY parity

Additional checks:
- docsOffline branch behaviour
- completion and partial-save email paths
- negative-path validation scenarios
- lint (warnings baseline unchanged)

## Risk

Low:
- changes are structural only
- no business logic changes
- no contract changes

## Rollback

Safe rollback via reverting this merge commit.

## Notes

This refactor reduces coupling and prepares the new-appeal flow for future appeal-type expansion.

Related work items: #22570, #22576, #22577, #22583, #22586, #22587, #22588, #22590
This commit is contained in:
Robert Bond
2026-04-13 13:09:12 +00:00
parent bd3d311cfb
commit 0de9268255
47 changed files with 2010 additions and 945 deletions
+28
View File
@@ -0,0 +1,28 @@
export const mergeWithExistingFilesList = (buildAppealFilesArray, values) => {
return values.hasOwnProperty("filesList")
? (buildAppealFilesArray.concat(values.filesList),
buildAppealFilesArray.filter(function (item, idx) {
return item.name;
}))
: buildAppealFilesArray;
};
export const removeDuplicatesByKey = (arr, key) => {
const seen = new Set();
return arr.filter((item) => {
const value = item[key];
if (seen.has(value)) {
return false; // Duplicate found
} else {
seen.add(value); // Add value to the set
return true; // Keep the item
}
});
};
export const dedupeFilesListByName = (filesList) => {
return filesList.filter(
(value, index, self) =>
index === self.findIndex((t) => t.name === value.name)
);
};
+102
View File
@@ -0,0 +1,102 @@
import xpath from "xpath";
export const parseXml = (xmlStr) => {
const parser = new DOMParser();
return parser.parseFromString(xmlStr, "text/xml");
};
export const selectCurrentSectionTitleDescriptions = (doc, currentSection) => {
return xpath.select(
"//form/tabs/tab[" + currentSection + " ]/labels/label/@description",
doc
);
};
export const selectCurrentSectionRows = (doc, currentSection) => {
return xpath.select(
"/form/tabs/tab[" +
currentSection +
"]/columns//sections/section/rows/row",
doc
);
};
export const selectFormTabs = (doc) => {
return xpath.select("/form/tabs", doc);
};
export const selectAllTabTitleDescriptions = (doc) => {
return xpath.select("//form/tabs/tab[*]/labels/label/@description", doc);
};
export const parseRowOuterHTML = (outerHTML) => {
const parser = new DOMParser();
return parser.parseFromString(outerHTML, "text/xml");
};
export const selectRowLabelDescriptions = (doc) => {
return xpath.select("//label/@description", doc);
};
export const selectRowFieldType = (doc) => {
return xpath.select("//@classid", doc);
};
export const selectRowValidation = (doc) => {
return xpath.select("//@validation", doc);
};
export const selectRowMaxFieldLength = (doc) => {
return xpath.select("//@maxFieldLength", doc);
};
export const selectRowMaxSubField = (doc) => {
return xpath.select("//@maxsubfield", doc);
};
export const selectRowDataFieldName = (doc) => {
return xpath.select("//@datafieldname", doc);
};
export const selectRowDataFieldContent = (doc) => {
return xpath.select("//@datafieldcontent", doc);
};
export const selectRowParentField = (doc) => {
return xpath.select("//@parentField", doc);
};
export const selectRowParentFieldShowOnValue = (doc) => {
return xpath.select("//@parentFieldShowOnValue", doc);
};
export const selectRowRequiredDocumentValue = (doc) => {
return xpath.select("//@requiredDocumentValue", doc);
};
export const selectRowRequiredDocumentLabel = (doc) => {
return xpath.select("//@requiredDocumentLabel", doc);
};
export const selectRowDocumentTypeCode = (doc) => {
return xpath.select("//@ishareDocumentCode", doc);
};
export const selectRowHint = (doc) => {
return xpath.select("//label/@hint", doc);
};
export const selectRowDateStart = (doc) => {
return xpath.select("//@dateStart", doc);
};
export const selectRowDateEnd = (doc) => {
return xpath.select("//@dateEnd", doc);
};
export const selectErrorFieldLabelDescription = (doc, whichField) => {
return xpath.select(
"//*[control/@id='" + whichField + "']//@description",
doc
);
};
+61
View File
@@ -0,0 +1,61 @@
import { uploadFiles } from "../../actions/services/documentService";
import { sendEmail } from "../../actions/services/notifyService";
import { sendCaseCompleteMessage } from "../../actions/services/portalService";
import { generateAppealPDF } from "../../actions/services/documentService";
export const uploadAppealFilesEffect = (
dataObj,
fileListObj,
containerID,
caseReference
) => {
return uploadFiles(dataObj, fileListObj, containerID, caseReference);
};
export const sendPartialSaveEmailEffect = (
templateId,
emailAddress,
personalisation,
reference
) => {
return sendEmail(templateId, emailAddress, personalisation, reference);
};
export const sendCompletionEmailEffect = (
templateId,
emailAddress,
personalisation,
reference
) => {
return sendEmail(templateId, emailAddress, personalisation, reference);
};
export const generateAppealPDFEffect = (
pdfObj,
containerID,
caseReference,
appealType,
filesList,
options
) => {
return generateAppealPDF(
pdfObj,
containerID,
caseReference,
appealType,
filesList,
options
);
};
export const sendCaseCompleteMessageEffect = (
containerID,
caseReference,
typeOfInvolvement
) => {
return sendCaseCompleteMessage(
containerID,
caseReference,
typeOfInvolvement
);
};
+28
View File
@@ -0,0 +1,28 @@
export const normalizeYesNoBooleans = (updateBody) => {
updateBody = JSON.stringify(updateBody);
updateBody = updateBody.replace(/:\"Yes\"/gm, `:true`);
updateBody = updateBody.replace(/:\"No\"/gm, `:false`);
updateBody = JSON.parse(updateBody);
return updateBody;
};
export const removeNullKeys = (updateBody) => {
Object.keys(updateBody).forEach((key) => {
if (updateBody[key] === null) {
delete updateBody[key];
}
});
return updateBody;
};
export const stripInternalKeys = (updateBody) => {
Object.keys(updateBody).forEach((key) => {
if (key.indexOf("_") == 0) {
delete updateBody[key];
}
});
return updateBody;
};