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
+87 -148
View File
@@ -12,15 +12,26 @@ import {
setFormComplete
} from "../../store/appealType/action";
import BuildCheckRow from "./buildcheckrow";
import { generateAppealPDF } from "../../actions/services/documentService";
import { sendCaseCompleteMessage } from "../../actions/services/portalService";
import {
bytesToSize,
getThumbnailIconByExtension,
updateLinks,
getDocLink
} from "../utils";
import {
dedupeFilesListByName,
removeDuplicatesByKey
} from "../../lib/newappeal/fileListHelpers";
import {
generateAppealPDFEffect,
sendCaseCompleteMessageEffect
} from "../../lib/newappeal/journeyEffects";
import CheckSectionHeading from "./buildCheckSection/CheckSectionHeading";
import ConfirmationCheckboxGroup from "./buildCheckSection/ConfirmationCheckboxGroup";
import DocsOfflineNotice from "./buildCheckSection/DocsOfflineNotice";
import FinalisingStatePanel from "./buildCheckSection/FinalisingStatePanel";
import SectionReviewList from "./buildCheckSection/SectionReviewList";
import SubmitActionsPanel from "./buildCheckSection/SubmitActionsPanel";
let BuildCheckSection = (props) => {
useEffect(() => {
@@ -44,6 +55,11 @@ let BuildCheckSection = (props) => {
docsOffline
} = props;
const legacyProps = props.props;
const legacyAppealType = legacyProps.appealType;
const legacyFormState = legacyProps.form;
const legacyAccountDetails = legacyProps.accountDetails;
const parser = new DOMParser();
var doc = parser.parseFromString(props.formXML, "text/xml");
var titles = xpath.select(
@@ -78,7 +94,7 @@ let BuildCheckSection = (props) => {
let buildAppealFilesArray = [];
//console.log("attached docs: - ", buildAppealFilesArray);
buildAppealFilesArray = props.props.appealType.fileList?.value[0] || [];
buildAppealFilesArray = legacyAppealType.fileList?.value[0] || [];
//let filesUploadObj = buildAppealFilesArray;
// let filesUploadObj = props.appealType.fileList.hasOwnProperty("value")
// ? props.appealType.fileList.value[0]
@@ -97,28 +113,18 @@ let BuildCheckSection = (props) => {
});
//console.log(buildAppealFilesArray);
function removeDuplicates(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
}
});
}
buildAppealFilesArray = removeDuplicatesByKey(
buildAppealFilesArray,
"name"
);
buildAppealFilesArray = removeDuplicates(buildAppealFilesArray, "name");
let pdfObj = props.props.form.appealForm.values;
let pdfObj = legacyFormState.appealForm.values;
pdfObj = updateLinks(pdfObj);
Object.assign(pdfObj, {
"filesList": buildAppealFilesArray,
"containerID": props.props.accountDetails.containerID,
"containerID": legacyAccountDetails.containerID,
"casefolderID": props.appealType.caseReference.ticketnumber
});
@@ -152,19 +158,16 @@ let BuildCheckSection = (props) => {
const finaliseAppeal = async () => {
//console.log("finalising appeal:", pdfObj);
const uniqueArray = pdfObj.filesList.filter(
(value, index, self) =>
index === self.findIndex((t) => t.name === value.name)
);
const uniqueArray = dedupeFilesListByName(pdfObj.filesList);
pdfObj.filesList = uniqueArray;
pdfObj.locale = locale;
confirmSections
? (setFinaliseAppealProcess(true),
await generateAppealPDF(
await generateAppealPDFEffect(
pdfObj,
props.props.accountDetails.containerID,
legacyAccountDetails.containerID,
props.appealType.caseReference.ticketnumber,
router.query.appealtypes,
pdfObj.filesList
@@ -172,10 +175,10 @@ let BuildCheckSection = (props) => {
)
.then((data) => {
console.log("have generated pdf:", data);
sendCaseCompleteMessage(
props.props.accountDetails.containerID,
sendCaseCompleteMessageEffect(
legacyAccountDetails.containerID,
props.appealType.caseReference.ticketnumber,
props.props.accountDetails.accountDetails
legacyAccountDetails.accountDetails
.pinswg_typeofinvolvement
);
})
@@ -197,18 +200,15 @@ let BuildCheckSection = (props) => {
try {
setDownloadInProgress(true);
const uniqueArray = pdfObj.filesList.filter(
(value, index, self) =>
index === self.findIndex((t) => t.name === value.name)
);
const uniqueArray = dedupeFilesListByName(pdfObj.filesList);
pdfObj.filesList = uniqueArray;
pdfObj.locale = locale;
const containerID =
props.accountDetails?.containerID ||
props.props?.accountDetails?.containerID ||
props.props?.props?.accountDetails?.containerID;
legacyProps?.accountDetails?.containerID ||
legacyProps?.props?.accountDetails?.containerID;
const caseRef =
props?.currentView?.caseReference?.ticketnumber ||
props?.appealType?.caseReference?.ticketnumber;
@@ -221,7 +221,7 @@ let BuildCheckSection = (props) => {
return;
}
const pdfBlob = await generateAppealPDF(
const pdfBlob = await generateAppealPDFEffect(
pdfObj,
containerID,
caseRef,
@@ -253,10 +253,8 @@ let BuildCheckSection = (props) => {
<div className="govuk-grid-row" key={1222}>
<div className="govuk-grid-column-full">
<div>
<h1 className="govuk-heading-m">
Are these answers correct?
</h1>
<dl className="govuk-summary-list ">
<CheckSectionHeading title="Are these answers correct?" />
<SectionReviewList>
{Object.keys(titles).map((key) => (
<div key={key}>
<h2 className="govuk-heading-m govuk-!-margin-top-9">
@@ -274,7 +272,7 @@ let BuildCheckSection = (props) => {
/>
</div>
))}
</dl>
</SectionReviewList>
{/* <dl className="govuk-summary-list govuk-!-margin-bottom-9 at-summary-list">
{buildAppealFilesArray.length > 0
? buildAppealFilesArray.map((blob, i) => {
@@ -329,120 +327,61 @@ let BuildCheckSection = (props) => {
</dl> */}
</div>
{finaliseAppealProcess ? (
<div className="">
<h3 className="govuk-heading-m govuk-!-margin-top-9">
{t("newappeal:new-appeal-finalising-heading")}
</h3>
<p className="govuk-body">
{t("newappeal:new-appeal-finalising-label")}
</p>
<p className="govuk-body textloading">
{t("newappeal:new-appeal-wait-label")}
</p>
</div>
<FinalisingStatePanel
heading={t("newappeal:new-appeal-finalising-heading")}
label={t("newappeal:new-appeal-finalising-label")}
waitLabel={t("newappeal:new-appeal-wait-label")}
/>
) : (
<>
<div className="govuk-form-group ">
<fieldset className="govuk-fieldset">
<div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item">
<input
name="pinswg_downloadAppealForm"
id="pinswg_downloadAppealForm"
type="checkbox"
className="govuk-checkboxes__input"
checked={downloadAppealForm}
onChange={(e) => {
setDownloadAppealForm(
e.target.checked
);
}}
/>
<label
className="govuk-label govuk-checkboxes__label"
htmlFor="pinswg_downloadAppealForm"
>
{t(
"newappeal:new-appeal-check-download-label"
)}
</label>
</div>
</div>
<div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item">
<input
name="pinswg_checkconfirmation"
id="pinswg_checkconfirmation"
type="checkbox"
className="govuk-checkboxes__input"
value=""
onChange={(e) => {
setConfirmSections(
!confirmSections
);
}}
/>
<label
className="govuk-label govuk-checkboxes__label"
htmlFor="pinswg_checkconfirmation"
>
{t(
"newappeal:new-appeal-check-confirm-label"
)}
</label>
</div>
</div>
</fieldset>
<div>
<p className="govuk-body">
{t(
"newappeal:new-appeal-check-confirm-paragraph-one"
<ConfirmationCheckboxGroup
downloadAppealForm={downloadAppealForm}
onDownloadChange={(e) => {
setDownloadAppealForm(e.target.checked);
}}
onConfirmChange={(e) => {
setConfirmSections(!confirmSections);
}}
downloadLabel={t(
"newappeal:new-appeal-check-download-label"
)}
confirmLabel={t(
"newappeal:new-appeal-check-confirm-label"
)}
paragraphOne={t(
"newappeal:new-appeal-check-confirm-paragraph-one"
)}
paragraphTwo={t(
"newappeal:new-appeal-check-confirm-paragraph-two"
)}
paragraphTwoLink={
<Link
target="_blank"
href={t(
"newappeal:new-appeal-check-confirm-paragraph-two-link"
)}
</p>
</div>
<div>
<p className="govuk-body">
>
{t(
"newappeal:new-appeal-check-confirm-paragraph-two"
)}{" "}
<Link
target="_blank"
href={t(
"newappeal:new-appeal-check-confirm-paragraph-two-link"
)}
>
{t(
"newappeal:new-appeal-check-confirm-paragraph-two-link"
)}
</Link>
</p>
</div>
</div>
"newappeal:new-appeal-check-confirm-paragraph-two-link"
)}
</Link>
}
/>
<div className="govuk-button-group">
<SubmitActionsPanel>
{ShowDocLinks ? (
<>
<div className="govuk-warning-text ">
<span
className="govuk-warning-text__icon"
aria-hidden="true"
>
!
</span>
<strong className="govuk-warning-text__text govuk-!-margin-top-2">
<span className="govuk-visually-hidden">
Notice
</span>
<DocsOfflineNotice
noticeText={t(
"myportal:docsoffline-newappeal-label"
)}
returnLink={
<Link className="govuk-link" href="/">
{t(
"myportal:docsoffline-newappeal-label"
"newappeal:return-to-myportal-link"
)}
{" "}
</strong>
</div>{" "}
<Link className="govuk-link" href="/">
{t("newappeal:return-to-myportal-link")}
</Link>
</>
</Link>
}
/>
) : (
<>
<button
@@ -468,7 +407,7 @@ let BuildCheckSection = (props) => {
</a>
</>
)}
</div>
</SubmitActionsPanel>
</>
)}
</div>