From 9d7b87fb365b50dc53d65e8cbc48a13a43366110 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 22 Aug 2025 11:23:04 -0700 Subject: [PATCH 001/105] ADO-2787 basic auth configured for getSavedFormFromPortal function --- portal/loadPortalDataHandler.js | 41 ++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index 0c8eabf..95a4fdf 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -56,22 +56,41 @@ async function expireTokenInPortal(portal,token, userId) { } async function getSavedFormFromPortal(portal,token, userId) { - let parametersForForm = ""; + let parametersForForm = ""; try { const urlForValidateTokenAndGetJson= portal.apiHost+ (portal.getSavedJsonEndpoint || process.env.PORTAL_VALIDATE_TOKEN_ENDPOINT); console.log("urlForValidateTokenAndGetJson >>",urlForValidateTokenAndGetJson); - const response = await axios.post(`${urlForValidateTokenAndGetJson}`, - { - token, - userId - }, - { - headers: { - 'Content-Type': 'application/json' + let response; + if (portal.portalAuth === "basic" && ((portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) || portal.apiSecret)) { + const auth = (portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) + ? "Basic " + btoa(portal.basicAuth.username + ":" + portal.basicAuth.password) + : "Basic " + btoa(portal.apiSecret); + response = await axios.post(`${urlForValidateTokenAndGetJson}`, + { + token, + userId + }, + { + headers: { + 'Content-Type': 'application/json', + 'Authorization': auth + }, + } + ); + } else { + response = await axios.post(`${urlForValidateTokenAndGetJson}`, + { + token, + userId + }, + { + headers: { + 'Content-Type': 'application/json' + } } - } - ); + ); + } //TODO: verify the json against schema or some other sanity checks return response.data; From c8762f45d9346c5bb70059f032aceeaafdc31ee9 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 22 Aug 2025 11:58:01 -0700 Subject: [PATCH 002/105] ADO-2787 style fix --- portal/loadPortalDataHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index 95a4fdf..ab87185 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -56,7 +56,7 @@ async function expireTokenInPortal(portal,token, userId) { } async function getSavedFormFromPortal(portal,token, userId) { - let parametersForForm = ""; + let parametersForForm = ""; try { const urlForValidateTokenAndGetJson= portal.apiHost+ (portal.getSavedJsonEndpoint || process.env.PORTAL_VALIDATE_TOKEN_ENDPOINT); From 1705b672563ecff04e9b334d2ea53f34dd704a05 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 22 Aug 2025 12:05:13 -0700 Subject: [PATCH 003/105] ADO-2787 style fix --- portal/loadPortalDataHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index ab87185..1890c6a 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -75,7 +75,7 @@ async function getSavedFormFromPortal(portal,token, userId) { headers: { 'Content-Type': 'application/json', 'Authorization': auth - }, + } } ); } else { From a79c3a3de0a7a147f7c4ec3ef5f6e696b5538aca Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Mon, 25 Aug 2025 13:19:24 -0700 Subject: [PATCH 004/105] (ADO-3242) create a date converter and change date format to ICM required format for XML conversion --- dateConverter.js | 21 +++++++++++++++++++++ saveICMdataHandler.js | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 dateConverter.js diff --git a/dateConverter.js b/dateConverter.js new file mode 100644 index 0000000..a5d36db --- /dev/null +++ b/dateConverter.js @@ -0,0 +1,21 @@ +/* Function changes string from YYYY-MM-DD date format to MM/DD/YYYY + * @param (String) originalDate + */ +function toICMFormat(originalDate) { + if (originalDate === '' || originalDate === null) { // If the date is empty, no need to proceed + return originalDate; + } + let newDate = ''; // The new date to be created + try { + const yearMonthDay = originalDate.split("-"); // Split into array [Year, Month, Day] + newDate = yearMonthDay[1] + "/" + yearMonthDay[2] + "/" + yearMonthDay[0] // Turn into "Month/Day/Year" + } catch (e) { + console.log("Something went wrong with configuring the date!"); + console.error(e); + return "-1"; + } + return newDate; +} + +// Export the function so it can be used in other files +module.exports = { toICMFormat }; \ No newline at end of file diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 9834e0e..30ea7b6 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -7,6 +7,7 @@ const { getUsername, isUsernameValid } = require("./usernameHandler.js"); const { getErrorMessage } = require("./errorHandling/errorHandler.js"); const { isJsonStringValid } = require("./validate.js"); const appCfg = require('./appConfig.js'); +const { toICMFormat } = require("./dateConverter.js"); const SIEBEL_ICM_API_FORMS_ENDPOINT = process.env.SIEBEL_ICM_API_FORMS_ENDPOINT; // utility function to fetch Attachment status (In Progress, Open...) @@ -133,6 +134,27 @@ async function saveICMdata(req, res) { saveJson["DocFileExt"] = "json"; saveJson["Doc Attachment Id"] = Buffer.from(savedFormParam).toString('base64');//savedForm is saved as attachment let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson + const formDefinitionItems = JSON.parse(savedFormParam)["form_definition"]["data"]["items"];// This is the field info for form items + const dateItemsId = []; // This will contain all of the IDs of the date fields + formDefinitionItems.forEach(item => { // Add any date types found in this loop into the dateItemsId + if (item.containerItems) { // Check for Date fields in containers + item.containerItems.forEach(subItem => { + if (subItem.type === "date") dateItemsId.push(subItem.id); + else if (subItem.type === "container") { // There is a possibility of dates being inside field type: container + subItem.containerItems.forEach(childItemData => { + if (childItemData.type === "date") dateItemsId.push(childItemData.id); + }); + } + }); + } else if (item.groupItems){ // Check for Date fields in groups + item.groupItems.forEach(subItem => { + subItem.fields.forEach(childItemData => { // Group fields is where the dates will be in + if (childItemData.type === "date") dateItemsId.push(childItemData.id); + }); + }); + } + }); + const truncatedKeysSaveData = {}; for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) const stringLength = oldKey.length; @@ -144,7 +166,15 @@ async function saveICMdata(req, res) { for (let oldChildKey in saveData[oldKey][i]) { const childStringLength = oldChildKey.length; const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); - truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; + if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + truncatedChildrenKeys[newChildKey] = newDateFormat; + } else { + truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; + } } childrenArray.push(truncatedChildrenKeys); } @@ -152,7 +182,15 @@ async function saveICMdata(req, res) { wrapperKey[newKey] = childrenArray; truncatedKeysSaveData[`${newKey}-List`] = wrapperKey // Add a wrapper around the children/dependecies } else { - truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + if (dateItemsId.includes(oldKey)) { // If data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + truncatedKeysSaveData[newKey] = newDateFormat; + } else { + truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + } } } let builder = new xml2js.Builder({xmldec: { version: '1.0' }}); From 4699614acd106c9bbebea215ccc12395d99fdae1 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Wed, 27 Aug 2025 15:16:53 -0700 Subject: [PATCH 005/105] ADO-3265 JSON/XML configuration and start to form exception dictionary --- dictionary/dictionaryUtils.js | 33 ++++++++++++++++++++++++++++ dictionary/jsonXmlConversion.js | 17 +++++++++++++++ saveICMdataHandler.js | 38 +++++++++++++++++++++++++++++++-- 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 dictionary/dictionaryUtils.js create mode 100644 dictionary/jsonXmlConversion.js diff --git a/dictionary/dictionaryUtils.js b/dictionary/dictionaryUtils.js new file mode 100644 index 0000000..92f8443 --- /dev/null +++ b/dictionary/dictionaryUtils.js @@ -0,0 +1,33 @@ +/* Checks if a dictionary key exists + * @param dictionary : the dictionary to search through + * @param key : the key in the dictionary + * @param property : the key's property + */ +function keyExists (dictionary, key) { + if (dictionary[key]) return true; + else return false; +}; + +/* Checks if a dictionary key has a property value + * @param dictionary : the dictionary to search through + * @param key : the key in the dictionary + * @param property : the key's property + */ +function propertyExists (dictionary, key, property) { + if (dictionary[key] && dictionary[key][property]) return true; + else return false; +}; + +/* Checks if a property value is not empty + * @param dictionary : the dictionary to search through + * @param key : the key in the dictionary + * @param property : the key's property + */ +function propertyNotEmpty (dictionary, key, property) { + const propertyType = typeof dictionary[key][property]; + if (propertyType === "string" && dictionary[key][property] != "") return true; + if (propertyType === "object" && dictionary[key][property] != []) return true; + else return false; +}; + +module.exports = { keyExists, propertyExists, propertyNotEmpty }; \ No newline at end of file diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js new file mode 100644 index 0000000..343bea3 --- /dev/null +++ b/dictionary/jsonXmlConversion.js @@ -0,0 +1,17 @@ +/* List of forms that do not convert directly from JSON to XML + * Keys are the form_definition.form_id + * Property: rootName : a string that will replace "root" from the XML + * Property: subRoots : an array of strings. These will be between root and JSON data. The first value in the array will be highest (after root) while the last will be lowest (before JSON) + * Property: omitFields : an array of strings. These are the UUIDs of fields that must be omitted before XML creation. (TO BE DONE in saveICMdataHandler.js) + * Property: version : a string that will check if the exception list should include the version of the form submitted. + */ +const formExceptions = { + "HR3689E": { + "rootName": "ListOfDtFormInstanceLW", + "subRoots": ["FormInstance"], + "omitFields": [], + "version": "2", + } +}; + +module.exports = { formExceptions }; \ No newline at end of file diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 30ea7b6..55eed40 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -8,6 +8,8 @@ const { getErrorMessage } = require("./errorHandling/errorHandler.js"); const { isJsonStringValid } = require("./validate.js"); const appCfg = require('./appConfig.js'); const { toICMFormat } = require("./dateConverter.js"); +const { formExceptions } = require("./dictionary/jsonXmlConversion.js"); +const { propertyExists, propertyNotEmpty, keyExists } = require("./dictionary/dictionaryUtils.js"); const SIEBEL_ICM_API_FORMS_ENDPOINT = process.env.SIEBEL_ICM_API_FORMS_ENDPOINT; // utility function to fetch Attachment status (In Progress, Open...) @@ -193,8 +195,40 @@ async function saveICMdata(req, res) { } } } - let builder = new xml2js.Builder({xmldec: { version: '1.0' }}); - saveJson["XML Hierarchy"] = builder.buildObject(truncatedKeysSaveData); + let builder; // This will be for building the XML + const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID + const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version + const dictionary = formExceptions; + if (keyExists(dictionary, formId) && dictionary[formId].version === formVersion) { // If any forms with the correct version have been listed as exceptions, then proceed with their form exceptions + // If the root needs a differernt name, apply it here. Otherwise use the default "root" + if (propertyExists(dictionary, formId, "rootName") && propertyNotEmpty(dictionary, formId, "rootName")) { + builder = new xml2js.Builder({xmldec: { version: '1.0' }, rootName: dictionary[formId]["rootName"]}); + } else { + builder = new xml2js.Builder({xmldec: { version: '1.0' }}); + } + + let wrapperJson = truncatedKeysSaveData; + // If subRoots exist, wrap the sub-roots around the JSON where the last array object will be closest to JSON and first array object will be closest to root/rootName + if (propertyExists(dictionary, formId, "subRoots") && propertyNotEmpty(dictionary, formId, "subRoots")) { + wrapperJson = {}; + let tempJson = {}; + const subRootLength = dictionary[formId]["subRoots"].length; + for (i = subRootLength; i > 0; i= i -1) { + if (i === subRootLength) { + tempJson[dictionary[formId]["subRoots"][i-1]] = truncatedKeysSaveData; + } else { + wrapperJson[dictionary[formId]["subRoots"][i-1]] = tempJson; + tempJson = wrapperJson; + wrapperJson = {}; + } + } + wrapperJson = tempJson; + } + saveJson["XML Hierarchy"] = builder.buildObject(wrapperJson); + } else { + builder = new xml2js.Builder({xmldec: { version: '1.0' }}); + saveJson["XML Hierarchy"] = builder.buildObject(truncatedKeysSaveData); + } //let url = buildUrlWithParams('SIEBEL_ICM_API_HOST', 'fwd/v1.0/data/DT Form Instance Thin/DT Form Instance Thin/' + attachment_id + '/', ''); let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); try { From 9228aff819e8c70a0dc086da8d0f9fc3730049a7 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Wed, 27 Aug 2025 16:11:12 -0700 Subject: [PATCH 006/105] ADO-3265 fix version layout and remove from form check --- dictionary/dictionaryUtils.js | 28 ++++++++++++++++++++++++++-- dictionary/jsonXmlConversion.js | 10 ++++++++-- saveICMdataHandler.js | 2 +- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/dictionary/dictionaryUtils.js b/dictionary/dictionaryUtils.js index 92f8443..6b984ac 100644 --- a/dictionary/dictionaryUtils.js +++ b/dictionary/dictionaryUtils.js @@ -18,6 +18,17 @@ function propertyExists (dictionary, key, property) { else return false; }; +/* Checks if a dictionary key's property's key exists + * @param dictionary : the dictionary to search through + * @param key : the key in the dictionary + * @param property : the key's property + * @param propertyKey : the property's key + */ +function propertyKeyExists (dictionary, key, property, propertyKey) { + if (dictionary[key] && dictionary[key][property] && dictionary[key][property][propertyKey]) return true; + else return false; +}; + /* Checks if a property value is not empty * @param dictionary : the dictionary to search through * @param key : the key in the dictionary @@ -26,8 +37,21 @@ function propertyExists (dictionary, key, property) { function propertyNotEmpty (dictionary, key, property) { const propertyType = typeof dictionary[key][property]; if (propertyType === "string" && dictionary[key][property] != "") return true; - if (propertyType === "object" && dictionary[key][property] != []) return true; + else if (propertyType === "object" && dictionary[key][property] != [] && dictionary[key][property] != {}) return true; + else return false; +}; + +/* Checks if a property value is not empty + * @param dictionary : the dictionary to search through + * @param key : the key in the dictionary + * @param property : the key's property + * @param propertyKey : the property's key + */ +function propertyKeyNotEmpty (dictionary, key, property, propertyKey) { + const propertyType = typeof dictionary[key][property]; + if (propertyType === "string" && dictionary[key][property] != "") return true; + else if (propertyType === "object" && dictionary[key][property][propertyKey] != [] && dictionary[key][property][propertyKey] != {}) return true; else return false; }; -module.exports = { keyExists, propertyExists, propertyNotEmpty }; \ No newline at end of file +module.exports = { keyExists, propertyExists, propertyKeyExists, propertyNotEmpty, propertyKeyNotEmpty }; \ No newline at end of file diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 343bea3..0cbde33 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -9,8 +9,14 @@ const formExceptions = { "HR3689E": { "rootName": "ListOfDtFormInstanceLW", "subRoots": ["FormInstance"], - "omitFields": [], - "version": "2", + "versions" : { + "1" :{ + "omitFields": [] + }, + "2" : { + "omitFields": [] + } + } } }; diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 55eed40..39f65d1 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -199,7 +199,7 @@ async function saveICMdata(req, res) { const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version const dictionary = formExceptions; - if (keyExists(dictionary, formId) && dictionary[formId].version === formVersion) { // If any forms with the correct version have been listed as exceptions, then proceed with their form exceptions + if (keyExists(dictionary, formId)) { // If any forms with the correct version have been listed as exceptions, then proceed with their form exceptions // If the root needs a differernt name, apply it here. Otherwise use the default "root" if (propertyExists(dictionary, formId, "rootName") && propertyNotEmpty(dictionary, formId, "rootName")) { builder = new xml2js.Builder({xmldec: { version: '1.0' }, rootName: dictionary[formId]["rootName"]}); From 34f6e664ea7074a6acbe62f58fce009ad527dc3a Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Thu, 28 Aug 2025 11:12:22 -0700 Subject: [PATCH 007/105] ADO-3273 change checkbox format for save ICM data handler --- saveICMdataHandler.js | 90 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 39f65d1..374d292 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -137,25 +137,10 @@ async function saveICMdata(req, res) { saveJson["Doc Attachment Id"] = Buffer.from(savedFormParam).toString('base64');//savedForm is saved as attachment let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson const formDefinitionItems = JSON.parse(savedFormParam)["form_definition"]["data"]["items"];// This is the field info for form items - const dateItemsId = []; // This will contain all of the IDs of the date fields - formDefinitionItems.forEach(item => { // Add any date types found in this loop into the dateItemsId - if (item.containerItems) { // Check for Date fields in containers - item.containerItems.forEach(subItem => { - if (subItem.type === "date") dateItemsId.push(subItem.id); - else if (subItem.type === "container") { // There is a possibility of dates being inside field type: container - subItem.containerItems.forEach(childItemData => { - if (childItemData.type === "date") dateItemsId.push(childItemData.id); - }); - } - }); - } else if (item.groupItems){ // Check for Date fields in groups - item.groupItems.forEach(subItem => { - subItem.fields.forEach(childItemData => { // Group fields is where the dates will be in - if (childItemData.type === "date") dateItemsId.push(childItemData.id); - }); - }); - } - }); + + // dateItemsId : This will contain all of the IDs of the date fields + // checkboxItemsId : This will contain all of the IDs of the checkbox fields + const { dateItemsId, checkboxItemsId } = getFormIds(formDefinitionItems); const truncatedKeysSaveData = {}; for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) @@ -174,6 +159,8 @@ async function saveICMdata(req, res) { throw new Error("Invalid date. Was unable to convert to ICM format!"); } truncatedChildrenKeys[newChildKey] = newDateFormat; + } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field, change from true/false/undefined to Yes/No/"" + truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); } else { truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } @@ -190,6 +177,8 @@ async function saveICMdata(req, res) { throw new Error("Invalid date. Was unable to convert to ICM format!"); } truncatedKeysSaveData[newKey] = newDateFormat; + } else if (checkboxItemsId.includes(oldKey)) { // If data is in a checkbox field, change from true/false/undefined to Yes/No/"" + truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); } else { truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key } @@ -418,6 +407,69 @@ async function clearICMLockedFlag(req, res) { } } + +/* Get the UUIDs (data.items "id"s) from the form for specific field types + * @params formDefinitionItems = JSON.parse(savedFormParam)["form_definition"]["data"]["items"] + * @returns dateItemsId : This will contain all of the IDs of the date fields + * @returns checkboxItemsId : This will contain all of the IDs of the checkbox fields + */ +function getFormIds (formDefinitionItems) { + const dateItemsId = []; + const checkboxItemsId = []; + formDefinitionItems.forEach(item => { // Add the field types found in this loop into their specific item id arrays + if (item.containerItems) { // Check for fields in containers (currently, there can be up to 5 container levels) + item.containerItems.forEach(subItem => { + if (subItem.type === "date") dateItemsId.push(subItem.id); + else if (subItem.type === "checkbox") checkboxItemsId.push(subItem.id); + else if (subItem.type === "container") { // Check container field level 2 + subItem.containerItems.forEach(subItem2 => { + if (subItem2.type === "date") dateItemsId.push(subItem2.id); + else if (subItem2.type === "checkbox") checkboxItemsId.push(subItem2.id); + else if (subItem2.type === "container") { // Check container field level 3 + subItem2.containerItems.forEach(subItem3 => { + if (subItem3.type === "date") dateItemsId.push(subItem3.id); + else if (subItem3.type === "checkbox") checkboxItemsId.push(subItem3.id); + else if (subItem3.type === "container") { // Check container field level 4 + subItem3.containerItems.forEach(subItem4 => { + if (subItem4.type === "date") dateItemsId.push(subItem4.id); + else if (subItem4.type === "checkbox") checkboxItemsId.push(subItem4.id); + else if (subItem4.type === "container") { // Check container field level 5 + subItem4.containerItems.forEach(subItem5 => { + if (subItem5.type === "date") dateItemsId.push(subItem5.id); + else if (subItem5.type === "checkbox") checkboxItemsId.push(subItem5.id); + }); + } + }); + } + }); + } + }); + } + }); + } else if (item.groupItems){ // Check for fields in groups + item.groupItems.forEach(subItem => { + subItem.fields.forEach(childItemData => { // Group's fields is where the fields will be in + if (childItemData.type === "date") dateItemsId.push(childItemData.id); + else if (childItemData.type === "checkbox") checkboxItemsId.push(childItemData.id); + }); + }); + } + }); + return { dateItemsId, checkboxItemsId }; +} + +/** + * Convert the checkbox value to one of the following: + * true -> "Yes" + * false -> "No" + * undefined -> "" + */ +function convertCheckboxFormatToICM (value) { + if (value === true) return "Yes"; + else if (value === false) return "No"; + else return ""; +} + module.exports.saveICMdata = saveICMdata; module.exports.loadICMdata = loadICMdata; module.exports.clearICMLockedFlag = clearICMLockedFlag; From 400eac748ea907a5dd369e33a31aee1341fafe13 Mon Sep 17 00:00:00 2001 From: bzimonja Date: Thu, 28 Aug 2025 14:16:51 -0700 Subject: [PATCH 008/105] For checkbox, yes/no answers in bindings are transformed to true/false --- databindingsHandler.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/databindingsHandler.js b/databindingsHandler.js index 1a3a65e..7e89869 100644 --- a/databindingsHandler.js +++ b/databindingsHandler.js @@ -266,6 +266,16 @@ function transformValueToBindIfNeeded(field, valueToBind) { const transformedValue = format(parsedDate, "yyyy-MM-dd"); return transformedValue; } + if (field && field.type == "checkbox" && valueToBind){ + if (valueToBind == "Yes" || valueToBind=="yes") + { + return (true); + } + if (valueToBind == "No"|| valueToBind=="no") + { + return (false); + } + } } catch (error) { console.error('Error processing date value:', error); } From 0a0af9ea22f9a2f75d8da345b27a26b55aeaf7f9 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Thu, 28 Aug 2025 15:05:13 -0700 Subject: [PATCH 009/105] ADO-3273 recursive function for get form ids/uuids --- saveICMdataHandler.js | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 374d292..d5d809c 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -414,36 +414,17 @@ async function clearICMLockedFlag(req, res) { * @returns checkboxItemsId : This will contain all of the IDs of the checkbox fields */ function getFormIds (formDefinitionItems) { - const dateItemsId = []; - const checkboxItemsId = []; + let dateItemsId = []; + let checkboxItemsId = []; formDefinitionItems.forEach(item => { // Add the field types found in this loop into their specific item id arrays if (item.containerItems) { // Check for fields in containers (currently, there can be up to 5 container levels) item.containerItems.forEach(subItem => { if (subItem.type === "date") dateItemsId.push(subItem.id); else if (subItem.type === "checkbox") checkboxItemsId.push(subItem.id); - else if (subItem.type === "container") { // Check container field level 2 - subItem.containerItems.forEach(subItem2 => { - if (subItem2.type === "date") dateItemsId.push(subItem2.id); - else if (subItem2.type === "checkbox") checkboxItemsId.push(subItem2.id); - else if (subItem2.type === "container") { // Check container field level 3 - subItem2.containerItems.forEach(subItem3 => { - if (subItem3.type === "date") dateItemsId.push(subItem3.id); - else if (subItem3.type === "checkbox") checkboxItemsId.push(subItem3.id); - else if (subItem3.type === "container") { // Check container field level 4 - subItem3.containerItems.forEach(subItem4 => { - if (subItem4.type === "date") dateItemsId.push(subItem4.id); - else if (subItem4.type === "checkbox") checkboxItemsId.push(subItem4.id); - else if (subItem4.type === "container") { // Check container field level 5 - subItem4.containerItems.forEach(subItem5 => { - if (subItem5.type === "date") dateItemsId.push(subItem5.id); - else if (subItem5.type === "checkbox") checkboxItemsId.push(subItem5.id); - }); - } - }); - } - }); - } - }); + else if (subItem.type === "container") { + const {dateItemsId: recursiveDateItemIds, checkboxItemsId: recursiveCheckboxItemIds} = getFormIds([subItem]); + dateItemsId = [...dateItemsId, ...recursiveDateItemIds]; + checkboxItemsId = [...checkboxItemsId, ...recursiveCheckboxItemIds]; } }); } else if (item.groupItems){ // Check for fields in groups From cb00fbcc2134393359ccbbe61be0a563a6838109 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 29 Aug 2025 15:34:32 -0700 Subject: [PATCH 010/105] ADO-3265 fix in utils and longer workaround for JSON to XML wrappertag lite --- dictionary/dictionaryUtils.js | 2 +- dictionary/jsonXmlConversion.js | 54 +++++++++++++++++++++++++++++++-- saveICMdataHandler.js | 36 +++++++++++++++++++--- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/dictionary/dictionaryUtils.js b/dictionary/dictionaryUtils.js index 6b984ac..f271570 100644 --- a/dictionary/dictionaryUtils.js +++ b/dictionary/dictionaryUtils.js @@ -37,7 +37,7 @@ function propertyKeyExists (dictionary, key, property, propertyKey) { function propertyNotEmpty (dictionary, key, property) { const propertyType = typeof dictionary[key][property]; if (propertyType === "string" && dictionary[key][property] != "") return true; - else if (propertyType === "object" && dictionary[key][property] != [] && dictionary[key][property] != {}) return true; + else if (propertyType === "object" && dictionary[key][property].length != 0 && dictionary[key][property] != {}) return true; else return false; }; diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 0cbde33..57edfd7 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -2,13 +2,61 @@ * Keys are the form_definition.form_id * Property: rootName : a string that will replace "root" from the XML * Property: subRoots : an array of strings. These will be between root and JSON data. The first value in the array will be highest (after root) while the last will be lowest (before JSON) + * Property: wrapperTags : an array of objects. If not empty, then should include object { [the wrapper Tag name] : { wrapFields: [], wrapperTags: []} } where the second wrapperTag repeats the full object if children are needed. + * Property: allowBooleanCheckbox : if a field is in this array, skip conversion step * Property: omitFields : an array of strings. These are the UUIDs of fields that must be omitted before XML creation. (TO BE DONE in saveICMdataHandler.js) - * Property: version : a string that will check if the exception list should include the version of the form submitted. + * Property: version : a string that will check if the exception list should include the version of the form submitted. All values in version should overwrite the default form properties. */ const formExceptions = { - "HR3689E": { + "HR3689E": { "rootName": "ListOfDtFormInstanceLW", "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowBooleanCheckbox": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "CF0925": { + "rootName": "", + "subRoots": [], + "wrapperTags": [ + { + "ParentGuardian": { + "wrapFields": [ + "last-name-91433118-4a0c-47fe-80d8-2676f83a2022", + "first-name-5dd5f94c-be98-4dd1-a442-3903c6330391", + "middle-names-690ef4b4-bf83-487e-8ef2-fd39f09fb545", + "address-parent-9dcf849c-b28b-4faa-afe3-8603af7c98fa", + "unit-fbbdce5c-a970-4d8f-98f5-d3a6e8af6005", + "address-1-a81293da-3d95-42cf-b6fa-ec15fac39ed1", + "address-2-3f5515c8-8d91-424f-8302-0e7fea6599bf", + "city-80cbf0f7-fa28-47f0-a4e4-94793e8a12fb", + "province-835a46e3-4a7c-4496-a196-8e93172092ed", + "postal-code-29edf972-2b5c-421d-a1ab-d97aa4005911", + "primary-phone-number-type-a7eba596-6474-4a01-93e5-6cd49c8ef4cc", + "primary-phone-number-f29b9ffd-9e7c-4f20-8bc3-57b333d88a0e", + "secondary-phone-number-type-fc15a5f0-bfcd-4fe8-8836-833fc2573525", + "secondary-phone-number-135375c3-f0bc-4b3a-aea2-b98995676ebc", + ], + "wrapperTags" : [] + }, + }, + { + "Child" : { + "wrapFields" : [], + "wrapperTags" : [] + } + } + ], + "allowBooleanCheckbox": [], + "omitFields": [], "versions" : { "1" :{ "omitFields": [] @@ -17,7 +65,7 @@ const formExceptions = { "omitFields": [] } } - } + } }; module.exports = { formExceptions }; \ No newline at end of file diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 374d292..b17e1da 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -142,7 +142,25 @@ async function saveICMdata(req, res) { // checkboxItemsId : This will contain all of the IDs of the checkbox fields const { dateItemsId, checkboxItemsId } = getFormIds(formDefinitionItems); + const wrapperValues = {}; + + const dictionary = formExceptions; + const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID + const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version + const isFormException = keyExists(dictionary, formId); // If true, then this form will have its exceptions formatted + const toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists + if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { + dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { + const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; + wrapperValues[tagKey] = []; + wrapperTag[tagKey]["wrapFields"].forEach(fieldId => { + toWrapIds[fieldId] = tagKey; + }); + }); + } + const truncatedKeysSaveData = {}; + for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) const stringLength = oldKey.length; const newKey = oldKey.substring(0, stringLength-28); @@ -180,15 +198,23 @@ async function saveICMdata(req, res) { } else if (checkboxItemsId.includes(oldKey)) { // If data is in a checkbox field, change from true/false/undefined to Yes/No/"" truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); } else { - truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + if (toWrapIds[oldKey]) { + wrapperValues[toWrapIds[oldKey]].push({[newKey]: saveData[oldKey]}); + } else { + truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + } } } } + + const wrapperTagKeys = Object.keys(wrapperValues); + wrapperTagKeys.forEach(key => { + const bundledItems = wrapperValues[key]; + if (bundledItems.length != 0) truncatedKeysSaveData[key] = [bundledItems]; + }); + let builder; // This will be for building the XML - const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID - const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version - const dictionary = formExceptions; - if (keyExists(dictionary, formId)) { // If any forms with the correct version have been listed as exceptions, then proceed with their form exceptions + if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions // If the root needs a differernt name, apply it here. Otherwise use the default "root" if (propertyExists(dictionary, formId, "rootName") && propertyNotEmpty(dictionary, formId, "rootName")) { builder = new xml2js.Builder({xmldec: { version: '1.0' }, rootName: dictionary[formId]["rootName"]}); From cf6e2fc6bfa255704d980f7b0504d48b6f8c8e56 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 29 Aug 2025 16:03:23 -0700 Subject: [PATCH 011/105] (ADO-3265) the better way of wrapping. Still need to do if-statement --- saveICMdataHandler.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index b17e1da..17a4583 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -142,7 +142,7 @@ async function saveICMdata(req, res) { // checkboxItemsId : This will contain all of the IDs of the checkbox fields const { dateItemsId, checkboxItemsId } = getFormIds(formDefinitionItems); - const wrapperValues = {}; + const truncatedKeysSaveData = {}; const dictionary = formExceptions; const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID @@ -152,14 +152,18 @@ async function saveICMdata(req, res) { if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; - wrapperValues[tagKey] = []; - wrapperTag[tagKey]["wrapFields"].forEach(fieldId => { - toWrapIds[fieldId] = tagKey; - }); + if (wrapperTag[tagKey]["wrapFields"].length != 0) { // If there are any wrappers with no fields, ignore. Otherwise, keep a list of UUID and the wrapper to put it in. + truncatedKeysSaveData[tagKey] = {}; + wrapperTag[tagKey]["wrapFields"].forEach(fieldId => { + toWrapIds[fieldId] = tagKey; + }); + } }); } - const truncatedKeysSaveData = {}; + + // TODO: The if-statement. The Else-statement at the very bottom is done. Consider making these checks a function. + for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) const stringLength = oldKey.length; @@ -194,24 +198,26 @@ async function saveICMdata(req, res) { if (newDateFormat === "-1") { throw new Error("Invalid date. Was unable to convert to ICM format!"); } - truncatedKeysSaveData[newKey] = newDateFormat; + if (toWrapIds[oldKey]) { + truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = newDateFormat; + } else { + truncatedKeysSaveData[newKey] = newDateFormat; + } } else if (checkboxItemsId.includes(oldKey)) { // If data is in a checkbox field, change from true/false/undefined to Yes/No/"" - truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); + if (toWrapIds[oldKey]) { + truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = convertCheckboxFormatToICM(saveData[oldKey]); + } else { + truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); + } } else { if (toWrapIds[oldKey]) { - wrapperValues[toWrapIds[oldKey]].push({[newKey]: saveData[oldKey]}); + truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = saveData[oldKey]; } else { truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key } } } } - - const wrapperTagKeys = Object.keys(wrapperValues); - wrapperTagKeys.forEach(key => { - const bundledItems = wrapperValues[key]; - if (bundledItems.length != 0) truncatedKeysSaveData[key] = [bundledItems]; - }); let builder; // This will be for building the XML if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions From ed6bf8d6af5f5951b812d81f72598d8f13c2a3ff Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Tue, 2 Sep 2025 11:31:30 -0700 Subject: [PATCH 012/105] (ADO-3265) wrapping child lists in tag --- saveICMdataHandler.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 17a4583..4b777ef 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -162,9 +162,6 @@ async function saveICMdata(req, res) { } - // TODO: The if-statement. The Else-statement at the very bottom is done. Consider making these checks a function. - - for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) const stringLength = oldKey.length; const newKey = oldKey.substring(0, stringLength-28); @@ -189,9 +186,15 @@ async function saveICMdata(req, res) { } childrenArray.push(truncatedChildrenKeys); } - const wrapperKey = {} - wrapperKey[newKey] = childrenArray; - truncatedKeysSaveData[`${newKey}-List`] = wrapperKey // Add a wrapper around the children/dependecies + if (toWrapIds[oldKey]) { + const wrapperKey = {}; + wrapperKey[newKey] = childrenArray; + truncatedKeysSaveData[toWrapIds[oldKey]][`${newKey}-List`] = wrapperKey // Add a wrapper around the children/dependecies + } else { + const wrapperKey = {}; + wrapperKey[newKey] = childrenArray; + truncatedKeysSaveData[`${newKey}-List`] = wrapperKey; // Add a wrapper around the children/dependecies + } } else { if (dateItemsId.includes(oldKey)) { // If data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY const newDateFormat = toICMFormat(saveData[oldKey]); From 7a212cf30d462bfa17668a6152001a0133c8fb0a Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Tue, 2 Sep 2025 12:56:00 -0700 Subject: [PATCH 013/105] (ADO)-3265 code cleaning and testing wrapper --- dictionary/dictionaryUtils.js | 17 ++-- dictionary/jsonXmlConversion.js | 13 +-- saveICMdataHandler.js | 147 ++++++++++++++++++-------------- 3 files changed, 102 insertions(+), 75 deletions(-) diff --git a/dictionary/dictionaryUtils.js b/dictionary/dictionaryUtils.js index f271570..6f24c85 100644 --- a/dictionary/dictionaryUtils.js +++ b/dictionary/dictionaryUtils.js @@ -1,38 +1,42 @@ -/* Checks if a dictionary key exists +/** Checks if a dictionary key exists * @param dictionary : the dictionary to search through * @param key : the key in the dictionary * @param property : the key's property + * @return boolean true/false */ function keyExists (dictionary, key) { if (dictionary[key]) return true; else return false; }; -/* Checks if a dictionary key has a property value +/** Checks if a dictionary key has a property value * @param dictionary : the dictionary to search through * @param key : the key in the dictionary * @param property : the key's property + * @return boolean true/false */ function propertyExists (dictionary, key, property) { if (dictionary[key] && dictionary[key][property]) return true; else return false; }; -/* Checks if a dictionary key's property's key exists +/** Checks if a dictionary key's property's key exists * @param dictionary : the dictionary to search through * @param key : the key in the dictionary * @param property : the key's property * @param propertyKey : the property's key + * @return boolean true/false */ function propertyKeyExists (dictionary, key, property, propertyKey) { if (dictionary[key] && dictionary[key][property] && dictionary[key][property][propertyKey]) return true; else return false; }; -/* Checks if a property value is not empty +/** Checks if a property value is not empty * @param dictionary : the dictionary to search through * @param key : the key in the dictionary * @param property : the key's property + * @return boolean true/false */ function propertyNotEmpty (dictionary, key, property) { const propertyType = typeof dictionary[key][property]; @@ -41,16 +45,17 @@ function propertyNotEmpty (dictionary, key, property) { else return false; }; -/* Checks if a property value is not empty +/** Checks if a property value is not empty * @param dictionary : the dictionary to search through * @param key : the key in the dictionary * @param property : the key's property * @param propertyKey : the property's key + * @return boolean true/false */ function propertyKeyNotEmpty (dictionary, key, property, propertyKey) { const propertyType = typeof dictionary[key][property]; if (propertyType === "string" && dictionary[key][property] != "") return true; - else if (propertyType === "object" && dictionary[key][property][propertyKey] != [] && dictionary[key][property][propertyKey] != {}) return true; + else if (propertyType === "object" && dictionary[key][property][propertyKey] != [] && dictionary[key][property][propertyKey].length != 0 && dictionary[key][property][propertyKey] != {}) return true; else return false; }; diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 57edfd7..d77ae71 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -2,7 +2,7 @@ * Keys are the form_definition.form_id * Property: rootName : a string that will replace "root" from the XML * Property: subRoots : an array of strings. These will be between root and JSON data. The first value in the array will be highest (after root) while the last will be lowest (before JSON) - * Property: wrapperTags : an array of objects. If not empty, then should include object { [the wrapper Tag name] : { wrapFields: [], wrapperTags: []} } where the second wrapperTag repeats the full object if children are needed. + * Property: wrapperTags : an array of objects. If not empty, then should include object { [the wrapper Tag name] : { wrapFields: [] } } * Property: allowBooleanCheckbox : if a field is in this array, skip conversion step * Property: omitFields : an array of strings. These are the UUIDs of fields that must be omitted before XML creation. (TO BE DONE in saveICMdataHandler.js) * Property: version : a string that will check if the exception list should include the version of the form submitted. All values in version should overwrite the default form properties. @@ -43,15 +43,18 @@ const formExceptions = { "primary-phone-number-type-a7eba596-6474-4a01-93e5-6cd49c8ef4cc", "primary-phone-number-f29b9ffd-9e7c-4f20-8bc3-57b333d88a0e", "secondary-phone-number-type-fc15a5f0-bfcd-4fe8-8836-833fc2573525", - "secondary-phone-number-135375c3-f0bc-4b3a-aea2-b98995676ebc", + "secondary-phone-number-135375c3-f0bc-4b3a-aea2-b98995676ebc" ], - "wrapperTags" : [] }, }, { "Child" : { - "wrapFields" : [], - "wrapperTags" : [] + "wrapFields" : [ + "child-last-name-15a0adbf-a3bd-4bd1-be3f-bfb9a2b02f3e", + "child-first-name-4a8302bb-6eca-46c8-ae50-efe37434ea8e", + "child-middle-names-81cf1856-1541-4eed-85d8-1f376b727fd0", + "child-dob-3d6f9a94-7c0f-4a06-89dc-872ee39fa818" + ], } } ], diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 4b777ef..599ab2f 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -142,18 +142,17 @@ async function saveICMdata(req, res) { // checkboxItemsId : This will contain all of the IDs of the checkbox fields const { dateItemsId, checkboxItemsId } = getFormIds(formDefinitionItems); - const truncatedKeysSaveData = {}; - const dictionary = formExceptions; const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version const isFormException = keyExists(dictionary, formId); // If true, then this form will have its exceptions formatted + const tempFormExceptionList = {}; const toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; if (wrapperTag[tagKey]["wrapFields"].length != 0) { // If there are any wrappers with no fields, ignore. Otherwise, keep a list of UUID and the wrapper to put it in. - truncatedKeysSaveData[tagKey] = {}; + tempFormExceptionList[tagKey] = {}; wrapperTag[tagKey]["wrapFields"].forEach(fieldId => { toWrapIds[fieldId] = tagKey; }); @@ -161,66 +160,8 @@ async function saveICMdata(req, res) { }); } - - for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) - const stringLength = oldKey.length; - const newKey = oldKey.substring(0, stringLength-28); - if (Array.isArray(saveData[oldKey]) > 0 && Object.keys(saveData[oldKey]).length > 0) { //This trunicates child/dependant objects - const childrenArray = []; - for(let i = 0; i < saveData[oldKey].length; i++) { - const truncatedChildrenKeys = {}; - for (let oldChildKey in saveData[oldKey][i]) { - const childStringLength = oldChildKey.length; - const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); - if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY - const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); - if (newDateFormat === "-1") { - throw new Error("Invalid date. Was unable to convert to ICM format!"); - } - truncatedChildrenKeys[newChildKey] = newDateFormat; - } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field, change from true/false/undefined to Yes/No/"" - truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); - } else { - truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; - } - } - childrenArray.push(truncatedChildrenKeys); - } - if (toWrapIds[oldKey]) { - const wrapperKey = {}; - wrapperKey[newKey] = childrenArray; - truncatedKeysSaveData[toWrapIds[oldKey]][`${newKey}-List`] = wrapperKey // Add a wrapper around the children/dependecies - } else { - const wrapperKey = {}; - wrapperKey[newKey] = childrenArray; - truncatedKeysSaveData[`${newKey}-List`] = wrapperKey; // Add a wrapper around the children/dependecies - } - } else { - if (dateItemsId.includes(oldKey)) { // If data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY - const newDateFormat = toICMFormat(saveData[oldKey]); - if (newDateFormat === "-1") { - throw new Error("Invalid date. Was unable to convert to ICM format!"); - } - if (toWrapIds[oldKey]) { - truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = newDateFormat; - } else { - truncatedKeysSaveData[newKey] = newDateFormat; - } - } else if (checkboxItemsId.includes(oldKey)) { // If data is in a checkbox field, change from true/false/undefined to Yes/No/"" - if (toWrapIds[oldKey]) { - truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = convertCheckboxFormatToICM(saveData[oldKey]); - } else { - truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); - } - } else { - if (toWrapIds[oldKey]) { - truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = saveData[oldKey]; - } else { - truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key - } - } - } - } + // The updated JSON values required for XML creation + const truncatedKeysSaveData = fixJSONValuesForXML(saveData, tempFormExceptionList, toWrapIds, dateItemsId, checkboxItemsId); let builder; // This will be for building the XML if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions @@ -443,7 +384,7 @@ async function clearICMLockedFlag(req, res) { } -/* Get the UUIDs (data.items "id"s) from the form for specific field types +/** Get the UUIDs (data.items "id"s) from the form for specific field types * @params formDefinitionItems = JSON.parse(savedFormParam)["form_definition"]["data"]["items"] * @returns dateItemsId : This will contain all of the IDs of the date fields * @returns checkboxItemsId : This will contain all of the IDs of the checkbox fields @@ -505,6 +446,84 @@ function convertCheckboxFormatToICM (value) { else return ""; } +/** Take the saveData JSON values and update them to new layout. Changes include: + * Shorten UUID. + * Wrap values with children inside a List. + * Wrap specified values under a parent. + * Change date format. + * Convert checkboxes to use "Yes" or "No". + * + * @param saveData : = JSON.parse(savedFormParam)["data"] + * @param truncatedKeysSaveData : list of key-object pairs that will be returned at end of function. These may or may not already include values related to toWrapIds. + * @param toWrapIds : list of key-string pairs where the keys are UUIDs and the strings are what wrapper tag they need to go under. + * @param dateItemsId : list of date fields + * @param checkboxItemsId : list of checkbox fields + * @returns truncatedKeysSaveData : a list of key-object pairs + */ + +function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId) { + for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) + const stringLength = oldKey.length; + const newKey = oldKey.substring(0, stringLength-28); + if (Array.isArray(saveData[oldKey]) > 0 && Object.keys(saveData[oldKey]).length > 0) { //This trunicates child/dependant objects + const childrenArray = []; + for(let i = 0; i < saveData[oldKey].length; i++) { + const truncatedChildrenKeys = {}; + for (let oldChildKey in saveData[oldKey][i]) { + const childStringLength = oldChildKey.length; + const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); + if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + truncatedChildrenKeys[newChildKey] = newDateFormat; + } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field, change from true/false/undefined to Yes/No/"" + truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); + } else { + truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; + } + } + childrenArray.push(truncatedChildrenKeys); + } + if (toWrapIds[oldKey]) { + const wrapperKey = {}; + wrapperKey[newKey] = childrenArray; + truncatedKeysSaveData[toWrapIds[oldKey]][`${newKey}-List`] = wrapperKey // Add a wrapper around the children/dependecies + } else { + const wrapperKey = {}; + wrapperKey[newKey] = childrenArray; + truncatedKeysSaveData[`${newKey}-List`] = wrapperKey; // Add a wrapper around the children/dependecies + } + } else { + if (dateItemsId.includes(oldKey)) { // If data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + if (toWrapIds[oldKey]) { + truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = newDateFormat; + } else { + truncatedKeysSaveData[newKey] = newDateFormat; + } + } else if (checkboxItemsId.includes(oldKey)) { // If data is in a checkbox field, change from true/false/undefined to Yes/No/"" + if (toWrapIds[oldKey]) { + truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = convertCheckboxFormatToICM(saveData[oldKey]); + } else { + truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); + } + } else { + if (toWrapIds[oldKey]) { + truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = saveData[oldKey]; + } else { + truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + } + } + } + } + return truncatedKeysSaveData; +} + module.exports.saveICMdata = saveICMdata; module.exports.loadICMdata = loadICMdata; module.exports.clearICMLockedFlag = clearICMLockedFlag; From a7adcd12a4ebb170c74df8915cb8b4fbdd2873b5 Mon Sep 17 00:00:00 2001 From: Songzi Zhang Date: Wed, 3 Sep 2025 08:45:09 -0700 Subject: [PATCH 014/105] ADO-3285 Batch print: Fix any issues in PDF generation endpoint in CommLayer --- generatePDFHandler.js | 59 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index cd2292f..ef64030 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -3,6 +3,7 @@ const puppeteer = require('puppeteer'); const fs = require('fs'); const { validateJson } = require('./validate'); const {storeData,retrieveData,deleteData} = require('./helper/redisHelperHandler'); +const sleep = (ms) => new Promise(res => setTimeout(res, ms)); async function generatePDFFromJSON(req, res) { try { @@ -165,6 +166,11 @@ async function generatePDFFromURL(req, res) { } async function getPDFFromURL(url) { + // Increases to 5000 seemed to be working well (could also set the params in env file if needed) + const HYDRATE_MS = Number(process.env.PDF_HYDRATE_MS ?? 5000); + const CLICK_WINDOW_MS = Number(process.env.PDF_CLICK_WINDOW_MS ?? 3000); + const CLICK_RETRY_EVERY_MS = Number(process.env.PDF_CLICK_INTERVAL_MS ?? 200); + const POST_CLICK_MS = Number(process.env.PDF_POST_CLICK_MS ?? 2500); // console.log("Puppeteer path:", process.env.PUPPETEER_EXECUTABLE_PATH); // console.log("Puppeteer URL:", url); @@ -183,13 +189,62 @@ async function getPDFFromURL(url) { //const browser = await puppeteer.launch(); const page = await browser.newPage(); + // Optional: Adjust the page's viewport for better PDF layout + await page.setViewport({ width: 1280, height: 800 }); + // Set the HTML content of the page await page.goto(url, { waitUntil: 'networkidle2', timeout: 150000 }); - // Optional: Adjust the page's viewport for better PDF layout - await page.setViewport({ width: 1280, height: 800 }); + // Let app hydrate + await sleep(HYDRATE_MS); + + // Click “Show Letter” with a short retry window + const clickScript = ` + (function(){ + function clickByText(substr){ + substr = substr.toLowerCase(); + const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT); + while (walker.nextNode()){ + const el = walker.currentNode; + const tag = (el.tagName||'').toLowerCase(); + const role = el.getAttribute && el.getAttribute('role'); + const clickable = tag==='button' || tag==='a' || role==='button'; + if (!clickable) continue; + const t = (el.innerText || el.textContent || '').trim().toLowerCase(); + if (t.includes(substr)){ + el.dispatchEvent(new MouseEvent('click', {bubbles:true,cancelable:true,view:window})); + return true; + } + } + return false; + } + if (!window.__clickedShowLetter) window.__clickedShowLetter = clickByText('show letter'); + return !!window.__clickedShowLetter; + })(); + `; + + const deadline = Date.now() + CLICK_WINDOW_MS; + let clicked = await page.evaluate(clickScript); + if (!clicked) { + for (const f of page.frames()) { + try { clicked = await f.evaluate(clickScript); if (clicked) break; } catch {} + } + } + while (!clicked && Date.now() < deadline) { + await sleep(CLICK_RETRY_EVERY_MS); + clicked = await page.evaluate(clickScript); + if (!clicked) { + for (const f of page.frames()) { + try { clicked = await f.evaluate(clickScript); if (clicked) break; } catch {} + } + } + } + + // Give it time to assemble the printable view + await sleep(POST_CLICK_MS); // Generate PDF with print CSS applied + await page.emulateMediaType('print'); const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true, From 556209220df529d6404975c10d7ccaac087d7ff1 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 4 Sep 2025 10:52:46 -0700 Subject: [PATCH 015/105] Deploy comm layer to portal namespace --- .github/workflows/build-and-deploy.yml | 178 ++++++++++++++----------- 1 file changed, 102 insertions(+), 76 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 122a17d..8bfad67 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -1,76 +1,102 @@ -name: Build and Deploy - -on: - push: - branches: - - main - - dev - - test - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - - build_and_push: - runs-on: ubuntu-latest - - steps: - - name: Checkout the repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch,ref=${{ github.ref_name }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - outputs: - image_tag: ${{ steps.meta.outputs.tags }} - - deploy: - needs: build_and_push - runs-on: ubuntu-latest - - steps: - - name: Checkout the repository - uses: actions/checkout@v4 - - - name: Install oc CLI - uses: redhat-actions/oc-installer@v1 - - - name: Authenticate with OpenShift - uses: redhat-actions/oc-login@v1 - with: - openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }} - namespace: ${{ github.ref == 'refs/heads/main' && secrets.OPENSHIFT_PROD_NAMESPACE || (github.ref == 'refs/heads/dev' && secrets.OPENSHIFT_DEV_NAMESPACE) || secrets.OPENSHIFT_TEST_NAMESPACE }} - openshift_token: ${{ github.ref == 'refs/heads/main' && secrets.OPENSHIFT_PROD_TOKEN || (github.ref == 'refs/heads/dev' && secrets.OPENSHIFT_DEV_TOKEN) || secrets.OPENSHIFT_TEST_TOKEN }} - insecure_skip_tls_verify: true - - - name: Deploy with Helm - run: | - helm upgrade --install communication-layer ./helm --set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} --set image.tag=${{ needs.build_and_push.outputs.image_tag }} - - name: Trigger OpenShift Rollout - run: | - oc rollout restart deployment/communication-layer +name: Build and Deploy + +on: + push: + branches: + - main + - dev + - test + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + + build_and_push: + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch,ref=${{ github.ref_name }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + outputs: + image_tag: ${{ steps.meta.outputs.tags }} + + deploy: + needs: build_and_push + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Install oc CLI + uses: redhat-actions/oc-installer@v1 + + - name: Authenticate with OpenShift + uses: redhat-actions/oc-login@v1 + with: + openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }} + namespace: ${{ github.ref == 'refs/heads/main' && secrets.OPENSHIFT_PROD_NAMESPACE || (github.ref == 'refs/heads/dev' && secrets.OPENSHIFT_DEV_NAMESPACE) || secrets.OPENSHIFT_TEST_NAMESPACE }} + openshift_token: ${{ github.ref == 'refs/heads/main' && secrets.OPENSHIFT_PROD_TOKEN || (github.ref == 'refs/heads/dev' && secrets.OPENSHIFT_DEV_TOKEN) || secrets.OPENSHIFT_TEST_TOKEN }} + insecure_skip_tls_verify: true + + - name: Deploy with Helm + run: | + helm upgrade --install communication-layer ./helm --set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} --set image.tag=${{ needs.build_and_push.outputs.image_tag }} + - name: Trigger OpenShift Rollout + run: | + oc rollout restart deployment/communication-layer + + deploy_to_portal: + needs: build_and_push + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Install oc CLI + uses: redhat-actions/oc-installer@v1 + + - name: Authenticate with OpenShift + uses: redhat-actions/oc-login@v1 + with: + openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }} + namespace: ${{ github.ref == 'refs/heads/main' && secrets.OPENSHIFT_PROD_PORTAL_NAMESPACE || (github.ref == 'refs/heads/dev' && secrets.OPENSHIFT_DEV_PORTAL_NAMESPACE) || secrets.OPENSHIFT_TEST_PORTAL_NAMESPACE }} + openshift_token: ${{ github.ref == 'refs/heads/main' && secrets.OPENSHIFT_PROD_PORTAL_TOKEN || (github.ref == 'refs/heads/dev' && secrets.OPENSHIFT_DEV_PORTAL_TOKEN) || secrets.OPENSHIFT_TEST_PORTAL_TOKEN }} + insecure_skip_tls_verify: true + + - name: Deploy with Helm + run: | + helm upgrade --install communication-layer ./helm --set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} --set image.tag=${{ needs.build_and_push.outputs.image_tag }} + - name: Trigger OpenShift Rollout + run: | + oc rollout restart deployment/communication-layer From e9a652dec971a0bdea0763f09ac33a1bbd870920 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 4 Sep 2025 14:46:05 -0700 Subject: [PATCH 016/105] Added more logging --- generatePDFHandler.js | 2 +- saveICMdataHandler.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index ef64030..2fc6f75 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -27,7 +27,7 @@ async function generatePDFFromJSON(req, res) { try { savedJson = JSON.parse(savedJsonString); - console.log("Saved Parsed JSON:",savedJson); + // console.log("Saved Parsed JSON:",savedJson); const { valid, errors } = validateJson(savedJson); if (valid) { diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 0748f1e..6ed874c 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -210,7 +210,20 @@ async function saveICMdata(req, res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } - response = await axios.put(url, saveJson, { params: query, headers }); + response = await axios.put(url, saveJson, { + params: query, + headers: { + ...headers, + 'Accept-Encoding': 'identity', + }, + decompress: false, + responseType: 'text', + transformResponse: x => x, + validateStatus: () => true, + }); + console.log('ICM PUT status:', response.status); + console.log('ICM PUT headers:', response.headers); + console.log('ICM PUT body (first 2k):', String(response.data).slice(0, 2000)); return res.status(200).send({}); } catch (error) { From 524f52c2cdca8b4598744cf0be2439b3bbdb549b Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 4 Sep 2025 14:56:40 -0700 Subject: [PATCH 017/105] turn off pretty render --- saveICMdataHandler.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 6ed874c..7d0f939 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -167,9 +167,9 @@ async function saveICMdata(req, res) { if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions // If the root needs a differernt name, apply it here. Otherwise use the default "root" if (propertyExists(dictionary, formId, "rootName") && propertyNotEmpty(dictionary, formId, "rootName")) { - builder = new xml2js.Builder({xmldec: { version: '1.0' }, rootName: dictionary[formId]["rootName"]}); + builder = new xml2js.Builder({xmldec: { version: '1.0' }, renderOpts: { pretty: false }, rootName: dictionary[formId]["rootName"]}); } else { - builder = new xml2js.Builder({xmldec: { version: '1.0' }}); + builder = new xml2js.Builder({xmldec: { version: '1.0' }, renderOpts: { pretty: false }}); } let wrapperJson = truncatedKeysSaveData; @@ -191,7 +191,7 @@ async function saveICMdata(req, res) { } saveJson["XML Hierarchy"] = builder.buildObject(wrapperJson); } else { - builder = new xml2js.Builder({xmldec: { version: '1.0' }}); + builder = new xml2js.Builder({xmldec: { version: '1.0' }, renderOpts: { pretty: false }}); saveJson["XML Hierarchy"] = builder.buildObject(truncatedKeysSaveData); } //let url = buildUrlWithParams('SIEBEL_ICM_API_HOST', 'fwd/v1.0/data/DT Form Instance Thin/DT Form Instance Thin/' + attachment_id + '/', ''); From 6a11d00dd72ea9293a5042114e6c0dad6888a0ca Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 4 Sep 2025 15:03:12 -0700 Subject: [PATCH 018/105] remove logs --- saveICMdataHandler.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 7d0f939..1083982 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -210,20 +210,7 @@ async function saveICMdata(req, res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } - response = await axios.put(url, saveJson, { - params: query, - headers: { - ...headers, - 'Accept-Encoding': 'identity', - }, - decompress: false, - responseType: 'text', - transformResponse: x => x, - validateStatus: () => true, - }); - console.log('ICM PUT status:', response.status); - console.log('ICM PUT headers:', response.headers); - console.log('ICM PUT body (first 2k):', String(response.data).slice(0, 2000)); + response = await axios.put(url, saveJson, { params: query, headers }); return res.status(200).send({}); } catch (error) { From 45ca536d2f20819b866ec53d26a2d9cebee2aaca Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 4 Sep 2025 15:19:37 -0700 Subject: [PATCH 019/105] XML debugging --- saveICMdataHandler.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 1083982..de4b77a 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -195,6 +195,12 @@ async function saveICMdata(req, res) { saveJson["XML Hierarchy"] = builder.buildObject(truncatedKeysSaveData); } //let url = buildUrlWithParams('SIEBEL_ICM_API_HOST', 'fwd/v1.0/data/DT Form Instance Thin/DT Form Instance Thin/' + attachment_id + '/', ''); + const xml = saveJson["XML Hierarchy"]; + const xmlSize = Buffer.byteLength(xml, 'utf8'); // size in bytes + + console.log("XML Hierarchy:", xml); + console.log("XML Hierarchy length (chars):", xml.length); + console.log("XML Hierarchy size (bytes):", xmlSize); let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); try { let response; From ced6d8652595e5b3c1abe5068e254384512b4d03 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 4 Sep 2025 15:44:58 -0700 Subject: [PATCH 020/105] Updated ChildKey index --- saveICMdataHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index de4b77a..4c6b805 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -466,7 +466,7 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt } truncatedChildrenKeys[newChildKey] = newDateFormat; } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field, change from true/false/undefined to Yes/No/"" - truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); + truncatedKeysSaveData[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); } else { truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } From 77be35f5594f79a234aff6a4b4c95803b0c7702c Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Thu, 4 Sep 2025 16:16:39 -0700 Subject: [PATCH 021/105] (ADO-3265) wrapper and update to dictionary layout --- dictionary/jsonXmlConversion.js | 77 +++++++++++++------ saveICMdataHandler.js | 128 ++++++++++++++++++++++++++++---- 2 files changed, 167 insertions(+), 38 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index d77ae71..3b21ddf 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -29,32 +29,63 @@ const formExceptions = { "wrapperTags": [ { "ParentGuardian": { - "wrapFields": [ - "last-name-91433118-4a0c-47fe-80d8-2676f83a2022", - "first-name-5dd5f94c-be98-4dd1-a442-3903c6330391", - "middle-names-690ef4b4-bf83-487e-8ef2-fd39f09fb545", - "address-parent-9dcf849c-b28b-4faa-afe3-8603af7c98fa", - "unit-fbbdce5c-a970-4d8f-98f5-d3a6e8af6005", - "address-1-a81293da-3d95-42cf-b6fa-ec15fac39ed1", - "address-2-3f5515c8-8d91-424f-8302-0e7fea6599bf", - "city-80cbf0f7-fa28-47f0-a4e4-94793e8a12fb", - "province-835a46e3-4a7c-4496-a196-8e93172092ed", - "postal-code-29edf972-2b5c-421d-a1ab-d97aa4005911", - "primary-phone-number-type-a7eba596-6474-4a01-93e5-6cd49c8ef4cc", - "primary-phone-number-f29b9ffd-9e7c-4f20-8bc3-57b333d88a0e", - "secondary-phone-number-type-fc15a5f0-bfcd-4fe8-8836-833fc2573525", - "secondary-phone-number-135375c3-f0bc-4b3a-aea2-b98995676ebc" - ], - }, + "last-name-91433118-4a0c-47fe-80d8-2676f83a2022": 0, + "first-name-5dd5f94c-be98-4dd1-a442-3903c6330391": 0, + "middle-names-690ef4b4-bf83-487e-8ef2-fd39f09fb545": 0, + "address-parent-9dcf849c-b28b-4faa-afe3-8603af7c98fa": 0, + "unit-fbbdce5c-a970-4d8f-98f5-d3a6e8af6005": 0, + "address-1-a81293da-3d95-42cf-b6fa-ec15fac39ed1": 0, + "address-2-3f5515c8-8d91-424f-8302-0e7fea6599bf": 0, + "city-80cbf0f7-fa28-47f0-a4e4-94793e8a12fb": 0, + "province-835a46e3-4a7c-4496-a196-8e93172092ed": 0, + "postal-code-29edf972-2b5c-421d-a1ab-d97aa4005911": 0, + "primary-phone-number-type-a7eba596-6474-4a01-93e5-6cd49c8ef4cc": 0, + "primary-phone-number-f29b9ffd-9e7c-4f20-8bc3-57b333d88a0e": 0, + "secondary-phone-number-type-fc15a5f0-bfcd-4fe8-8836-833fc2573525": 0, + "secondary-phone-number-135375c3-f0bc-4b3a-aea2-b98995676ebc": 0 + } }, { "Child" : { - "wrapFields" : [ - "child-last-name-15a0adbf-a3bd-4bd1-be3f-bfb9a2b02f3e", - "child-first-name-4a8302bb-6eca-46c8-ae50-efe37434ea8e", - "child-middle-names-81cf1856-1541-4eed-85d8-1f376b727fd0", - "child-dob-3d6f9a94-7c0f-4a06-89dc-872ee39fa818" - ], + "child-last-name-15a0adbf-a3bd-4bd1-be3f-bfb9a2b02f3e": 0, + "child-first-name-4a8302bb-6eca-46c8-ae50-efe37434ea8e": 0, + "child-middle-names-81cf1856-1541-4eed-85d8-1f376b727fd0": 0, + "child-dob-3d6f9a94-7c0f-4a06-89dc-872ee39fa818": 0 + } + }, + { + "PartB" : { + "sub-total-equipment-and-supplies-96a6e253-7a5d-4c7e-b2f4-abe26d48c478": 0, + "sub-total-training-ff57ad93-2e67-496e-aace-830303b0172e": 0, + "total-tte-e9836886-e4c3-49cf-83c8-c708dd005ab7": 0, + "eligible-total-tte-8de84d25-c659-4b7d-91b8-e16f99e44a29": 0, + "i-agree-that-these-expenses-are-related-to-my-childs-autism-intervention-52374221-28ad-4214-ad55-24c731900c21": 0, + "equipment-and-supplies-expense-605f2d0c-41d7-412d-9eff-6786964e5c6f": 0, + "TravelList": { + "Travel": { + "reason-for-travel-da2832f4-b848-4601-bd2f-2d796bf085c8": 2, + "start-date-of-trip-719d8dd1-fbb9-422c-afa8-2aaacaf61ccb": 2, + "end-date-of-trip-11639718-ab5b-40a6-a724-b89aecbc4a62" : 2, + "name-of-travellers-67f591a6-8f2c-4242-9653-49c6b95ae9e2": 2, + "from-location-5b51204f-e85c-4846-8c12-38373df07ef4": 2, + "to-location-52571198-2842-41b9-aec6-3e4f4bb23599": 2, + "travel-expense-68d80d8a-ed7f-44cc-8d85-74b10d62d7d8": 2 + } + }, + "training-expense-6452d421-896d-4ae3-9588-621fca63151f": 0, + "sub-total-travel-e78551b9-35f1-40d5-8e7e-8ef00637753d": 0, + "eligible-sub-total-travel-bf11f98e-d39f-4a67-a1a2-a0b6b36a7440": 0, + } + }, + { + "InterventionService": { + "start-date-intervention-e2cda870-27fc-4b35-876d-0af737e13ea0": 0, + "end-date-intervention-13687d44-ebfc-419e-8fa0-238d7ef29836": 0, + "total-intervention-54d8d0df-9a34-4474-9e34-077a9bb3b3ff": 0, + "eligible-total-intervention-751659e6-5f37-41ab-b179-21c22e2292a5": 0, + "intervention-expense-77e6cc72-66f9-4b6d-a437-cf64b7017b1f": 0, + "funding-period-start-dc5ab528-454b-4746-a556-a2591ff3967b": 0, + "funding-period-end-date-b47975a4-2d33-4578-9c00-ce42da63a99e": 0 } } ], diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 0748f1e..70e9919 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -146,22 +146,18 @@ async function saveICMdata(req, res) { const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version const isFormException = keyExists(dictionary, formId); // If true, then this form will have its exceptions formatted - const tempFormExceptionList = {}; - const toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists + let toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; - if (wrapperTag[tagKey]["wrapFields"].length != 0) { // If there are any wrappers with no fields, ignore. Otherwise, keep a list of UUID and the wrapper to put it in. - tempFormExceptionList[tagKey] = {}; - wrapperTag[tagKey]["wrapFields"].forEach(fieldId => { - toWrapIds[fieldId] = tagKey; - }); + if (wrapperTag[tagKey].length != 0) { // If there are any wrappers with no fields, ignore. Otherwise, keep a list of UUID and the wrapper to put it in. + toWrapIds = {...toWrapIds, ...getWrapperIds(wrapperTag[tagKey], [tagKey])}; } }); } // The updated JSON values required for XML creation - const truncatedKeysSaveData = fixJSONValuesForXML(saveData, tempFormExceptionList, toWrapIds, dateItemsId, checkboxItemsId); + const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId); let builder; // This will be for building the XML if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions @@ -427,6 +423,97 @@ function convertCheckboxFormatToICM (value) { else return ""; } +/** Recursive function that gets all of the wrapper tags from a value in a dictionary's wrapperTags + * + * @param keyPair : {} + * @param topLevelKeys : [] + * @returns + */ +function getWrapperIds (keyPair, topLevelKeys) { + const keys = Object.keys(keyPair); + if (typeof keyPair === "undefined" || keys.length === 0) return undefined + let toWrapIds = {}; + keys.forEach(key => { + if (typeof keyPair[key] === "number") { + toWrapIds[key] = {tags: topLevelKeys, level: keyPair[key]}; + } else if (typeof keyPair[key] === "object") { + const tempKeyWrappers = topLevelKeys.concat([key]); + const tempWrapperContainer = getWrapperIds(keyPair[key], tempKeyWrappers); + if (typeof tempWrapperContainer != undefined) { + Object.keys(tempWrapperContainer).forEach(value => { + toWrapIds[value] = tempWrapperContainer[value]; + }); + } + } + }); + return toWrapIds; +} + +/** When multiple wrappers are listed in dictionary for a uuid, wrap for JSON -> XML here + * + * @param truncatedKeysSaveData + * @param toWrapIds + * @param dataToWrap + * @param oldKey + * @param newKey + * @param isChild : boolean, checks if the multilevel wrapper was called from the child uuid shortening if-else + * @returns + */ +function multilevelWrappers(truncatedKeysSaveData, toWrapIds, dataToWrap, oldKey, newKey, isChild){ + if (toWrapIds[oldKey].tags.length === 1 || toWrapIds[oldKey].level === 0) { //One wrapper + if(isChild) { + const wrapperKey = {}; + wrapperKey[newKey] = dataToWrap; + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][`${newKey}-List`] = wrapperKey; // Add a wrapper around the children/dependecies + } else { + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][newKey] = dataToWrap; + } + } else if (toWrapIds[oldKey].tags.length === 2 || toWrapIds[oldKey].level === 1) { //Two wrappers + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]]) { + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]] = {}; + } + if(isChild) { + const wrapperKey = {}; + wrapperKey[newKey] = dataToWrap; + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][`${newKey}-List`] = wrapperKey; + } else { + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][newKey] = dataToWrap; + } + } else if (toWrapIds[oldKey].tags.length === 3 || toWrapIds[oldKey].level === 2) { //Three wrappers + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]]) { + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]] = {}; + } + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]]){ + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]] = {}; + } + if(isChild) { + const wrapperKey = {}; + wrapperKey[newKey] = dataToWrap; + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]][`${newKey}-List`] = wrapperKey; + } else { + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]][newKey] = dataToWrap; + } + } else if (toWrapIds[oldKey].tags.length === 4 || toWrapIds[oldKey].level === 3) { //Four wrappers + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]]) { + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]] = {}; + } + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]]){ + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]] = {}; + } + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]][toWrapIds[oldKey].tags[3]]){ + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]][toWrapIds[oldKey].tags[3]] = {}; + } + if(isChild) { + const wrapperKey = {}; + wrapperKey[newKey] = dataToWrap; + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]][toWrapIds[oldKey].tags[3]][`${newKey}-List`] = wrapperKey; + } else { + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]][toWrapIds[oldKey].tags[1]][toWrapIds[oldKey].tags[2]][toWrapIds[oldKey].tags[3]][newKey] = dataToWrap; + } + } + return truncatedKeysSaveData +} + /** Take the saveData JSON values and update them to new layout. Changes include: * Shorten UUID. * Wrap values with children inside a List. @@ -460,7 +547,7 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt } truncatedChildrenKeys[newChildKey] = newDateFormat; } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field, change from true/false/undefined to Yes/No/"" - truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); + truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); } else { truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } @@ -468,9 +555,10 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt childrenArray.push(truncatedChildrenKeys); } if (toWrapIds[oldKey]) { - const wrapperKey = {}; - wrapperKey[newKey] = childrenArray; - truncatedKeysSaveData[toWrapIds[oldKey]][`${newKey}-List`] = wrapperKey // Add a wrapper around the children/dependecies + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + } + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, childrenArray, oldKey, newKey, true); } else { const wrapperKey = {}; wrapperKey[newKey] = childrenArray; @@ -483,19 +571,29 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt throw new Error("Invalid date. Was unable to convert to ICM format!"); } if (toWrapIds[oldKey]) { - truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = newDateFormat; + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + } + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, newDateFormat, oldKey, newKey, false); } else { truncatedKeysSaveData[newKey] = newDateFormat; } } else if (checkboxItemsId.includes(oldKey)) { // If data is in a checkbox field, change from true/false/undefined to Yes/No/"" if (toWrapIds[oldKey]) { - truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = convertCheckboxFormatToICM(saveData[oldKey]); + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + } + const newCheckboxFormat = convertCheckboxFormatToICM(saveData[oldKey]); + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, newCheckboxFormat, oldKey, newKey, false); } else { truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); } } else { if (toWrapIds[oldKey]) { - truncatedKeysSaveData[toWrapIds[oldKey]][newKey] = saveData[oldKey]; + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + } + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, saveData[oldKey], oldKey, newKey, false); } else { truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key } From 2a6ab965045115212c6365051a6b6cc24b954333 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 5 Sep 2025 11:15:23 -0700 Subject: [PATCH 022/105] (ADO-3265) checkbox allowed not to convert --- dictionary/jsonXmlConversion.js | 6 +++--- saveICMdataHandler.js | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 3b21ddf..b45a198 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -3,7 +3,7 @@ * Property: rootName : a string that will replace "root" from the XML * Property: subRoots : an array of strings. These will be between root and JSON data. The first value in the array will be highest (after root) while the last will be lowest (before JSON) * Property: wrapperTags : an array of objects. If not empty, then should include object { [the wrapper Tag name] : { wrapFields: [] } } - * Property: allowBooleanCheckbox : if a field is in this array, skip conversion step + * Property: allowCheckboxWithNoChange : if a field is in this array, skip conversion step * Property: omitFields : an array of strings. These are the UUIDs of fields that must be omitted before XML creation. (TO BE DONE in saveICMdataHandler.js) * Property: version : a string that will check if the exception list should include the version of the form submitted. All values in version should overwrite the default form properties. */ @@ -12,7 +12,7 @@ const formExceptions = { "rootName": "ListOfDtFormInstanceLW", "subRoots": ["FormInstance"], "wrapperTags": [], - "allowBooleanCheckbox": [], + "allowCheckboxWithNoChange": [], "omitFields": [], "versions": { "1": { @@ -89,7 +89,7 @@ const formExceptions = { } } ], - "allowBooleanCheckbox": [], + "allowCheckboxWithNoChange": [], "omitFields": [], "versions" : { "1" :{ diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index f71ba82..e9fcf70 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -147,6 +147,7 @@ async function saveICMdata(req, res) { const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version const isFormException = keyExists(dictionary, formId); // If true, then this form will have its exceptions formatted let toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists + const noCheckboxChange = dictionary[formId].allowCheckboxWithNoChange; if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; @@ -157,7 +158,7 @@ async function saveICMdata(req, res) { } // The updated JSON values required for XML creation - const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId); + const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange); let builder; // This will be for building the XML if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions @@ -532,10 +533,11 @@ function multilevelWrappers(truncatedKeysSaveData, toWrapIds, dataToWrap, oldKey * @param toWrapIds : list of key-string pairs where the keys are UUIDs and the strings are what wrapper tag they need to go under. * @param dateItemsId : list of date fields * @param checkboxItemsId : list of checkbox fields + * @param noCheckboxChange : list of checkbox UUIDs that should not have their value changed * @returns truncatedKeysSaveData : a list of key-object pairs */ -function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId) { +function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange) { for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) const stringLength = oldKey.length; const newKey = oldKey.substring(0, stringLength-28); @@ -552,7 +554,7 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt throw new Error("Invalid date. Was unable to convert to ICM format!"); } truncatedChildrenKeys[newChildKey] = newDateFormat; - } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field, change from true/false/undefined to Yes/No/"" + } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); } else { truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; @@ -584,7 +586,7 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt } else { truncatedKeysSaveData[newKey] = newDateFormat; } - } else if (checkboxItemsId.includes(oldKey)) { // If data is in a checkbox field, change from true/false/undefined to Yes/No/"" + } else if (checkboxItemsId.includes(oldKey) && !noCheckboxChange.includes(oldKey)) { // If data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" if (toWrapIds[oldKey]) { if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; From c6ae1e1326681daf1e7f7368948b4843766b233f Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Mon, 8 Sep 2025 11:00:18 -0700 Subject: [PATCH 023/105] ADO-2787 environment variables example update --- .env.example | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.env.example b/.env.example index 02117e9..6a56aff 100644 --- a/.env.example +++ b/.env.example @@ -37,6 +37,12 @@ PORTAL_EXPIRE_TOKEN_ENDPOINT=expireToken APP_CONFIG='{ "localhost": { "apiHost":"someHost", + "apiKey": "secret", + "portalAuth": "basic", + "basicAuth": { + "username": "test", + "password": "thing" + }, "saveEndpoint":"someEndpoint", "employeeEndpoint":"someEndpoint" } From 4c3710c6bee8dcf269ee4563bc3aabd29da82a20 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Mon, 8 Sep 2025 11:54:32 -0700 Subject: [PATCH 024/105] Fix checkbox summons for non-dictionary values --- saveICMdataHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index e9fcf70..31a537d 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -147,7 +147,7 @@ async function saveICMdata(req, res) { const formVersion = JSON.parse(savedFormParam)["form_definition"]["version"]; // Get the form version const isFormException = keyExists(dictionary, formId); // If true, then this form will have its exceptions formatted let toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists - const noCheckboxChange = dictionary[formId].allowCheckboxWithNoChange; + const noCheckboxChange = isFormException ? dictionary[formId].allowCheckboxWithNoChange : []; if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; From 03a64fc062296cd67e4eee55de3a23a344ce500a Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Mon, 8 Sep 2025 12:11:30 -0700 Subject: [PATCH 025/105] (ADO-3265) omit parent keys --- saveICMdataHandler.js | 114 ++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index e9fcf70..e20d82e 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -148,6 +148,7 @@ async function saveICMdata(req, res) { const isFormException = keyExists(dictionary, formId); // If true, then this form will have its exceptions formatted let toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists const noCheckboxChange = dictionary[formId].allowCheckboxWithNoChange; + const omitFields = dictionary[formId].omitFields; if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; @@ -158,7 +159,7 @@ async function saveICMdata(req, res) { } // The updated JSON values required for XML creation - const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange); + const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields); let builder; // This will be for building the XML if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions @@ -534,76 +535,79 @@ function multilevelWrappers(truncatedKeysSaveData, toWrapIds, dataToWrap, oldKey * @param dateItemsId : list of date fields * @param checkboxItemsId : list of checkbox fields * @param noCheckboxChange : list of checkbox UUIDs that should not have their value changed + * @param omitFields : list of field UUIDS that should not be included in the XML * @returns truncatedKeysSaveData : a list of key-object pairs */ -function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange) { +function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields) { for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) - const stringLength = oldKey.length; - const newKey = oldKey.substring(0, stringLength-28); - if (Array.isArray(saveData[oldKey]) > 0 && Object.keys(saveData[oldKey]).length > 0) { //This trunicates child/dependant objects - const childrenArray = []; - for(let i = 0; i < saveData[oldKey].length; i++) { - const truncatedChildrenKeys = {}; - for (let oldChildKey in saveData[oldKey][i]) { - const childStringLength = oldChildKey.length; - const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); - if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY - const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); - if (newDateFormat === "-1") { - throw new Error("Invalid date. Was unable to convert to ICM format!"); + if (!omitFields.includes(oldKey)) { + const stringLength = oldKey.length; + const newKey = oldKey.substring(0, stringLength-28); + if (Array.isArray(saveData[oldKey]) > 0 && Object.keys(saveData[oldKey]).length > 0) { //This trunicates child/dependant objects + const childrenArray = []; + for(let i = 0; i < saveData[oldKey].length; i++) { + const truncatedChildrenKeys = {}; + for (let oldChildKey in saveData[oldKey][i]) { + const childStringLength = oldChildKey.length; + const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); + if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + truncatedChildrenKeys[newChildKey] = newDateFormat; + } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" + truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); + } else { + truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } - truncatedChildrenKeys[newChildKey] = newDateFormat; - } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" - truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); - } else { - truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } + childrenArray.push(truncatedChildrenKeys); } - childrenArray.push(truncatedChildrenKeys); - } - if (toWrapIds[oldKey]) { - if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist - truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; - } - truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, childrenArray, oldKey, newKey, true); - } else { - const wrapperKey = {}; - wrapperKey[newKey] = childrenArray; - truncatedKeysSaveData[`${newKey}-List`] = wrapperKey; // Add a wrapper around the children/dependecies - } - } else { - if (dateItemsId.includes(oldKey)) { // If data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY - const newDateFormat = toICMFormat(saveData[oldKey]); - if (newDateFormat === "-1") { - throw new Error("Invalid date. Was unable to convert to ICM format!"); - } - if (toWrapIds[oldKey]) { - if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist - truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; - } - truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, newDateFormat, oldKey, newKey, false); - } else { - truncatedKeysSaveData[newKey] = newDateFormat; - } - } else if (checkboxItemsId.includes(oldKey) && !noCheckboxChange.includes(oldKey)) { // If data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" if (toWrapIds[oldKey]) { if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; } - const newCheckboxFormat = convertCheckboxFormatToICM(saveData[oldKey]); - truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, newCheckboxFormat, oldKey, newKey, false); + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, childrenArray, oldKey, newKey, true); } else { - truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); + const wrapperKey = {}; + wrapperKey[newKey] = childrenArray; + truncatedKeysSaveData[`${newKey}-List`] = wrapperKey; // Add a wrapper around the children/dependecies } } else { - if (toWrapIds[oldKey]) { - if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist - truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + if (dateItemsId.includes(oldKey)) { // If data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + if (toWrapIds[oldKey]) { + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + } + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, newDateFormat, oldKey, newKey, false); + } else { + truncatedKeysSaveData[newKey] = newDateFormat; + } + } else if (checkboxItemsId.includes(oldKey) && !noCheckboxChange.includes(oldKey)) { // If data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" + if (toWrapIds[oldKey]) { + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + } + const newCheckboxFormat = convertCheckboxFormatToICM(saveData[oldKey]); + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, newCheckboxFormat, oldKey, newKey, false); + } else { + truncatedKeysSaveData[newKey] = convertCheckboxFormatToICM(saveData[oldKey]); } - truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, saveData[oldKey], oldKey, newKey, false); } else { - truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + if (toWrapIds[oldKey]) { + if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist + truncatedKeysSaveData[toWrapIds[oldKey].tags[0]] = {}; + } + truncatedKeysSaveData = multilevelWrappers(truncatedKeysSaveData, toWrapIds, saveData[oldKey], oldKey, newKey, false); + } else { + truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + } } } } From af0fde10699063b67d60b16d7809bdebcc4b3323 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Mon, 8 Sep 2025 12:57:08 -0700 Subject: [PATCH 026/105] ADO-3265 child key omissions --- dictionary/jsonXmlConversion.js | 4 ++-- saveICMdataHandler.js | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index b45a198..06b4fa8 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -2,9 +2,9 @@ * Keys are the form_definition.form_id * Property: rootName : a string that will replace "root" from the XML * Property: subRoots : an array of strings. These will be between root and JSON data. The first value in the array will be highest (after root) while the last will be lowest (before JSON) - * Property: wrapperTags : an array of objects. If not empty, then should include object { [the wrapper Tag name] : { wrapFields: [] } } + * Property: wrapperTags : an array of objects. If not empty, then should include object { [the wrapper Tag name] : { wrapFieldsName: indexOfDepth } } * Property: allowCheckboxWithNoChange : if a field is in this array, skip conversion step - * Property: omitFields : an array of strings. These are the UUIDs of fields that must be omitted before XML creation. (TO BE DONE in saveICMdataHandler.js) + * Property: omitFields : an array of strings. These are the UUIDs of fields that must be omitted before XML creation. For child UUIDs, include only the values after "-i-" where i is the index of the list * Property: version : a string that will check if the exception list should include the version of the form submitted. All values in version should overwrite the default form properties. */ const formExceptions = { diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 584a735..75459d3 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -551,19 +551,23 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt for (let oldChildKey in saveData[oldKey][i]) { const childStringLength = oldChildKey.length; const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); - if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY - const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); - if (newDateFormat === "-1") { - throw new Error("Invalid date. Was unable to convert to ICM format!"); + if (!omitFields.includes(oldChildKey.substring(stringLength+3, childStringLength))) { + if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + truncatedChildrenKeys[newChildKey] = newDateFormat; + } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" + truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); + } else { + truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } - truncatedChildrenKeys[newChildKey] = newDateFormat; - } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" - truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); - } else { - truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } } - childrenArray.push(truncatedChildrenKeys); + if (Object.keys(truncatedChildrenKeys).length != 0) { + childrenArray.push(truncatedChildrenKeys); + } } if (toWrapIds[oldKey]) { if (!truncatedKeysSaveData[toWrapIds[oldKey].tags[0]]) { //Initalize wrapper if top level doesn't exist From 391a9a9b2152b0426b1343c24fa6c0e5421c4978 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 10 Sep 2025 09:17:53 -0700 Subject: [PATCH 027/105] CORS debug --- app.js | 84 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/app.js b/app.js index a63265e..21d3b4f 100644 --- a/app.js +++ b/app.js @@ -1,41 +1,43 @@ -const express = require("express"); -const cors = require("cors"); -const env = require("dotenv").config(); -const routes = require("./routes"); -const swaggerUi = require("swagger-ui-express"); -const swaggerDocument = require("./swagger.json"); -const bodyParser = require("body-parser"); - -const app = express(); -const PORT = process.env.PORT || 3000; - -app.use(express.json({ limit: '50mb' })); -app.use(bodyParser.json()); - -app.use(express.urlencoded({ limit: '50mb', extended: true })); - - -// Serve Swagger UI at `/api-docs` -app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); - -const allowedOrigins = process.env.ALLOWEDORIGINS ? process.env.ALLOWEDORIGINS.split(',') : []; - -const corsOptions = { - origin: (origin, callback) => { - if (!origin || allowedOrigins.includes(origin)) { - callback(null, true); - } else { - callback(new Error('Not allowed by CORS')); - } - }, - optionsSuccessStatus: 200 -}; - -app.use(cors(corsOptions)); - -// Use the routes defined in routes.js -app.use("/", routes); - -app.listen(PORT, () => { - console.log("Server Listening on PORT:", PORT); -}); +const express = require("express"); +const cors = require("cors"); +const env = require("dotenv").config(); +const routes = require("./routes"); +const swaggerUi = require("swagger-ui-express"); +const swaggerDocument = require("./swagger.json"); +const bodyParser = require("body-parser"); + +const app = express(); +const PORT = process.env.PORT || 3000; + +app.use(express.json({ limit: '50mb' })); +app.use(bodyParser.json()); + +app.use(express.urlencoded({ limit: '50mb', extended: true })); + + +// Serve Swagger UI at `/api-docs` +app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + +const allowedOrigins = process.env.ALLOWEDORIGINS ? process.env.ALLOWEDORIGINS.split(',') : []; +console.log("Allowed origins:", allowedOrigins); + +const corsOptions = { + origin: (origin, callback) => { + if (!origin || allowedOrigins.includes(origin)) { + callback(null, true); + } else { + console.log("CORS blocked for:", origin); + callback(new Error('Not allowed by CORS')); + } + }, + optionsSuccessStatus: 200 +}; + +app.use(cors(corsOptions)); + +// Use the routes defined in routes.js +app.use("/", routes); + +app.listen(PORT, () => { + console.log("Server Listening on PORT:", PORT); +}); From eb02ca15444e271db9101060d8f6a79227bcb6fe Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 10 Sep 2025 09:27:01 -0700 Subject: [PATCH 028/105] More logging --- app.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app.js b/app.js index 21d3b4f..ee4fb8e 100644 --- a/app.js +++ b/app.js @@ -23,7 +23,9 @@ console.log("Allowed origins:", allowedOrigins); const corsOptions = { origin: (origin, callback) => { + console.log("Incoming Origin:", origin); // <— ADD if (!origin || allowedOrigins.includes(origin)) { + console.log("CORS allowed for:", origin); // <— ADD callback(null, true); } else { console.log("CORS blocked for:", origin); @@ -33,6 +35,11 @@ const corsOptions = { optionsSuccessStatus: 200 }; +app.use((req, _res, next) => { + if (req.headers.origin) console.log("Header Origin:", req.headers.origin); // <— ADD + next(); +}); + app.use(cors(corsOptions)); // Use the routes defined in routes.js From c97dbb798b21c9c93df01cfe5f0e313ee5a6c6ec Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 10 Sep 2025 09:27:17 -0700 Subject: [PATCH 029/105] Updated logging --- app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index ee4fb8e..dee704e 100644 --- a/app.js +++ b/app.js @@ -23,9 +23,9 @@ console.log("Allowed origins:", allowedOrigins); const corsOptions = { origin: (origin, callback) => { - console.log("Incoming Origin:", origin); // <— ADD + console.log("Incoming Origin:", origin); if (!origin || allowedOrigins.includes(origin)) { - console.log("CORS allowed for:", origin); // <— ADD + console.log("CORS allowed for:", origin); callback(null, true); } else { console.log("CORS blocked for:", origin); From 0a081037bcf2ae72d85c6cad9a266e96de76e3fa Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 10 Sep 2025 09:38:19 -0700 Subject: [PATCH 030/105] Moved logging --- app.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index dee704e..0a01de0 100644 --- a/app.js +++ b/app.js @@ -21,6 +21,12 @@ app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); const allowedOrigins = process.env.ALLOWEDORIGINS ? process.env.ALLOWEDORIGINS.split(',') : []; console.log("Allowed origins:", allowedOrigins); +app.use((req, _res, next) => { + console.log("Full request:"); + console.log(util.inspect(req, { showHidden: false, depth: null })); + next(); +}); + const corsOptions = { origin: (origin, callback) => { console.log("Incoming Origin:", origin); @@ -35,10 +41,6 @@ const corsOptions = { optionsSuccessStatus: 200 }; -app.use((req, _res, next) => { - if (req.headers.origin) console.log("Header Origin:", req.headers.origin); // <— ADD - next(); -}); app.use(cors(corsOptions)); From acf30531fe1c60e5f6a2d7d3945ea4cf5856dd28 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 10 Sep 2025 09:53:34 -0700 Subject: [PATCH 031/105] Updated request --- app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 0a01de0..480ed80 100644 --- a/app.js +++ b/app.js @@ -22,8 +22,10 @@ const allowedOrigins = process.env.ALLOWEDORIGINS ? process.env.ALLOWEDORIGINS.s console.log("Allowed origins:", allowedOrigins); app.use((req, _res, next) => { - console.log("Full request:"); - console.log(util.inspect(req, { showHidden: false, depth: null })); + console.log("Requests:", req.method, req.originalUrl); + console.log("Origin:", req.headers.origin || "none"); + console.log("Headers:", req.headers); + console.log("Body:", req.body); next(); }); From 47b839f7db2058ca7b8a9f4936c189632822b8db Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 10 Sep 2025 10:33:46 -0700 Subject: [PATCH 032/105] removed logging --- app.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app.js b/app.js index 480ed80..920a9ec 100644 --- a/app.js +++ b/app.js @@ -19,24 +19,12 @@ app.use(express.urlencoded({ limit: '50mb', extended: true })); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); const allowedOrigins = process.env.ALLOWEDORIGINS ? process.env.ALLOWEDORIGINS.split(',') : []; -console.log("Allowed origins:", allowedOrigins); - -app.use((req, _res, next) => { - console.log("Requests:", req.method, req.originalUrl); - console.log("Origin:", req.headers.origin || "none"); - console.log("Headers:", req.headers); - console.log("Body:", req.body); - next(); -}); const corsOptions = { origin: (origin, callback) => { - console.log("Incoming Origin:", origin); if (!origin || allowedOrigins.includes(origin)) { - console.log("CORS allowed for:", origin); callback(null, true); } else { - console.log("CORS blocked for:", origin); callback(new Error('Not allowed by CORS')); } }, From 738e01f713b859b082e367ded5c947924486cf34 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 11 Sep 2025 15:03:40 -0700 Subject: [PATCH 033/105] portal auth debugging --- portal/loadPortalDataHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index 1890c6a..bbd749d 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -66,6 +66,7 @@ async function getSavedFormFromPortal(portal,token, userId) { const auth = (portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) ? "Basic " + btoa(portal.basicAuth.username + ":" + portal.basicAuth.password) : "Basic " + btoa(portal.apiSecret); + console.log("Auth:",auth); response = await axios.post(`${urlForValidateTokenAndGetJson}`, { token, @@ -78,6 +79,7 @@ async function getSavedFormFromPortal(portal,token, userId) { } } ); + console.log("Response:",response); } else { response = await axios.post(`${urlForValidateTokenAndGetJson}`, { From 15ee6b1a1cc82b7dc8025c976a8588d804dfb8ef Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 11 Sep 2025 15:10:32 -0700 Subject: [PATCH 034/105] Update portal naming --- portal/loadPortalDataHandler.js | 2 -- portal/loadPortalIntegratedHandler.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index bbd749d..1890c6a 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -66,7 +66,6 @@ async function getSavedFormFromPortal(portal,token, userId) { const auth = (portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) ? "Basic " + btoa(portal.basicAuth.username + ":" + portal.basicAuth.password) : "Basic " + btoa(portal.apiSecret); - console.log("Auth:",auth); response = await axios.post(`${urlForValidateTokenAndGetJson}`, { token, @@ -79,7 +78,6 @@ async function getSavedFormFromPortal(portal,token, userId) { } } ); - console.log("Response:",response); } else { response = await axios.post(`${urlForValidateTokenAndGetJson}`, { diff --git a/portal/loadPortalIntegratedHandler.js b/portal/loadPortalIntegratedHandler.js index 0b7c2b1..58d3dfe 100644 --- a/portal/loadPortalIntegratedHandler.js +++ b/portal/loadPortalIntegratedHandler.js @@ -28,7 +28,7 @@ async function loadPortalIntegratedForm(req, res) { return res.status(400).send({ error: getErrorMessage("FORM_NOT_FOUND", { templateId: template_id }) }); } //the formJson is a base64 string . Converting to json here. - const savedJson = Buffer.from(formJson["form"], 'base64').toString('utf-8'); + const savedJson = Buffer.from(formJson["formJson"], 'base64').toString('utf-8'); const data = JSON.parse(savedJson); res.status(200).send(data); From 907747a0f40f9b0d3332dd44d787f882f3000abe Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Mon, 15 Sep 2025 16:01:47 -0700 Subject: [PATCH 035/105] ADO-3215 partial kiln v2 updates for xml --- saveICMdataHandler.js | 71 ++++++++++++++++++++++++++++++++----------- validate.js | 12 ++++---- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 75459d3..321f6cd 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -119,8 +119,23 @@ async function saveICMdata(req, res) { .status(401) .send({ error: getErrorMessage("FORM_ALREADY_FINALIZED") }); } - //saveForm validate before saving to ICM - const valid = isJsonStringValid(savedFormParam); + + //saveForm validate before saving to ICM and determine kiln version being used + const includesData = Object.keys(JSON.parse(savedFormParam)).includes("data"); // Check if data exists. + if (!includesData) { + console.log('JSON is not valid '); + return res + .status(400) + .send({ error: getErrorMessage("FORM_NOT_VALID") }); + } + /** + * Apply Kiln Version + * Kiln V1 uses data: { items: []} + * Kiln V2 uses dataSources [] + */ + const kilnVersion = Object.keys(JSON.parse(savedFormParam)["data"]).includes("items") ? 1 : 2; + + const valid = isJsonStringValid(savedFormParam, kilnVersion); if (!valid) { console.log('JSON is not valid '); return res @@ -135,8 +150,8 @@ async function saveICMdata(req, res) { saveJson["DocFileName"] = (form_metadata["DocFileName"] && form_metadata["DocFileName"] !== "") ? form_metadata["DocFileName"] : attachment_id.replace(/^[^-]+-/, 'Form_'); saveJson["DocFileExt"] = "json"; saveJson["Doc Attachment Id"] = Buffer.from(savedFormParam).toString('base64');//savedForm is saved as attachment - let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson - const formDefinitionItems = JSON.parse(savedFormParam)["form_definition"]["data"]["items"];// This is the field info for form items + let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson + const formDefinitionItems = kilnVersion === 1 ? JSON.parse(savedFormParam)["form_definition"]["data"]["items"] : JSON.parse(savedFormParam)["form_definition"]["elements"];// This is the field info for form items // dateItemsId : This will contain all of the IDs of the date fields // checkboxItemsId : This will contain all of the IDs of the checkbox fields @@ -285,7 +300,7 @@ async function loadICMdata(req, res) { response = await axios.get(url, { params: query, headers }); let return_data = Buffer.from(response.data["Doc Attachment Id"], 'base64').toString('utf-8'); //validate the returned data to be of the expected format - const valid = isJsonStringValid(return_data); + const valid = isJsonStringValid(return_data, 1); //TODO ADO-3215 change for kiln version if (!valid) { console.log('JSON is not valid '); return res @@ -536,10 +551,11 @@ function multilevelWrappers(truncatedKeysSaveData, toWrapIds, dataToWrap, oldKey * @param checkboxItemsId : list of checkbox fields * @param noCheckboxChange : list of checkbox UUIDs that should not have their value changed * @param omitFields : list of field UUIDS that should not be included in the XML + * @param {number} kilnVersion : the Kiln version matching the save data json layout * @returns truncatedKeysSaveData : a list of key-object pairs */ -function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields) { +function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields, kilnVersion) { for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) if (!omitFields.includes(oldKey)) { const stringLength = oldKey.length; @@ -549,19 +565,38 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt for(let i = 0; i < saveData[oldKey].length; i++) { const truncatedChildrenKeys = {}; for (let oldChildKey in saveData[oldKey][i]) { - const childStringLength = oldChildKey.length; - const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); - if (!omitFields.includes(oldChildKey.substring(stringLength+3, childStringLength))) { - if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY - const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); - if (newDateFormat === "-1") { - throw new Error("Invalid date. Was unable to convert to ICM format!"); + if (kilnVersion === 1) { // Kiln V1 + const childStringLength = oldChildKey.length; + const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); + if (!omitFields.includes(oldChildKey.substring(stringLength+3, childStringLength))) { + if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + truncatedChildrenKeys[newChildKey] = newDateFormat; + } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" + truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); + } else { + truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; + } + } + } + else { // Kiln V2 + const childStringLength = oldChildKey.length; + const newChildKey = oldChildKey.substring(0, childStringLength-28); + if (!omitFields.includes(oldChildKey.substring(0, childStringLength))) { + if (dateItemsId.includes(oldChildKey.substring(0, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY + const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); + if (newDateFormat === "-1") { + throw new Error("Invalid date. Was unable to convert to ICM format!"); + } + truncatedChildrenKeys[newChildKey] = newDateFormat; + } else if (checkboxItemsId.includes(oldChildKey.substring(0, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(0, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" + truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); + } else { + truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } - truncatedChildrenKeys[newChildKey] = newDateFormat; - } else if (checkboxItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !noCheckboxChange.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a checkbox field AND is not listed for ommission, change from true/false/undefined to Yes/No/"" - truncatedChildrenKeys[newChildKey] = convertCheckboxFormatToICM(saveData[oldKey][i][oldChildKey]); - } else { - truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; } } } diff --git a/validate.js b/validate.js index 00c9373..fee7c28 100644 --- a/validate.js +++ b/validate.js @@ -10,9 +10,9 @@ function loadSchema(schemaPath) { } // Function to validate JSON data against a YAML schema -function validateJson(jsonData) { +function validateJson(jsonData, kilnVersion) { const schema = loadSchema("schema/saved_json.yaml"); - const formDefinitionSchema = loadSchema("schema/form_definition.yaml"); + const formDefinitionSchema = kilnVersion === 1 ? loadSchema("schema/form_definition.yaml") : loadSchema("schema/form_definitionV2.yaml"); const ajv = new Ajv({ allErrors: true }); addFormats(ajv); @@ -26,10 +26,10 @@ function validateJson(jsonData) { return { valid, errors: validate.errors }; } -function isJsonStringValid(jsonDataString) { +function isJsonStringValid(jsonDataString, kilnVersion) { try { const jsonData = JSON.parse(jsonDataString); - return isJsonValid(jsonData) + return isJsonValid(jsonData, kilnVersion) } catch (error) { console.error("Error converting incoming json:", error); @@ -37,11 +37,11 @@ function isJsonStringValid(jsonDataString) { } } -function isJsonValid(jsonData) { +function isJsonValid(jsonData, kilnVersion) { try { //const jsonData = JSON.parse(jsonDataString); if(jsonData) { - const { valid, errors } = validateJson(jsonData); + const { valid, errors } = validateJson(jsonData, kilnVersion); if (valid) { console.log('JSON is valid ✅'); } else { From 30927743c54b6ca26c6b5f904ca70945ec0cad19 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Tue, 16 Sep 2025 10:28:33 -0700 Subject: [PATCH 036/105] ADO-3215 move kiln version validation into validate.js, form definition v2 yaml --- saveICMdataHandler.js | 20 ++- schema/form_definitionV2.yaml | 321 ++++++++++++++++++++++++++++++++++ validate.js | 17 +- 3 files changed, 344 insertions(+), 14 deletions(-) create mode 100644 schema/form_definitionV2.yaml diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 321f6cd..1cb5101 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -128,14 +128,8 @@ async function saveICMdata(req, res) { .status(400) .send({ error: getErrorMessage("FORM_NOT_VALID") }); } - /** - * Apply Kiln Version - * Kiln V1 uses data: { items: []} - * Kiln V2 uses dataSources [] - */ - const kilnVersion = Object.keys(JSON.parse(savedFormParam)["data"]).includes("items") ? 1 : 2; - - const valid = isJsonStringValid(savedFormParam, kilnVersion); + + const valid = isJsonStringValid(savedFormParam); if (!valid) { console.log('JSON is not valid '); return res @@ -151,6 +145,14 @@ async function saveICMdata(req, res) { saveJson["DocFileExt"] = "json"; saveJson["Doc Attachment Id"] = Buffer.from(savedFormParam).toString('base64');//savedForm is saved as attachment let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson + + + /** + * Apply Kiln Version + * Kiln V1 uses data: { items: []} + * Kiln V2 uses dataSources [] + */ + const kilnVersion = Object.keys(JSON.parse(savedFormParam)["data"]).includes("items") ? 1 : 2; const formDefinitionItems = kilnVersion === 1 ? JSON.parse(savedFormParam)["form_definition"]["data"]["items"] : JSON.parse(savedFormParam)["form_definition"]["elements"];// This is the field info for form items // dateItemsId : This will contain all of the IDs of the date fields @@ -300,7 +302,7 @@ async function loadICMdata(req, res) { response = await axios.get(url, { params: query, headers }); let return_data = Buffer.from(response.data["Doc Attachment Id"], 'base64').toString('utf-8'); //validate the returned data to be of the expected format - const valid = isJsonStringValid(return_data, 1); //TODO ADO-3215 change for kiln version + const valid = isJsonStringValid(return_data); if (!valid) { console.log('JSON is not valid '); return res diff --git a/schema/form_definitionV2.yaml b/schema/form_definitionV2.yaml new file mode 100644 index 0000000..8460da4 --- /dev/null +++ b/schema/form_definitionV2.yaml @@ -0,0 +1,321 @@ +type: object +required: + - id + - version + - ministry_id + - data +properties: + version: + anyOf: + - type: string + - type: number + ministry_id: + anyOf: + - type: string + - type: number + id: + type: string + lastModified: + type: string + title: + type: string + readOnly: + type: boolean + form_id: + type: string + deployed_to: + anyOf: + - type: string + - type: "null" + dataSources: + type: array + items: + type: object + default: [] + data: + type: object + properties: + form_id: + type: number + form_developer: + type: object + comments: + type: "null" + created_at: + type: string + updated_at: + type: string + interface: + type: array + styles: + type: array + scripts: + type: array + elements: + type: array + items: + type: object + properties: + fields: + type: array + items: + $ref: "#/definitions/Item" + +definitions: + Item: + required: + - uuid + - type + type: object + properties: + type: + type: string + name: + type: string + description: + anyOf: + - type: string + - type: "null" + help_text: + anyOf: + - type: string + - type: "null" + is_required: + type: boolean + visible_web: + type: boolean + visible_pdf: + type: boolean + custom_visibility: + anyOf: + - type: string + - type: "null" + is_read_only: + type: boolean + save_on_submit: + type: boolean + order: + type: number + options: + type: array + parent_id: + anyOf: + - type: number + - type: "null" + attributes: + type: array + items: + type: object + properties: + hideLabel: + type: boolean + defaultChecked: + type: boolean + deletedAt: + anyOf: + - type: string + - type: "null" + text: + type: string + kind: + type: string + placeholder: + type: string + minDate: + type: string + maxDate: + type: string + dateFormat: + type: string + formatStyle: + type: string + max: + type: string + min: + type: string + step: + type: string + value: + type: string + label: + anyOf: + - type: string + - type: "null" + placeholder: + anyOf: + - type: string + - type: "null" + id: + type: string + mask: + anyOf: + - type: string + - type: "null" + codeContext: + type: object + properties: + name: + type: string + header: + type: string + offText: + type: string + onText: + type: string + size: + type: string + listItems: + type: array + items: + type: object + properties: + value: + type: string + text: + type: string + required: + - text + - value + groupItems: + type: array + items: + type: object + properties: + fields: + type: array + items: + $ref: "#/definitions/Item" + repeater: + type: boolean + helperText: + anyOf: + - type: string + - type: "null" + value: + anyOf: + - type: string + - type: "null" + filenameStatus: + type: string + labelDescription: + type: string + initialRows: + type: string + initialColumns: + type: string + initialHeaderNames: + type: string + repeaterItemLabel: + type: string + validation: + type: array + items: + type: object + properties: + type: + type: string + enum: + - required + - pattern + - minLength + - maxLength + - minDate + - maxDate + - minValue + - maxValue + - javascript + value: + anyOf: + - type: string + - type: number + - type: boolean + errorMessage: + anyOf: + - type: string + - type: "null" + required: + - type + - value + - errorMessage + conditions: + type: array + items: + type: object + properties: + type: + type: string + enum: + - visibility + - readOnly + - calculatedValue + - saveOnSubmit + value: + type: string + required: + - type + - value + additionalProperties: false + webStyles: + anyOf: + - type: "null" + - type: object + additionalProperties: + anyOf: + - type: string + - type: number + - type: boolean + - type: object + - type: array + - type: "null" + pdfStyles: + anyOf: + - type: "null" + - type: object + additionalProperties: + anyOf: + - type: string + - type: number + - type: boolean + - type: object + - type: array + - type: "null" + databindings: + oneOf: + - type: array + items: + type: object + properties: + path: + type: string + source: + type: string + order: + type: number + condition: + anyOf: + - type: string + - type: "null" + required: + - path + - source + additionalProperties: false + - type: object + properties: + path: + type: string + source: + type: string + order: + type: number + condition: + anyOf: + - type: string + - type: "null" + required: + - path + - source + additionalProperties: false + containerItems: + type: array + items: + $ref: "#/definitions/Item" diff --git a/validate.js b/validate.js index fee7c28..cd538d9 100644 --- a/validate.js +++ b/validate.js @@ -10,7 +10,14 @@ function loadSchema(schemaPath) { } // Function to validate JSON data against a YAML schema -function validateJson(jsonData, kilnVersion) { +function validateJson(jsonData) { + + /** + * Apply Kiln Version + * Kiln V1 uses data: { items: []} + * Kiln V2 uses dataSources [] + */ + const kilnVersion = Object.keys(jsonData["data"]).includes("items") ? 1 : 2; const schema = loadSchema("schema/saved_json.yaml"); const formDefinitionSchema = kilnVersion === 1 ? loadSchema("schema/form_definition.yaml") : loadSchema("schema/form_definitionV2.yaml"); @@ -26,10 +33,10 @@ function validateJson(jsonData, kilnVersion) { return { valid, errors: validate.errors }; } -function isJsonStringValid(jsonDataString, kilnVersion) { +function isJsonStringValid(jsonDataString) { try { const jsonData = JSON.parse(jsonDataString); - return isJsonValid(jsonData, kilnVersion) + return isJsonValid(jsonData) } catch (error) { console.error("Error converting incoming json:", error); @@ -37,11 +44,11 @@ function isJsonStringValid(jsonDataString, kilnVersion) { } } -function isJsonValid(jsonData, kilnVersion) { +function isJsonValid(jsonData) { try { //const jsonData = JSON.parse(jsonDataString); if(jsonData) { - const { valid, errors } = validateJson(jsonData, kilnVersion); + const { valid, errors } = validateJson(jsonData); if (valid) { console.log('JSON is valid ✅'); } else { From 26262a5b763d5dee285045c45e3d0a59204f3636 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Wed, 17 Sep 2025 08:15:41 -0700 Subject: [PATCH 037/105] ADO-3215 update which kiln version of values is used for data bindings handler --- databindingsHandler.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/databindingsHandler.js b/databindingsHandler.js index 7e89869..f7bb693 100644 --- a/databindingsHandler.js +++ b/databindingsHandler.js @@ -148,8 +148,10 @@ function bindDataToFields(formJson, fetchedData, params = {}) { }); }; - if (Array.isArray(formJson?.data?.items)) { + if (Array.isArray(formJson?.data?.items)) { // Kiln-v1 processItemsForDatabinding(formJson.data.items); + } else if (Array.isArray(formJson?.elements)) { // Kilnv2 + processItemsForDatabinding(formJson.elements); } // console.dir(formData, { depth: null, colors: true }); return formData; From d40a24af1240a9bba0ebbbd6558cd89992a6adc9 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 17 Sep 2025 14:48:11 -0700 Subject: [PATCH 038/105] submit action debug --- portal/savePortalFormDataHandler.js | 146 +++++++++++++++------------- 1 file changed, 79 insertions(+), 67 deletions(-) diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index 0bc06d4..30b4791 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -1,68 +1,80 @@ -const appConfig = require('../appConfig.js'); -async function submitForPortalAction (req,res) { - const { tokenId, savedForm ,config} = req.body; - try{ - - if (!config?.actions || !Array.isArray(config.actions)) { - return res - .status(400) - .send({ error: getErrorMessage("NO_ACTION_FOUND")}); - } - const rawHost = (req.get("X-Original-Server") || req.hostname); - const portalConfig = appConfig[rawHost] || Object.values(appConfig).find(cfg => { - try { - return new URL(cfg.apiHost).hostname === rawHost; - } catch { - return false; - } - }) || {}; - - for (const action of config.actions) { - if (action.action_type === 'endpoint') { - await handleEndpointAction(tokenId,action, savedForm,portalConfig); - } else { - console.warn('Unexpected action type:', action.action_type); - return res - .status(400) - .send({ error: getErrorMessage("UNKNOWN_ACTION") }); - } - } - - res.json({ status: 'success' }); - } catch(error) { - return res - .status(400) - .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") }); - } -} -async function handleEndpointAction(tokenId,action, formData, portalConfig) { - - try { - const portalHost = portalConfig["apiHost"] || action.host; - const url = portalHost+action.path; - const headers = Object.fromEntries(action.headers.map(h => Object.entries(h)[0])); - - const savedJson = { - "token": tokenId, - "formJson": formData - }; - const actionBody = Object.fromEntries( - (action.body || []).map(b => Object.entries(b)[0]) - ); - const payload = { ...savedJson, ...actionBody }; - const response = await fetch(url, { - method: action.type, - headers: { ...headers, Authorization: `Bearer ${action.authentication}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - - if (!response.ok) { - const text = await response.text(); - throw new Error(`Endpoint error: ${response.status} ${text}`); - } - } catch(error) { - console.error('Error in handleEndPoint:', error); - throw error; - } -} +const appConfig = require('../appConfig.js'); +async function submitForPortalAction (req,res) { + const { tokenId, savedForm ,config} = req.body; + console.log('submitForPortalAction:', { + tokenId: req.body?.tokenId, + savedFormType: typeof req.body?.savedForm, + actionCount: req.body?.config?.actions?.length + }); + try{ + + if (!config?.actions || !Array.isArray(config.actions)) { + return res + .status(400) + .send({ error: getErrorMessage("NO_ACTION_FOUND")}); + } + const rawHost = (req.get("X-Original-Server") || req.hostname); + const portalConfig = appConfig[rawHost] || Object.values(appConfig).find(cfg => { + try { + return new URL(cfg.apiHost).hostname === rawHost; + } catch { + return false; + } + }) || {}; + + for (const action of config.actions) { + if (action.action_type === 'endpoint') { + await handleEndpointAction(tokenId,action, savedForm,portalConfig); + } else { + console.warn('Unexpected action type:', action.action_type); + return res + .status(400) + .send({ error: getErrorMessage("UNKNOWN_ACTION") }); + } + } + + res.json({ status: 'success' }); + } catch(error) { + return res + .status(400) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") }); + } +} +async function handleEndpointAction(tokenId,action, formData, portalConfig) { + + try { + const portalHost = portalConfig["apiHost"] || action.host; + const url = portalHost+action.path; + const headers = Object.fromEntries(action.headers.map(h => Object.entries(h)[0])); + + const savedJson = { + "token": tokenId, + "formJson": formData + }; + const actionBody = Object.fromEntries( + (action.body || []).map(b => Object.entries(b)[0]) + ); + const payload = { ...savedJson, ...actionBody }; + console.log('handleEndpointAction ->', { + url, + method: action.type, + headers, + payloadKeys: Object.keys(payload) + }); + const response = await fetch(url, { + method: action.type, + headers: { ...headers, Authorization: `Bearer ${action.authentication}`, 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + console.log('Fetch status:', response.status); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Endpoint error: ${response.status} ${text}`); + } + } catch(error) { + console.error('Error in handleEndPoint:', error); + throw error; + } +} module.exports = submitForPortalAction; \ No newline at end of file From 28def17766e7ce870589c15889ec72a3c6f8fc04 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Thu, 18 Sep 2025 10:13:09 -0700 Subject: [PATCH 039/105] kiln version check in save ICM fix --- saveICMdataHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 1cb5101..52418f8 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -152,7 +152,7 @@ async function saveICMdata(req, res) { * Kiln V1 uses data: { items: []} * Kiln V2 uses dataSources [] */ - const kilnVersion = Object.keys(JSON.parse(savedFormParam)["data"]).includes("items") ? 1 : 2; + const kilnVersion = Object.keys(JSON.parse(savedFormParam)["form_definition"]["data"]).includes("items") ? 1 : 2; const formDefinitionItems = kilnVersion === 1 ? JSON.parse(savedFormParam)["form_definition"]["data"]["items"] : JSON.parse(savedFormParam)["form_definition"]["elements"];// This is the field info for form items // dateItemsId : This will contain all of the IDs of the date fields From 89149f533adbf69de28147418b707d8591295d56 Mon Sep 17 00:00:00 2001 From: Viswam Date: Thu, 18 Sep 2025 15:47:52 -0700 Subject: [PATCH 040/105] Changes for baseEncodedJson --- portal/savePortalFormDataHandler.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index 30b4791..871d13f 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -1,4 +1,5 @@ const appConfig = require('../appConfig.js'); +const { getErrorMessage } = require("../errorHandling/errorHandler.js"); async function submitForPortalAction (req,res) { const { tokenId, savedForm ,config} = req.body; console.log('submitForPortalAction:', { @@ -47,9 +48,10 @@ async function handleEndpointAction(tokenId,action, formData, portalConfig) { const url = portalHost+action.path; const headers = Object.fromEntries(action.headers.map(h => Object.entries(h)[0])); + const base64EncodedJson = Buffer.from(formData, 'utf8').toString('base64'); const savedJson = { "token": tokenId, - "formJson": formData + "formJson": base64EncodedJson, }; const actionBody = Object.fromEntries( (action.body || []).map(b => Object.entries(b)[0]) From 0efbfb571d9f5f91ccab2ef1d30f65239e06ec0b Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Tue, 23 Sep 2025 15:37:24 -0700 Subject: [PATCH 041/105] Update to dictionary values. Putting them in alphabetical order --- dictionary/jsonXmlConversion.js | 173 +++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 4 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 06b4fa8..57d197d 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -8,8 +8,53 @@ * Property: version : a string that will check if the exception list should include the version of the form submitted. All values in version should overwrite the default form properties. */ const formExceptions = { - "HR3689E": { - "rootName": "ListOfDtFormInstanceLW", + "CF0630": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "CF0631": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "CF0632": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "CF0633": { + "rootName": "ListOfDtFormInstanceLw", "subRoots": ["FormInstance"], "wrapperTags": [], "allowCheckboxWithNoChange": [], @@ -24,7 +69,7 @@ const formExceptions = { } }, "CF0925": { - "rootName": "", + "rootName": "form1", "subRoots": [], "wrapperTags": [ { @@ -99,7 +144,127 @@ const formExceptions = { "omitFields": [] } } - } + }, + "CF0926": { + "rootName": "form1", + "subRoots": [], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "CF2900": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": [], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "HR0080": { + "rootName": "Results", + "subRoots": [], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "HR3687E": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "HR3688E": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "HR3689E": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "HR3690E": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, + "HR3704E": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + } }; module.exports = { formExceptions }; \ No newline at end of file From 17be1b0120978252ef218465c16551e0157f54b1 Mon Sep 17 00:00:00 2001 From: Viswam Date: Thu, 25 Sep 2025 14:39:47 -0700 Subject: [PATCH 042/105] Changes for headers and body not being array for action interface --- portal/savePortalFormDataHandler.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index 871d13f..0206377 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -45,17 +45,19 @@ async function handleEndpointAction(tokenId,action, formData, portalConfig) { try { const portalHost = portalConfig["apiHost"] || action.host; - const url = portalHost+action.path; - const headers = Object.fromEntries(action.headers.map(h => Object.entries(h)[0])); - + const url = portalHost+action.path; + const headers = Array.isArray(action.headers) + ? Object.fromEntries(action.headers.map(b => Object.entries(b)[0])) + : (action.headers || {}); const base64EncodedJson = Buffer.from(formData, 'utf8').toString('base64'); const savedJson = { "token": tokenId, "formJson": base64EncodedJson, }; - const actionBody = Object.fromEntries( - (action.body || []).map(b => Object.entries(b)[0]) - ); + + const actionBody = Array.isArray(action.body) + ? Object.fromEntries(action.body.map(b => Object.entries(b)[0])) + : (action.body || {}); const payload = { ...savedJson, ...actionBody }; console.log('handleEndpointAction ->', { url, From e2e0ddd16c77b8021045467b90ec93a2ac3826e0 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 25 Sep 2025 15:28:07 -0700 Subject: [PATCH 043/105] Updated portal handlers/routes, created interfaces --- databindingsHandler.js | 2 +- dictionary/interfaces.js | 75 ++++++++++++++ interface.js | 46 +++++++++ portal/authHandler.js | 19 ++++ portal/cancelPortalFormDataHandler.js | 61 +++++++++++ portal/loadPortalDataHandler.js | 60 ++++------- portal/loadPortalIntegratedHandler.js | 3 +- portal/savePortalFormDataHandler.js | 140 +++++++++++++------------- portal/submitPortalFormDataHandler.js | 74 ++++++++++++++ routes.js | 10 +- 10 files changed, 372 insertions(+), 118 deletions(-) create mode 100644 dictionary/interfaces.js create mode 100644 interface.js create mode 100644 portal/authHandler.js create mode 100644 portal/cancelPortalFormDataHandler.js create mode 100644 portal/submitPortalFormDataHandler.js diff --git a/databindingsHandler.js b/databindingsHandler.js index f7bb693..5f35f04 100644 --- a/databindingsHandler.js +++ b/databindingsHandler.js @@ -314,7 +314,7 @@ function updateParams(params, pathParams = {}, allFetchedData = {}) { }); } - // 2. Replace all !!.[Source]=jsonpath in the string + // 2. Replace all '!!.[Source]=jsonpath' in the string if (typeof val === 'string') { val = val.replace(/'!!\.\[([^\]]+)\]=(.+\])'/g, (match, sourceName, jsonPath) => { const sourceData = allFetchedData[sourceName.trim()]; diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js new file mode 100644 index 0000000..05ba3af --- /dev/null +++ b/dictionary/interfaces.js @@ -0,0 +1,75 @@ +/* List of interfaces based on server config + */ +const interfaces = { + NETportals: { + interface: [ + { + type: "button", + label: "Submit", + mode: ["portalEdit"], + style: "", + actions: [ + { + action_type: "javascript", + script: `if (!validateAllFields()) + { setModalTitle("Validation Error"); + setModalMessage("Please clear the errors in the form before submitting."); + setModalOpen(true); + return false; }` + }, + + { + action_type: "endpoint", + path: "API.saveButtonAction", + body: "tokenId: params[\"id\"],savedForm: JSON.stringify(createSavedData())", + type: "POST" + }, + { + action_type: "endpoint", + path: "API.submitButtonAction", + body: "tokenId: params[\"id\"]", + type: "POST" + }, + { + action_type: "javascript", + script: `setModalTitle("Success ✅"); + setModalMessage("Form Submitted Successfully."); + setModalOpen(true); + await handleSubmit();` + }] + }, + { + type: "button", + label: "Print", + mode: ["portalView"], + style: "", + actions: [ + { + action_type: "javascript", + script: `handlePrint();` + }] + }, + { + type: "button", + label: "Cancel", + mode: ["portalEdit","portalView"], + style: "", + actions: [ + { + action_type: "endpoint", + path: "API.cancelButtonAction", + body: `tokenId: params["id"]`, + type: "POST" + }, + { + action_type: "javascript", + script: `await handleCancel();` + }] + }, + + + ] + } +}; + +module.exports = { interfaces }; \ No newline at end of file diff --git a/interface.js b/interface.js new file mode 100644 index 0000000..e8350b9 --- /dev/null +++ b/interface.js @@ -0,0 +1,46 @@ +const appConfig = require('./appConfig.js'); +const { interfaces } = require("./dictionary/interfaces.js"); +async function interface(req, res) { + try { + const rawHost = (req.get("X-Original-Server") || req.hostname); + const configOpt = appConfig[rawHost]; + + // If no interface configured, return early + if (!configOpt || !configOpt.interface) { + return res.status(200).json({ + host: rawHost, + interfaceKey: null, + interface: null, + note: "No interface configured for this host" + }); + } + + const interfaceKey = configOpt.interface; + const interfaceTemplate = interfaces[interfaceKey]; + + if (!interfaceTemplate) { + return res.status(404).json({ + error: "INTERFACE_NOT_FOUND", + message: `Interface "${interfaceKey}" not found in interfaces.js.` + }); + } + + const interfaceJson = JSON.parse(JSON.stringify(interfaceTemplate)); + + return res.json({ + host: rawHost, + interfaceKey: interfaceKey, + interface: interfaceJson + }); + } + + catch (err) { + console.error("interfaceRoute error:", err); + return res.status(500).json({ + error: "INTERNAL_ERROR", + message: "Failed to build interface for this host" + }); + } +} + +module.exports = interface; \ No newline at end of file diff --git a/portal/authHandler.js b/portal/authHandler.js new file mode 100644 index 0000000..76a03a7 --- /dev/null +++ b/portal/authHandler.js @@ -0,0 +1,19 @@ +//Build Header Auth based on Portal config +function buildPortalAuthHeader(portal) { + if ( + portal?.portalAuth === "basic" && + ( + (portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) || + portal.apiSecret + ) + ) { + const creds = (portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) + ? `${portal.basicAuth.username}:${portal.basicAuth.password}` + : portal.apiSecret; // allow "user:pass" as apiSecret + const auth = "Basic " + Buffer.from(creds, "utf8").toString("base64"); + return { Authorization: auth }; + } + return {}; +} + +module.exports = buildPortalAuthHeader; diff --git a/portal/cancelPortalFormDataHandler.js b/portal/cancelPortalFormDataHandler.js new file mode 100644 index 0000000..3d2d549 --- /dev/null +++ b/portal/cancelPortalFormDataHandler.js @@ -0,0 +1,61 @@ +// portal/expirePortalToken.js +const appConfig = require('../appConfig.js'); +const { getErrorMessage } = require("../errorHandling/errorHandler.js"); +const { expireTokenInPortal } = require('./loadPortalDataHandler.js'); + +async function cancelPortalAction(req, res) { + const { tokenId} = req.body || {}; + + console.log('expireNETPortal:', { + tokenId: req.body?.tokenId + }); + + try { + if (!tokenId) { + return res + .status(400) + .send({ error: getErrorMessage("FORM_NOT_FOUND_IN_REQUEST") || "Missing tokenId" }); + } + + // Resolve portal config the same way as submit + const rawHost = (req.get("X-Original-Server") || req.hostname); + const portalConfig = + appConfig[rawHost] || + Object.values(appConfig).find(cfg => { + try { + return new URL(cfg.apiHost).hostname === rawHost; + } catch { + return false; + } + }) || + {}; + + const apiHost = portalConfig.apiHost; + const expirePath = (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); + + if (!apiHost || !expirePath) { + return res + .status(500) + .send({ error: getErrorMessage("PORTAL_CONFIG_NOT_FOUND") || "Missing apiHost or expire endpoint in appConfig" }); + } + + const headers = { + 'Content-Type': 'application/json', + }; + + const expired = await expireTokenInPortal(portalConfig, tokenId, headers); + + if (!expired) { + return res.status(502).send({ error: "Portal did not confirm token expiration" }); + } + + return res.json({ status: 'success', expired: true }); + } catch (err) { + console.error('expireNETPortal error:', err); + return res + .status(500) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") || (err?.message || 'Unhandled error') }); + } +} + +module.exports = cancelPortalAction; diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index 1890c6a..21ddc43 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -1,6 +1,8 @@ const axios = require("axios"); const { getErrorMessage } = require("../errorHandling/errorHandler.js"); -async function getParametersFromPortal(portal,token, userId) { +const buildPortalAuthHeader = require('./authHandler.js'); + +async function getParametersFromPortal(portal,token) { let parametersForForm = ""; try { @@ -8,12 +10,12 @@ async function getParametersFromPortal(portal,token, userId) { console.log("urlForValidateTokenAndGetParams >>",urlForValidateTokenAndGetParams); const response = await axios.post(`${urlForValidateTokenAndGetParams}`, { - token, - userId + token }, { headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...buildPortalAuthHeader(portal), } } ); @@ -28,7 +30,7 @@ async function getParametersFromPortal(portal,token, userId) { } -async function expireTokenInPortal(portal,token, userId) { +async function expireTokenInPortal(portal,token) { //call another api from portal to get the params let isTokenExpired = false; @@ -38,11 +40,11 @@ async function expireTokenInPortal(portal,token, userId) { console.log("urlForExpiringToken",urlForExpiringToken); const response = await axios.post(`${urlForExpiringToken}`, { - token, - userId + token }, { headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...buildPortalAuthHeader(portal), } }); @@ -55,46 +57,22 @@ async function expireTokenInPortal(portal,token, userId) { return isTokenExpired; } -async function getSavedFormFromPortal(portal,token, userId) { +async function getSavedFormFromPortal(portal,token) { let parametersForForm = ""; try { const urlForValidateTokenAndGetJson= portal.apiHost+ (portal.getSavedJsonEndpoint || process.env.PORTAL_VALIDATE_TOKEN_ENDPOINT); console.log("urlForValidateTokenAndGetJson >>",urlForValidateTokenAndGetJson); - let response; - if (portal.portalAuth === "basic" && ((portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) || portal.apiSecret)) { - const auth = (portal.basicAuth && portal.basicAuth.username && portal.basicAuth.password) - ? "Basic " + btoa(portal.basicAuth.username + ":" + portal.basicAuth.password) - : "Basic " + btoa(portal.apiSecret); - response = await axios.post(`${urlForValidateTokenAndGetJson}`, - { - token, - userId - }, - { - headers: { - 'Content-Type': 'application/json', - 'Authorization': auth - } - } - ); - } else { - response = await axios.post(`${urlForValidateTokenAndGetJson}`, - { - token, - userId - }, - { - headers: { - 'Content-Type': 'application/json' - } - } - ); - } + + const headers = { + "Content-Type": "application/json", + ...buildPortalAuthHeader(portal), + }; + const response = await axios.post(urlForValidateTokenAndGetJson, { token}, { headers }); + //TODO: verify the json against schema or some other sanity checks return response.data; - - + } catch (err) { console.log( 'Failed to contact target app', err ); diff --git a/portal/loadPortalIntegratedHandler.js b/portal/loadPortalIntegratedHandler.js index 58d3dfe..123772b 100644 --- a/portal/loadPortalIntegratedHandler.js +++ b/portal/loadPortalIntegratedHandler.js @@ -9,7 +9,6 @@ async function loadPortalIntegratedForm(req, res) { try { const params = req.body; const token = params["id"]; - const userId = "test"; if (!token ) { return res .status(400) @@ -22,7 +21,7 @@ async function loadPortalIntegratedForm(req, res) { if(!targetApp) { return res.status(400).send({ error: getErrorMessage("UNKNOWN_ORIGIN_SERVER") }); } - const formJson = await getSavedFormFromPortal(targetApp,token,userId); + const formJson = await getSavedFormFromPortal(targetApp,token); if(!formJson) { return res.status(400).send({ error: getErrorMessage("FORM_NOT_FOUND", { templateId: template_id }) }); diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index 871d13f..b1b73b6 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -1,82 +1,78 @@ const appConfig = require('../appConfig.js'); const { getErrorMessage } = require("../errorHandling/errorHandler.js"); -async function submitForPortalAction (req,res) { - const { tokenId, savedForm ,config} = req.body; - console.log('submitForPortalAction:', { - tokenId: req.body?.tokenId, - savedFormType: typeof req.body?.savedForm, - actionCount: req.body?.config?.actions?.length - }); - try{ - - if (!config?.actions || !Array.isArray(config.actions)) { - return res - .status(400) - .send({ error: getErrorMessage("NO_ACTION_FOUND")}); - } - const rawHost = (req.get("X-Original-Server") || req.hostname); - const portalConfig = appConfig[rawHost] || Object.values(appConfig).find(cfg => { - try { - return new URL(cfg.apiHost).hostname === rawHost; - } catch { - return false; - } - }) || {}; - - for (const action of config.actions) { - if (action.action_type === 'endpoint') { - await handleEndpointAction(tokenId,action, savedForm,portalConfig); - } else { - console.warn('Unexpected action type:', action.action_type); - return res - .status(400) - .send({ error: getErrorMessage("UNKNOWN_ACTION") }); +const buildPortalAuthHeader = require('./authHandler.js'); + +async function saveForPortalAction(req, res) { + const { tokenId, savedForm } = req.body; + + console.log('saveForPortalAction:', { + tokenId: req.body?.tokenId, + savedForm: typeof req.body?.savedForm, + }); + + try { + if (!tokenId || !savedForm) { + return res + .status(400) + .send({ error: "Missing tokenId or savedForm" }); + } + + const rawHost = (req.get("X-Original-Server") || req.hostname); + const portalConfig = + appConfig[rawHost] || + Object.values(appConfig).find(cfg => { + try { + return new URL(cfg.apiHost).hostname === rawHost; + } catch { + return false; } - } + }) || {}; - res.json({ status: 'success' }); - } catch(error) { + const portalHost = portalConfig.apiHost; + const endpoint = portalConfig.saveEndpoint; + const url = portalHost+endpoint; + const method = "POST"; + + if (!portalHost || !endpoint) { return res - .status(400) - .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") }); + .status(400) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") || "Missing portalHost or endpoint path" }); } -} -async function handleEndpointAction(tokenId,action, formData, portalConfig) { - - try { - const portalHost = portalConfig["apiHost"] || action.host; - const url = portalHost+action.path; - const headers = Object.fromEntries(action.headers.map(h => Object.entries(h)[0])); - const base64EncodedJson = Buffer.from(formData, 'utf8').toString('base64'); - const savedJson = { - "token": tokenId, - "formJson": base64EncodedJson, - }; - const actionBody = Object.fromEntries( - (action.body || []).map(b => Object.entries(b)[0]) - ); - const payload = { ...savedJson, ...actionBody }; - console.log('handleEndpointAction ->', { - url, - method: action.type, - headers, - payloadKeys: Object.keys(payload) - }); - const response = await fetch(url, { - method: action.type, - headers: { ...headers, Authorization: `Bearer ${action.authentication}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - console.log('Fetch status:', response.status); + const base64EncodedJson = Buffer.from(savedForm, "utf8").toString("base64"); + const savedJson = { token: tokenId, jsonToSave: base64EncodedJson }; - if (!response.ok) { - const text = await response.text(); - throw new Error(`Endpoint error: ${response.status} ${text}`); - } - } catch(error) { - console.error('Error in handleEndPoint:', error); - throw error; + console.log('SaveForPortalAction ->', { + url, + method, + savedJson + }); + + const response = await fetch(url, { + method, + headers: { + "Content-Type": "application/json", + ...buildPortalAuthHeader(portalConfig), + }, + body: JSON.stringify(savedJson), + }); + + console.log("Fetch status:", response.status); + + if (!response.ok) { + const text = await response.text(); + return res + .status(400) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") || `Endpoint error: ${response.status} ${text}` }); } + + res.json({ status: "success" }); + } catch (error) { + console.error("saveForPortalAction error:", error); + return res + .status(400) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") }); + } } -module.exports = submitForPortalAction; \ No newline at end of file + +module.exports = saveForPortalAction; diff --git a/portal/submitPortalFormDataHandler.js b/portal/submitPortalFormDataHandler.js new file mode 100644 index 0000000..4ef13e6 --- /dev/null +++ b/portal/submitPortalFormDataHandler.js @@ -0,0 +1,74 @@ +const appConfig = require('../appConfig.js'); +const { getErrorMessage } = require("../errorHandling/errorHandler.js"); +const buildPortalAuthHeader = require('./authHandler.js'); + +async function submitForPortalAction(req, res) { + const { tokenId } = req.body; + + try { + if (!tokenId) { + return res + .status(400) + .send({ error:"Missing tokenId or form" }); + } + + const rawHost = (req.get("X-Original-Server") || req.hostname); + const portalConfig = + appConfig[rawHost] || + Object.values(appConfig).find(cfg => { + try { + return new URL(cfg.apiHost).hostname === rawHost; + } catch { + return false; + } + }) || + {}; + + const interfaceHost = portalConfig.apiHost; + const submitPath = portalConfig.submitEndpoint; + + if (!interfaceHost || !submitPath) { + return res + .status(500) + .send({ error: getErrorMessage("PORTAL_CONFIG_NOT_FOUND") || "Missing interface host/endpoints in appConfig" }); + } + + const submitUrl = interfaceHost+submitPath; + + console.log("SUBMIT URL",submitUrl); + + const headers = { + 'Content-Type': 'application/json', + ...buildPortalAuthHeader(portalConfig), + }; + + const savePayload = { + token: tokenId + }; + + + const submitResp = await fetch(submitUrl.toString(), { + method: 'POST', + headers, + body: JSON.stringify(savePayload) + }); + + console.log("SUBMIT RESPONSE",submitResp); + + if (!submitResp.ok) { + const text = await submitResp.text(); + return res + .status(502) + .send({ error: `Submit failed: ${submitResp.status} ${text}` }); + } + + return res.json({ status: 'success' }); + } catch (err) { + console.error('submitNETPortal error:', err); + return res + .status(500) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") || (err?.message || 'Unhandled error') }); + } +} + +module.exports = submitForPortalAction; diff --git a/routes.js b/routes.js index 3a1a28a..2f4850c 100644 --- a/routes.js +++ b/routes.js @@ -10,8 +10,11 @@ const appCfg = require('./appConfig.js'); const {generatePDFFromHTML,generatePDFFromURL,generatePDFFromJSON,loadSavedJson } = require("./generatePDFHandler"); const generatePortalIntegratedTemplate = require("./portal/generatePortalIntegratedHandler.js"); -const submitForPortalAction = require("./portal/savePortalFormDataHandler.js"); +const saveForPortalAction = require("./portal/savePortalFormDataHandler.js"); const loadPortalIntegratedForm = require("./portal/loadPortalIntegratedHandler.js"); +const submitForPortalAction = require("./portal/submitPortalFormDataHandler.js"); +const cancelForPortalAction = require('./portal/cancelPortalFormDataHandler.js'); +const interface = require("./interface.js"); require("./formRepoHandler"); const {getProcessedData} = require("./icmJsonClobHandler"); const router = express.Router(); @@ -197,7 +200,10 @@ router.post("/loadSavedJson", loadSavedJson); router.post("/generatePortalForm", generatePortalIntegratedTemplate); router.post("/generateNewTemplate", generateNewTemplate); router.use('/pdfRender', renderRouter); -router.use('/submitForPortalAction', submitForPortalAction); +router.use('/saveForPortalAction', saveForPortalAction); router.post("/loadPortalForm", loadPortalIntegratedForm); +router.use("/interface", interface); +router.post('/submitForPortalAction', submitForPortalAction); +router.post('/cancelForPortalAction', cancelForPortalAction); module.exports = router; \ No newline at end of file From f201d69e4591d1a622af6689b5d5993b2c38f98a Mon Sep 17 00:00:00 2001 From: David Okulski Date: Fri, 26 Sep 2025 09:09:40 -0700 Subject: [PATCH 044/105] Updated to allow requests to set headers and path --- portal/cancelPortalFormDataHandler.js | 3 ++- portal/savePortalFormDataHandler.js | 3 ++- portal/submitPortalFormDataHandler.js | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/portal/cancelPortalFormDataHandler.js b/portal/cancelPortalFormDataHandler.js index 3d2d549..63a64ef 100644 --- a/portal/cancelPortalFormDataHandler.js +++ b/portal/cancelPortalFormDataHandler.js @@ -31,7 +31,7 @@ async function cancelPortalAction(req, res) { {}; const apiHost = portalConfig.apiHost; - const expirePath = (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); + const expirePath = req.body?.path || (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); if (!apiHost || !expirePath) { return res @@ -41,6 +41,7 @@ async function cancelPortalAction(req, res) { const headers = { 'Content-Type': 'application/json', + ...(req.body?.headers || {}) }; const expired = await expireTokenInPortal(portalConfig, tokenId, headers); diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index b1b73b6..21983a2 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -29,7 +29,7 @@ async function saveForPortalAction(req, res) { }) || {}; const portalHost = portalConfig.apiHost; - const endpoint = portalConfig.saveEndpoint; + const endpoint = req.body?.path ||portalConfig.saveEndpoint; const url = portalHost+endpoint; const method = "POST"; @@ -53,6 +53,7 @@ async function saveForPortalAction(req, res) { headers: { "Content-Type": "application/json", ...buildPortalAuthHeader(portalConfig), + ...(req.body?.headers || {}), }, body: JSON.stringify(savedJson), }); diff --git a/portal/submitPortalFormDataHandler.js b/portal/submitPortalFormDataHandler.js index 4ef13e6..31b6bdf 100644 --- a/portal/submitPortalFormDataHandler.js +++ b/portal/submitPortalFormDataHandler.js @@ -24,8 +24,8 @@ async function submitForPortalAction(req, res) { }) || {}; - const interfaceHost = portalConfig.apiHost; - const submitPath = portalConfig.submitEndpoint; + const interfaceHost = portalConfig.apiHost; + const submitPath = req.body?.path ||portalConfig.submitEndpoint; if (!interfaceHost || !submitPath) { return res @@ -40,6 +40,7 @@ async function submitForPortalAction(req, res) { const headers = { 'Content-Type': 'application/json', ...buildPortalAuthHeader(portalConfig), + ...(req.body?.headers || {}), }; const savePayload = { From 48905dd422fa01a4af6c6e607a1426422d09ca42 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Fri, 26 Sep 2025 11:01:22 -0700 Subject: [PATCH 045/105] Filter "API." requests from path --- portal/cancelPortalFormDataHandler.js | 7 ++++++- portal/savePortalFormDataHandler.js | 8 +++++++- portal/submitPortalFormDataHandler.js | 7 ++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/portal/cancelPortalFormDataHandler.js b/portal/cancelPortalFormDataHandler.js index 63a64ef..2800e7a 100644 --- a/portal/cancelPortalFormDataHandler.js +++ b/portal/cancelPortalFormDataHandler.js @@ -31,7 +31,12 @@ async function cancelPortalAction(req, res) { {}; const apiHost = portalConfig.apiHost; - const expirePath = req.body?.path || (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); + let expirePath = req.body?.path || (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); + + //Skip "API." request paths + if (expirePath && /API\./i.test(expirePath)) { + expirePath = (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); + } if (!apiHost || !expirePath) { return res diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index 21983a2..e593c27 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -29,7 +29,13 @@ async function saveForPortalAction(req, res) { }) || {}; const portalHost = portalConfig.apiHost; - const endpoint = req.body?.path ||portalConfig.saveEndpoint; + let endpoint = req.body?.path ||portalConfig.saveEndpoint; + + //Skip "API." request paths + if (endpoint && /API\./i.test(endpoint)) { + endpoint = portalConfig.saveEndpoint; + } + const url = portalHost+endpoint; const method = "POST"; diff --git a/portal/submitPortalFormDataHandler.js b/portal/submitPortalFormDataHandler.js index 31b6bdf..a124356 100644 --- a/portal/submitPortalFormDataHandler.js +++ b/portal/submitPortalFormDataHandler.js @@ -25,7 +25,12 @@ async function submitForPortalAction(req, res) { {}; const interfaceHost = portalConfig.apiHost; - const submitPath = req.body?.path ||portalConfig.submitEndpoint; + let submitPath = req.body?.path ||portalConfig.submitEndpoint; + + //Skip "API." request paths + if (submitPath && /API\./i.test(submitPath)) { + submitPath = portalConfig.submitEndpoint; + } if (!interfaceHost || !submitPath) { return res From 3fbc370bb867bbc72b9947f14aae860058f1614e Mon Sep 17 00:00:00 2001 From: David Okulski Date: Fri, 26 Sep 2025 14:40:25 -0700 Subject: [PATCH 046/105] Updated interface and removed API path filtering --- dictionary/interfaces.js | 6 +++--- portal/cancelPortalFormDataHandler.js | 5 ----- portal/savePortalFormDataHandler.js | 5 ----- portal/submitPortalFormDataHandler.js | 5 ----- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index 05ba3af..a629006 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -20,13 +20,13 @@ const interfaces = { { action_type: "endpoint", - path: "API.saveButtonAction", + api_path: "API.saveButtonAction", body: "tokenId: params[\"id\"],savedForm: JSON.stringify(createSavedData())", type: "POST" }, { action_type: "endpoint", - path: "API.submitButtonAction", + api_path: "API.submitButtonAction", body: "tokenId: params[\"id\"]", type: "POST" }, @@ -57,7 +57,7 @@ const interfaces = { actions: [ { action_type: "endpoint", - path: "API.cancelButtonAction", + api_path: "API.cancelButtonAction", body: `tokenId: params["id"]`, type: "POST" }, diff --git a/portal/cancelPortalFormDataHandler.js b/portal/cancelPortalFormDataHandler.js index 2800e7a..88aaed9 100644 --- a/portal/cancelPortalFormDataHandler.js +++ b/portal/cancelPortalFormDataHandler.js @@ -33,11 +33,6 @@ async function cancelPortalAction(req, res) { const apiHost = portalConfig.apiHost; let expirePath = req.body?.path || (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); - //Skip "API." request paths - if (expirePath && /API\./i.test(expirePath)) { - expirePath = (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); - } - if (!apiHost || !expirePath) { return res .status(500) diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index e593c27..544dad3 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -31,11 +31,6 @@ async function saveForPortalAction(req, res) { const portalHost = portalConfig.apiHost; let endpoint = req.body?.path ||portalConfig.saveEndpoint; - //Skip "API." request paths - if (endpoint && /API\./i.test(endpoint)) { - endpoint = portalConfig.saveEndpoint; - } - const url = portalHost+endpoint; const method = "POST"; diff --git a/portal/submitPortalFormDataHandler.js b/portal/submitPortalFormDataHandler.js index a124356..e7f3d95 100644 --- a/portal/submitPortalFormDataHandler.js +++ b/portal/submitPortalFormDataHandler.js @@ -27,11 +27,6 @@ async function submitForPortalAction(req, res) { const interfaceHost = portalConfig.apiHost; let submitPath = req.body?.path ||portalConfig.submitEndpoint; - //Skip "API." request paths - if (submitPath && /API\./i.test(submitPath)) { - submitPath = portalConfig.submitEndpoint; - } - if (!interfaceHost || !submitPath) { return res .status(500) From aa3a08a1110bfdded3601693a420f18be6614902 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 26 Sep 2025 15:11:06 -0700 Subject: [PATCH 047/105] (ADO-3390) addFields applied to dictionary and XML --- dictionary/jsonXmlConversion.js | 159 +++++++++++++++++++++++++++++++- saveICMdataHandler.js | 121 +++++++++++++++++++++++- 2 files changed, 277 insertions(+), 3 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 57d197d..9d45315 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -14,6 +14,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -29,6 +30,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -44,6 +46,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -59,6 +62,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -136,6 +140,7 @@ const formExceptions = { ], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions" : { "1" :{ "omitFields": [] @@ -151,6 +156,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -163,9 +169,154 @@ const formExceptions = { "CF2900": { "rootName": "ListOfDtFormInstanceLw", "subRoots": [], - "wrapperTags": [], + "wrapperTags": [ + { + "ListofDependent": { + "Dependent": { + "CareArrangementsList": { + "CareArrangements": { + "licensed-group-b650d0a6-2871-433c-9a2b-9e80a2f7c9a8": 4, + "mailing-address-1-003b31e6-6661-4229-b17e-1d1121bb4de0": 4, + "citytown-b8a078b2-4985-4cc6-9de4-48674704cd75": 4, + "postal-code-7cf09061-3472-4a74-802f-76aa8c115aa0": 4, + "comments-4532571d-f24a-4877-bf9e-d69f979b2a08": 4 + } + }, + "first-name-4f1d33c2-cd25-4801-9902-d1d33a0ba010": 2, + "middle-name-9db65275-66a0-4f3d-856b-9cd9ded0f385": 2, + "last-name-dc75eb1e-d57d-4383-a9e6-5e0a86513700": 2, + "date-of-birth-yyyy-mmm-dd-0e756b1c-47c9-482b-82fa-9c681c87ea55": 2, + "radiobuttonlist-d7ada287-f4af-4a1a-ad9f-daa893b8789f": 2, + "radiobuttonlist-bdbd4d0f-daf0-445e-abb0-4eb42f3e15c0": 2, + "licensed-group-b650d0a6-2871-433c-9a2b-9e80a2f7c9a8": 2, + "licensed-family-f14e4cd1-204e-41a7-b1b2-d7b910cc2ee4": 2, + "licensed-preschool-program-15821c42-4a1f-4fc4-98e0-6a0fedc6cf91": 2, + "registered-licence-not-required-455d1970-fe3a-4d7c-9ca0-d365f1177fb1": 2, + "licence-not-required-a0b0868e-c7e0-43d3-b688-d2569108c595": 2, + "in-childs-home-2b8a7f32-e472-4cad-aba5-254b33125844": 2, + "radiobuttonlist-28b592b7-aba8-46dc-8caf-815b8684fd31": 2, + "custody-details-e38e1e1a-4db9-460d-bde3-a9b626419173": 2 + } + } + }, + { + "ListReasonForCare": { + "ReasonForCare": { + "name-of-employers-school-training-program-or-dates-looking-for-work-3bb24589-196e-4a3b-bf9d-0b2616efec89": 2, + "start-date-yyyy-mmm-dd-a745b70e-8304-4814-be69-1b232c44beeb": 2, + "end-date-yyyy-mmm-dd-d2df073a-101d-4a68-bf42-1a040ebf7585": 2, + "week-days-andor-week-ends-317392fa-4395-401a-b9ec-fd60930177f1": 2, + "week-end-days-per-week-8baf2547-c051-4fc5-849a-5aa527fb326a": 2, + "week-end-hours-per-day-3afabfee-a210-4465-80e3-788bae3d59d4": 2, + "week-day-days-per-week-3176c7d7-751b-4753-871c-d53bf6128e3e": 2, + "week-day-hours-per-day-00303def-6fad-49d6-b72b-183abef2fbd5": 2, + "regular-schedule-73687eed-2e55-4aa7-821a-deab607da5d3": 2, + "start-time-0187c5ad-c8b0-420b-bded-595d2ecad5bd": 2, + "end-time-d7f384c0-f80f-4aa3-9430-7a86187897df": 2, + "additional-information-or-attach-a-schedule-77a4d493-0468-40a7-8d0e-af9ab9906085": 2, + "travel-time-c1ab5674-a91d-4ab2-b096-213b07bdb69d": 2 + } + } + } + ], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": { + "ICMApplicantDateofBirth": null, + "ApplicantGender": null, + "ApplicantPrimaryPhoneNumberType": null, + "ApplicantHomeApartment": null, + "ApplicantHomeMAKID": null, + "ApplicantMailingApartment": null, + "ApplicantEmail": null, + "CareArrangementUploaded": null, + "ICMSpouseDateofBirth": null, + "SpouseGender": null, + "ListofDependent": { + "Dependent": { + "CareArrangementsList": { + "CareArrangements": { + "FacilityID": null, + "CCAProviderName": null, + "CCAProviderDaytimePhone": null, + "CCAProviderSecondaryPhone": null, + "FacilityName": null, + "ServiceAddress": null, + "ServiceCity": null, + "ServicePostalCode": null, + "CCACareType": null, + "SundayFlag": null, + "SaturdayFlag": null, + "CCASupplierNumber": null, + "CCAStartDate": null, + "CCAEndDate": null, + "MonthlyFee": null, + "DailyFee": null, + "SchoolClosureFee": null, + "EnrolledYN": null, + "SummerFlag": null, + "CCAChildRelatedFlag": null, + "CCAChildHomeFlag": null, + "CCARelativeFlag": null, + "CCARelationship": null, + "CCASameHomeFlag": null, + "CCAParentComments": null, + "ParentSumissionDate": null, + "ProviderSubmissionDate": null, + "ProviderBCeID": null, + "MondayFlag": null, + "MondayTime1Start": null, + "MondayTime1End": null, + "TuesdayFlag": null, + "TuesdayStart1Time": null, + "TuesdayEnd1Time": null, + "WednesdayFlag": null, + "WednesdayStart1Time": null, + "WednesdayEnd1Time": null, + "ThursdayFlag": null, + "ThursdayStart1Time": null, + "ThursdayEnd1Time": null, + "FridayFlag": null, + "FridayStart1Time": null, + "FridayEnd1Time": null, + "ProviderOrLicenseeName": null, + "FirstTimeApplyingFlag": null, + "ReplacingProviderFlag": null, + "ReplacingProviderName": null, + "AdditionalProviderFlag": null, + "OtherProviderName": null + } + }, + "ICMDependantDateofBirth": null, + "DependantGender": null, + "DependantMinistryPlacement": null, + "ICMDependentCount": null + } + }, + "ListofIncome": { + "AdditionalIncomeReported": null + }, + "ListReasonForCare": { + "ReasonForCare": { + "Role": null, + "CareType": null, + "ICMTravelTime": null, + "ICMStartDate": null, + "ICMCareType": null + } + }, + "ICMBCFlag": null, + "ICMFerderalFlagSpouse": null, + "ICMFederalFlag": null, + "Field1": null, + "ICMSpouseGUID": null, + "CareArrangementExists": null, + "CareArrangementDetailsPresent": null, + "CRAFiled": null, + "IncomeSameSinceTaxFiling": null, + "SpouseCRAFiled": null, + "SpouseIncomeSameSinceTaxFiling": null + }, "versions": { "1": { "omitFields": [] @@ -181,6 +332,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -196,6 +348,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -211,6 +364,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -226,6 +380,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -241,6 +396,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] @@ -256,6 +412,7 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], + "addFields": {}, "versions": { "1": { "omitFields": [] diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 52418f8..2946f46 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -166,6 +166,8 @@ async function saveICMdata(req, res) { let toWrapIds = {}; //List of ids that will need to be placed in a wrapper. This only happens if form exception is true and wrapperTags exists const noCheckboxChange = (isFormException && propertyExists(dictionary, formId, "allowCheckboxWithNoChange")) ? dictionary[formId].allowCheckboxWithNoChange : []; const omitFields = (isFormException && propertyExists(dictionary, formId, "omitFields")) ? dictionary[formId].omitFields : []; + const addFields = (isFormException && propertyExists(dictionary, formId, "addFields")) ? dictionary[formId].addFields : {}; + if (isFormException && propertyExists(dictionary, formId, "wrapperTags")) { dictionary[formId]["wrapperTags"].forEach((wrapperTag, index) => { const tagKey = Object.keys(dictionary[formId]["wrapperTags"][index])[0]; @@ -176,7 +178,7 @@ async function saveICMdata(req, res) { } // The updated JSON values required for XML creation - const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields); + const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields, addFields, kilnVersion); let builder; // This will be for building the XML if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions @@ -553,11 +555,12 @@ function multilevelWrappers(truncatedKeysSaveData, toWrapIds, dataToWrap, oldKey * @param checkboxItemsId : list of checkbox fields * @param noCheckboxChange : list of checkbox UUIDs that should not have their value changed * @param omitFields : list of field UUIDS that should not be included in the XML + * @param addFields : list of empty fields to add for form to succeed in saving as XML * @param {number} kilnVersion : the Kiln version matching the save data json layout * @returns truncatedKeysSaveData : a list of key-object pairs */ -function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields, kilnVersion) { +function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields, addFields, kilnVersion) { for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) if (!omitFields.includes(oldKey)) { const stringLength = oldKey.length; @@ -653,6 +656,120 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt } } } + //Check additional fields needed before returning final value + const additionalFieldKeys = Object.keys(addFields); + if (additionalFieldKeys.length > 0) { + additionalFieldKeys.forEach(key => { + if (addFields[key] === null) { //Parent key/field. If it passes, then this should not exist in form already because this is for ADDING fields. If it exists in form, it will be overwritten. + truncatedKeysSaveData[key] = null; + } else if (truncatedKeysSaveData[key] === undefined) { //If truncatedKeysSaveData[key] does not exist, create it with all values under the add key from dictionary + truncatedKeysSaveData[key] = addFields[key]; + } else { //Has children in dictionary AND truncatedKeysSaveData exists already + + //Consider making the following a recursive function in a future iteration + /** + * For each key: + * Check to see if field is undefined. If undefined, then define it as empty + * Get the keys of the object if avaliable. Otherwise, get empty array + * If truncatedKeysSaveData keys lead to a group/list: apply values from dictionary + * Else if there are child keys avaliable: check the child keys (i.e. repeat this for each key) + * Else: save current level truncatedKeysSaveData with the current dictionary key values + */ + + const childFields1 = addFields[key] != null ? Object.keys(addFields[key]) : []; + if (truncatedKeysSaveData[key] && truncatedKeysSaveData[key].length != undefined) { + truncatedKeysSaveData[key][childKey1].forEach((value, index) => { + truncatedKeysSaveData[key][index] = {...truncatedKeysSaveData[key][index], ...addFields[key]}; + }); + } else if(childFields1.length > 0) { + childFields1.forEach(childKey1 => { + if (truncatedKeysSaveData[key][childKey1] === undefined) { + truncatedKeysSaveData[key][childKey1] = {}; + } + const childFields2 = addFields[key][childKey1] != null ? Object.keys(addFields[key][childKey1]) : []; + if (truncatedKeysSaveData[key][childKey1] && truncatedKeysSaveData[key][childKey1].length != undefined) { //Add the second layer of empty fields to groups + truncatedKeysSaveData[key][childKey1].forEach((value, index) => { + truncatedKeysSaveData[key][childKey1][index] = {...truncatedKeysSaveData[key][childKey1][index], ...addFields[key][childKey1]}; + }); + } else if (childFields2.length > 0) { //Continue if there are more fields listed + childFields2.forEach(childKey2 => { + if (truncatedKeysSaveData[key][childKey1][childKey2] === undefined) { + truncatedKeysSaveData[key][childKey1][childKey2] = {}; + } + const childFields3 = addFields[key][childKey1][childKey2] != null ? Object.keys(addFields[key][childKey1][childKey2]) : []; + if (truncatedKeysSaveData[key][childKey1][childKey2] && truncatedKeysSaveData[key][childKey1][childKey2].length != undefined) { + truncatedKeysSaveData[key][childKey1][childKey2].forEach((value, index) => { + truncatedKeysSaveData[key][childKey1][childKey2][index] = {...truncatedKeysSaveData[key][childKey1][childKey2][index], ...addFields[key][childKey1][childKey2]}; + }); + } else if (childFields3.length > 0) { + childFields3.forEach(childKey3 => { + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3] === undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3] = {}; + } + const childFields4 = addFields[key][childKey1][childKey2][childKey3] != null ? Object.keys(addFields[key][childKey1][childKey2][childKey3]) : []; + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3] && truncatedKeysSaveData[key][childKey1][childKey2][childKey3].length != undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3].forEach((value, index) => { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][index] = {...truncatedKeysSaveData[key][childKey1][childKey2][childKey3][index], ...addFields[key][childKey1][childKey2][childKey3]}; + }); + } else if (childFields4.length > 0) { + childFields4.forEach(childKey4 => { + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4] === undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4] = {}; + } + const childFields5 = addFields[key][childKey1][childKey2][childKey3][childKey4] != null ? Object.keys(addFields[key][childKey1][childKey2][childKey3][childKey4]) : []; + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4] && truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4].length != undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4].forEach((value, index) => { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][index] = {...truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][index], ...addFields[key][childKey1][childKey2][childKey3][childKey4]}; + }); + } else if (childFields5.length > 0) { + childFields5.forEach(childKey5 => { + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5] === undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5] = {}; + } + const childFields6 = addFields[key][childKey1][childKey2][childKey3][childKey4][childKey5] != null ? Object.keys(addFields[key][childKey1][childKey2][childKey3][childKey4][childKey5]) : []; + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5] && truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5].length != undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5].forEach((value, index) => { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][index] = {...truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][index], ...addFields[key][childKey1][childKey2][childKey3][childKey4][childKey5]}; + }); + } else if (childFields6.length > 0) { + childFields6.forEach(childKey6 => { + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6] === undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6] = {}; + } + if (truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6] && truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6].length != undefined) { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5].forEach((value, index) => { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6][index] = {...truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6][index], ...addFields[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6]}; + }); + } else { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6] = addFields[key][childKey1][childKey2][childKey3][childKey4][childKey5][childKey6]; + } + }); + } else { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4][childKey5] = addFields[key][childKey1][childKey2][childKey3][childKey4][childKey5]; + } + }); + } else { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3][childKey4] = addFields[key][childKey1][childKey2][childKey3][childKey4]; + } + }); + } else { + truncatedKeysSaveData[key][childKey1][childKey2][childKey3] = addFields[key][childKey1][childKey2][childKey3]; + } + }); + } else { + truncatedKeysSaveData[key][childKey1][childKey2] = addFields[key][childKey1][childKey2]; + } + }); + } else { //If value is null or if truncatedKeysSaveData[key][childKey1] did not exist + truncatedKeysSaveData[key][childKey1] = addFields[key][childKey1]; + } + }); + } else { //The parent key has not been made yet, so make it and add dictionary values. This is just a precaution because the scenario should be solved during addFields null check + truncatedKeysSaveData[key] = addFields[key]; + } + } + }); + } return truncatedKeysSaveData; } From ec9ccbc46543560a98a2e6ef0695f61334289998 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Fri, 26 Sep 2025 15:35:10 -0700 Subject: [PATCH 048/105] Added method type from interface/removed expired --- portal/cancelPortalFormDataHandler.js | 44 +++++++++++++++++++-------- portal/loadPortalDataHandler.js | 27 ---------------- portal/savePortalFormDataHandler.js | 7 +++-- portal/submitPortalFormDataHandler.js | 3 +- 4 files changed, 38 insertions(+), 43 deletions(-) diff --git a/portal/cancelPortalFormDataHandler.js b/portal/cancelPortalFormDataHandler.js index 88aaed9..78774e2 100644 --- a/portal/cancelPortalFormDataHandler.js +++ b/portal/cancelPortalFormDataHandler.js @@ -30,27 +30,47 @@ async function cancelPortalAction(req, res) { }) || {}; - const apiHost = portalConfig.apiHost; - let expirePath = req.body?.path || (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); + const portalHost = portalConfig.apiHost; + let endpoint = req.body?.path || (portalConfig.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT); + const interfaceMethod = req.body?.type || "POST"; - if (!apiHost || !expirePath) { + const url = portalHost+endpoint; + + if (!portalHost || !endpoint) { return res - .status(500) - .send({ error: getErrorMessage("PORTAL_CONFIG_NOT_FOUND") || "Missing apiHost or expire endpoint in appConfig" }); + .status(400) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") || "Missing portalHost or endpoint path" }); } - const headers = { - 'Content-Type': 'application/json', - ...(req.body?.headers || {}) - }; + const savedJson = { token: tokenId }; + + console.log('CancelForPortalAction ->', { + url, + interfaceMethod, + savedJson + }); - const expired = await expireTokenInPortal(portalConfig, tokenId, headers); + const response = await fetch(url, { + interfaceMethod, + headers: { + "Content-Type": "application/json", + ...buildPortalAuthHeader(portalConfig), + ...(req.body?.headers || {}), + }, + body: JSON.stringify(savedJson), + }); - if (!expired) { - return res.status(502).send({ error: "Portal did not confirm token expiration" }); + console.log("Fetch status:", response.status); + + if (!response.ok) { + const text = await response.text(); + return res + .status(400) + .send({ error: getErrorMessage("ERROR_IN_EXECUTING_ACTION") || `Endpoint error: ${response.status} ${text}` }); } return res.json({ status: 'success', expired: true }); + } catch (err) { console.error('expireNETPortal error:', err); return res diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index 21ddc43..ab583e1 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -30,33 +30,6 @@ async function getParametersFromPortal(portal,token) { } -async function expireTokenInPortal(portal,token) { - //call another api from portal to get the params - - let isTokenExpired = false; - - try { - const urlForExpiringToken = portal.apiHost + (portal.expireTokenEndPoint || process.env.PORTAL_EXPIRE_TOKEN_ENDPOINT);; - console.log("urlForExpiringToken",urlForExpiringToken); - const response = await axios.post(`${urlForExpiringToken}`, - { - token - }, { - headers: { - 'Content-Type': 'application/json', - ...buildPortalAuthHeader(portal), - } - }); - - isTokenExpired = response.data; - } catch (err) { - console.log( 'Failed to contact target app', err.message ); - - return true; - } - return isTokenExpired; -} - async function getSavedFormFromPortal(portal,token) { let parametersForForm = ""; diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index 544dad3..ca97417 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -30,9 +30,10 @@ async function saveForPortalAction(req, res) { const portalHost = portalConfig.apiHost; let endpoint = req.body?.path ||portalConfig.saveEndpoint; + const interfaceMethod = req.body?.type || "POST"; + const url = portalHost+endpoint; - const method = "POST"; if (!portalHost || !endpoint) { return res @@ -45,12 +46,12 @@ async function saveForPortalAction(req, res) { console.log('SaveForPortalAction ->', { url, - method, + interfaceMethod, savedJson }); const response = await fetch(url, { - method, + interfaceMethod, headers: { "Content-Type": "application/json", ...buildPortalAuthHeader(portalConfig), diff --git a/portal/submitPortalFormDataHandler.js b/portal/submitPortalFormDataHandler.js index e7f3d95..50b818c 100644 --- a/portal/submitPortalFormDataHandler.js +++ b/portal/submitPortalFormDataHandler.js @@ -26,6 +26,7 @@ async function submitForPortalAction(req, res) { const interfaceHost = portalConfig.apiHost; let submitPath = req.body?.path ||portalConfig.submitEndpoint; + const interfaceMethod = req.body?.type || "POST"; if (!interfaceHost || !submitPath) { return res @@ -49,7 +50,7 @@ async function submitForPortalAction(req, res) { const submitResp = await fetch(submitUrl.toString(), { - method: 'POST', + method: interfaceMethod, headers, body: JSON.stringify(savePayload) }); From 626151ccf274b3ac1dc9b1eecddef8c9934c1d7b Mon Sep 17 00:00:00 2001 From: David Okulski Date: Fri, 26 Sep 2025 15:39:13 -0700 Subject: [PATCH 049/105] removed module from export --- portal/loadPortalDataHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/loadPortalDataHandler.js b/portal/loadPortalDataHandler.js index ab583e1..0864aea 100644 --- a/portal/loadPortalDataHandler.js +++ b/portal/loadPortalDataHandler.js @@ -54,4 +54,4 @@ async function getSavedFormFromPortal(portal,token) { } -module.exports = {getParametersFromPortal , expireTokenInPortal, getSavedFormFromPortal }; \ No newline at end of file +module.exports = {getParametersFromPortal , getSavedFormFromPortal }; \ No newline at end of file From 3f3316b3b3c2a649db4bbe2b6d59f05cafc35ca1 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Fri, 26 Sep 2025 15:47:34 -0700 Subject: [PATCH 050/105] update method references --- portal/cancelPortalFormDataHandler.js | 6 +++--- portal/savePortalFormDataHandler.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/portal/cancelPortalFormDataHandler.js b/portal/cancelPortalFormDataHandler.js index 78774e2..89fe2f7 100644 --- a/portal/cancelPortalFormDataHandler.js +++ b/portal/cancelPortalFormDataHandler.js @@ -1,7 +1,7 @@ // portal/expirePortalToken.js const appConfig = require('../appConfig.js'); const { getErrorMessage } = require("../errorHandling/errorHandler.js"); -const { expireTokenInPortal } = require('./loadPortalDataHandler.js'); +const buildPortalAuthHeader = require('./authHandler.js'); async function cancelPortalAction(req, res) { const { tokenId} = req.body || {}; @@ -51,7 +51,7 @@ async function cancelPortalAction(req, res) { }); const response = await fetch(url, { - interfaceMethod, + method: interfaceMethod, headers: { "Content-Type": "application/json", ...buildPortalAuthHeader(portalConfig), @@ -70,7 +70,7 @@ async function cancelPortalAction(req, res) { } return res.json({ status: 'success', expired: true }); - + } catch (err) { console.error('expireNETPortal error:', err); return res diff --git a/portal/savePortalFormDataHandler.js b/portal/savePortalFormDataHandler.js index ca97417..f374427 100644 --- a/portal/savePortalFormDataHandler.js +++ b/portal/savePortalFormDataHandler.js @@ -51,7 +51,7 @@ async function saveForPortalAction(req, res) { }); const response = await fetch(url, { - interfaceMethod, + method: interfaceMethod, headers: { "Content-Type": "application/json", ...buildPortalAuthHeader(portalConfig), From 73ee11f0a51d0a6d4f41fc27abadc7296ce1eb3a Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 26 Sep 2025 15:50:37 -0700 Subject: [PATCH 051/105] (ADO-3390) wrapper tag fix --- dictionary/jsonXmlConversion.js | 64 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 9d45315..17e6a40 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -175,46 +175,46 @@ const formExceptions = { "Dependent": { "CareArrangementsList": { "CareArrangements": { - "licensed-group-b650d0a6-2871-433c-9a2b-9e80a2f7c9a8": 4, - "mailing-address-1-003b31e6-6661-4229-b17e-1d1121bb4de0": 4, - "citytown-b8a078b2-4985-4cc6-9de4-48674704cd75": 4, - "postal-code-7cf09061-3472-4a74-802f-76aa8c115aa0": 4, - "comments-4532571d-f24a-4877-bf9e-d69f979b2a08": 4 + "licensed-group-b650d0a6-2871-433c-9a2b-9e80a2f7c9a8": 3, + "mailing-address-1-003b31e6-6661-4229-b17e-1d1121bb4de0": 3, + "citytown-b8a078b2-4985-4cc6-9de4-48674704cd75": 3, + "postal-code-7cf09061-3472-4a74-802f-76aa8c115aa0": 3, + "comments-4532571d-f24a-4877-bf9e-d69f979b2a08": 3 } }, - "first-name-4f1d33c2-cd25-4801-9902-d1d33a0ba010": 2, - "middle-name-9db65275-66a0-4f3d-856b-9cd9ded0f385": 2, - "last-name-dc75eb1e-d57d-4383-a9e6-5e0a86513700": 2, - "date-of-birth-yyyy-mmm-dd-0e756b1c-47c9-482b-82fa-9c681c87ea55": 2, - "radiobuttonlist-d7ada287-f4af-4a1a-ad9f-daa893b8789f": 2, - "radiobuttonlist-bdbd4d0f-daf0-445e-abb0-4eb42f3e15c0": 2, - "licensed-group-b650d0a6-2871-433c-9a2b-9e80a2f7c9a8": 2, - "licensed-family-f14e4cd1-204e-41a7-b1b2-d7b910cc2ee4": 2, - "licensed-preschool-program-15821c42-4a1f-4fc4-98e0-6a0fedc6cf91": 2, - "registered-licence-not-required-455d1970-fe3a-4d7c-9ca0-d365f1177fb1": 2, - "licence-not-required-a0b0868e-c7e0-43d3-b688-d2569108c595": 2, - "in-childs-home-2b8a7f32-e472-4cad-aba5-254b33125844": 2, - "radiobuttonlist-28b592b7-aba8-46dc-8caf-815b8684fd31": 2, - "custody-details-e38e1e1a-4db9-460d-bde3-a9b626419173": 2 + "first-name-4f1d33c2-cd25-4801-9902-d1d33a0ba010": 1, + "middle-name-9db65275-66a0-4f3d-856b-9cd9ded0f385": 1, + "last-name-dc75eb1e-d57d-4383-a9e6-5e0a86513700": 1, + "date-of-birth-yyyy-mmm-dd-0e756b1c-47c9-482b-82fa-9c681c87ea55": 1, + "radiobuttonlist-d7ada287-f4af-4a1a-ad9f-daa893b8789f": 1, + "radiobuttonlist-bdbd4d0f-daf0-445e-abb0-4eb42f3e15c0": 1, + "licensed-group-b650d0a6-2871-433c-9a2b-9e80a2f7c9a8": 1, + "licensed-family-f14e4cd1-204e-41a7-b1b2-d7b910cc2ee4": 1, + "licensed-preschool-program-15821c42-4a1f-4fc4-98e0-6a0fedc6cf91": 1, + "registered-licence-not-required-455d1970-fe3a-4d7c-9ca0-d365f1177fb1": 1, + "licence-not-required-a0b0868e-c7e0-43d3-b688-d2569108c595": 1, + "in-childs-home-2b8a7f32-e472-4cad-aba5-254b33125844": 1, + "radiobuttonlist-28b592b7-aba8-46dc-8caf-815b8684fd31": 1, + "custody-details-e38e1e1a-4db9-460d-bde3-a9b626419173": 1 } } }, { "ListReasonForCare": { "ReasonForCare": { - "name-of-employers-school-training-program-or-dates-looking-for-work-3bb24589-196e-4a3b-bf9d-0b2616efec89": 2, - "start-date-yyyy-mmm-dd-a745b70e-8304-4814-be69-1b232c44beeb": 2, - "end-date-yyyy-mmm-dd-d2df073a-101d-4a68-bf42-1a040ebf7585": 2, - "week-days-andor-week-ends-317392fa-4395-401a-b9ec-fd60930177f1": 2, - "week-end-days-per-week-8baf2547-c051-4fc5-849a-5aa527fb326a": 2, - "week-end-hours-per-day-3afabfee-a210-4465-80e3-788bae3d59d4": 2, - "week-day-days-per-week-3176c7d7-751b-4753-871c-d53bf6128e3e": 2, - "week-day-hours-per-day-00303def-6fad-49d6-b72b-183abef2fbd5": 2, - "regular-schedule-73687eed-2e55-4aa7-821a-deab607da5d3": 2, - "start-time-0187c5ad-c8b0-420b-bded-595d2ecad5bd": 2, - "end-time-d7f384c0-f80f-4aa3-9430-7a86187897df": 2, - "additional-information-or-attach-a-schedule-77a4d493-0468-40a7-8d0e-af9ab9906085": 2, - "travel-time-c1ab5674-a91d-4ab2-b096-213b07bdb69d": 2 + "name-of-employers-school-training-program-or-dates-looking-for-work-3bb24589-196e-4a3b-bf9d-0b2616efec89": 1, + "start-date-yyyy-mmm-dd-a745b70e-8304-4814-be69-1b232c44beeb": 1, + "end-date-yyyy-mmm-dd-d2df073a-101d-4a68-bf42-1a040ebf7585": 1, + "week-days-andor-week-ends-317392fa-4395-401a-b9ec-fd60930177f1": 1, + "week-end-days-per-week-8baf2547-c051-4fc5-849a-5aa527fb326a": 1, + "week-end-hours-per-day-3afabfee-a210-4465-80e3-788bae3d59d4": 1, + "week-day-days-per-week-3176c7d7-751b-4753-871c-d53bf6128e3e": 1, + "week-day-hours-per-day-00303def-6fad-49d6-b72b-183abef2fbd5": 1, + "regular-schedule-73687eed-2e55-4aa7-821a-deab607da5d3": 1, + "start-time-0187c5ad-c8b0-420b-bded-595d2ecad5bd": 1, + "end-time-d7f384c0-f80f-4aa3-9430-7a86187897df": 1, + "additional-information-or-attach-a-schedule-77a4d493-0468-40a7-8d0e-af9ab9906085": 1, + "travel-time-c1ab5674-a91d-4ab2-b096-213b07bdb69d": 1 } } } From aae1d842cc6620ee82b160933f6639bcb8e9c67c Mon Sep 17 00:00:00 2001 From: bzimonja Date: Fri, 26 Sep 2025 16:24:58 -0700 Subject: [PATCH 052/105] Add parameters serializer method --- databindingsHandler.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/databindingsHandler.js b/databindingsHandler.js index 5f35f04..a43298d 100644 --- a/databindingsHandler.js +++ b/databindingsHandler.js @@ -5,7 +5,29 @@ const axios = require("axios"); const { getUsername, isUsernameValid } = require('./usernameHandler.js'); const { parse, format } = require("date-fns"); - +/** + * Custom Axios params serializer that encodes whitespace as %20 + */ +function customParamsSerializer(params) { + const queryParts = []; + for (const key in params) { + if (!Object.prototype.hasOwnProperty.call(params, key)) continue; + + const value = params[key]; + + if (value === null || typeof value === 'undefined') continue; + + // Handle arrays by repeating the key for each value + if (Array.isArray(value)) { + value.forEach((val) => { + queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`); + }); + } else { + queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); + } + } + return queryParts.join('&'); +} async function populateDatabindings(formJson, params) { //start code here try { @@ -194,12 +216,12 @@ async function readJsonFormApi(datasource, pathParams) { } if (type.toUpperCase() === 'GET') { // For GET requests, add params directly in axios config - response = await axios.get(url, { params: pathParams, headers } + response = await axios.get(url, { params: pathParams, headers, paramsSerializer: customParamsSerializer} ); } else if (type.toUpperCase() === 'POST') { // For POST requests, pass params in the body if applicable const bodyForPost = buildBodyWithParams(body, pathParams); - response = await axios.post(url, bodyForPost, { params: pathParams, headers }); + response = await axios.post(url, bodyForPost, { params: pathParams, headers, paramsSerializer: customParamsSerializer}); } // Store response data From f243602e6a2bc76ee676d5eb9841b6090cbd51dd Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 29 Sep 2025 12:58:18 -0700 Subject: [PATCH 053/105] deploy to tools namespace --- .github/workflows/build-and-deploy.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 8bfad67..4cd221b 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -74,6 +74,30 @@ jobs: - name: Trigger OpenShift Rollout run: | oc rollout restart deployment/communication-layer + + # Deploy to Tools only if branch = dev + - name: Authenticate with OpenShift (tools env) + if: github.ref == 'refs/heads/dev' + uses: redhat-actions/oc-login@v1 + with: + openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }} + namespace: ${{ secrets.OPENSHIFT_TOOLS_NAMESPACE }} + openshift_token: ${{ secrets.OPENSHIFT_TOOLS_TOKEN }} + insecure_skip_tls_verify: true + + - name: Deploy with Helm (tools env) + if: github.ref == 'refs/heads/dev' + run: | + helm upgrade --install communication-layer ./helm \ + --namespace "${{ secrets.OPENSHIFT_TOOLS_NAMESPACE }}" \ + --set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \ + --set image.tag=${{ needs.build_and_push.outputs.image_tag }} + + - name: Trigger OpenShift Rollout (tools env) + if: github.ref == 'refs/heads/dev' + run: | + oc rollout restart deployment/communication-layer \ + -n "${{ secrets.OPENSHIFT_TOOLS_NAMESPACE }}" deploy_to_portal: needs: build_and_push From 43351909a1d52a22d0d20241d1dac827092a1ad6 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 29 Sep 2025 15:45:54 -0700 Subject: [PATCH 054/105] Updated action --- .github/workflows/build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 4cd221b..2c76cc4 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -6,6 +6,7 @@ on: - main - dev - test + workflow_dispatch: env: REGISTRY: ghcr.io From 087da0bdc34059b33defa7154615aba1e73f7246 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Wed, 1 Oct 2025 11:31:23 -0700 Subject: [PATCH 055/105] Add fields has been applied in dictionary for CF0640 and CF1070 --- dictionary/jsonXmlConversion.js | 290 ++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 17e6a40..e7528b9 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -72,6 +72,56 @@ const formExceptions = { } } }, + "CF0640" : { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "addFields": { + "ListOfPUBLead": { + "PUBLead": { + "LeadID": null, + "Name": null, + "ListOfICMCPScreeningAssessment": { + "ICMCPScreeningAssessment": { + "DateApproved": null, + "GeneralNeglectC": null, + "GeneralNeglectFlag": null, + "Neglect": null, + "OverrideNPRA": null, + "OverrideNPRB": null, + "OverridePRA": null, + "OverrideReadOnlyCalc": null, + "Phone": null, + "PhysicalAbuseF": null, + "Position": null, + "ResponseNameExplanation": null, + "SevereNeglectFlag": null, + "SexualAbuseC": null, + "TLSummary": null, + "TeamLeaderPositionId": null + } + } + } + }, + "FormInstanceId": null, + "SRNum": null, + "CaseId": null, + "IncidentNo": null, + "SRId": null, + "Template": null, + "TemplateLocation": null + }, + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, "CF0925": { "rootName": "form1", "subRoots": [], @@ -166,6 +216,246 @@ const formExceptions = { } } }, + "CF1070": { + "rootName": "ListOfDtFormInstanceLw", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "addFields": { + "Created": null, + "FormInstanceId": null, + "CreatedBy": null, + "SRNum": null, + "BenefitPlanId": null, + "CaseId": null, + "Category": null, + "DocFileName": null, + "DocFileSize": null, + "DocFileSrcType": null, + "FinalFlag": null, + "MISCaseNum": null, + "CaseNum": null, + "SRId": null, + "SubCategory": null, + "Template": null, + "RenderFlatFlag": null, + "Bool01": null, + "Bool02": null, + "Bool03": null, + "Bool04": null, + "Bool05": null, + "TemplateLocation": null, + "Date01": null, + "Date02": null, + "Date03": null, + "Date04": null, + "Date05": null, + "String01": null, + "String02": null, + "String03": null, + "String04": null, + "String05": null, + "String06": null, + "String07": null, + "String08": null, + "String09": null, + "String10": null, + "String11": null, + "String12": null, + "AuxName": null, + "AuxType": null, + "Number01": null, + "Number02": null, + "Number03": null, + "Number04": null, + "Number05": null, + "Bool06": null, + "Bool07": null, + "Bool08": null, + "Bool09": null, + "Bool10": null, + "Date06": null, + "Date07": null, + "Bool11": null, + "Bool12": null, + "Bool13": null, + "Bool14": null, + "Bool15": null, + "Bool16": null, + "Bool17": null, + "Bool18": null, + "Bool19": null, + "Bool20": null, + "Bool21": null, + "Bool22": null, + "Bool23": null, + "Bool24": null, + "Bool25": null, + "Bool26": null, + "Bool27": null, + "Bool28": null, + "Bool29": null, + "Bool30": null, + "Bool31": null, + "Bool32": null, + "Bool33": null, + "Bool34": null, + "Bool35": null, + "Bool36": null, + "Bool37": null, + "Bool38": null, + "Bool39": null, + "Bool40": null, + "Bool41": null, + "Bool42": null, + "Bool43": null, + "Bool44": null, + "Bool45": null, + "Bool46": null, + "Bool47": null, + "Bool48": null, + "Bool49": null, + "Bool50": null, + "String13": null, + "String14": null, + "String15": null, + "String16": null, + "String17": null, + "String18": null, + "String19": null, + "String20": null, + "String21": null, + "String22": null, + "String23": null, + "String24": null, + "String25": null, + "String26": null, + "String27": null, + "String28": null, + "String29": null, + "String30": null, + "String31": null, + "String32": null, + "String33": null, + "String34": null, + "String35": null, + "String36": null, + "String37": null, + "String38": null, + "String39": null, + "String40": null, + "String41": null, + "String42": null, + "String43": null, + "String44": null, + "String45": null, + "String46": null, + "String47": null, + "String48": null, + "String49": null, + "String50": null, + "String51": null, + "String52": null, + "String53": null, + "String54": null, + "String55": null, + "String56": null, + "String57": null, + "String58": null, + "String59": null, + "String60": null, + "String61": null, + "String62": null, + "String63": null, + "String64": null, + "String65": null, + "ListOfEmployee": { + "Employee": { + "EMailAddr": null, + "Emp": null, + "Fax": null, + "FirstName": null, + "FullName": null, + "FullNameMiddle": null, + "JobTitle": null, + "LastName": null, + "MiddleName": null, + "MiddleNameInitial": null, + "WorkPhoneNumber": null + } + }, + "ListOfTeamLeads": { + "TeamLead": { + "EMailAddr": null, + "Emp": null, + "Fax": null, + "FirstName": null, + "FullName": null, + "FullNameMiddle": null, + "JobTitle": null, + "LastName": null, + "MiddleName": null, + "MiddleNameInitial": null, + "WorkPhoneNumber": null + } + }, + "ListOfOffice": { + "Office": { + "OfficeName": null, + "OfficeRegion": null, + "OfficeAddressLine1": null, + "OfficeAddressLine2": null, + "OfficeAddressLine3": null, + "OfficeAddressCity": null, + "OfficeAddressProvince": null, + "OfficeAddressCountry": null, + "OfficeAddressPostalCode": null, + "OfficeAddressUnit": null, + "OfficePhone": null, + "OfficeFax": null, + "OfficeAddressComplete": null, + "OfficeAddressCompleteCity": null + } + }, + "ListOfPubHlsIncident": { + "PubHlsIncident": { + "ListOfIcmIncidentSafetyAssessmentBc": { + "IcmIncidentSafetyAssessmentBc": { + "ApprovedtoFinalizeDate": null, + "FinalizedBy": null, + "FinalizedDate": null, + "SafetyFactorA": null, + "AssessmentId": null + } + }, + "IncidentCity": null, + "IncidentLocation": null, + "IncidentPostalCode": null, + "Location": null, + "Name": null, + "ListOfIncidentContactBelow18": { + "IncidentContactBelow18": { + "ContactFullName": null, + "ContactFullNameMiddle": null, + "MiddleNameInitial": null, + "MiddleName": null, + "Number01": null, + "Number03": null + } + } + } + } + }, + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, "CF2900": { "rootName": "ListOfDtFormInstanceLw", "subRoots": [], From c2902ee7f3db58bf54c2147d8807871484d1c686 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 3 Oct 2025 08:13:36 -0700 Subject: [PATCH 056/105] Updates to Dictionary values: CF0633, CF0925, CF2900, HR0080 --- dictionary/jsonXmlConversion.js | 333 +++++++++++++++++++++++++++++++- 1 file changed, 326 insertions(+), 7 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index e7528b9..c2f7880 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -62,7 +62,313 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], - "addFields": {}, + "addFields": { + "ListOfUserEnteredData9": { + "UserEnteredData9": { + "String35": null, + "String34": null, + "String33": null, + "Date1": null + }, + "ListOfUserEnteredData8": { + "UserEnteredData8": { + "String42": null, + "String41": null + } + }, + "ListOfUserEnteredData12": { + "UserEnteredData12": { + "String42": null, + "String41": null, + "String36": null, + "Date9": null + } + }, + "HeaderERIQ": null, + "Created": null, + "FormInstanceId": null, + "CreatedBy": null, + "CaseContactERIQ": null, + "SRNum": null, + "BenefitPlanId": null, + "CaseId": null, + "Category": null, + "ContactId": null, + "DocFileName": null, + "DocFileSize": null, + "DocFileSrcType": null, + "FinalFlag": null, + "ICPId": null, + "IncidentNo": null, + "MISCaseNum": null, + "CaseNum": null, + "SRId": null, + "SubCategory": null, + "Template": null, + "RenderFlatFlag": null, + "Bool01": null, + "Bool02": null, + "Bool03": null, + "Bool04": null, + "Bool05": null, + "TemplateLocation": null, + "Date01": null, + "Date03": null, + "Date04": null, + "Date05": null, + "String01": null, + "String02": null, + "String03": null, + "String04": null, + "String05": null, + "String06": null, + "String07": null, + "String08": null, + "String09": null, + "String10": null, + "String11": null, + "AuxName": null, + "AuxType": null, + "Number01": null, + "Number02": null, + "Number03": null, + "Number04": null, + "Number05": null, + "Bool06": null, + "Bool07": null, + "Bool08": null, + "Bool09": null, + "Bool010": null, + "Date06": null, + "Date07": null, + "Bool11": null, + "Bool12": null, + "Bool13": null, + "Bool14": null, + "Bool15": null, + "Bool16": null, + "Bool17": null, + "Bool18": null, + "Bool19": null, + "Bool20": null, + "Bool21": null, + "Bool22": null, + "Bool23": null, + "Bool24": null, + "Bool25": null, + "Bool26": null, + "Bool27": null, + "Bool28": null, + "Bool29": null, + "Bool30": null, + "Bool31": null, + "Bool32": null, + "Bool33": null, + "Bool34": null, + "Bool35": null, + "Bool36": null, + "Bool37": null, + "Bool38": null, + "Bool39": null, + "Bool40": null, + "Bool41": null, + "Bool42": null, + "Bool43": null, + "Bool44": null, + "Bool45": null, + "Bool46": null, + "Bool47": null, + "Bool48": null, + "Bool49": null, + "Bool50": null, + "String14": null, + "String15": null, + "String16": null, + "String17": null, + "String18": null, + "String19": null, + "String20": null, + "String21": null, + "String22": null, + "String23": null, + "String24": null, + "String25": null, + "String26": null, + "String27": null, + "String28": null, + "String29": null, + "String30": null, + "String31": null, + "String32": null, + "String33": null, + "String34": null, + "String35": null, + "String36": null, + "String37": null, + "String38": null, + "String39": null, + "String40": null, + "String41": null, + "String42": null, + "String43": null, + "String44": null, + "String45": null, + "String46": null, + "String47": null, + "String48": null, + "String49": null, + "String50": null, + "String51": null, + "String52": null, + "String53": null, + "String54": null, + "String55": null, + "String56": null, + "String57": null, + "String58": null, + "String59": null, + "String60": null, + "String61": null, + "String62": null, + "String63": null, + "String64": null, + "String65": null, + "ListOfContact": { + "Contact": { + "BirthDate": null, + "CaseRelTypeCode": null, + "ClientIDNumber": null, + "EmailAddress": null, + "FirstName": null, + "FullName": null, + "FullNameMiddle": null, + "LastName": null, + "MF": null, + "MiddleName": null, + "MiddleNameInitial": null + } + }, + "ListOfCase": { + "Case": { + "CaseNum": null, + "Name": null, + "Status": null, + "ListOfContactAbove18": { + "ContactAbove18": { + "ContactFullName": null, + "ContactFullNameMiddle": null, + "AddressLine2": null, + "Province": null, + "AddressLine1": null, + "City": null, + "AddressLine3": null, + "PostalCode": null, + "ClientIDNumber": null, + "MiddleNameInitial": null, + "PersonUId": null + } + }, + "ListOfContactBelow18": { + "ContactBelow18": { + "AddressLine1": null, + "AddressLine2": null, + "City": null, + "PostalCode": null, + "AddressLine3": null, + "ContactFullName": null, + "Province": null, + "ContactFullNameMiddle": null, + "Gender": null, + "ContactId": null, + "Age": null, + "BirthDate": null, + "FirstName": null, + "Role": null, + "ClientIDNumber": null, + "MiddleName": null, + "Relationship": null, + "MiddleNameInital": null, + "LastName": null, + "PersonUId": null, + "Date01": null, + "PersonIdICM": null, + "Number02": null + } + } + } + }, + "ListOfEmployee": { + "Employee": { + "EMailAddr": null, + "Emp": null, + "Fax": null, + "FirstName": null, + "FullName": null, + "FullNameMiddle": null, + "JobTitle": null, + "LastName": null, + "MiddleName": null, + "MiddleNameInitial": null, + "WorkPhoneNumber": null + } + }, + "ListOfDtFormInstanceChildData": { + "DtFormInstanceChildData": { + "Date01": null, + "Date02": null, + "Number01": null, + "Number02": null, + "Number03": null, + "Number04": null, + "String01": null, + "String02": null + } + }, + "ListOfDtFormInstanceChildData2": { + "DtFormInstanceChildData2": { + "Bool01": null, + "Date01": null, + "Number05": null, + "String01": null, + "String02": null + } + }, + "ListOfDtFormInstanceChildData3": { + "DtFormInstanceChildData3": { + "Bool03": null, + "String01": null, + "String02": null, + "String03": null, + "String04": null, + "String05": null, + "String07": null, + "String08": null, + "String09": null + } + }, + "ListOfDtFormInstanceChildData4": { + "DtFormInstanceChildData4": { + "Bool01": null + } + }, + "ListOfDtFormInstanceChildData11": { + "DtFormInstanceChildData11": { + "Bool01": null, + "Date01": null, + "Date02": null, + "String01": null, + "String02": null + } + }, + "ListOfDtFormInstanceChildData5": { + "DtFormInstanceChildData5": { + "String12": null, + "String13": null, + "String14": null + } + } + } + }, "versions": { "1": { "omitFields": [] @@ -190,7 +496,12 @@ const formExceptions = { ], "allowCheckboxWithNoChange": [], "omitFields": [], - "addFields": {}, + "addFields": { + "SRSubType": null, + "ParentGuardian": { + "ApplicantMailingAddress": null + } + }, "versions" : { "1" :{ "omitFields": [] @@ -466,10 +777,6 @@ const formExceptions = { "CareArrangementsList": { "CareArrangements": { "licensed-group-b650d0a6-2871-433c-9a2b-9e80a2f7c9a8": 3, - "mailing-address-1-003b31e6-6661-4229-b17e-1d1121bb4de0": 3, - "citytown-b8a078b2-4985-4cc6-9de4-48674704cd75": 3, - "postal-code-7cf09061-3472-4a74-802f-76aa8c115aa0": 3, - "comments-4532571d-f24a-4877-bf9e-d69f979b2a08": 3 } }, "first-name-4f1d33c2-cd25-4801-9902-d1d33a0ba010": 1, @@ -526,6 +833,11 @@ const formExceptions = { "Dependent": { "CareArrangementsList": { "CareArrangements": { + "LicenceNumber": null, + "MailingAddress": null, + "MailingCity": null, + "MailingPostalCode": null, + "CCAProviderComments": null, "FacilityID": null, "CCAProviderName": null, "CCAProviderDaytimePhone": null, @@ -622,7 +934,14 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], - "addFields": {}, + "addFields": { + "OfficeCity": null, + "FormInstanceId": null, + "Sanctioned": null, + "CanadianCitizen": null, + "ApplicantTwoYearIndependence": null, + "ApplicantTwoYearRationale": null + }, "versions": { "1": { "omitFields": [] From d85dc1e4557f0ad7d70ae4187ad2e9a5c6ee8b74 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 3 Oct 2025 11:29:01 -0700 Subject: [PATCH 057/105] Dictionary update HR0080 --- dictionary/jsonXmlConversion.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index c2f7880..b8f19e9 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -940,7 +940,9 @@ const formExceptions = { "Sanctioned": null, "CanadianCitizen": null, "ApplicantTwoYearIndependence": null, - "ApplicantTwoYearRationale": null + "ApplicantTwoYearRationale": null, + "SpouseTwoYearIndependence": null, + "SpouseTwoYearRationale": null }, "versions": { "1": { From 01a77221fcdbc44ac3841e1c0f433524e232c053 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Fri, 3 Oct 2025 13:45:27 -0700 Subject: [PATCH 058/105] (ADO-3428) remove text-info fields from XML creation --- saveICMdataHandler.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 2946f46..d512b26 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -157,7 +157,7 @@ async function saveICMdata(req, res) { // dateItemsId : This will contain all of the IDs of the date fields // checkboxItemsId : This will contain all of the IDs of the checkbox fields - const { dateItemsId, checkboxItemsId } = getFormIds(formDefinitionItems); + const { dateItemsId, checkboxItemsId, textInfoFields } = getFormIds(formDefinitionItems); const dictionary = formExceptions; const formId = JSON.parse(savedFormParam)["form_definition"]["form_id"]; // Get the form ID @@ -178,7 +178,7 @@ async function saveICMdata(req, res) { } // The updated JSON values required for XML creation - const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields, addFields, kilnVersion); + const truncatedKeysSaveData = fixJSONValuesForXML(saveData, {}, toWrapIds, dateItemsId, checkboxItemsId, textInfoFields, noCheckboxChange, omitFields, addFields, kilnVersion); let builder; // This will be for building the XML if (isFormException) { // If any forms with the correct version (TODO) have been listed as exceptions, then proceed with their form exceptions @@ -411,19 +411,23 @@ async function clearICMLockedFlag(req, res) { * @params formDefinitionItems = JSON.parse(savedFormParam)["form_definition"]["data"]["items"] * @returns dateItemsId : This will contain all of the IDs of the date fields * @returns checkboxItemsId : This will contain all of the IDs of the checkbox fields + * @returns textInfoFields : This will contain all of the IDs of the text-info fields */ function getFormIds (formDefinitionItems) { let dateItemsId = []; let checkboxItemsId = []; + let textInfoFields = []; formDefinitionItems.forEach(item => { // Add the field types found in this loop into their specific item id arrays if (item.containerItems) { // Check for fields in containers (currently, there can be up to 5 container levels) item.containerItems.forEach(subItem => { if (subItem.type === "date") dateItemsId.push(subItem.id); else if (subItem.type === "checkbox") checkboxItemsId.push(subItem.id); + else if (subItem.type === "text-info") textInfoFields.push(subItem.id); else if (subItem.type === "container") { - const {dateItemsId: recursiveDateItemIds, checkboxItemsId: recursiveCheckboxItemIds} = getFormIds([subItem]); + const {dateItemsId: recursiveDateItemIds, checkboxItemsId: recursiveCheckboxItemIds, textInfoFields: recursiveTextInfoFields} = getFormIds([subItem]); dateItemsId = [...dateItemsId, ...recursiveDateItemIds]; checkboxItemsId = [...checkboxItemsId, ...recursiveCheckboxItemIds]; + textInfoFields = [ ...textInfoFields, ...recursiveTextInfoFields]; } }); } else if (item.groupItems){ // Check for fields in groups @@ -431,11 +435,16 @@ function getFormIds (formDefinitionItems) { subItem.fields.forEach(childItemData => { // Group's fields is where the fields will be in if (childItemData.type === "date") dateItemsId.push(childItemData.id); else if (childItemData.type === "checkbox") checkboxItemsId.push(childItemData.id); + else if (childItemData.type === "text-info") textInfoFields.push(childItemData.id); }); }); + } else { + if (item.type === "date") dateItemsId.push(item.id); + else if (item.type === "checkbox") checkboxItemsId.push(item.id); + else if (item.type === "text-info") textInfoFields.push(item.id); } }); - return { dateItemsId, checkboxItemsId }; + return { dateItemsId, checkboxItemsId, textInfoFields }; } /** @@ -553,6 +562,7 @@ function multilevelWrappers(truncatedKeysSaveData, toWrapIds, dataToWrap, oldKey * @param toWrapIds : list of key-string pairs where the keys are UUIDs and the strings are what wrapper tag they need to go under. * @param dateItemsId : list of date fields * @param checkboxItemsId : list of checkbox fields + * @param textInfoFields : list of text-info fields. These will be omitted from adding to the XML * @param noCheckboxChange : list of checkbox UUIDs that should not have their value changed * @param omitFields : list of field UUIDS that should not be included in the XML * @param addFields : list of empty fields to add for form to succeed in saving as XML @@ -560,9 +570,9 @@ function multilevelWrappers(truncatedKeysSaveData, toWrapIds, dataToWrap, oldKey * @returns truncatedKeysSaveData : a list of key-object pairs */ -function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, noCheckboxChange, omitFields, addFields, kilnVersion) { +function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateItemsId, checkboxItemsId, textInfoFields, noCheckboxChange, omitFields, addFields, kilnVersion) { for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) - if (!omitFields.includes(oldKey)) { + if (!omitFields.includes(oldKey) && !textInfoFields.includes(oldKey)) { const stringLength = oldKey.length; const newKey = oldKey.substring(0, stringLength-28); if (Array.isArray(saveData[oldKey]) > 0 && Object.keys(saveData[oldKey]).length > 0) { //This trunicates child/dependant objects @@ -573,7 +583,7 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt if (kilnVersion === 1) { // Kiln V1 const childStringLength = oldChildKey.length; const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); - if (!omitFields.includes(oldChildKey.substring(stringLength+3, childStringLength))) { + if (!omitFields.includes(oldChildKey.substring(stringLength+3, childStringLength)) && !textInfoFields.includes(oldChildKey.substring(stringLength+3, childStringLength))) { if (dateItemsId.includes(oldChildKey.substring(stringLength+3, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); if (newDateFormat === "-1") { @@ -590,7 +600,7 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt else { // Kiln V2 const childStringLength = oldChildKey.length; const newChildKey = oldChildKey.substring(0, childStringLength-28); - if (!omitFields.includes(oldChildKey.substring(0, childStringLength))) { + if (!omitFields.includes(oldChildKey.substring(0, childStringLength)) && !textInfoFields.includes(oldChildKey.substring(0, childStringLength))) { if (dateItemsId.includes(oldChildKey.substring(0, childStringLength))) { // If child data is in a date field, change date format from YYYY-MM-DD to MM/DD/YYYY const newDateFormat = toICMFormat(saveData[oldKey][i][oldChildKey]); if (newDateFormat === "-1") { From 1b84352dcd62f4496d1c22b2f65b41a11338e697 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Tue, 7 Oct 2025 14:29:46 -0700 Subject: [PATCH 059/105] Updates to CF2900 and CF0633 --- dictionary/jsonXmlConversion.js | 611 ++++++++++++++++---------------- 1 file changed, 315 insertions(+), 296 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index b8f19e9..e3045e6 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -59,7 +59,20 @@ const formExceptions = { "CF0633": { "rootName": "ListOfDtFormInstanceLw", "subRoots": ["FormInstance"], - "wrapperTags": [], + "wrapperTags": [ + { + "ListOfTeamLeads": { + "TeamLead": { + "team-leader-name-25e284ba-5eaf-4b67-afb0-9d05e7214669": 1 + } + }, + "ListOfOffice": { + "Office": { + "office-code-a7c2501c-d181-4b1a-a44e-1036e4f1f77b": 1 + } + } + } + ], "allowCheckboxWithNoChange": [], "omitFields": [], "addFields": { @@ -69,303 +82,303 @@ const formExceptions = { "String34": null, "String33": null, "Date1": null - }, - "ListOfUserEnteredData8": { - "UserEnteredData8": { - "String42": null, - "String41": null - } - }, - "ListOfUserEnteredData12": { - "UserEnteredData12": { - "String42": null, - "String41": null, - "String36": null, - "Date9": null - } - }, - "HeaderERIQ": null, - "Created": null, - "FormInstanceId": null, - "CreatedBy": null, - "CaseContactERIQ": null, - "SRNum": null, - "BenefitPlanId": null, - "CaseId": null, - "Category": null, - "ContactId": null, - "DocFileName": null, - "DocFileSize": null, - "DocFileSrcType": null, - "FinalFlag": null, - "ICPId": null, - "IncidentNo": null, - "MISCaseNum": null, - "CaseNum": null, - "SRId": null, - "SubCategory": null, - "Template": null, - "RenderFlatFlag": null, - "Bool01": null, - "Bool02": null, - "Bool03": null, - "Bool04": null, - "Bool05": null, - "TemplateLocation": null, - "Date01": null, - "Date03": null, - "Date04": null, - "Date05": null, - "String01": null, - "String02": null, - "String03": null, - "String04": null, - "String05": null, - "String06": null, - "String07": null, - "String08": null, - "String09": null, - "String10": null, - "String11": null, - "AuxName": null, - "AuxType": null, - "Number01": null, - "Number02": null, - "Number03": null, - "Number04": null, - "Number05": null, - "Bool06": null, - "Bool07": null, - "Bool08": null, - "Bool09": null, - "Bool010": null, - "Date06": null, - "Date07": null, - "Bool11": null, - "Bool12": null, - "Bool13": null, - "Bool14": null, - "Bool15": null, - "Bool16": null, - "Bool17": null, - "Bool18": null, - "Bool19": null, - "Bool20": null, - "Bool21": null, - "Bool22": null, - "Bool23": null, - "Bool24": null, - "Bool25": null, - "Bool26": null, - "Bool27": null, - "Bool28": null, - "Bool29": null, - "Bool30": null, - "Bool31": null, - "Bool32": null, - "Bool33": null, - "Bool34": null, - "Bool35": null, - "Bool36": null, - "Bool37": null, - "Bool38": null, - "Bool39": null, - "Bool40": null, - "Bool41": null, - "Bool42": null, - "Bool43": null, - "Bool44": null, - "Bool45": null, - "Bool46": null, - "Bool47": null, - "Bool48": null, - "Bool49": null, - "Bool50": null, - "String14": null, - "String15": null, - "String16": null, - "String17": null, - "String18": null, - "String19": null, - "String20": null, - "String21": null, - "String22": null, - "String23": null, - "String24": null, - "String25": null, - "String26": null, - "String27": null, - "String28": null, - "String29": null, - "String30": null, - "String31": null, - "String32": null, - "String33": null, - "String34": null, - "String35": null, - "String36": null, - "String37": null, - "String38": null, - "String39": null, - "String40": null, - "String41": null, - "String42": null, - "String43": null, - "String44": null, - "String45": null, - "String46": null, - "String47": null, - "String48": null, - "String49": null, - "String50": null, - "String51": null, - "String52": null, - "String53": null, - "String54": null, - "String55": null, - "String56": null, - "String57": null, - "String58": null, - "String59": null, - "String60": null, - "String61": null, - "String62": null, - "String63": null, - "String64": null, - "String65": null, - "ListOfContact": { - "Contact": { - "BirthDate": null, - "CaseRelTypeCode": null, - "ClientIDNumber": null, - "EmailAddress": null, - "FirstName": null, - "FullName": null, - "FullNameMiddle": null, - "LastName": null, - "MF": null, - "MiddleName": null, - "MiddleNameInitial": null - } - }, - "ListOfCase": { - "Case": { - "CaseNum": null, - "Name": null, - "Status": null, - "ListOfContactAbove18": { - "ContactAbove18": { - "ContactFullName": null, - "ContactFullNameMiddle": null, - "AddressLine2": null, - "Province": null, - "AddressLine1": null, - "City": null, - "AddressLine3": null, - "PostalCode": null, - "ClientIDNumber": null, - "MiddleNameInitial": null, - "PersonUId": null - } - }, - "ListOfContactBelow18": { - "ContactBelow18": { - "AddressLine1": null, - "AddressLine2": null, - "City": null, - "PostalCode": null, - "AddressLine3": null, - "ContactFullName": null, - "Province": null, - "ContactFullNameMiddle": null, - "Gender": null, - "ContactId": null, - "Age": null, - "BirthDate": null, - "FirstName": null, - "Role": null, - "ClientIDNumber": null, - "MiddleName": null, - "Relationship": null, - "MiddleNameInital": null, - "LastName": null, - "PersonUId": null, - "Date01": null, - "PersonIdICM": null, - "Number02": null - } + } + }, + "ListOfUserEnteredData8": { + "UserEnteredData8": { + "String42": null, + "String41": null + } + }, + "ListOfUserEnteredData12": { + "UserEnteredData12": { + "String42": null, + "String41": null, + "String36": null, + "Date9": null + } + }, + "HeaderERIQ": null, + "Created": null, + "FormInstanceId": null, + "CreatedBy": null, + "CaseContactERIQ": null, + "SRNum": null, + "BenefitPlanId": null, + "CaseId": null, + "Category": null, + "ContactId": null, + "DocFileName": null, + "DocFileSize": null, + "DocFileSrcType": null, + "FinalFlag": null, + "ICPId": null, + "IncidentNo": null, + "MISCaseNum": null, + "CaseNum": null, + "SRId": null, + "SubCategory": null, + "Template": null, + "RenderFlatFlag": null, + "Bool01": null, + "Bool02": null, + "Bool03": null, + "Bool04": null, + "Bool05": null, + "TemplateLocation": null, + "Date01": null, + "Date03": null, + "Date04": null, + "Date05": null, + "String01": null, + "String02": null, + "String03": null, + "String04": null, + "String05": null, + "String06": null, + "String07": null, + "String08": null, + "String09": null, + "String10": null, + "String11": null, + "AuxName": null, + "AuxType": null, + "Number01": null, + "Number02": null, + "Number03": null, + "Number04": null, + "Number05": null, + "Bool06": null, + "Bool07": null, + "Bool08": null, + "Bool09": null, + "Bool010": null, + "Date06": null, + "Date07": null, + "Bool11": null, + "Bool12": null, + "Bool13": null, + "Bool14": null, + "Bool15": null, + "Bool16": null, + "Bool17": null, + "Bool18": null, + "Bool19": null, + "Bool20": null, + "Bool21": null, + "Bool22": null, + "Bool23": null, + "Bool24": null, + "Bool25": null, + "Bool26": null, + "Bool27": null, + "Bool28": null, + "Bool29": null, + "Bool30": null, + "Bool31": null, + "Bool32": null, + "Bool33": null, + "Bool34": null, + "Bool35": null, + "Bool36": null, + "Bool37": null, + "Bool38": null, + "Bool39": null, + "Bool40": null, + "Bool41": null, + "Bool42": null, + "Bool43": null, + "Bool44": null, + "Bool45": null, + "Bool46": null, + "Bool47": null, + "Bool48": null, + "Bool49": null, + "Bool50": null, + "String14": null, + "String15": null, + "String16": null, + "String17": null, + "String18": null, + "String19": null, + "String20": null, + "String21": null, + "String22": null, + "String23": null, + "String24": null, + "String25": null, + "String26": null, + "String27": null, + "String28": null, + "String29": null, + "String30": null, + "String31": null, + "String32": null, + "String33": null, + "String34": null, + "String35": null, + "String36": null, + "String37": null, + "String38": null, + "String39": null, + "String40": null, + "String41": null, + "String42": null, + "String43": null, + "String44": null, + "String45": null, + "String46": null, + "String47": null, + "String48": null, + "String49": null, + "String50": null, + "String51": null, + "String52": null, + "String53": null, + "String54": null, + "String55": null, + "String56": null, + "String57": null, + "String58": null, + "String59": null, + "String60": null, + "String61": null, + "String62": null, + "String63": null, + "String64": null, + "String65": null, + "ListOfContact": { + "Contact": { + "BirthDate": null, + "CaseRelTypeCode": null, + "ClientIDNumber": null, + "EmailAddress": null, + "FirstName": null, + "FullName": null, + "FullNameMiddle": null, + "LastName": null, + "MF": null, + "MiddleName": null, + "MiddleNameInitial": null + } + }, + "ListOfCase": { + "Case": { + "CaseNum": null, + "Name": null, + "Status": null, + "ListOfContactAbove18": { + "ContactAbove18": { + "ContactFullName": null, + "ContactFullNameMiddle": null, + "AddressLine2": null, + "Province": null, + "AddressLine1": null, + "City": null, + "AddressLine3": null, + "PostalCode": null, + "ClientIDNumber": null, + "MiddleNameInitial": null, + "PersonUId": null + } + }, + "ListOfContactBelow18": { + "ContactBelow18": { + "AddressLine1": null, + "AddressLine2": null, + "City": null, + "PostalCode": null, + "AddressLine3": null, + "ContactFullName": null, + "Province": null, + "ContactFullNameMiddle": null, + "Gender": null, + "ContactId": null, + "Age": null, + "BirthDate": null, + "FirstName": null, + "Role": null, + "ClientIDNumber": null, + "MiddleName": null, + "Relationship": null, + "MiddleNameInital": null, + "LastName": null, + "PersonUId": null, + "Date01": null, + "PersonIdICM": null, + "Number02": null } } - }, - "ListOfEmployee": { - "Employee": { - "EMailAddr": null, - "Emp": null, - "Fax": null, - "FirstName": null, - "FullName": null, - "FullNameMiddle": null, - "JobTitle": null, - "LastName": null, - "MiddleName": null, - "MiddleNameInitial": null, - "WorkPhoneNumber": null - } - }, - "ListOfDtFormInstanceChildData": { - "DtFormInstanceChildData": { - "Date01": null, - "Date02": null, - "Number01": null, - "Number02": null, - "Number03": null, - "Number04": null, - "String01": null, - "String02": null - } - }, - "ListOfDtFormInstanceChildData2": { - "DtFormInstanceChildData2": { - "Bool01": null, - "Date01": null, - "Number05": null, - "String01": null, - "String02": null - } - }, - "ListOfDtFormInstanceChildData3": { - "DtFormInstanceChildData3": { - "Bool03": null, - "String01": null, - "String02": null, - "String03": null, - "String04": null, - "String05": null, - "String07": null, - "String08": null, - "String09": null - } - }, - "ListOfDtFormInstanceChildData4": { - "DtFormInstanceChildData4": { - "Bool01": null - } - }, - "ListOfDtFormInstanceChildData11": { - "DtFormInstanceChildData11": { - "Bool01": null, - "Date01": null, - "Date02": null, - "String01": null, - "String02": null - } - }, - "ListOfDtFormInstanceChildData5": { - "DtFormInstanceChildData5": { - "String12": null, - "String13": null, - "String14": null - } + } + }, + "ListOfEmployee": { + "Employee": { + "EMailAddr": null, + "Emp": null, + "Fax": null, + "FirstName": null, + "FullName": null, + "FullNameMiddle": null, + "JobTitle": null, + "LastName": null, + "MiddleName": null, + "MiddleNameInitial": null, + "WorkPhoneNumber": null + } + }, + "ListOfDtFormInstanceChildData": { + "DtFormInstanceChildData": { + "Date01": null, + "Date02": null, + "Number01": null, + "Number02": null, + "Number03": null, + "Number04": null, + "String01": null, + "String02": null + } + }, + "ListOfDtFormInstanceChildData2": { + "DtFormInstanceChildData2": { + "Bool01": null, + "Date01": null, + "Number05": null, + "String01": null, + "String02": null + } + }, + "ListOfDtFormInstanceChildData3": { + "DtFormInstanceChildData3": { + "Bool03": null, + "String01": null, + "String02": null, + "String03": null, + "String04": null, + "String05": null, + "String07": null, + "String08": null, + "String09": null + } + }, + "ListOfDtFormInstanceChildData4": { + "DtFormInstanceChildData4": { + "Bool01": null + } + }, + "ListOfDtFormInstanceChildData11": { + "DtFormInstanceChildData11": { + "Bool01": null, + "Date01": null, + "Date02": null, + "String01": null, + "String02": null + } + }, + "ListOfDtFormInstanceChildData5": { + "DtFormInstanceChildData5": { + "String12": null, + "String13": null, + "String14": null } } }, @@ -819,6 +832,12 @@ const formExceptions = { "allowCheckboxWithNoChange": [], "omitFields": [], "addFields": { + "AlternateIncomePresent": null, + "ICMAssistance": null, + "SpousalConsent": null, + "SpouseAdditionalIncomeReported": null, + "SpouseAlternateIncomePresent": null, + "ApplicantSocialWorkerInvolvement": null, "ICMApplicantDateofBirth": null, "ApplicantGender": null, "ApplicantPrimaryPhoneNumberType": null, From 31535c8aa221b03e8ad85d5bc9621c776fac0882 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Thu, 23 Oct 2025 12:13:19 -0700 Subject: [PATCH 060/105] (ADO-3452) form definition kiln-v2 update --- schema/form_definitionV2.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/schema/form_definitionV2.yaml b/schema/form_definitionV2.yaml index 8460da4..fd49672 100644 --- a/schema/form_definitionV2.yaml +++ b/schema/form_definitionV2.yaml @@ -36,11 +36,15 @@ properties: type: object properties: form_id: - type: number + anyOf: + - type: number + - type: string form_developer: type: object comments: - type: "null" + anyOf: + - type: string + - type: "null" created_at: type: string updated_at: From 68ccbb0f0221cd92aeef8fbe5624def7ae0436ec Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 27 Oct 2025 12:30:32 -0700 Subject: [PATCH 061/105] console log for portal --- portal/loadPortalIntegratedHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/portal/loadPortalIntegratedHandler.js b/portal/loadPortalIntegratedHandler.js index 123772b..78e616c 100644 --- a/portal/loadPortalIntegratedHandler.js +++ b/portal/loadPortalIntegratedHandler.js @@ -27,6 +27,7 @@ async function loadPortalIntegratedForm(req, res) { return res.status(400).send({ error: getErrorMessage("FORM_NOT_FOUND", { templateId: template_id }) }); } //the formJson is a base64 string . Converting to json here. + console.log("Form Json:",formJson); const savedJson = Buffer.from(formJson["formJson"], 'base64').toString('utf-8'); const data = JSON.parse(savedJson); res.status(200).send(data); From ded09d634264239a04685da808b4106a07eeaa0a Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 28 Oct 2025 10:46:02 -0700 Subject: [PATCH 062/105] Added confirmation modal action to interface --- dictionary/interfaces.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index a629006..3c4e95f 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -17,7 +17,11 @@ const interfaces = { setModalOpen(true); return false; }` }, - + { + action_type: "javascript", + script: `const confirmed = await confirmModal(); + if (!confirmed) return false;` + }, { action_type: "endpoint", api_path: "API.saveButtonAction", @@ -34,6 +38,8 @@ const interfaces = { action_type: "javascript", script: `setModalTitle("Success ✅"); setModalMessage("Form Submitted Successfully."); + setPrimaryButton(""); + setSecondaryButton(""); setModalOpen(true); await handleSubmit();` }] From 1e665a681321af73a4bfdce220d3b7f2ebeb175f Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 28 Oct 2025 15:47:14 -0700 Subject: [PATCH 063/105] remove success modal --- dictionary/interfaces.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index 3c4e95f..ec41db6 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -14,6 +14,8 @@ const interfaces = { script: `if (!validateAllFields()) { setModalTitle("Validation Error"); setModalMessage("Please clear the errors in the form before submitting."); + setPrimaryButton(""); + setSecondaryButton(""); setModalOpen(true); return false; }` }, @@ -33,16 +35,8 @@ const interfaces = { api_path: "API.submitButtonAction", body: "tokenId: params[\"id\"]", type: "POST" - }, - { - action_type: "javascript", - script: `setModalTitle("Success ✅"); - setModalMessage("Form Submitted Successfully."); - setPrimaryButton(""); - setSecondaryButton(""); - setModalOpen(true); - await handleSubmit();` - }] + } + ] }, { type: "button", From a026667953157806c7f62fed10cff6e59d5d2cd9 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 28 Oct 2025 16:13:11 -0700 Subject: [PATCH 064/105] add success message to iframe --- dictionary/interfaces.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index ec41db6..3e580e8 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -36,6 +36,11 @@ const interfaces = { body: "tokenId: params[\"id\"]", type: "POST" } + ,{ + action_type: "javascript", + script: ` + await handleSubmit();` + } ] }, { From 2342ee5cfc940c05525d04b3276b0fa0b1ef71b7 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 28 Oct 2025 16:14:09 -0700 Subject: [PATCH 065/105] fix spacing --- dictionary/interfaces.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index 3e580e8..6a21be3 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -38,8 +38,7 @@ const interfaces = { } ,{ action_type: "javascript", - script: ` - await handleSubmit();` + script: `await handleSubmit();` } ] }, From de8aa128a2c2d7f822c196ddda5441c7a4524ae7 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Wed, 29 Oct 2025 13:52:04 -0700 Subject: [PATCH 066/105] kiln v1 form_definition check in validation --- validate.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/validate.js b/validate.js index cd538d9..4c66e10 100644 --- a/validate.js +++ b/validate.js @@ -17,7 +17,9 @@ function validateJson(jsonData) { * Kiln V1 uses data: { items: []} * Kiln V2 uses dataSources [] */ - const kilnVersion = Object.keys(jsonData["data"]).includes("items") ? 1 : 2; + const kilnVersion = Object.keys(jsonData["data"]).includes("items") ? 1 + : Object.keys(jsonData?.["form_definition"]["data"]).includes("items") ? 1 + : 2; const schema = loadSchema("schema/saved_json.yaml"); const formDefinitionSchema = kilnVersion === 1 ? loadSchema("schema/form_definition.yaml") : loadSchema("schema/form_definitionV2.yaml"); From 4b9712e9d61b41b6d1ac26cf324d13fdc090f1c1 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Tue, 4 Nov 2025 10:44:59 -0800 Subject: [PATCH 067/105] (ADO-3477) fixing form defiinition for kiln v2 based on template repo changes --- schema/form_definitionV2.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schema/form_definitionV2.yaml b/schema/form_definitionV2.yaml index fd49672..c5a1ee1 100644 --- a/schema/form_definitionV2.yaml +++ b/schema/form_definitionV2.yaml @@ -33,6 +33,8 @@ properties: type: object default: [] data: + type: array + form_data: type: object properties: form_id: From 78e83df45c73b784d89a88f36df76dfc954d3368 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 4 Nov 2025 15:25:53 -0800 Subject: [PATCH 068/105] Update pod count and use HPA for pod scaling --- helm/templates/hpa.yaml | 24 +++++++++++++++++++ helm/values.yaml | 52 ++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 helm/templates/hpa.yaml diff --git a/helm/templates/hpa.yaml b/helm/templates/hpa.yaml new file mode 100644 index 0000000..cf69bd9 --- /dev/null +++ b/helm/templates/hpa.yaml @@ -0,0 +1,24 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: communication-layer-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: communication-layer + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 75 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 75 diff --git a/helm/values.yaml b/helm/values.yaml index 56ee505..eaab3d8 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -1,26 +1,26 @@ -replicaCount: 2 - -image: - repository: ghcr.io/bcgov/communication-layer - tag: latest - pullPolicy: Always - -service: - type: ClusterIP - port: 3030 - -configMapName: commlayer - -resources: - requests: - memory: "128Mi" - cpu: "50m" - limits: - memory: "512Mi" - cpu: "250m" - -nodeSelector: {} - -tolerations: [] - -affinity: {} +replicaCount: 1 + +image: + repository: ghcr.io/bcgov/communication-layer + tag: latest + pullPolicy: Always + +service: + type: ClusterIP + port: 3030 + +configMapName: commlayer + +resources: + requests: + memory: "128Mi" + cpu: "50m" + limits: + memory: "512Mi" + cpu: "250m" + +nodeSelector: {} + +tolerations: [] + +affinity: {} From aa5b34c5c46db968ff699719d3c8475562ba07f4 Mon Sep 17 00:00:00 2001 From: NicolaSDPR1 Date: Wed, 5 Nov 2025 13:32:16 -0800 Subject: [PATCH 069/105] (ADO-3514) field id and field uuid --- databindingsHandler.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/databindingsHandler.js b/databindingsHandler.js index a43298d..f948dd3 100644 --- a/databindingsHandler.js +++ b/databindingsHandler.js @@ -141,9 +141,10 @@ function bindDataToFields(formJson, fetchedData, params = {}) { function processItemsForDatabinding(items) { items.forEach(field => { + const fieldId = field?.id ? field.id : field?.uuid; // containers - if (field.type === 'container' && field.containerItems) { - return processItemsForDatabinding(field.containerItems); + if (field.type === 'container' && (field.containerItems || field.children)) { + return field.containerItems ? processItemsForDatabinding(field.containerItems) : processItemsForDatabinding(field.children); } // groups @@ -152,12 +153,12 @@ function bindDataToFields(formJson, fetchedData, params = {}) { // repeatable group const binding = Array.isArray(field.databindings) ? field.databindings[0] : field.databindings || null; const rows = binding ? JSONPath({ path: binding.path, json: fetchedData }) || [] : []; - formData[field.id] = bindRowsToGroup(field, rows); + formData[fieldId] = bindRowsToGroup(field, rows); } else { // single-instance group const binding = Array.isArray(field.databindings) ? field.databindings[0]: field.databindings || null; const rows = binding? (JSONPath({ path: binding.path, json: fetchedData }) || []).slice(0, 1) : [{}]; - formData[field.id] = bindRowsToGroup(field, rows); + formData[fieldId] = bindRowsToGroup(field, rows); } return; } @@ -165,7 +166,7 @@ function bindDataToFields(formJson, fetchedData, params = {}) { //Single fields if (field.databindings) { const raw = getBindingValue(field.databindings, fetchedData, params); - formData[field.id] = raw != null ? transformValueToBindIfNeeded(field, raw) : null; + formData[fieldId] = raw != null ? transformValueToBindIfNeeded(field, raw) : null; } }); }; From 0fbf1b868c63f3dc77b7c084dac558391af538cc Mon Sep 17 00:00:00 2001 From: saranyaviswam Date: Wed, 5 Nov 2025 15:16:13 -0800 Subject: [PATCH 070/105] Update interfaces for Caregiver --- dictionary/interfaces.js | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index 6a21be3..3376455 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -73,6 +73,62 @@ const interfaces = { ] + }, + CAREGIVER: { + interface: [ + { + mode: [ + "portalNew", + "portalEdit" + ], + type: "button", + label: "Save", + style: "", + actions: [ + { + script: "setFormErrors({});if (!validateAllFields()){ setModalTitle(\"Validation Error\"); setModalMessage(\"There are errors in form.The form will be saved in draft form\"); setModalOpen(true); return true; }", + action_type: "javascript" + }, + { + body: "tokenId: params[\"id\"],savedForm: JSON.stringify(createSavedData())", + path: "/application-forms/saveDraft", + type: "POST", + api_path: "API.saveButtonAction", + action_type: "endpoint" + }, + { + script: "const isErrorEmpty = Object.keys(formErrors).length === 0;if(!isErrorEmpty){setModalTitle(\"Validation Error\"); setModalMessage(\"There are errors in form.The form will be saved in draft form\"); setModalOpen(true); return true;}else {setModalTitle(\"Success ✅\");setModalMessage(\"Form Saved Successfully as draft.\"); setModalOpen(true);}", + action_type: "javascript" + } + ] + }, + { + mode: [ + "portalNew", + "portalEdit" + ], + type: "button", + label: "Complete", + style: "", + actions: [ + { + script: "setFormErrors({});if (!validateAllFields()){ setModalTitle(\"Validation Error\"); setModalMessage(\"There are errors in form.Please clear them before saving.\"); setModalOpen(true); return false; }", + action_type: "javascript" + }, + { + body: "tokenId: params[\"id\"],savedForm: JSON.stringify(createSavedData())", + path: "/application-forms/submit", + type: "POST", + api_path: "API.saveButtonAction", + action_type: "endpoint" + }, + { + script: "setModalTitle(\"Success ✅\");setModalMessage(\"Form Submitted Successfully.\"); setModalOpen(true);await handleSubmit();", + action_type: "javascript" + } + ] + } + ] } }; From 74ed55f17c56232c568ab8950d0f336314950b16 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 6 Nov 2025 15:19:19 -0800 Subject: [PATCH 071/105] Logging to for save error --- saveICMdataHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index d512b26..cd21527 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -68,6 +68,7 @@ async function getICMAttachmentStatus(attachment_id, username, params) { async function saveICMdata(req, res) { try { let params = req.body; + console.log("Req for Save:",params); const rawHost = (req.get("X-Original-Server") || req.hostname); const configOpt = appCfg[rawHost] || Object.values(appCfg).find(cfg => { try { @@ -77,6 +78,7 @@ async function saveICMdata(req, res) { } }) || {}; params = { ...params,...configOpt }; + console.log("Params for Save:",params); const attachment_id = params["attachmentId"]; const savedFormParam = params["savedForm"]; From 955b9d967d6a22191c1c01ce9d0fd413522da3aa Mon Sep 17 00:00:00 2001 From: saranyaviswam Date: Mon, 10 Nov 2025 10:09:51 -0800 Subject: [PATCH 072/105] Change for removing modals in the button action. Change for removing modals in the button action. Portal will handle with the messages passed --- dictionary/interfaces.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index 3376455..895076d 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -86,7 +86,7 @@ const interfaces = { style: "", actions: [ { - script: "setFormErrors({});if (!validateAllFields()){ setModalTitle(\"Validation Error\"); setModalMessage(\"There are errors in form.The form will be saved in draft form\"); setModalOpen(true); return true; }", + script: "setFormErrors({});if (!validateAllFields()){ window.parent.postMessage(JSON.stringify({ \"event\": \"error\" }), \"*\"); return true; }", action_type: "javascript" }, { @@ -95,11 +95,7 @@ const interfaces = { type: "POST", api_path: "API.saveButtonAction", action_type: "endpoint" - }, - { - script: "const isErrorEmpty = Object.keys(formErrors).length === 0;if(!isErrorEmpty){setModalTitle(\"Validation Error\"); setModalMessage(\"There are errors in form.The form will be saved in draft form\"); setModalOpen(true); return true;}else {setModalTitle(\"Success ✅\");setModalMessage(\"Form Saved Successfully as draft.\"); setModalOpen(true);}", - action_type: "javascript" - } + } ] }, { @@ -112,7 +108,7 @@ const interfaces = { style: "", actions: [ { - script: "setFormErrors({});if (!validateAllFields()){ setModalTitle(\"Validation Error\"); setModalMessage(\"There are errors in form.Please clear them before saving.\"); setModalOpen(true); return false; }", + script: "setFormErrors({});if (!validateAllFields()){ window.parent.postMessage(JSON.stringify({ \"event\": \"error\" }), \"*\"); return false; }", action_type: "javascript" }, { @@ -123,7 +119,7 @@ const interfaces = { action_type: "endpoint" }, { - script: "setModalTitle(\"Success ✅\");setModalMessage(\"Form Submitted Successfully.\"); setModalOpen(true);await handleSubmit();", + script: "await handleSubmit();", action_type: "javascript" } ] From 01d53a5b3a8cb005cac53f472fe72917a0e571c9 Mon Sep 17 00:00:00 2001 From: saranyaviswam Date: Mon, 10 Nov 2025 10:35:04 -0800 Subject: [PATCH 073/105] Changes for error message to post --- dictionary/interfaces.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index 895076d..2b709f1 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -86,7 +86,7 @@ const interfaces = { style: "", actions: [ { - script: "setFormErrors({});if (!validateAllFields()){ window.parent.postMessage(JSON.stringify({ \"event\": \"error\" }), \"*\"); return true; }", + script: "setFormErrors({});if (!validateAllFields()){ window.parent.postMessage(JSON.stringify({ \"event\": \"errorOnSave\" }), \"*\"); return true; }", action_type: "javascript" }, { @@ -108,7 +108,7 @@ const interfaces = { style: "", actions: [ { - script: "setFormErrors({});if (!validateAllFields()){ window.parent.postMessage(JSON.stringify({ \"event\": \"error\" }), \"*\"); return false; }", + script: "setFormErrors({});if (!validateAllFields()){ window.parent.postMessage(JSON.stringify({ \"event\": \"errorOnComplete\" }), \"*\"); return false; }", action_type: "javascript" }, { From 11797c9d2ceb9e527d72e9c95924ae85f3a42b23 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 18 Nov 2025 12:28:42 -0800 Subject: [PATCH 074/105] created net portal interface for kiln v2 --- dictionary/interfaces.js | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index 2b709f1..ef9587d 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -74,6 +74,72 @@ const interfaces = { ] }, + NETportalsV2: { + interface: [ + { + type: "button", + label: "Submit", + mode: ["portalEdit"], + style: "primary", + actions: [ + { + "action_type": "javascript", + "script": "if (!validateAllFields()?.isValid) { setModalTitle('Validation Error'); setModalMessage('Please fix the highlighted fields.'); setModalOpen(true); return false; }" + }, + { + "action_type": "javascript", + "script": "const confirmed = await confirmModal(); if (!confirmed) { return false; }" + }, + { + action_type: "endpoint", + api_path: "API.saveButtonAction", + type: "POST", + body: "tokenId: params['id'], savedForm: JSON.stringify(createSavedData())" + }, + { + action_type: "endpoint", + api_path: "API.submitButtonAction", + type: "POST", + body: "tokenId: params['id']" + }, + { + action_type: "javascript", + script: `await handleSubmit();` + } + ] + }, + { + type: "button", + label: "Cancel", + mode: ["portalEdit", "portalView"], + style: "secondary", + actions: [ + { + action_type: "endpoint", + api_path: "API.cancelButtonAction", + type: "POST", + body: "tokenId: params['id']", + }, + { + action_type: "javascript", + script: "await handleCancel();" + } + ] + }, + { + type: "button", + label: "Print", + mode: ["portalView"], + style: "tertiary", + actions: [ + { + action_type: "javascript", + script: "handlePrint();" + } + ] + } + ] + }, CAREGIVER: { interface: [ { From fdd84cb2d8d97245420a947684395487c5d6c7fd Mon Sep 17 00:00:00 2001 From: Viswam Date: Tue, 25 Nov 2025 14:56:42 -0800 Subject: [PATCH 075/105] Changes for CaregiverV2 interface --- dictionary/interfaces.js | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/dictionary/interfaces.js b/dictionary/interfaces.js index ef9587d..8c24a5c 100644 --- a/dictionary/interfaces.js +++ b/dictionary/interfaces.js @@ -190,6 +190,58 @@ const interfaces = { } ] } + ] + }, + CAREGIVERV2: { + interface: [ + { + mode: [ + "portalNew", + "portalEdit" + ], + type: "button", + label: "Save", + style: "", + actions: [ + { + script: "if (!validateAllFields()?.isValid){ window.parent.postMessage(JSON.stringify({ \"event\": \"errorOnSave\" }), \"*\");return true; }", + action_type: "javascript" + }, + { + body: "tokenId: params['id'], savedForm: JSON.stringify(createSavedData())", + path: "/application-forms/saveDraft", + type: "POST", + api_path: "API.saveButtonAction", + action_type: "endpoint" + } + ] + }, + { + mode: [ + "portalNew", + "portalEdit" + ], + type: "button", + label: "Complete", + style: "", + actions: [ + { + script: "if (!validateAllFields()?.isValid){ window.parent.postMessage(JSON.stringify({ \"event\": \"errorOnComplete\" }), \"*\"); return false; }", + action_type: "javascript" + }, + { + body: "tokenId: params['id'], savedForm: JSON.stringify(createSavedData())", + path: "/application-forms/submit", + type: "POST", + api_path: "API.saveButtonAction", + action_type: "endpoint" + }, + { + script: `await handleSubmit();`, + action_type: "javascript" + } + ] + } ] } }; From 1a68d35532be91fb1dadcad09759e56a61eb5497 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 26 Nov 2025 16:40:21 -0800 Subject: [PATCH 076/105] Added logging to clearICM api call --- saveICMdataHandler.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index cd21527..66ca883 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -398,7 +398,13 @@ async function clearICMLockedFlag(req, res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } + console.log("Params for clear:",params); + console.log("Query for clear:",query); + console.log("Raw Host for clear:",rawHost); + console.log("Config Opts clear:",configOpt); + response = await axios.put(url, saveJson, { params: query, headers }); + console.log("Response Clear:",response.status); return res.status(200).send({}); } From 97aadb9877183afcdc42dfdb63529778c7f43d51 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 27 Nov 2025 09:14:04 -0800 Subject: [PATCH 077/105] More logs --- saveICMdataHandler.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 66ca883..836c11a 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -341,6 +341,10 @@ async function clearICMLockedFlag(req, res) { .send({ error: getErrorMessage("ATTACHMENT_ID_REQUIRED") }); } let username = null; + console.log("1.Params for clear:",params); + console.log("1.Query for clear:",query); + console.log("1.Raw Host for clear:",rawHost); + console.log("1.Config Opts clear:",configOpt); if (params["token"]) { username = await getUsername(params["token"], params["employeeEndpoint"]); @@ -398,10 +402,10 @@ async function clearICMLockedFlag(req, res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } - console.log("Params for clear:",params); - console.log("Query for clear:",query); - console.log("Raw Host for clear:",rawHost); - console.log("Config Opts clear:",configOpt); + console.log("2.Params for clear:",params); + console.log("2.Query for clear:",query); + console.log("2.Raw Host for clear:",rawHost); + console.log("2.Config Opts clear:",configOpt); response = await axios.put(url, saveJson, { params: query, headers }); console.log("Response Clear:",response.status); From 8f7dc571c2deaba8d5c88e4048c3071d049d4ef2 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 27 Nov 2025 09:21:01 -0800 Subject: [PATCH 078/105] updated logs --- saveICMdataHandler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 836c11a..5e41112 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -342,7 +342,6 @@ async function clearICMLockedFlag(req, res) { } let username = null; console.log("1.Params for clear:",params); - console.log("1.Query for clear:",query); console.log("1.Raw Host for clear:",rawHost); console.log("1.Config Opts clear:",configOpt); From c4d715943db514fd2da1f33c5ce429f920b70201 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 27 Nov 2025 09:34:52 -0800 Subject: [PATCH 079/105] remove debugging logs --- saveICMdataHandler.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 5e41112..e09c06d 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -401,10 +401,7 @@ async function clearICMLockedFlag(req, res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } - console.log("2.Params for clear:",params); - console.log("2.Query for clear:",query); - console.log("2.Raw Host for clear:",rawHost); - console.log("2.Config Opts clear:",configOpt); + response = await axios.put(url, saveJson, { params: query, headers }); console.log("Response Clear:",response.status); From 7adbdd0e903f47c72b2c92ab4b4e68da6a0026f5 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 27 Nov 2025 09:35:05 -0800 Subject: [PATCH 080/105] removed more logs --- saveICMdataHandler.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index e09c06d..60c9d97 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -68,7 +68,6 @@ async function getICMAttachmentStatus(attachment_id, username, params) { async function saveICMdata(req, res) { try { let params = req.body; - console.log("Req for Save:",params); const rawHost = (req.get("X-Original-Server") || req.hostname); const configOpt = appCfg[rawHost] || Object.values(appCfg).find(cfg => { try { @@ -78,7 +77,6 @@ async function saveICMdata(req, res) { } }) || {}; params = { ...params,...configOpt }; - console.log("Params for Save:",params); const attachment_id = params["attachmentId"]; const savedFormParam = params["savedForm"]; @@ -217,7 +215,7 @@ async function saveICMdata(req, res) { const xml = saveJson["XML Hierarchy"]; const xmlSize = Buffer.byteLength(xml, 'utf8'); // size in bytes - console.log("XML Hierarchy:", xml); + // console.log("XML Hierarchy:", xml); console.log("XML Hierarchy length (chars):", xml.length); console.log("XML Hierarchy size (bytes):", xmlSize); let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); @@ -341,9 +339,6 @@ async function clearICMLockedFlag(req, res) { .send({ error: getErrorMessage("ATTACHMENT_ID_REQUIRED") }); } let username = null; - console.log("1.Params for clear:",params); - console.log("1.Raw Host for clear:",rawHost); - console.log("1.Config Opts clear:",configOpt); if (params["token"]) { username = await getUsername(params["token"], params["employeeEndpoint"]); @@ -401,10 +396,8 @@ async function clearICMLockedFlag(req, res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } - response = await axios.put(url, saveJson, { params: query, headers }); - console.log("Response Clear:",response.status); return res.status(200).send({}); } From d0718d3043e2ce9c468ddc4993248b6ee0b16d30 Mon Sep 17 00:00:00 2001 From: Danilo Meireles Date: Fri, 28 Nov 2025 15:16:54 -0800 Subject: [PATCH 081/105] Reuse Keycloak token in loadICMdata to reduce duplicate auth calls --- saveICMdataHandler.js | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 60c9d97..f78f015 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -12,9 +12,10 @@ const { formExceptions } = require("./dictionary/jsonXmlConversion.js"); const { propertyExists, propertyNotEmpty, keyExists } = require("./dictionary/dictionaryUtils.js"); const SIEBEL_ICM_API_FORMS_ENDPOINT = process.env.SIEBEL_ICM_API_FORMS_ENDPOINT; -// utility function to fetch Attachment status (In Progress, Open...) -// and Locked By User field -async function getICMAttachmentStatus(attachment_id, username, params) { + +// Fetch attachment status and locked-by fields from ICM +// authHeaders: optional pre-acquired headers to avoid duplicate Keycloak token calls +async function getICMAttachmentStatus(attachment_id, username, params, authHeaders = null) { let return_data = {}; return_data["Status"] = ""; return_data["Locked by User"] = ""; @@ -39,6 +40,7 @@ async function getICMAttachmentStatus(attachment_id, username, params) { Authorization: `Bearer ${grant.id_token.token}`, "X-ICM-TrustedUsername": username, } + const query = { viewMode: "Catalog" } @@ -253,7 +255,7 @@ async function loadICMdata(req, res) { let params = req.body; const rawHost = (req.get("X-Original-Server") || req.hostname); const configOpt = appCfg[rawHost]; - params = { ...params,...configOpt }; + params = { ...params,...configOpt }; const attachment_id = params["attachmentId"]; const office_name = params["OfficeName"]; console.log("attachment_id>>", attachment_id); @@ -275,9 +277,23 @@ async function loadICMdata(req, res) { return res .status(401) .send({ error: getErrorMessage("INVALID_USER") }); - } - let icm_metadata = await getICMAttachmentStatus(attachment_id, username, params); - let icm_status = icm_metadata["Status"]; + } + + // Acquire token once and reuse for all ICM calls + let authHeaders; + try { + const grant = await keycloakForSiebel.grantManager.obtainFromClientCredentials(); + authHeaders = { + Authorization: `Bearer ${grant.id_token.token}`, + "X-ICM-TrustedUsername": username, + }; + } catch (error) { + console.error("Failed to acquire Keycloak token:", error); + return res.status(500).send({ error: getErrorMessage("GENERIC_ERROR_MSG") }); + } + + let icm_metadata = await getICMAttachmentStatus(attachment_id, username, params, authHeaders); + let icm_status = icm_metadata["Status"]; if (!icm_status || icm_status == "") { console.log("Error fetching Form Instance Thin data for ", attachment_id); return res @@ -288,12 +304,6 @@ async function loadICMdata(req, res) { let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); try { let response; - const grant = - await keycloakForSiebel.grantManager.obtainFromClientCredentials(); - const headers = { - Authorization: `Bearer ${grant.id_token.token}`, - "X-ICM-TrustedUsername": username, - } const query = { viewMode: "Catalog", inlineattachment: true @@ -301,9 +311,9 @@ async function loadICMdata(req, res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } - response = await axios.get(url, { params: query, headers }); + response = await axios.get(url, { params: query, headers: authHeaders }); let return_data = Buffer.from(response.data["Doc Attachment Id"], 'base64').toString('utf-8'); - //validate the returned data to be of the expected format + //validate the returned data to be of the expected format const valid = isJsonStringValid(return_data); if (!valid) { console.log('JSON is not valid '); From aa3a68e75aa450d552248be857e8c983569aae3e Mon Sep 17 00:00:00 2001 From: Danilo Meireles Date: Fri, 28 Nov 2025 15:17:13 -0800 Subject: [PATCH 082/105] reuse Keycloak token in loadICMdata to reduce duplicate auth calls --- saveICMdataHandler.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index f78f015..df6997a 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -34,11 +34,16 @@ async function getICMAttachmentStatus(attachment_id, username, params, authHeade let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); try { let response; - const grant = - await keycloakForSiebel.grantManager.obtainFromClientCredentials(); - const headers = { - Authorization: `Bearer ${grant.id_token.token}`, - "X-ICM-TrustedUsername": username, + let headers; + + if (authHeaders) { + headers = authHeaders; + } else { + const grant = await keycloakForSiebel.grantManager.obtainFromClientCredentials(); + headers = { + Authorization: `Bearer ${grant.id_token.token}`, + "X-ICM-TrustedUsername": username, + }; } const query = { From 5babccbd18c3229b67e927f4b3a4f7b21ba249bb Mon Sep 17 00:00:00 2001 From: Viswam Date: Tue, 2 Dec 2025 14:58:54 -0800 Subject: [PATCH 083/105] changes for loading ICM Data as PDF --- generatePDFHandler.js | 40 ++++++++++--------- routes.js | 3 +- saveICMdataHandler.js | 92 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 22 deletions(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index 2fc6f75..2b49bab 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -7,10 +7,7 @@ const sleep = (ms) => new Promise(res => setTimeout(res, ms)); async function generatePDFFromJSON(req, res) { try { - let attachment = req.body.attachment ?? req.body[0]; - - // console.log("PDF Request:",req); - // console.log("PDF Request body:",req.body); + let attachment = req.body.attachment ?? req.body[0]; // Validate attachment is present in incoming message if (!attachment) { @@ -23,11 +20,10 @@ async function generatePDFFromJSON(req, res) { const savedJsonString = Buffer.from(attachment, 'base64').toString('utf-8'); let savedJson; - // console.log("Saved JSON String:",savedJsonString); + try { - savedJson = JSON.parse(savedJsonString); - // console.log("Saved Parsed JSON:",savedJson); + savedJson = JSON.parse(savedJsonString); const { valid, errors } = validateJson(savedJson); if (valid) { @@ -61,8 +57,7 @@ async function generatePDFFromJSON(req, res) { // Generate PDF buffer const pdfBuffer = await generatePDF(savedJsonString); //get the pdf from the savedJson - const pdfBase64 = Buffer.from(pdfBuffer).toString('base64'); - // console.log("PDF Base64:",pdfBase64); + const pdfBase64 = Buffer.from(pdfBuffer).toString('base64'); res.status(200).json({ errorCode: 0, @@ -83,8 +78,7 @@ async function generatePDFFromJSON(req, res) { async function generatePDF(savedJson) { const id = await storeData(savedJson); - const endPointForPDF = process.env.GENERATE_KILN_URL + "?jsonId=" + id; - // console.log("PDF Endpoint:",endPointForPDF); + const endPointForPDF = process.env.GENERATE_KILN_URL + "?jsonId=" + id; const pdfBufferFromURL = await getPDFFromURL(endPointForPDF); deleteData(id); return pdfBufferFromURL; @@ -146,8 +140,7 @@ async function generatePDFFromHTML(req, res) { async function generatePDFFromURL(req, res) { const { path } = req.body; - try { - + try { const pdfBuffer = await getPDFFromURL(path); // Send PDF back to client @@ -171,9 +164,7 @@ async function getPDFFromURL(url) { const CLICK_WINDOW_MS = Number(process.env.PDF_CLICK_WINDOW_MS ?? 3000); const CLICK_RETRY_EVERY_MS = Number(process.env.PDF_CLICK_INTERVAL_MS ?? 200); const POST_CLICK_MS = Number(process.env.PDF_POST_CLICK_MS ?? 2500); - - // console.log("Puppeteer path:", process.env.PUPPETEER_EXECUTABLE_PATH); - // console.log("Puppeteer URL:", url); + const browser = await puppeteer.launch({ executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || "/usr/bin/chromium", args: [ @@ -242,7 +233,20 @@ async function getPDFFromURL(url) { // Give it time to assemble the printable view await sleep(POST_CLICK_MS); - + + // Ensure JS fully loaded. Calling this is critical as the function need to called before printing + await page.waitForSelector('#print',{ timeout: 200000 }); // Use the actual selector + + // Step 3: Click the button + await page.click('#print'); // Same selector as above + + await page.evaluate((t) => { + document.title = t; + const titleTag = document.querySelector("title") || + document.head.appendChild(document.createElement("title")); + titleTag.textContent = t; + }, "Untitled"); + // Generate PDF with print CSS applied await page.emulateMediaType('print'); const pdfBuffer = await page.pdf({ @@ -296,4 +300,4 @@ async function loadSavedJson(req, res) { -module.exports = { generatePDFFromHTML, generatePDFFromURL, generatePDFFromJSON, loadSavedJson }; \ No newline at end of file +module.exports = { generatePDFFromHTML, generatePDFFromURL, generatePDFFromJSON, loadSavedJson ,generatePDF }; \ No newline at end of file diff --git a/routes.js b/routes.js index 2f4850c..6fcb57e 100644 --- a/routes.js +++ b/routes.js @@ -3,7 +3,7 @@ const axios = require("axios"); const xmlparser = require("express-xml-bodyparser"); const { keycloakForSiebel, keycloakForFormRepo } = require("./keycloak.js"); const { generateTemplate,generateNewTemplate } = require("./generateHandler"); -const { saveICMdata, loadICMdata, clearICMLockedFlag } = require("./saveICMdataHandler"); +const { saveICMdata, loadICMdata, clearICMLockedFlag, loadICMdataAsPDF } = require("./saveICMdataHandler"); const { getUsername } = require("./usernameHandler.js"); const renderRouter = require("./renderHandler"); const appCfg = require('./appConfig.js'); @@ -205,5 +205,6 @@ router.post("/loadPortalForm", loadPortalIntegratedForm); router.use("/interface", interface); router.post('/submitForPortalAction', submitForPortalAction); router.post('/cancelForPortalAction', cancelForPortalAction); +router.post("/loadICMDataAsPDF", loadICMdataAsPDF); module.exports = router; \ No newline at end of file diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index df6997a..7e3e818 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -10,6 +10,7 @@ const appCfg = require('./appConfig.js'); const { toICMFormat } = require("./dateConverter.js"); const { formExceptions } = require("./dictionary/jsonXmlConversion.js"); const { propertyExists, propertyNotEmpty, keyExists } = require("./dictionary/dictionaryUtils.js"); +const {generatePDF }= require("./generatePDFHandler.js"); const SIEBEL_ICM_API_FORMS_ENDPOINT = process.env.SIEBEL_ICM_API_FORMS_ENDPOINT; @@ -152,8 +153,7 @@ async function saveICMdata(req, res) { saveJson["DocFileExt"] = "json"; saveJson["Doc Attachment Id"] = Buffer.from(savedFormParam).toString('base64');//savedForm is saved as attachment let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson - - + /** * Apply Kiln Version * Kiln V1 uses data: { items: []} @@ -799,7 +799,93 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt return truncatedKeysSaveData; } +async function loadICMdataAsPDF(req,res) { + + //load the savedJson form ICM. + let params = req.body; + const rawHost = (req.get("X-Original-Server") || req.hostname); + const configOpt = appCfg[rawHost]; + params = { ...params,...configOpt }; + const attachment_id = params["attachmentId"]; + const office_name = params["OfficeName"]; + if (!attachment_id) { + return res + .status(400) + .send({ error: getErrorMessage("ATTACHMENT_ID_REQUIRED") }); + } + let username = null; + + if (params["token"]) { + username = await getUsername(params["token"], params["employeeEndpoint"]); + } else if (params["username"]) { + const valid = await isUsernameValid(params["username"], params["employeeEndpoint"]); + username = valid ? params["username"] : null; + } + + if (!username || !isNaN(username)) { + return res + .status(401) + .send({ error: getErrorMessage("INVALID_USER") }); + } + let icm_metadata = await getICMAttachmentStatus(attachment_id, username, params); + let icm_status = icm_metadata["Status"]; + if (!icm_status || icm_status == "") { + console.log("Error fetching Form Instance Thin data for ", attachment_id); + return res + .status(400) + .send({ error: getErrorMessage("FORM_STATUS_NOT_FOUND") }); + } + //let url = buildUrlWithParams('SIEBEL_ICM_API_HOST', 'fwd/v1.0/data/DT FormFoundry Upsert/DT Form Instance Orbeon Revise/'+attachment_id+'/',''); + let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); + try { + let response; + const grant = + await keycloakForSiebel.grantManager.obtainFromClientCredentials(); + const headers = { + Authorization: `Bearer ${grant.id_token.token}`, + "X-ICM-TrustedUsername": username, + } + const query = { + viewMode: "Catalog", + inlineattachment: true + } + if (params.icmWorkspace) { + query.workspace = params.icmWorkspace; + } + response = await axios.get(url, { params: query, headers }); + let return_data = Buffer.from(response.data["Doc Attachment Id"], 'base64').toString('utf-8'); + //validate the returned data to be of the expected format + const valid = isJsonStringValid(return_data); + if (!valid) { + console.log('JSON is not valid '); + return res + .status(400) + .send({ error: getErrorMessage("FORM_NOT_VALID") }); + } + //validate ends here. Once validated continue to modify json based on status and send back. + if (icm_status == "Complete") { + let new_data = JSON.parse(return_data); + new_data.form_definition.readOnly = true; + return_data = JSON.stringify(new_data); + } + + const pdfBuffer = await generatePDF(return_data); //get the pdf from the savedJson + + const pdfBase64 = Buffer.from(pdfBuffer).toString('base64'); + res.status(200).json({ + pdf_base64: pdfBase64 + }); + } + catch (error) { + console.error(`Error loading data from ICM for PDF:`, error); + return res + .status(400) + .send({ error: getErrorMessage("GENERIC_ERROR_MSG") }); + } +} + module.exports.saveICMdata = saveICMdata; module.exports.loadICMdata = loadICMdata; module.exports.clearICMLockedFlag = clearICMLockedFlag; -module.exports.getICMAttachmentStatus = getICMAttachmentStatus; \ No newline at end of file +module.exports.getICMAttachmentStatus = getICMAttachmentStatus; +module.exports.loadICMdataAsPDF = loadICMdataAsPDF; \ No newline at end of file From 957c7a82f27dea1fd7d60a7332f97998352584e8 Mon Sep 17 00:00:00 2001 From: Viswam Date: Wed, 3 Dec 2025 09:54:20 -0800 Subject: [PATCH 084/105] Changes for ADO-3618 Footer missing issue in backend PDF --- generatePDFHandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index 2b49bab..953e5a8 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -79,8 +79,8 @@ async function generatePDF(savedJson) { const id = await storeData(savedJson); const endPointForPDF = process.env.GENERATE_KILN_URL + "?jsonId=" + id; - const pdfBufferFromURL = await getPDFFromURL(endPointForPDF); - deleteData(id); + const pdfBufferFromURL = await getPDFFromURL(endPointForPDF); + await deleteData(id); return pdfBufferFromURL; } From 99486f60afedc3f2625dc83963ea692b61384b24 Mon Sep 17 00:00:00 2001 From: Viswam Date: Wed, 3 Dec 2025 15:10:32 -0800 Subject: [PATCH 085/105] Added timed clicking for PRINT and logs --- generatePDFHandler.js | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index 953e5a8..632f5fb 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -184,7 +184,7 @@ async function getPDFFromURL(url) { await page.setViewport({ width: 1280, height: 800 }); // Set the HTML content of the page - await page.goto(url, { waitUntil: 'networkidle2', timeout: 150000 }); + await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 150000 }); // Let app hydrate await sleep(HYDRATE_MS); @@ -234,11 +234,37 @@ async function getPDFFromURL(url) { // Give it time to assemble the printable view await sleep(POST_CLICK_MS); - // Ensure JS fully loaded. Calling this is critical as the function need to called before printing - await page.waitForSelector('#print',{ timeout: 200000 }); // Use the actual selector + console.log("Waiting for print button..."); + + let clickedPrint = false; + const PRINT_DEADLINE = Date.now() + 20000; // up to 20 sec + while (!clickedPrint && Date.now() < PRINT_DEADLINE) { + // Try main document + try { + await page.waitForSelector("#print", { visible: true, timeout: 1000 }); + await page.click("#print"); + clickedPrint = true; + console.log("Clicked print button on main page"); + break; + } catch {} + + // Try every frame + for (const frame of page.frames()) { + try { + await frame.waitForSelector("#print", { visible: true, timeout: 1000 }); + await frame.click("#print"); + clickedPrint = true; + console.log("Clicked print button inside iframe"); + break; + } catch {} + } + + await sleep(250); // avoid hot-looping + } - // Step 3: Click the button - await page.click('#print'); // Same selector as above + if (!clickedPrint) { + console.log("Could not click on print button"); + } await page.evaluate((t) => { document.title = t; @@ -259,6 +285,7 @@ async function getPDFFromURL(url) { await browser.close(); + console.log("Returning PDF"); return pdfBuffer; } catch (error) { From 1cf0916d313225831cce2e3d10924da85d7f80d9 Mon Sep 17 00:00:00 2001 From: Viswam Date: Wed, 3 Dec 2025 15:53:15 -0800 Subject: [PATCH 086/105] More checks for background pdf --- generatePDFHandler.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index 632f5fb..d207c29 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -261,8 +261,22 @@ async function getPDFFromURL(url) { await sleep(250); // avoid hot-looping } + await sleep(POST_CLICK_MS); + if (clickedPrint) { + + console.log("Waiting for printable layout to finish…"); - if (!clickedPrint) { + // Wait for data-form-id attribute + try { + await page.waitForFunction( + () => document.documentElement.getAttribute("data-form-id"), + { timeout: 20000 } + ); + console.log("Printable layout ready (data-form-id detected)"); + } catch { + console.log("Timeout waiting for data-form-id"); + } + } else { console.log("Could not click on print button"); } From f4e97eda8b2bc0a4d2899e6334aeb8fb0d85e30e Mon Sep 17 00:00:00 2001 From: Viswam Date: Wed, 3 Dec 2025 16:19:11 -0800 Subject: [PATCH 087/105] Adding more conditions to check readiness for Background PDF --- generatePDFHandler.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index d207c29..0cec06a 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -276,6 +276,29 @@ async function getPDFFromURL(url) { } catch { console.log("Timeout waiting for data-form-id"); } + + // Svelte stabilization + console.log("Waiting for Svelte DOM stability…"); + await page.waitForFunction(() => { + const now = performance.now(); + if (!window.__lastMutationTime) window.__lastMutationTime = now; + + if (!window.__mutationObserverInstalled) { + const observer = new MutationObserver(() => { + window.__lastMutationTime = performance.now(); + }); + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + characterData: true + }); + window.__mutationObserverInstalled = true; + } + + return performance.now() - window.__lastMutationTime > 600; + }, { timeout: 20000 }); + console.log("Svelte DOM stabilized"); } else { console.log("Could not click on print button"); } From 576b05923d0a607e92cf947c3304f97009f61611 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Wed, 3 Dec 2025 16:26:30 -0800 Subject: [PATCH 088/105] Use HPA only on prod --- helm/templates/hpa.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/helm/templates/hpa.yaml b/helm/templates/hpa.yaml index cf69bd9..0c7d7c1 100644 --- a/helm/templates/hpa.yaml +++ b/helm/templates/hpa.yaml @@ -1,3 +1,4 @@ +{{- if contains "prod" .Release.Namespace }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: @@ -15,10 +16,11 @@ spec: name: cpu target: type: Utilization - averageUtilization: 75 + averageUtilization: 80 - type: Resource resource: name: memory target: type: Utilization - averageUtilization: 75 + averageUtilization: 80 +{{- end }} \ No newline at end of file From f88667bff51ac0599aeb6a6cb0146d54d015b889 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Thu, 4 Dec 2025 10:51:08 -0800 Subject: [PATCH 089/105] Update helm values --- helm/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/values.yaml b/helm/values.yaml index eaab3d8..b26c76b 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -13,8 +13,8 @@ configMapName: commlayer resources: requests: - memory: "128Mi" - cpu: "50m" + memory: "256Mi" + cpu: "20m" limits: memory: "512Mi" cpu: "250m" From 8d2eef83d0421d9d448ad26591081ad3e92074db Mon Sep 17 00:00:00 2001 From: Viswam Date: Thu, 4 Dec 2025 11:23:09 -0800 Subject: [PATCH 090/105] Changes for improving performance --- generatePDFHandler.js | 2 +- saveICMdataHandler.js | 39 ++++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index 0cec06a..b3003f3 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -261,7 +261,7 @@ async function getPDFFromURL(url) { await sleep(250); // avoid hot-looping } - await sleep(POST_CLICK_MS); + //await sleep(POST_CLICK_MS); if (clickedPrint) { console.log("Waiting for printable layout to finish…"); diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 7e3e818..ebc5cc9 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -802,12 +802,13 @@ function fixJSONValuesForXML (saveData, truncatedKeysSaveData, toWrapIds, dateIt async function loadICMdataAsPDF(req,res) { //load the savedJson form ICM. - let params = req.body; + let params = req.body; const rawHost = (req.get("X-Original-Server") || req.hostname); const configOpt = appCfg[rawHost]; - params = { ...params,...configOpt }; + params = { ...params,...configOpt }; const attachment_id = params["attachmentId"]; - const office_name = params["OfficeName"]; + const office_name = params["OfficeName"]; + console.log("attachment_id>>", attachment_id); if (!attachment_id) { return res .status(400) @@ -826,9 +827,23 @@ async function loadICMdataAsPDF(req,res) { return res .status(401) .send({ error: getErrorMessage("INVALID_USER") }); - } - let icm_metadata = await getICMAttachmentStatus(attachment_id, username, params); - let icm_status = icm_metadata["Status"]; + } + + // Acquire token once and reuse for all ICM calls + let authHeaders; + try { + const grant = await keycloakForSiebel.grantManager.obtainFromClientCredentials(); + authHeaders = { + Authorization: `Bearer ${grant.id_token.token}`, + "X-ICM-TrustedUsername": username, + }; + } catch (error) { + console.error("Failed to acquire Keycloak token:", error); + return res.status(500).send({ error: getErrorMessage("GENERIC_ERROR_MSG") }); + } + + let icm_metadata = await getICMAttachmentStatus(attachment_id, username, params, authHeaders); + let icm_status = icm_metadata["Status"]; if (!icm_status || icm_status == "") { console.log("Error fetching Form Instance Thin data for ", attachment_id); return res @@ -839,12 +854,6 @@ async function loadICMdataAsPDF(req,res) { let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); try { let response; - const grant = - await keycloakForSiebel.grantManager.obtainFromClientCredentials(); - const headers = { - Authorization: `Bearer ${grant.id_token.token}`, - "X-ICM-TrustedUsername": username, - } const query = { viewMode: "Catalog", inlineattachment: true @@ -852,9 +861,9 @@ async function loadICMdataAsPDF(req,res) { if (params.icmWorkspace) { query.workspace = params.icmWorkspace; } - response = await axios.get(url, { params: query, headers }); + response = await axios.get(url, { params: query, headers: authHeaders }); let return_data = Buffer.from(response.data["Doc Attachment Id"], 'base64').toString('utf-8'); - //validate the returned data to be of the expected format + //validate the returned data to be of the expected format const valid = isJsonStringValid(return_data); if (!valid) { console.log('JSON is not valid '); @@ -867,7 +876,7 @@ async function loadICMdataAsPDF(req,res) { let new_data = JSON.parse(return_data); new_data.form_definition.readOnly = true; return_data = JSON.stringify(new_data); - } + } const pdfBuffer = await generatePDF(return_data); //get the pdf from the savedJson From e583f6f4ba1e50242a0010eb984f4ba0d83f7ae1 Mon Sep 17 00:00:00 2001 From: Viswam Date: Fri, 5 Dec 2025 11:40:04 -0800 Subject: [PATCH 091/105] Commenting out code that pose delays --- generatePDFHandler.js | 77 ++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index b3003f3..9196791 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -160,10 +160,10 @@ async function generatePDFFromURL(req, res) { async function getPDFFromURL(url) { // Increases to 5000 seemed to be working well (could also set the params in env file if needed) - const HYDRATE_MS = Number(process.env.PDF_HYDRATE_MS ?? 5000); + /* const HYDRATE_MS = Number(process.env.PDF_HYDRATE_MS ?? 5000); const CLICK_WINDOW_MS = Number(process.env.PDF_CLICK_WINDOW_MS ?? 3000); const CLICK_RETRY_EVERY_MS = Number(process.env.PDF_CLICK_INTERVAL_MS ?? 200); - const POST_CLICK_MS = Number(process.env.PDF_POST_CLICK_MS ?? 2500); + const POST_CLICK_MS = Number(process.env.PDF_POST_CLICK_MS ?? 2500); */ const browser = await puppeteer.launch({ executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || "/usr/bin/chromium", @@ -186,7 +186,11 @@ async function getPDFFromURL(url) { // Set the HTML content of the page await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 150000 }); - // Let app hydrate + //The below code block clicks the show letter button on the UI. This functionality is not needed any more + // as the PRINT directly should show the letter setting/resetting visible_pdf and visbile_web flags + // in the form definition. This below code takes a lot of await times and is making the process delayed as a whole + // Until confirmed the commenting out doesnt pose any issues, leave this here just in case to put it back. + /* // Let app hydrate await sleep(HYDRATE_MS); // Click “Show Letter” with a short retry window @@ -229,10 +233,12 @@ async function getPDFFromURL(url) { try { clicked = await f.evaluate(clickScript); if (clicked) break; } catch {} } } - } + } */ + //here // Give it time to assemble the printable view - await sleep(POST_CLICK_MS); + //await sleep(POST_CLICK_MS); + console.log("Waiting for print button..."); @@ -242,6 +248,19 @@ async function getPDFFromURL(url) { // Try main document try { await page.waitForSelector("#print", { visible: true, timeout: 1000 }); + // Wait until enabled + await page.waitForFunction( + sel => { + const el = document.querySelector(sel); + if (!el) return false; + if (el.disabled) return false; + if (el.getAttribute("aria-disabled") === "true") return false; + if (el.classList.contains("disabled")) return false; + return true; + }, + { timeout: 5000 }, + "#print" + ); await page.click("#print"); clickedPrint = true; console.log("Clicked print button on main page"); @@ -252,6 +271,18 @@ async function getPDFFromURL(url) { for (const frame of page.frames()) { try { await frame.waitForSelector("#print", { visible: true, timeout: 1000 }); + await frame.waitForFunction( + sel => { + const el = document.querySelector(sel); + if (!el) return false; + if (el.disabled) return false; + if (el.getAttribute("aria-disabled") === "true") return false; + if (el.classList.contains("disabled")) return false; + return true; + }, + { timeout: 5000 }, + "#print" + ); await frame.click("#print"); clickedPrint = true; console.log("Clicked print button inside iframe"); @@ -261,7 +292,7 @@ async function getPDFFromURL(url) { await sleep(250); // avoid hot-looping } - //await sleep(POST_CLICK_MS); + if (clickedPrint) { console.log("Waiting for printable layout to finish…"); @@ -278,23 +309,23 @@ async function getPDFFromURL(url) { } // Svelte stabilization - console.log("Waiting for Svelte DOM stability…"); - await page.waitForFunction(() => { - const now = performance.now(); - if (!window.__lastMutationTime) window.__lastMutationTime = now; - - if (!window.__mutationObserverInstalled) { - const observer = new MutationObserver(() => { - window.__lastMutationTime = performance.now(); - }); - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: true, - characterData: true - }); - window.__mutationObserverInstalled = true; - } + console.log("Waiting for Svelte DOM stability…"); + await page.waitForFunction(() => { + const now = performance.now(); + if (!window.__lastMutationTime) window.__lastMutationTime = now; + + if (!window.__mutationObserverInstalled) { + const observer = new MutationObserver(() => { + window.__lastMutationTime = performance.now(); + }); + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + characterData: true + }); + window.__mutationObserverInstalled = true; + } return performance.now() - window.__lastMutationTime > 600; }, { timeout: 20000 }); From facbbe1acffe48d86037374b01efdb6cdda464b6 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 8 Dec 2025 12:00:48 -0800 Subject: [PATCH 092/105] add puppeteer logs --- generateHandler.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/generateHandler.js b/generateHandler.js index f764690..21295d8 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -227,6 +227,14 @@ async function performGenerateFunction(url) { //const browser = await puppeteer.launch(); const page = await browser.newPage(); + page.on('console', msg => { + console.log('Browser logs:', msg.type(), msg.text()); + }); + + page.on('request', req => { + console.log('Request:', req.url(), req.failure()); + }); + // Set the HTML content of the page await page.goto(url,{ timeout: 200000, // 60 seconds From 14bfc0774a37224cff1e7ecce6aa7cfa272200b0 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 8 Dec 2025 15:15:08 -0800 Subject: [PATCH 093/105] Add token and username from incoming generate call --- generateHandler.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/generateHandler.js b/generateHandler.js index 21295d8..b0eb49a 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -106,6 +106,7 @@ async function generateNewTemplate(req, res) { params = { ...params, ...configOpt }; const template_id = params["formId"]; const attachment_Id = params["attachmentId"]; + const rawToken = params["token"]; if (!template_id) { return res .status(400) @@ -180,7 +181,7 @@ async function generateNewTemplate(req, res) { const saveDataForLater = JSON.stringify(formJson) const id = await storeData(saveDataForLater); const endPointForGenerate = process.env.GENERATE_KILN_URL + "?jsonId=" + id; - const isGenerateSuccess = await performGenerateFunction(endPointForGenerate); + const isGenerateSuccess = await performGenerateFunction(endPointForGenerate,rawToken,username); deleteData(id); //if successfully generated send back response @@ -209,7 +210,7 @@ async function generateNewTemplate(req, res) { } } -async function performGenerateFunction(url) { +async function performGenerateFunction(url,token,username) { try { const browser = await puppeteer.launch({ @@ -242,6 +243,14 @@ async function performGenerateFunction(url) { }); // Replace with your actual URL console.log('Page loaded.'); + if (token) { + cookies.push({name: 'token',value: token}); + } + + if (username) { + cookies.push({ name: 'username', value: username}); + } + // Step 2: Wait for the button to be available await page.waitForSelector('#generate',{ timeout: 200000 }); // Use the actual selector From d06838eda546f91a32d7ca84dcc1367cdbd2e25d Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 8 Dec 2025 15:42:54 -0800 Subject: [PATCH 094/105] Extract token from incoming Bearer token --- generateHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generateHandler.js b/generateHandler.js index b0eb49a..c8ce5c0 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -106,7 +106,8 @@ async function generateNewTemplate(req, res) { params = { ...params, ...configOpt }; const template_id = params["formId"]; const attachment_Id = params["attachmentId"]; - const rawToken = params["token"]; + const authHeader = req.get('Authorization') || ''; + const rawToken = authHeader.split(' ')[0] === 'Bearer'? authHeader.split(' ')[1] : authHeader || null; if (!template_id) { return res .status(400) From 927a48d4f2056d52b80161f279a4aaaf3503e59a Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 8 Dec 2025 15:58:05 -0800 Subject: [PATCH 095/105] added set cookies --- generateHandler.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/generateHandler.js b/generateHandler.js index c8ce5c0..2976d08 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -243,6 +243,7 @@ async function performGenerateFunction(url,token,username) { waitUntil: 'domcontentloaded', // You can also use 'networkidle2' or 'domcontentloaded' }); // Replace with your actual URL console.log('Page loaded.'); + const cookies = []; if (token) { cookies.push({name: 'token',value: token}); @@ -251,6 +252,9 @@ async function performGenerateFunction(url,token,username) { if (username) { cookies.push({ name: 'username', value: username}); } + if (cookies.length) { + await browser.setCookie(...cookies); + } // Step 2: Wait for the button to be available await page.waitForSelector('#generate',{ timeout: 200000 }); // Use the actual selector From 34781c583924d9a8562da21219dd66ce002ed845 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 8 Dec 2025 16:20:04 -0800 Subject: [PATCH 096/105] Add missing parameteres to set cookies --- generateHandler.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/generateHandler.js b/generateHandler.js index 2976d08..116efe1 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -246,11 +246,21 @@ async function performGenerateFunction(url,token,username) { const cookies = []; if (token) { - cookies.push({name: 'token',value: token}); + cookies.push({ + name: 'token', + value: token, + domain: appUrl.hostname, + path: '/' + }); } if (username) { - cookies.push({ name: 'username', value: username}); + cookies.push({ + name: 'username', + value: username, + domain: appUrl.hostname, + path: '/' + }); } if (cookies.length) { await browser.setCookie(...cookies); From 481afa732ec11703457c0a58d08a8d596f1849b5 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 8 Dec 2025 16:25:47 -0800 Subject: [PATCH 097/105] update URL refrence --- generateHandler.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/generateHandler.js b/generateHandler.js index 116efe1..864ad7b 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -249,8 +249,7 @@ async function performGenerateFunction(url,token,username) { cookies.push({ name: 'token', value: token, - domain: appUrl.hostname, - path: '/' + url: appUrl.origin }); } @@ -258,8 +257,7 @@ async function performGenerateFunction(url,token,username) { cookies.push({ name: 'username', value: username, - domain: appUrl.hostname, - path: '/' + url: appUrl.origin }); } if (cookies.length) { From 0a5fd4244bf3d3e0671f8c3a9a3efcc7994a5e62 Mon Sep 17 00:00:00 2001 From: David Okulski Date: Mon, 8 Dec 2025 16:37:29 -0800 Subject: [PATCH 098/105] Add missing URL reference --- generateHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/generateHandler.js b/generateHandler.js index 864ad7b..b91a8fd 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -243,6 +243,7 @@ async function performGenerateFunction(url,token,username) { waitUntil: 'domcontentloaded', // You can also use 'networkidle2' or 'domcontentloaded' }); // Replace with your actual URL console.log('Page loaded.'); + const appUrl = new URL(url); const cookies = []; if (token) { From d4ea83fad18e0d8fbc0bf273d2db786011c1947a Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 9 Dec 2025 10:22:40 -0800 Subject: [PATCH 099/105] add officeName to params if missing --- generateHandler.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/generateHandler.js b/generateHandler.js index b91a8fd..ce6b2cd 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -170,7 +170,13 @@ async function generateNewTemplate(req, res) { return res.status(400).send({ error: getErrorMessage("INVALID_AREA") }); } } - + + const icmOfficeName = icm_metadata?.["Office Name"]; + const requestOfficeName = typeof params.OfficeName === "string" ? params.OfficeName.trim() : ""; + if (!requestOfficeName && icmOfficeName) { + params.OfficeName = icmOfficeName; + } + const formJson = await constructFormJson(template_id, params); if (formJson != null) { From 2aae1fc9e2f0e2885a3f40a7b157732a20e5d13d Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 9 Dec 2025 11:33:04 -0800 Subject: [PATCH 100/105] Add logging for officeName in generate call --- generateHandler.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/generateHandler.js b/generateHandler.js index ce6b2cd..89d7ece 100644 --- a/generateHandler.js +++ b/generateHandler.js @@ -170,12 +170,15 @@ async function generateNewTemplate(req, res) { return res.status(400).send({ error: getErrorMessage("INVALID_AREA") }); } } - + const icmOfficeName = icm_metadata?.["Office Name"]; const requestOfficeName = typeof params.OfficeName === "string" ? params.OfficeName.trim() : ""; + console.log('RequestOfficeName:', requestOfficeName); + console.log('IcmOfficeName:', icmOfficeName); if (!requestOfficeName && icmOfficeName) { params.OfficeName = icmOfficeName; } + console.log("Params:",params); const formJson = await constructFormJson(template_id, params); From 18a2d068f38b6d81ea58e1824f5eb4e5f254588f Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 9 Dec 2025 13:00:19 -0800 Subject: [PATCH 101/105] add logging to PDF generate --- generatePDFHandler.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index 9196791..d349ae1 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -104,6 +104,14 @@ async function generatePDFFromHTML(req, res) { ], }); const page = await browser.newPage(); + + page.on('console', msg => { + console.log('Browser logs:', msg.type(), msg.text()); + }); + + page.on('request', req => { + console.log('Request:', req.url(), req.failure()); + }); // Set the HTML content of the page await page.setContent(htmlContent, { waitUntil: 'load' }); From 8d892281545b6c557baeb46f0741bea860174f2a Mon Sep 17 00:00:00 2001 From: David Okulski Date: Tue, 9 Dec 2025 13:07:28 -0800 Subject: [PATCH 102/105] added logs --- generatePDFHandler.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/generatePDFHandler.js b/generatePDFHandler.js index d349ae1..ea0b166 100644 --- a/generatePDFHandler.js +++ b/generatePDFHandler.js @@ -188,6 +188,14 @@ async function getPDFFromURL(url) { //const browser = await puppeteer.launch(); const page = await browser.newPage(); + page.on('console', msg => { + console.log('Browser logs:', msg.type(), msg.text()); + }); + + page.on('request', req => { + console.log('Request:', req.url(), req.failure()); + }); + // Optional: Adjust the page's viewport for better PDF layout await page.setViewport({ width: 1280, height: 800 }); From 6191e45da92a315459fdb7340c91f797269a43a8 Mon Sep 17 00:00:00 2001 From: JohYoshidaBCGov Date: Wed, 17 Dec 2025 15:03:10 -0800 Subject: [PATCH 103/105] Add HR0080R to JSON XML conversion dictionary --- dictionary/jsonXmlConversion.js | 153 ++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index e3045e6..8f37026 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -972,6 +972,159 @@ const formExceptions = { } } }, + "HR0080R": { + "rootName": "Replacement", + "subRoots": ["FormInstance"], + "wrapperTags": [], + "allowCheckboxWithNoChange": [], + "omitFields": [], + "addFields": { + "FormInstanceId": null, + "CaseWorkerId": null, + "SRNum": null, + "CaseId": null, + "Category": null, + "CaseOfficeAddrCity": null, + "CaseOfficeAddrCountry": null, + "CaseOfficeAddrLine1": null, + "CaseOfficeAddrLine2": null, + "CaseOfficeAddrLine3": null, + "CaseOfficeAddrPostalCode": null, + "CaseOfficeAddrProvince": null, + "CaseType": null, + "SRId": null, + "Template": null, + "TemplateLocation": null, + "CaseName": null, + "CaseOfficeAddrComplete": null, + "CaseOfficeAddrCompleteCity": null, + "ListOfContact": { + "Contact":{ + "CaseRelTypeCode": null, + "EmailAddress": null, + "PHN": null, + "PersonUId": null + //Reminging fields + } + }, + "ListOfCase": { + "Case": { + "ListOfICMSSAAExpenseVBC": { + "ICMSSAAExpenseVBC": { + "CaseId": null, + "CaseRelTypeCode": null, + "PhoneBasicRate": null, + "RoomandBoardpaidtofamily": null + } + }, + "CaseNum": null, + "ClosedDate": null, + "Name": null, + "Status": null, + "Type": null, + "LastName2": null, + "CloseReason": null, + "LegacyFileNumber": null, + "ReopenedDate": null, + "ListOfApplicant": { + "Applicant": { + //Only hierarchy neeed to be added + "ListOfICMSSAAApplicantAdditionalIncome": { + "ICMSSAAApplicantAdditionalIncome": { + // "additional-income-amount-87fd69aa" + // "additional-income-type-c915324e" + } + }, + "ListOfICMSSAAApplicantIncome": { + "ICMSSAAApplicantIncome": { + "Boarder": null, + "SupportMaintenance": null, + "WorkersCompensation": null, + "EmploymentInsurance": null, + "ExtendedSpousesAllowance": null, + "CPPQPPOverride": null, + "CaseId": null, + "InterestDividends": null + + } + } + + }, + "ContactFullName": null, + "Age": null, + "Role": null, + "PHN": null, + "MovedFrmProvince": null, + "SelfIdentified": null, + "YRIndMet": null, + "DateSeparated": null, + "PossiblyAboriginal": null, + "AboriginalOriginCode": null, + "AboriginBand": null, + "DivorcedDate": null, + "ContactFullNameMiddle": null + }, + "ListOfSpouse": { + "Spouse": { + //Only hierarchy neeed to be added + "ListOfICMSSAASpouseAdditionalIncome": { + "ICMSSAASpouseAdditionalIncome": { + // "additional-income-fa18c691" + // "additional-income-type-f4d77833" + + } + }, + "ListOfICMSSAASpouseIncome": { + "ICMSSAASpouseIncome": { + "WorkersCompensation": null, + "ExtendedSpousesAllowance": null, + "CPPQPPOverride": null, + "CaseId": null, + "InterestDividends": null + + } + }, + "ContactFullName": null, + "Gender": null, + "Age": null, + "Role": null, + "PHN": null, + "HomePhone": null, + "MovedFrmProvince": null, + "SelfIdentified": null, + "AboriginBand": null, + "AboriginalOriginCode": null, + "WorkPhone": null, + "ImmigrationEntryType": null, + "CurrentImmigrationType": null, + "YRIndMet": null, + "DateSeparated": null, + "PossiblyAboriginal": null, + "ContactFullNameMiddle": null, + + } + }, + "ListOfDependent": null, + "ListOfOccupant": null, + "ListOfICMCaseAddress": { + "ICMCaseAddress": { + "Country": null, + "CompleteAddressCity": null, + + } + } + } + } + }, + "versions": { + "1": { + "omitFields": [] + }, + "2": { + "omitFields": [] + } + } + }, "HR3687E": { "rootName": "ListOfDtFormInstanceLw", "subRoots": ["FormInstance"], From 46bd630e92a3cf7474559d1d8beda42e59f25ade Mon Sep 17 00:00:00 2001 From: JohYoshidaBCGov Date: Thu, 18 Dec 2025 09:42:27 -0800 Subject: [PATCH 104/105] Remove comments --- dictionary/jsonXmlConversion.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index 8f37026..0adac54 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -1004,7 +1004,6 @@ const formExceptions = { "EmailAddress": null, "PHN": null, "PersonUId": null - //Reminging fields } }, "ListOfCase": { @@ -1028,11 +1027,8 @@ const formExceptions = { "ReopenedDate": null, "ListOfApplicant": { "Applicant": { - //Only hierarchy neeed to be added "ListOfICMSSAAApplicantAdditionalIncome": { "ICMSSAAApplicantAdditionalIncome": { - // "additional-income-amount-87fd69aa" - // "additional-income-type-c915324e" } }, "ListOfICMSSAAApplicantIncome": { @@ -1066,11 +1062,8 @@ const formExceptions = { }, "ListOfSpouse": { "Spouse": { - //Only hierarchy neeed to be added "ListOfICMSSAASpouseAdditionalIncome": { "ICMSSAASpouseAdditionalIncome": { - // "additional-income-fa18c691" - // "additional-income-type-f4d77833" } }, From 383f6f0aa913b8ee9992d84bd3e88782e4f430b4 Mon Sep 17 00:00:00 2001 From: JohYoshidaBCGov Date: Thu, 18 Dec 2025 13:46:53 -0800 Subject: [PATCH 105/105] Add missing HR3687 data extraction tags --- dictionary/jsonXmlConversion.js | 211 +++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-) diff --git a/dictionary/jsonXmlConversion.js b/dictionary/jsonXmlConversion.js index e3045e6..880febb 100644 --- a/dictionary/jsonXmlConversion.js +++ b/dictionary/jsonXmlConversion.js @@ -978,7 +978,216 @@ const formExceptions = { "wrapperTags": [], "allowCheckboxWithNoChange": [], "omitFields": [], - "addFields": {}, + "addFields": { + "FormInstanceId": null, + "CreatedBy": null, + "SRNum": null, + "BenefitPlanId": null, + "CaseId": null, + "Category": null, + "ContactId": null, + "DocFileName": null, + "DocFileSize": null, + "DocFileSrcType": null, + "FinalFlag": null, + "ICPId": null, + "MISCaseNum": null, + "SRId": null, + "SubCategory": null, + "Template": null, + "RenderFlatFlag": null, + "Bool01": null, + "Bool02": null, + "Bool03": null, + "Bool04": null, + "Bool05": null, + "TemplateLocation": null, + "Date01": null, + "Date02": null, + "Date03": null, + "Date04": null, + "Date05": null, + "String01": null, + "String02": null, + "String03": null, + "String04": null, + "String05": null, + "String06": null, + "String07": null, + "String08": null, + "String09": null, + "String10": null, + "String11": null, + "String12": null, + "AuxName": null, + "AuxType": null, + "Number01": null, + "Number02": null, + "Number03": null, + "Number04": null, + "Number05": null, + "Bool06": null, + "Bool07": null, + "Bool08": null, + "Bool09": null, + "Bool10": null, + "Date06": null, + "Date07": null, + "Bool11": null, + "Bool12": null, + "Bool13": null, + "Bool14": null, + "Bool15": null, + "Bool16": null, + "Bool17": null, + "Bool18": null, + "Bool19": null, + "Bool20": null, + "Bool21": null, + "Bool22": null, + "Bool23": null, + "Bool24": null, + "Bool25": null, + "Bool26": null, + "Bool27": null, + "Bool28": null, + "Bool29": null, + "Bool30": null, + "Bool31": null, + "Bool32": null, + "Bool33": null, + "Bool34": null, + "Bool35": null, + "Bool36": null, + "Bool37": null, + "Bool38": null, + "Bool39": null, + "Bool40": null, + "Bool41": null, + "Bool42": null, + "Bool43": null, + "Bool44": null, + "Bool45": null, + "Bool46": null, + "Bool47": null, + "Bool48": null, + "Bool49": null, + "Bool50": null, + "String13": null, + "String14": null, + "String15": null, + "String16": null, + "String17": null, + "String18": null, + "String19": null, + "String20": null, + "String21": null, + "String22": null, + "String23": null, + "String24": null, + "String25": null, + "String26": null, + "String27": null, + "String28": null, + "String29": null, + "String30": null, + "String31": null, + "String32": null, + "String33": null, + "String34": null, + "String35": null, + "String36": null, + "String37": null, + "String38": null, + "String39": null, + "String40": null, + "String41": null, + "String42": null, + "String43": null, + "String44": null, + "String45": null, + "String46": null, + "String47": null, + "String48": null, + "String49": null, + "String50": null, + "String51": null, + "String52": null, + "String53": null, + "String54": null, + "String55": null, + "String56": null, + "String57": null, + "String58": null, + "String59": null, + "String60": null, + "String61": null, + "String62": null, + "String63": null, + "String64": null, + "String65": null, + "ListOfContact": { + "Contact": { + "BirthDate": null, + "CaseRelTypeCode": null, + "ClientIDNumber": null, + "FullName": null, + "FullNameMiddle": null, + "MF": null, + "MiddleName": null, + "MiddleNameInitial": null, + "SIN": null, + "WorkPhone": null, + "ListOfContactAddresses": null + }, + "ListOfCase": { + "Case": { + "PrimaryOrganizationName": null, + "Name": null, + "Status": null, + "AssignToFN": null, + "AssignToLN": null, + "ListOfICMCaseAddress": null + } + }, + "ListOfEmployee": { + "Employee": { + "EMailAddr": null, + "Emp": null, + "Fax": null, + "FirstName": null, + "Login": null, + "FullName": null, + "FullNameMiddle": null, + "JobTitle": null, + "LastName": null, + "MiddleName": null, + "MiddleNameInitial": null, + "WorkPhoneNumber": null + } + }, + "ListOfAttCreatedBy": { + "AttCreatedBy": { + "Id": null, + "Emp": null, + "FullName": null, + "JobTitle": null + } + }, + "ListOfOffice": { + "Office": { + "OfficeRegion": null, + "OfficeAddressLine2": null, + "OfficeAddressLine3": null, + "OfficeAddressCountry": null, + "OfficeAddressUnit": null, + "OfficePhone": null, + "OfficeFax": null, + "OfficeAddressCompleteCity": null + } + } + } + }, "versions": { "1": { "omitFields": []