From b6ca73d76a6d474665531e1d9dddeac0793464e6 Mon Sep 17 00:00:00 2001 From: Robert Bond Date: Fri, 17 Apr 2026 13:23:55 +0000 Subject: [PATCH] Merged PR 2257: refactor(representations): isolate finalisation boundary helpers (slice R8) refactor(representations): isolate finalisation boundary helpers (slice R8) Related work items: #22441 --- components/case/representation/index.js | 128 +++++------------- .../utils/finalisationBoundary.js | 126 +++++++++++++++++ context/representations-refactor-tracker.md | 23 ++++ 3 files changed, 182 insertions(+), 95 deletions(-) create mode 100644 components/case/representation/utils/finalisationBoundary.js diff --git a/components/case/representation/index.js b/components/case/representation/index.js index 449822d6..19273a89 100644 --- a/components/case/representation/index.js +++ b/components/case/representation/index.js @@ -46,6 +46,11 @@ import { resolveInitialCaseDataSources, resolveSubmitCaseDetailsSource } from "./utils/dataResolution"; +import { + buildRepresentationUpdateBody, + buildSubmitEnrichedValues, + runFinalisationSequence +} from "./utils/finalisationBoundary"; let MakeRepresentation = (props) => { let { t } = useTranslation(); @@ -871,24 +876,10 @@ let MakeRepresentation = (props) => { ] }; - let updateBody = setRepFileName(formattedValues); - - // Clean and sanitize the body - const sanitizedBody = Object.entries(updateBody).reduce( - (acc, [key, val]) => { - if (val !== null && val !== undefined && !key.startsWith("_")) { - acc[key] = val; - } - return acc; - }, - {} - ); - - // Normalize "Yes"/"No" to booleans - const stringified = JSON.stringify(sanitizedBody) - .replace(/:"Yes"/g, ":true") - .replace(/:"No"/g, ":false"); - updateBody = JSON.parse(stringified); + const updateBody = buildRepresentationUpdateBody({ + formattedValues, + setRepFileName + }); const sendEmailIfNeeded = () => { if (sendSavedEmail) { @@ -976,23 +967,6 @@ let MakeRepresentation = (props) => { ) { const formValues = formObj.representationForm.values; - const buildSiteAddress = () => { - const { - pinswg_siteaddressline1, - pinswg_siteaddressline2, - pinswg_siteaddresstown, - pinswg_siteaddresspostcode - } = detailsObj; - return [ - pinswg_siteaddressline1, - pinswg_siteaddressline2, - pinswg_siteaddresstown, - pinswg_siteaddresspostcode - ] - .filter(Boolean) - .join(" "); - }; - const representationCapacity = router.query?.state === "edit" ? props.props.currentView.representationCapacity @@ -1000,55 +974,19 @@ let MakeRepresentation = (props) => { ? "LPA" : values.representationCapacity; - Object.assign(values, { - ...formValues, - containerID: props.props.accountDetails.containerID, - appealType: - appealType || caseReferenceObj.repDetails.appealType, - incidentID: - caseReferenceObj.incidentid || - caseReferenceObj.repDetails.incidentid, - caseRef: caseReferenceObj.currentReference, - casereference: caseReferenceObj.currentReference, - pinswg_questionnaireduedate: - detailsObj.pinswg_questionnaireduedate, - pinswg_endofrepresentationperiod: - detailsObj.pinswg_endofrepresentationperiod, - pinswg_applicationacceptedasvalid: - detailsObj.pinswg_applicationacceptedasvalid, - pinswg_statementduedate: detailsObj.pinswg_statementduedate, - pinswg_statementsduedate: detailsObj.pinswg_statementsduedate, - pinswg_finalcommentsduedate: - detailsObj.pinswg_finalcommentsduedate, - pinswg_name: caseReferenceObj.currentReference, - firstname: accountDetails.firstname, - lastname: accountDetails.lastname, - emailAddress: accountDetails.emailaddress1, - siteAddress: buildSiteAddress(), - pinswg_siteaddressline1: detailsObj.pinswg_siteaddressline1, - pinswg_siteaddressline2: detailsObj.pinswg_siteaddressline2, - pinswg_siteaddresstown: detailsObj.pinswg_siteaddresstown, - pinswg_siteaddresspostcode: - detailsObj.pinswg_siteaddresspostcode, - pinswg_lpareference: resultsObj?.pinswg_lpareference, - _pinswg_appellant_value: detailsObj._pinswg_appellant_value, - "_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue": - detailsObj[ - "_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue" - ], - _pinswg_localplanningauthority_value: - detailsObj._pinswg_localplanningauthority_value || - detailsObj._pinswg_associatedlpa_value, - "_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue": - detailsObj[ - "_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue" - ] || - detailsObj[ - "_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue" - ], - representationCapacity, - locale: router.locale - }); + Object.assign( + values, + buildSubmitEnrichedValues({ + formValues, + accountDetails, + appealType, + caseReferenceObj, + detailsObj, + resultsObj, + representationCapacity, + locale: router.locale + }) + ); } const shouldSubmit = !currentView.representationSubmit; @@ -1102,19 +1040,19 @@ let MakeRepresentation = (props) => { } // Final PDF & upload - setFinaliseAppealProcess(true); - generateRepPDF( + runFinalisationSequence({ values, - props.props.accountDetails.containerID, - props.currentView.caseReference.currentReference - ).then((data) => { + containerID: props.props.accountDetails.containerID, + currentReference: + props.currentView.caseReference.currentReference, + setFinaliseAppealProcess, + generateRepPDF, + updateLinks, + uploadRepresentationFiles, + setRepresentationSubmit, + setRepresentationSubmitConfirmation + }).then((data) => { console.log(data, ""); - if (data.status === "success") { - values = updateLinks(values); - uploadRepresentationFiles(values); - setRepresentationSubmit(true); - setRepresentationSubmitConfirmation(true); - } }); } }; diff --git a/components/case/representation/utils/finalisationBoundary.js b/components/case/representation/utils/finalisationBoundary.js new file mode 100644 index 00000000..ef2a0aee --- /dev/null +++ b/components/case/representation/utils/finalisationBoundary.js @@ -0,0 +1,126 @@ +export const buildRepresentationUpdateBody = ({ + formattedValues, + setRepFileName +}) => { + let updateBody = setRepFileName(formattedValues); + + const sanitizedBody = Object.entries(updateBody).reduce( + (acc, [key, val]) => { + if (val !== null && val !== undefined && !key.startsWith("_")) { + acc[key] = val; + } + return acc; + }, + {} + ); + + const stringified = JSON.stringify(sanitizedBody) + .replace(/:"Yes"/g, ":true") + .replace(/:"No"/g, ":false"); + + updateBody = JSON.parse(stringified); + + return updateBody; +}; + +export const buildSubmitEnrichedValues = ({ + formValues, + accountDetails, + appealType, + caseReferenceObj, + detailsObj, + resultsObj, + representationCapacity, + locale +}) => { + const buildSiteAddress = () => { + const { + pinswg_siteaddressline1, + pinswg_siteaddressline2, + pinswg_siteaddresstown, + pinswg_siteaddresspostcode + } = detailsObj; + + return [ + pinswg_siteaddressline1, + pinswg_siteaddressline2, + pinswg_siteaddresstown, + pinswg_siteaddresspostcode + ] + .filter(Boolean) + .join(" "); + }; + + return { + ...formValues, + containerID: accountDetails.containerID, + appealType: appealType || caseReferenceObj.repDetails.appealType, + incidentID: + caseReferenceObj.incidentid || + caseReferenceObj.repDetails.incidentid, + caseRef: caseReferenceObj.currentReference, + casereference: caseReferenceObj.currentReference, + pinswg_questionnaireduedate: detailsObj.pinswg_questionnaireduedate, + pinswg_endofrepresentationperiod: + detailsObj.pinswg_endofrepresentationperiod, + pinswg_applicationacceptedasvalid: + detailsObj.pinswg_applicationacceptedasvalid, + pinswg_statementduedate: detailsObj.pinswg_statementduedate, + pinswg_statementsduedate: detailsObj.pinswg_statementsduedate, + pinswg_finalcommentsduedate: detailsObj.pinswg_finalcommentsduedate, + pinswg_name: caseReferenceObj.currentReference, + firstname: accountDetails.firstname, + lastname: accountDetails.lastname, + emailAddress: accountDetails.emailaddress1, + siteAddress: buildSiteAddress(), + pinswg_siteaddressline1: detailsObj.pinswg_siteaddressline1, + pinswg_siteaddressline2: detailsObj.pinswg_siteaddressline2, + pinswg_siteaddresstown: detailsObj.pinswg_siteaddresstown, + pinswg_siteaddresspostcode: detailsObj.pinswg_siteaddresspostcode, + pinswg_lpareference: resultsObj?.pinswg_lpareference, + _pinswg_appellant_value: detailsObj._pinswg_appellant_value, + "_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue": + detailsObj[ + "_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue" + ], + _pinswg_localplanningauthority_value: + detailsObj._pinswg_localplanningauthority_value || + detailsObj._pinswg_associatedlpa_value, + "_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue": + detailsObj[ + "_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue" + ] || + detailsObj[ + "_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue" + ], + representationCapacity, + locale + }; +}; + +export const runFinalisationSequence = ({ + values, + containerID, + currentReference, + setFinaliseAppealProcess, + generateRepPDF, + updateLinks, + uploadRepresentationFiles, + setRepresentationSubmit, + setRepresentationSubmitConfirmation +}) => { + setFinaliseAppealProcess(true); + + return generateRepPDF(values, containerID, currentReference).then( + (data) => { + if (data.status === "success") { + const updatedValues = updateLinks(values); + uploadRepresentationFiles(updatedValues); + setRepresentationSubmit(true); + setRepresentationSubmitConfirmation(true); + } + + return data; + } + ); +}; diff --git a/context/representations-refactor-tracker.md b/context/representations-refactor-tracker.md index e1633f73..499918f1 100644 --- a/context/representations-refactor-tracker.md +++ b/context/representations-refactor-tracker.md @@ -321,6 +321,29 @@ Isolate submission logic. **Risk Level:** HIGH (Must only be done after earlier slices stabilised) +**Completion notes (this slice):** + +- Implemented on feature branch created from `refactor`: `rep-slice-r8-finalisation-boundary`. +- Added a small representation-specific helper module: + - `components/case/representation/utils/finalisationBoundary.js` +- Extracted only approved R8 seams: + - `buildRepresentationUpdateBody(...)` (pure shaping) + - `buildSubmitEnrichedValues(...)` (pure shaping) + - `runFinalisationSequence(...)` (ultra-thin orchestration wrapper) +- Wired `components/case/representation/index.js` to use the new helpers while preserving parent-owned business branching. +- Explicitly preserved R8 constraints: + - no completion-side-effect extraction from `representationComplete.js` + - no route/query changes + - no payload contract changes + - no validation logic changes + - no file upload/PDF/email behaviour changes + - no sequencing drift in finalisation success path + +**Validation evidence:** + +- `npm run lint` completed (warnings only, no new errors). +- `npm run test:reps` completed: **7 passed (4.4m)**. + --- ## Rules