From 2b691ac4ef32f99c901d51bc10eb24f1c2df497b Mon Sep 17 00:00:00 2001 From: Robert Bond Date: Tue, 27 Oct 2026 13:07:41 +0000 Subject: [PATCH] Merged PR 2653: fix: preserve LPA questionnaire PDF metadata during submission fix: preserve LPA questionnaire PDF metadata during submission Related work items: #25435 --- components/case/representation/index.js | 2 +- .../utils/finalisationBoundary.js | 27 +- .../representation-pdf-metadata.test.cjs | 236 ++++++++++++++++++ 3 files changed, 260 insertions(+), 5 deletions(-) create mode 100644 tests/phase22/representation-pdf-metadata.test.cjs diff --git a/components/case/representation/index.js b/components/case/representation/index.js index 1fbf18a1..2538bad9 100644 --- a/components/case/representation/index.js +++ b/components/case/representation/index.js @@ -297,7 +297,7 @@ let MakeRepresentation = (props) => { }); let resultsObj = findResultsObjForCurrentReference({ - "currentReference": currentView.caseReference.currentReference, + "currentReference": currentView.caseReference.ticketnumber, "caseResultsSource": caseResultsSource }); diff --git a/components/case/representation/utils/finalisationBoundary.js b/components/case/representation/utils/finalisationBoundary.js index 0ed03996..054ef656 100644 --- a/components/case/representation/utils/finalisationBoundary.js +++ b/components/case/representation/utils/finalisationBoundary.js @@ -33,6 +33,14 @@ export const buildSubmitEnrichedValues = ({ representationCapacity, locale }) => { + const isPopulatedText = (value) => + value !== null && + value !== undefined && + (typeof value !== "string" || value.trim() !== ""); + + const preserveExistingWhenMissing = (canonicalValue, existingValue) => + isPopulatedText(canonicalValue) ? canonicalValue : existingValue; + const buildSiteAddress = () => { const { pinswg_siteaddressline1, @@ -47,10 +55,12 @@ export const buildSubmitEnrichedValues = ({ pinswg_siteaddresstown, pinswg_siteaddresspostcode ] - .filter(Boolean) + .filter(isPopulatedText) .join(" "); }; + const siteAddress = buildSiteAddress(); + return { ...formValues, containerID: accountDetails.containerID, @@ -58,7 +68,10 @@ export const buildSubmitEnrichedValues = ({ incidentID: caseReferenceObj.incidentid || caseReferenceObj.repDetails.incidentid, - caseRef: caseReferenceObj.ticketnumber, + caseRef: preserveExistingWhenMissing( + caseReferenceObj.ticketnumber, + formValues.caseRef + ), casereference: caseReferenceObj.currentReference, pinswg_questionnaireduedate: detailsObj.pinswg_questionnaireduedate, pinswg_endofrepresentationperiod: @@ -72,12 +85,18 @@ export const buildSubmitEnrichedValues = ({ firstname: accountDetails.firstname, lastname: accountDetails.lastname, emailAddress: accountDetails.emailaddress1, - siteAddress: buildSiteAddress(), + siteAddress: preserveExistingWhenMissing( + siteAddress, + formValues.siteAddress + ), 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_lpareference: preserveExistingWhenMissing( + resultsObj?.pinswg_lpareference, + formValues.pinswg_lpareference + ), _pinswg_appellant_value: detailsObj._pinswg_appellant_value, "_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue": detailsObj[ diff --git a/tests/phase22/representation-pdf-metadata.test.cjs b/tests/phase22/representation-pdf-metadata.test.cjs new file mode 100644 index 00000000..21651be9 --- /dev/null +++ b/tests/phase22/representation-pdf-metadata.test.cjs @@ -0,0 +1,236 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const rootDir = path.resolve(__dirname, "..", ".."); +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +const loadModule = (relativePath, exportNames, contextOverrides = {}) => { + const filePath = path.join(rootDir, ...relativePath); + let source = fs.readFileSync(filePath, "utf8"); + + source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); + source = source.replace(/export\s+const\s+/g, "const "); + source += `\nmodule.exports = { ${exportNames.join(", ")} };\n`; + + const context = { + module: { exports: {} }, + exports: {}, + ...contextOverrides + }; + + vm.runInNewContext(source, context, { filename: filePath }); + return context.module.exports; +}; + +const { buildSubmitEnrichedValues } = loadModule( + [ + "components", + "case", + "representation", + "utils", + "finalisationBoundary.js" + ], + ["buildSubmitEnrichedValues"] +); + +const findTicketnumberMatch = ({ path: jsonPath, json }) => { + const ticketnumber = jsonPath.match(/ticketnumber=="([^"]+)"/)?.[1]; + const queue = [json]; + + while (queue.length > 0) { + const value = queue.shift(); + + if (Array.isArray(value)) { + queue.push(...value); + } else if (value && typeof value === "object") { + if (value.ticketnumber === ticketnumber) { + return [value]; + } + queue.push(...Object.values(value)); + } + } + + return []; +}; + +const { findResultsObjForCurrentReference } = loadModule( + [ + "components", + "case", + "representation", + "utils", + "dataResolution.js" + ], + ["findResultsObjForCurrentReference"], + { jsonpath: findTicketnumberMatch } +); + +const buildInput = (overrides = {}) => ({ + formValues: { + caseRef: "FORM-CASE", + pinswg_lpareference: "FORM-LPA", + siteAddress: "Form address", + questionnaireAnswer: "Preserved", + ...overrides.formValues + }, + accountDetails: { + containerID: "container-1", + firstname: "Test", + lastname: "User", + emailaddress1: "test@example.com" + }, + appealType: "S78", + caseReferenceObj: { + ticketnumber: "CANONICAL-CASE", + currentReference: "Display reference", + incidentid: "incident-1", + repDetails: {}, + ...overrides.caseReferenceObj + }, + detailsObj: { + pinswg_siteaddressline1: "1 Canonical Street", + pinswg_siteaddressline2: "Canonical Area", + pinswg_siteaddresstown: "Canonical Town", + pinswg_siteaddresspostcode: "CF10 1AA", + ...overrides.detailsObj + }, + resultsObj: { + pinswg_lpareference: "CANONICAL-LPA", + ...overrides.resultsObj + }, + representationCapacity: "LPA", + locale: "en" +}); + +test("populated canonical metadata overrides conflicting form metadata", () => { + const result = buildSubmitEnrichedValues(buildInput()); + + assert.strictEqual(result.caseRef, "CANONICAL-CASE"); + assert.strictEqual(result.pinswg_lpareference, "CANONICAL-LPA"); + assert.strictEqual( + result.siteAddress, + "1 Canonical Street Canonical Area Canonical Town CF10 1AA" + ); + assert.strictEqual(result.questionnaireAnswer, "Preserved"); +}); + +test("missing canonical case reference preserves the submitted case reference", () => { + const input = buildInput({ caseReferenceObj: { ticketnumber: undefined } }); + const result = buildSubmitEnrichedValues(input); + + assert.strictEqual(result.caseRef, "FORM-CASE"); + assert.strictEqual(input.formValues.caseRef, "FORM-CASE"); +}); + +test("missing result metadata preserves the submitted LPA reference", () => { + const input = buildInput(); + input.resultsObj = undefined; + + const result = buildSubmitEnrichedValues(input); + + assert.strictEqual(result.pinswg_lpareference, "FORM-LPA"); +}); + +test("empty canonical address preserves the submitted site address", () => { + const result = buildSubmitEnrichedValues( + buildInput({ + detailsObj: { + pinswg_siteaddressline1: "", + pinswg_siteaddressline2: null, + pinswg_siteaddresstown: undefined, + pinswg_siteaddresspostcode: "" + } + }) + ); + + assert.strictEqual(result.siteAddress, "Form address"); +}); + +test("whitespace-only canonical metadata cannot erase submitted metadata", () => { + const result = buildSubmitEnrichedValues( + buildInput({ + caseReferenceObj: { ticketnumber: " " }, + resultsObj: { pinswg_lpareference: "\t" }, + detailsObj: { + pinswg_siteaddressline1: " ", + pinswg_siteaddressline2: "\t", + pinswg_siteaddresstown: " ", + pinswg_siteaddresspostcode: "\n" + } + }) + ); + + assert.strictEqual(result.caseRef, "FORM-CASE"); + assert.strictEqual(result.pinswg_lpareference, "FORM-LPA"); + assert.strictEqual(result.siteAddress, "Form address"); +}); + +test("case-results lookup resolves the record by ticket number", () => { + const selected = findResultsObjForCurrentReference({ + currentReference: "CAS-EXAMPLE-001", + caseResultsSource: { + value: [ + { + ticketnumber: "CAS-OTHER-001", + title: "CAS-EXAMPLE-001" + }, + { + ticketnumber: "CAS-EXAMPLE-001", + title: "Different Display Reference" + } + ] + } + }); + + assert.strictEqual(selected.ticketnumber, "CAS-EXAMPLE-001"); + assert.strictEqual(selected.title, "Different Display Reference"); +}); + +test("parent lookup supplies the case ticket number", () => { + const source = fs.readFileSync( + path.join(rootDir, "components", "case", "representation", "index.js"), + "utf8" + ); + + assert.match( + source, + /findResultsObjForCurrentReference\(\{\s*"currentReference": currentView\.caseReference\.ticketnumber,\s*"caseResultsSource": caseResultsSource\s*\}\)/ + ); +}); + +test("existing and edit representations remain outside submit enrichment", () => { + const source = fs.readFileSync( + path.join(rootDir, "components", "case", "representation", "index.js"), + "utf8" + ); + + assert.match( + source, + /formObj\?\.representationForm\?\.values\s*&&\s*!router\.query\.hasOwnProperty\("state"\)/ + ); +}); + +const run = async () => { + let passed = 0; + + for (const currentTest of tests) { + await currentTest.fn(); + passed += 1; + } + + console.log( + `Phase 22 representation PDF metadata tests passed (${passed}/${tests.length}).` + ); +}; + +module.exports = run; + +if (require.main === module) { + run().catch((error) => { + console.error(error); + process.exit(1); + }); +} \ No newline at end of file