From 93ed05b67a0895394d1dc50e83186a739e693f17 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 17 May 2024 13:33:27 -0400 Subject: [PATCH 1/7] Rename frfFormModified parameter to frfModified in creating a new PRF submission --- app/client/src/routes/submissions.tsx | 4 ++-- app/server/app/utilities/formio.js | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/client/src/routes/submissions.tsx b/app/client/src/routes/submissions.tsx index 4bb2779a..2ecff827 100644 --- a/app/client/src/routes/submissions.tsx +++ b/app/client/src/routes/submissions.tsx @@ -440,7 +440,7 @@ function PRF2022Submission(props: { rebate: Rebate }) { comboKey: frf.bap.comboKey, rebateId: frf.bap.rebateId, // CSB Rebate ID (6 digits) frfReviewItemId: frf.bap.reviewItemId, // CSB Rebate ID with form/version ID (9 digits) - frfFormModified: frf.bap.modified, + frfModified: frf.bap.modified, }) .then((_res) => { navigate(`/prf/2022/${frf.bap?.rebateId}`); @@ -1084,7 +1084,7 @@ function PRF2023Submission(props: { rebate: Rebate }) { comboKey: frf.bap.comboKey, rebateId: frf.bap.rebateId, // CSB Rebate ID (6 digits) frfReviewItemId: frf.bap.reviewItemId, // CSB Rebate ID with form/version ID (9 digits) - frfFormModified: frf.bap.modified, + frfModified: frf.bap.modified, }) .then((_res) => { navigate(`/prf/2023/${frf.bap?.rebateId}`); diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 0d6116d0..d8a1b56c 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -29,8 +29,8 @@ function getComboKeyFieldName({ rebateYear }) { return rebateYear === "2022" ? "bap_hidden_entity_combo_key" : rebateYear === "2023" - ? "_bap_entity_combo_key" - : ""; + ? "_bap_entity_combo_key" + : ""; } /** @@ -41,8 +41,8 @@ function getRebateIdFieldName({ rebateYear }) { return rebateYear === "2022" ? "hidden_bap_rebate_id" : rebateYear === "2023" - ? "_bap_rebate_id" - : ""; + ? "_bap_rebate_id" + : ""; } /** @@ -88,7 +88,7 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { * comboKey: ?string * rebateId: ?string * frfReviewItemId: ?string - * frfFormModified: ?string + * frfModified: ?string * }} */ const { email, @@ -98,7 +98,7 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { comboKey, rebateId, frfReviewItemId, - frfFormModified, + frfModified, } = req.body; const { @@ -162,7 +162,7 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { return { data: { bap_hidden_entity_combo_key: comboKey, - hidden_application_form_modified: frfFormModified, + hidden_application_form_modified: frfModified, hidden_current_user_email: email, hidden_current_user_title: title, hidden_current_user_name: name, @@ -379,7 +379,7 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { return { data: { - _application_form_modified: frfFormModified, + _application_form_modified: frfModified, _bap_entity_combo_key: comboKey, _bap_rebate_id: rebateId, _user_email: email, @@ -517,10 +517,10 @@ function uploadS3FileMetadata({ rebateYear, req, res }) { formType === "frf" ? "CSB Application" : formType === "prf" - ? "CSB Payment Request" - : formType === "cof" - ? "CSB Close Out" - : "CSB"; + ? "CSB Payment Request" + : formType === "cof" + ? "CSB Close Out" + : "CSB"; const logMessage = `User with email '${mail}' attempted to upload a file when the ` + From 94e4a4ac3ccaf2f8fa210a02b93c853b59f0758e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 17 May 2024 13:35:58 -0400 Subject: [PATCH 2/7] Move logic for creating of CRF submission out of formio 2022 route and into formio utility file --- app/server/app/routes/formio2022.js | 195 +-------------------- app/server/app/utilities/formio.js | 251 ++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 190 deletions(-) diff --git a/app/server/app/routes/formio2022.js b/app/server/app/routes/formio2022.js index b3428f7d..1113e00b 100644 --- a/app/server/app/routes/formio2022.js +++ b/app/server/app/routes/formio2022.js @@ -4,7 +4,6 @@ const ObjectId = require("mongodb").ObjectId; const { axiosFormio, formUrl, - submissionPeriodOpen, formioCSBMetadata, formioExampleRebateId, formioNoUserAccess, @@ -14,10 +13,7 @@ const { storeBapComboKeys, verifyMongoObjectId, } = require("../middleware"); -const { - getBapDataFor2022CRF, - checkFormSubmissionPeriodAndBapStatus, -} = require("../utilities/bap"); +const { checkFormSubmissionPeriodAndBapStatus } = require("../utilities/bap"); const { uploadS3FileMetadata, downloadS3FileMetadata, @@ -34,6 +30,9 @@ const { deletePRFSubmission, // fetchCRFSubmissions, + createCRFSubmission, + // fetchCRFSubmission, + // updateCRFSubmission, } = require("../utilities/formio"); const log = require("../utilities/logger"); @@ -124,191 +123,7 @@ router.get("/crf-submissions", storeBapComboKeys, (req, res) => { // --- post a new 2022 CRF submission to Formio router.post("/crf-submission", storeBapComboKeys, (req, res) => { - const { bapComboKeys, body } = req; - const { mail } = req.user; - const { - email, - title, - name, - entity, - comboKey, - rebateId, - frfReviewItemId, - prfReviewItemId, - prfModified, - } = body; - - // NOTE: included to support EPA API scan - if (Object.keys(body).length === 0) { - return res.json({}); - } - - if (!submissionPeriodOpen["2022"].crf) { - const errorStatus = 400; - const errorMessage = `CSB Close Out form enrollment period is closed.`; - return res.status(errorStatus).json({ message: errorMessage }); - } - - if (!bapComboKeys.includes(comboKey)) { - const logMessage = - `User with email '${mail}' attempted to post a new CRF submission ` + - `without a matching BAP combo key.`; - log({ level: "error", message: logMessage, req }); - - const errorStatus = 401; - const errorMessage = `Unauthorized.`; - return res.status(errorStatus).json({ message: errorMessage }); - } - - const { - UNIQUE_ENTITY_ID__c, - ENTITY_EFT_INDICATOR__c, - ELEC_BUS_POC_EMAIL__c, - ALT_ELEC_BUS_POC_EMAIL__c, - GOVT_BUS_POC_EMAIL__c, - ALT_GOVT_BUS_POC_EMAIL__c, - } = entity; - - return getBapDataFor2022CRF(req, frfReviewItemId, prfReviewItemId) - .then( - ({ frf2022RecordQuery, prf2022RecordQuery, prf2022busRecordsQuery }) => { - const { - Fleet_Name__c, - Fleet_Street_Address__c, - Fleet_City__c, - Fleet_State__c, - Fleet_Zip__c, - Fleet_Contact_Name__c, - Fleet_Contact_Title__c, - Fleet_Contact_Phone__c, - Fleet_Contact_Email__c, - School_District_Contact__r, - } = frf2022RecordQuery[0]; - - const { - CSB_NCES_ID__c, - Primary_Applicant__r, - Alternate_Applicant__r, - Applicant_Organization__r, - CSB_School_District__r, - School_District_Prioritized__c, - Total_Rebate_Funds_Requested_PO__c, - Total_Bus_And_Infrastructure_Rebate__c, - Total_Infrastructure_Funds__c, - Num_Of_Buses_Requested_From_Application__c, - Total_Price_All_Buses__c, - Total_Bus_Rebate_Amount__c, - Total_All_Eligible_Infrastructure_Costs__c, - Total_Infrastructure_Rebate__c, - Total_Level_2_Charger_Costs__c, - Total_DC_Fast_Charger_Costs__c, - Total_Other_Infrastructure_Costs__c, - } = prf2022RecordQuery[0]; - - const busInfo = prf2022busRecordsQuery.map((record) => ({ - busNum: record.Rebate_Item_num__c, - oldBusNcesDistrictId: CSB_NCES_ID__c, - oldBusVin: record.CSB_VIN__c, - oldBusModelYear: record.CSB_Model_Year__c, - oldBusFuelType: record.CSB_Fuel_Type__c, - oldBusEstimatedRemainingLife: record.Old_Bus_Estimated_Remaining_Life__c, // prettier-ignore - oldBusExclude: record.Old_Bus_Exclude__c, - hidden_prf_oldBusExclude: record.Old_Bus_Exclude__c, - newBusDealer: record.Related_Line_Item__r?.Vendor_Name__c, - newBusFuelType: record.New_Bus_Fuel_Type__c, - hidden_prf_newBusFuelType: record.New_Bus_Fuel_Type__c, - newBusMake: record.New_Bus_Make__c, - hidden_prf_newBusMake: record.New_Bus_Make__c, - newBusMakeOther: record.CSB_Manufacturer_if_Other__c, - hidden_prf_newBusMakeOther: record.CSB_Manufacturer_if_Other__c, - newBusModel: record.New_Bus_Model__c, - hidden_prf_newBusModel: record.New_Bus_Model__c, - newBusModelYear: record.New_Bus_Model_Year__c, - hidden_prf_newBusModelYear: record.New_Bus_Model_Year__c, - newBusGvwr: record.New_Bus_GVWR__c, - hidden_prf_newBusGvwr: record.New_Bus_GVWR__c, - newBusPurchasePrice: record.New_Bus_Purchase_Price__c, - hidden_prf_newBusPurchasePrice: record.New_Bus_Purchase_Price__c, - hidden_prf_rebate: record.New_Bus_Rebate_Amount__c, - })); - - const submission = { - data: { - bap_hidden_entity_combo_key: comboKey, - hidden_prf_modified: prfModified, - hidden_current_user_email: email, - hidden_current_user_title: title, - hidden_current_user_name: name, - hidden_bap_rebate_id: rebateId, - hidden_sam_uei: UNIQUE_ENTITY_ID__c, - hidden_sam_efti: ENTITY_EFT_INDICATOR__c || "0000", - hidden_sam_elec_bus_poc_email: ELEC_BUS_POC_EMAIL__c, - hidden_sam_alt_elec_bus_poc_email: ALT_ELEC_BUS_POC_EMAIL__c, - hidden_sam_govt_bus_poc_email: GOVT_BUS_POC_EMAIL__c, - hidden_sam_alt_govt_bus_poc_email: ALT_GOVT_BUS_POC_EMAIL__c, - hidden_bap_district_id: CSB_NCES_ID__c, - hidden_bap_district_name: CSB_School_District__r?.Name, - hidden_bap_primary_fname: Primary_Applicant__r?.FirstName, - hidden_bap_primary_lname: Primary_Applicant__r?.LastName, - hidden_bap_primary_title: Primary_Applicant__r?.Title, - hidden_bap_primary_phone_number: Primary_Applicant__r?.Phone, - hidden_bap_primary_email: Primary_Applicant__r?.Email, - hidden_bap_alternate_fname: Alternate_Applicant__r?.FirstName || "", - hidden_bap_alternate_lname: Alternate_Applicant__r?.LastName || "", - hidden_bap_alternate_title: Alternate_Applicant__r?.Title || "", - hidden_bap_alternate_phone_number: Alternate_Applicant__r?.Phone || "", // prettier-ignore - hidden_bap_alternate_email: Alternate_Applicant__r?.Email || "", - hidden_bap_org_name: Applicant_Organization__r?.Name, - hidden_bap_fleet_name: Fleet_Name__c, - hidden_bap_fleet_address: Fleet_Street_Address__c, - hidden_bap_fleet_city: Fleet_City__c, - hidden_bap_fleet_state: Fleet_State__c, - hidden_bap_fleet_zip: Fleet_Zip__c, - hidden_bap_fleet_contact_name: Fleet_Contact_Name__c, - hidden_bap_fleet_contact_title: Fleet_Contact_Title__c, - hidden_bap_fleet_phone: Fleet_Contact_Phone__c, - hidden_bap_fleet_email: Fleet_Contact_Email__c, - hidden_bap_prioritized: School_District_Prioritized__c, - hidden_bap_requested_funds: Total_Rebate_Funds_Requested_PO__c, - hidden_bap_received_funds: Total_Bus_And_Infrastructure_Rebate__c, - hidden_bap_prf_infra_max_rebate: Total_Infrastructure_Funds__c, - hidden_bap_buses_requested_app: Num_Of_Buses_Requested_From_Application__c, // prettier-ignore - hidden_bap_total_bus_costs_prf: Total_Price_All_Buses__c, - hidden_bap_total_bus_rebate_received: Total_Bus_Rebate_Amount__c, - hidden_bap_total_infra_costs_prf: Total_All_Eligible_Infrastructure_Costs__c, // prettier-ignore - hidden_bap_total_infra_rebate_received: Total_Infrastructure_Rebate__c, // prettier-ignore - hidden_bap_total_infra_level2_charger: Total_Level_2_Charger_Costs__c, // prettier-ignore - hidden_bap_total_infra_dc_fast_charger: Total_DC_Fast_Charger_Costs__c, // prettier-ignore - hidden_bap_total_infra_other_costs: Total_Other_Infrastructure_Costs__c, // prettier-ignore - hidden_bap_district_contact_fname: School_District_Contact__r?.FirstName, // prettier-ignore - hidden_bap_district_contact_lname: School_District_Contact__r?.LastName, // prettier-ignore - busInfo, - }, - /** Add custom metadata to track formio submissions from wrapper. */ - metadata: { - ...formioCSBMetadata, - }, - state: "draft", - }; - - axiosFormio(req) - .post(`${formioCRFUrl}/submission`, submission) - .then((axiosRes) => axiosRes.data) - .then((submission) => res.json(submission)) - .catch((error) => { - // NOTE: error is logged in axiosFormio response interceptor - const errorStatus = error.response?.status || 500; - const errorMessage = `Error posting Formio Close Out form submission.`; - return res.status(errorStatus).json({ message: errorMessage }); - }); - }, - ) - .catch((error) => { - // NOTE: logged in bap verifyBapConnection - const errorStatus = 500; - const errorMessage = `Error getting data for a new Close Out form submission from the BAP.`; - return res.status(errorStatus).json({ message: errorMessage }); - }); + createCRFSubmission({ rebateYear, req, res }); }); // --- get an existing 2022 CRF's schema and submission data from Formio diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index d8a1b56c..98d01247 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -15,6 +15,7 @@ const { getBapFormSubmissionsStatuses, getBapDataFor2022PRF, getBapDataFor2023PRF, + getBapDataFor2022CRF, checkFormSubmissionPeriodAndBapStatus, } = require("../utilities/bap"); const log = require("./logger"); @@ -452,6 +453,200 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { } } +/** + * @param {Object} param + * @param {'2022' | '2023'} param.rebateYear + * @param {express.Request} param.req + * @param {express.Response} param.res + */ +function fetchDataForCRFSubmission({ rebateYear, req, res }) { + /** @type {{ + * email: string + * title: string + * name: string + * entity: import('./bap.js').BapSamEntity + * comboKey: ?string + * rebateId: ?string + * frfReviewItemId: ?string + * prfReviewItemId: ?string + * prfModified: ?string + * }} */ + const { + email, + title, + name, + entity, + comboKey, + rebateId, + frfReviewItemId, + prfReviewItemId, + prfModified, + } = req.body; + + const { + UNIQUE_ENTITY_ID__c, + ENTITY_EFT_INDICATOR__c, + ELEC_BUS_POC_EMAIL__c, + ALT_ELEC_BUS_POC_EMAIL__c, + GOVT_BUS_POC_EMAIL__c, + ALT_GOVT_BUS_POC_EMAIL__c, + } = entity; + + if (rebateYear === "2022") { + return getBapDataFor2022CRF(req, frfReviewItemId, prfReviewItemId) + .then((results) => { + const { + frf2022RecordQuery, + prf2022RecordQuery, + prf2022busRecordsQuery, + } = results; + + const { + Fleet_Name__c, + Fleet_Street_Address__c, + Fleet_City__c, + Fleet_State__c, + Fleet_Zip__c, + Fleet_Contact_Name__c, + Fleet_Contact_Title__c, + Fleet_Contact_Phone__c, + Fleet_Contact_Email__c, + School_District_Contact__r, + } = frf2022RecordQuery[0]; + + const { + CSB_NCES_ID__c, + Primary_Applicant__r, + Alternate_Applicant__r, + Applicant_Organization__r, + CSB_School_District__r, + School_District_Prioritized__c, + Total_Rebate_Funds_Requested_PO__c, + Total_Bus_And_Infrastructure_Rebate__c, + Total_Infrastructure_Funds__c, + Num_Of_Buses_Requested_From_Application__c, + Total_Price_All_Buses__c, + Total_Bus_Rebate_Amount__c, + Total_All_Eligible_Infrastructure_Costs__c, + Total_Infrastructure_Rebate__c, + Total_Level_2_Charger_Costs__c, + Total_DC_Fast_Charger_Costs__c, + Total_Other_Infrastructure_Costs__c, + } = prf2022RecordQuery[0]; + + const busInfo = prf2022busRecordsQuery.map((prf2022BusRecord) => { + const { + Rebate_Item_num__c, + CSB_VIN__c, + CSB_Model_Year__c, + CSB_Fuel_Type__c, + Old_Bus_Estimated_Remaining_Life__c, + Old_Bus_Exclude__c, + Related_Line_Item__r, + New_Bus_Fuel_Type__c, + New_Bus_Make__c, + CSB_Manufacturer_if_Other__c, + New_Bus_Model__c, + New_Bus_Model_Year__c, + New_Bus_GVWR__c, + New_Bus_Purchase_Price__c, + New_Bus_Rebate_Amount__c, + } = prf2022BusRecord; + + return { + busNum: Rebate_Item_num__c, + oldBusNcesDistrictId: CSB_NCES_ID__c, + oldBusVin: CSB_VIN__c, + oldBusModelYear: CSB_Model_Year__c, + oldBusFuelType: CSB_Fuel_Type__c, + oldBusEstimatedRemainingLife: Old_Bus_Estimated_Remaining_Life__c, + oldBusExclude: Old_Bus_Exclude__c, + hidden_prf_oldBusExclude: Old_Bus_Exclude__c, + newBusDealer: Related_Line_Item__r?.Vendor_Name__c, + newBusFuelType: New_Bus_Fuel_Type__c, + hidden_prf_newBusFuelType: New_Bus_Fuel_Type__c, + newBusMake: New_Bus_Make__c, + hidden_prf_newBusMake: New_Bus_Make__c, + newBusMakeOther: CSB_Manufacturer_if_Other__c, + hidden_prf_newBusMakeOther: CSB_Manufacturer_if_Other__c, + newBusModel: New_Bus_Model__c, + hidden_prf_newBusModel: New_Bus_Model__c, + newBusModelYear: New_Bus_Model_Year__c, + hidden_prf_newBusModelYear: New_Bus_Model_Year__c, + newBusGvwr: New_Bus_GVWR__c, + hidden_prf_newBusGvwr: New_Bus_GVWR__c, + newBusPurchasePrice: New_Bus_Purchase_Price__c, + hidden_prf_newBusPurchasePrice: New_Bus_Purchase_Price__c, + hidden_prf_rebate: New_Bus_Rebate_Amount__c, + }; + }); + + return { + data: { + bap_hidden_entity_combo_key: comboKey, + hidden_prf_modified: prfModified, + hidden_current_user_email: email, + hidden_current_user_title: title, + hidden_current_user_name: name, + hidden_bap_rebate_id: rebateId, + hidden_sam_uei: UNIQUE_ENTITY_ID__c, + hidden_sam_efti: ENTITY_EFT_INDICATOR__c || "0000", + hidden_sam_elec_bus_poc_email: ELEC_BUS_POC_EMAIL__c, + hidden_sam_alt_elec_bus_poc_email: ALT_ELEC_BUS_POC_EMAIL__c, + hidden_sam_govt_bus_poc_email: GOVT_BUS_POC_EMAIL__c, + hidden_sam_alt_govt_bus_poc_email: ALT_GOVT_BUS_POC_EMAIL__c, + hidden_bap_district_id: CSB_NCES_ID__c, + hidden_bap_district_name: CSB_School_District__r?.Name, + hidden_bap_primary_fname: Primary_Applicant__r?.FirstName, + hidden_bap_primary_lname: Primary_Applicant__r?.LastName, + hidden_bap_primary_title: Primary_Applicant__r?.Title, + hidden_bap_primary_phone_number: Primary_Applicant__r?.Phone, + hidden_bap_primary_email: Primary_Applicant__r?.Email, + hidden_bap_alternate_fname: Alternate_Applicant__r?.FirstName || "", + hidden_bap_alternate_lname: Alternate_Applicant__r?.LastName || "", + hidden_bap_alternate_title: Alternate_Applicant__r?.Title || "", + hidden_bap_alternate_phone_number: Alternate_Applicant__r?.Phone || "", // prettier-ignore + hidden_bap_alternate_email: Alternate_Applicant__r?.Email || "", + hidden_bap_org_name: Applicant_Organization__r?.Name, + hidden_bap_fleet_name: Fleet_Name__c, + hidden_bap_fleet_address: Fleet_Street_Address__c, + hidden_bap_fleet_city: Fleet_City__c, + hidden_bap_fleet_state: Fleet_State__c, + hidden_bap_fleet_zip: Fleet_Zip__c, + hidden_bap_fleet_contact_name: Fleet_Contact_Name__c, + hidden_bap_fleet_contact_title: Fleet_Contact_Title__c, + hidden_bap_fleet_phone: Fleet_Contact_Phone__c, + hidden_bap_fleet_email: Fleet_Contact_Email__c, + hidden_bap_prioritized: School_District_Prioritized__c, + hidden_bap_requested_funds: Total_Rebate_Funds_Requested_PO__c, + hidden_bap_received_funds: Total_Bus_And_Infrastructure_Rebate__c, + hidden_bap_prf_infra_max_rebate: Total_Infrastructure_Funds__c, + hidden_bap_buses_requested_app: Num_Of_Buses_Requested_From_Application__c, // prettier-ignore + hidden_bap_total_bus_costs_prf: Total_Price_All_Buses__c, + hidden_bap_total_bus_rebate_received: Total_Bus_Rebate_Amount__c, + hidden_bap_total_infra_costs_prf: Total_All_Eligible_Infrastructure_Costs__c, // prettier-ignore + hidden_bap_total_infra_rebate_received: Total_Infrastructure_Rebate__c, // prettier-ignore + hidden_bap_total_infra_level2_charger: Total_Level_2_Charger_Costs__c, // prettier-ignore + hidden_bap_total_infra_dc_fast_charger: Total_DC_Fast_Charger_Costs__c, // prettier-ignore + hidden_bap_total_infra_other_costs: Total_Other_Infrastructure_Costs__c, // prettier-ignore + hidden_bap_district_contact_fname: School_District_Contact__r?.FirstName, // prettier-ignore + hidden_bap_district_contact_lname: School_District_Contact__r?.LastName, // prettier-ignore + busInfo, + }, + /** Add custom metadata to track formio submissions from wrapper. */ + metadata: { ...formioCSBMetadata }, + state: "draft", + }; + }) + .catch((error) => { + // NOTE: logged in bap verifyBapConnection + const errorStatus = 500; + const errorMessage = `Error getting data for a new 2022 Close Out form submission from the BAP.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); + } +} + /** * @param {Object} param * @param {'2022' | '2023'} param.rebateYear @@ -1214,6 +1409,61 @@ function fetchCRFSubmissions({ rebateYear, req, res }) { }); } +/** + * @param {Object} param + * @param {'2022' | '2023'} param.rebateYear + * @param {express.Request} param.req + * @param {express.Response} param.res + */ +function createCRFSubmission({ rebateYear, req, res }) { + const { bapComboKeys, body } = req; + const { mail } = req.user; + const { comboKey } = body; + + // NOTE: included to support EPA API scan + if (Object.keys(body).length === 0) { + return res.json({}); + } + + const formioFormUrl = formUrl[rebateYear].crf; + + if (!formioFormUrl) { + const errorStatus = 400; + const errorMessage = `Formio form URL does not exist for ${rebateYear} CRF.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + if (!submissionPeriodOpen[rebateYear].crf) { + const errorStatus = 400; + const errorMessage = `${rebateYear} CSB Close Out form enrollment period is closed.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + if (!bapComboKeys.includes(comboKey)) { + const logMessage = + `User with email '${mail}' attempted to post a new ${rebateYear} ` + + `CRF submission without a matching BAP combo key.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + fetchDataForCRFSubmission({ rebateYear, req, res }).then((submission) => { + axiosFormio(req) + .post(`${formioFormUrl}/submission`, submission) + .then((axiosRes) => axiosRes.data) + .then((submission) => res.json(submission)) + .catch((error) => { + // NOTE: error is logged in axiosFormio response interceptor + const errorStatus = error.response?.status || 500; + const errorMessage = `Error posting Formio ${rebateYear} Close Out form submission.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); + }); +} + /** * @param {Object} param * @param {'2022' | '2023'} param.rebateYear @@ -1409,6 +1659,7 @@ module.exports = { deletePRFSubmission, // fetchCRFSubmissions, + createCRFSubmission, // fetchChangeRequests, fetchChangeRequestSchema, From da0ef8f57147d020aa1afa9c95626a6538841094 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 17 May 2024 14:48:14 -0400 Subject: [PATCH 3/7] Move logic for fetching a CRF submission out of formio 2022 route and into formio utility file --- app/server/app/routes/formio2022.js | 73 +---------------------- app/server/app/utilities/formio.js | 90 +++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 71 deletions(-) diff --git a/app/server/app/routes/formio2022.js b/app/server/app/routes/formio2022.js index 1113e00b..5bf69a7f 100644 --- a/app/server/app/routes/formio2022.js +++ b/app/server/app/routes/formio2022.js @@ -6,7 +6,6 @@ const { formUrl, formioCSBMetadata, formioExampleRebateId, - formioNoUserAccess, } = require("../config/formio"); const { ensureAuthenticated, @@ -31,7 +30,7 @@ const { // fetchCRFSubmissions, createCRFSubmission, - // fetchCRFSubmission, + fetchCRFSubmission, // updateCRFSubmission, } = require("../utilities/formio"); const log = require("../utilities/logger"); @@ -128,75 +127,7 @@ router.post("/crf-submission", storeBapComboKeys, (req, res) => { // --- get an existing 2022 CRF's schema and submission data from Formio router.get("/crf-submission/:rebateId", storeBapComboKeys, async (req, res) => { - const { bapComboKeys } = req; - const { mail } = req.user; - const { rebateId } = req.params; // CSB Rebate ID (6 digits) - - // NOTE: included to support EPA API scan - if (rebateId === formioExampleRebateId) { - return res.json(formioNoUserAccess); - } - - const matchedCRFSubmissions = - `${formioCRFUrl}/submission` + - `?data.hidden_bap_rebate_id=${rebateId}` + - `&select=_id,data.bap_hidden_entity_combo_key`; - - Promise.all([ - axiosFormio(req).get(matchedCRFSubmissions), - axiosFormio(req).get(formioCRFUrl), - ]) - .then((axiosResponses) => axiosResponses.map((axiosRes) => axiosRes.data)) - .then(([submissions, schema]) => { - if (submissions.length === 0) { - return res.json(formioNoUserAccess); - } - - const submission = submissions[0]; - const mongoId = submission._id; - const comboKey = submission.data.bap_hidden_entity_combo_key; - - if (!bapComboKeys.includes(comboKey)) { - const logMessage = - `User with email '${mail}' attempted to access CRF submission '${rebateId}' ` + - `that they do not have access to.`; - log({ level: "warn", message: logMessage, req }); - - return res.json(formioNoUserAccess); - } - - /** NOTE: verifyMongoObjectId */ - if (mongoId && !ObjectId.isValid(mongoId)) { - const errorStatus = 400; - const errorMessage = `MongoDB ObjectId validation error for: '${mongoId}'.`; - return res.status(errorStatus).json({ message: errorMessage }); - } - - /** - * NOTE: We can't just use the returned submission data here because - * Formio returns the string literal 'YES' instead of a base64 encoded - * image string for signature fields when you query for all submissions - * matching on a field's value (`/submission?data.hidden_bap_rebate_id=${rebateId}`). - * We need to query for a specific submission (e.g. `/submission/${mongoId}`), - * to have Formio return the correct signature field data. - */ - axiosFormio(req) - .get(`${formioCRFUrl}/submission/${mongoId}`) - .then((axiosRes) => axiosRes.data) - .then((submission) => { - return res.json({ - userAccess: true, - formSchema: { url: formioCRFUrl, json: schema }, - submission, - }); - }); - }) - .catch((error) => { - // NOTE: error is logged in axiosFormio response interceptor - const errorStatus = error.response?.status || 500; - const errorMessage = `Error getting Formio Close Out form submission '${rebateId}'.`; - return res.status(errorStatus).json({ message: errorMessage }); - }); + fetchCRFSubmission({ rebateYear, req, res }); }); // --- post an update to an existing draft 2022 CRF submission to Formio diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 98d01247..878fb02c 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -1464,6 +1464,95 @@ function createCRFSubmission({ rebateYear, req, res }) { }); } +/** + * @param {Object} param + * @param {'2022' | '2023'} param.rebateYear + * @param {express.Request} param.req + * @param {express.Response} param.res + */ +function fetchCRFSubmission({ rebateYear, req, res }) { + const { bapComboKeys } = req; + const { mail } = req.user; + const { rebateId } = req.params; // CSB Rebate ID (6 digits) + + // NOTE: included to support EPA API scan + if (rebateId === formioExampleRebateId) { + return res.json(formioNoUserAccess); + } + + const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); + const rebateIdFieldName = getRebateIdFieldName({ rebateYear }); + + const formioFormUrl = formUrl[rebateYear].crf; + + if (!formioFormUrl) { + const errorStatus = 400; + const errorMessage = `Formio form URL does not exist for ${rebateYear} CRF.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + const matchedCRFSubmissions = + `${formioFormUrl}/submission` + + `?data.${rebateIdFieldName}=${rebateId}` + + `&select=_id,data.${comboKeyFieldName}`; + + Promise.all([ + axiosFormio(req).get(matchedCRFSubmissions), + axiosFormio(req).get(formioFormUrl), + ]) + .then((axiosResponses) => axiosResponses.map((axiosRes) => axiosRes.data)) + .then(([submissions, schema]) => { + if (submissions.length === 0) { + return res.json(formioNoUserAccess); + } + + const submission = submissions[0]; + const mongoId = submission._id; + const comboKey = submission.data?.[comboKeyFieldName]; + + if (!bapComboKeys.includes(comboKey)) { + const logMessage = + `User with email '${mail}' attempted to access ${rebateYear} ` + + `CRF submission '${mongoId}' that they do not have access to.`; + log({ level: "warn", message: logMessage, req }); + + return res.json(formioNoUserAccess); + } + + /** NOTE: verifyMongoObjectId */ + if (mongoId && !ObjectId.isValid(mongoId)) { + const errorStatus = 400; + const errorMessage = `MongoDB ObjectId validation error for: '${mongoId}'.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + /** + * NOTE: We can't just use the returned submission data here because + * Formio returns the string literal 'YES' instead of a base64 encoded + * image string for signature fields when you query for all submissions + * matching on a field's value (`/submission?data.${rebateIdFieldName}=${rebateId}`). + * We need to query for a specific submission (e.g. `/submission/${mongoId}`), + * to have Formio return the correct signature field data. + */ + axiosFormio(req) + .get(`${formioFormUrl}/submission/${mongoId}`) + .then((axiosRes) => axiosRes.data) + .then((submission) => { + return res.json({ + userAccess: true, + formSchema: { url: formioFormUrl, json: schema }, + submission, + }); + }); + }) + .catch((error) => { + // NOTE: error is logged in axiosFormio response interceptor + const errorStatus = error.response?.status || 500; + const errorMessage = `Error getting Formio ${rebateYear} Close Out form submission '${rebateId}'.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); +} + /** * @param {Object} param * @param {'2022' | '2023'} param.rebateYear @@ -1660,6 +1749,7 @@ module.exports = { // fetchCRFSubmissions, createCRFSubmission, + fetchCRFSubmission, // fetchChangeRequests, fetchChangeRequestSchema, From 6907d124df36d687fa4b1d03173666c58025a36e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 17 May 2024 15:06:24 -0400 Subject: [PATCH 4/7] Move logic for updating a CRF submission out of formio 2022 route and into formio utility file --- app/server/app/routes/formio2022.js | 85 +-------------------------- app/server/app/utilities/formio.js | 90 +++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 83 deletions(-) diff --git a/app/server/app/routes/formio2022.js b/app/server/app/routes/formio2022.js index 5bf69a7f..c079c2e9 100644 --- a/app/server/app/routes/formio2022.js +++ b/app/server/app/routes/formio2022.js @@ -1,18 +1,10 @@ const express = require("express"); -const ObjectId = require("mongodb").ObjectId; // --- -const { - axiosFormio, - formUrl, - formioCSBMetadata, - formioExampleRebateId, -} = require("../config/formio"); const { ensureAuthenticated, storeBapComboKeys, verifyMongoObjectId, } = require("../middleware"); -const { checkFormSubmissionPeriodAndBapStatus } = require("../utilities/bap"); const { uploadS3FileMetadata, downloadS3FileMetadata, @@ -31,11 +23,8 @@ const { fetchCRFSubmissions, createCRFSubmission, fetchCRFSubmission, - // updateCRFSubmission, + updateCRFSubmission, } = require("../utilities/formio"); -const log = require("../utilities/logger"); - -const formioCRFUrl = formUrl["2022"].crf; const rebateYear = "2022"; const router = express.Router(); @@ -132,77 +121,7 @@ router.get("/crf-submission/:rebateId", storeBapComboKeys, async (req, res) => { // --- post an update to an existing draft 2022 CRF submission to Formio router.post("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { - const { bapComboKeys, body } = req; - const { mail } = req.user; - const { rebateId } = req.params; // CSB Rebate ID (6 digits) - const { mongoId, submission } = body; - - // NOTE: included to support EPA API scan - if (rebateId === formioExampleRebateId) { - return res.json({}); - } - - if (!mongoId || !submission) { - const errorStatus = 400; - const errorMessage = `Missing required data to update ${rebateYear} CRF submission '${rebateId}'.`; - return res.status(errorStatus).json({ message: errorMessage }); - } - - const comboKey = submission.data?.bap_hidden_entity_combo_key; - - checkFormSubmissionPeriodAndBapStatus({ - rebateYear, - formType: "crf", - mongoId, - comboKey, - req, - }) - .then(() => { - if (!bapComboKeys.includes(comboKey)) { - const logMessage = - `User with email '${mail}' attempted to update CRF submission '${rebateId}' ` + - `without a matching BAP combo key.`; - log({ level: "error", message: logMessage, req }); - - const errorStatus = 401; - const errorMessage = `Unauthorized.`; - return res.status(errorStatus).json({ message: errorMessage }); - } - - /** NOTE: verifyMongoObjectId */ - if (mongoId && !ObjectId.isValid(mongoId)) { - const errorStatus = 400; - const errorMessage = `MongoDB ObjectId validation error for: '${mongoId}'.`; - return res.status(errorStatus).json({ message: errorMessage }); - } - - /** Add custom metadata to track formio submissions from wrapper. */ - submission.metadata = { - ...submission.metadata, - ...formioCSBMetadata, - }; - - axiosFormio(req) - .put(`${formioCRFUrl}/submission/${mongoId}`, submission) - .then((axiosRes) => axiosRes.data) - .then((submission) => res.json(submission)) - .catch((error) => { - // NOTE: error is logged in axiosFormio response interceptor - const errorStatus = error.response?.status || 500; - const errorMessage = `Error updating Formio Close Out form submission '${rebateId}'.`; - return res.status(errorStatus).json({ message: errorMessage }); - }); - }) - .catch((error) => { - const logMessage = - `User with email '${mail}' attempted to update CRF submission '${rebateId}' ` + - `when the CSB CRF enrollment period was closed.`; - log({ level: "error", message: logMessage, req }); - - const errorStatus = 400; - const errorMessage = `CSB Close Out form enrollment period is closed.`; - return res.status(errorStatus).json({ message: errorMessage }); - }); + updateCRFSubmission({ rebateYear, req, res }); }); module.exports = router; diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 878fb02c..149380ae 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -1553,6 +1553,95 @@ function fetchCRFSubmission({ rebateYear, req, res }) { }); } +/** + * @param {Object} param + * @param {'2022' | '2023'} param.rebateYear + * @param {express.Request} param.req + * @param {express.Response} param.res + */ +function updateCRFSubmission({ rebateYear, req, res }) { + const { bapComboKeys, body } = req; + const { mail } = req.user; + const { rebateId } = req.params; // CSB Rebate ID (6 digits) + const { mongoId, submission } = body; + + // NOTE: included to support EPA API scan + if (rebateId === formioExampleRebateId) { + return res.json({}); + } + + if (!mongoId || !submission) { + const errorStatus = 400; + const errorMessage = `Missing required data to update ${rebateYear} CRF submission '${rebateId}'.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); + const comboKey = submission.data?.[comboKeyFieldName]; + + const formioFormUrl = formUrl[rebateYear].prf; + + if (!formioFormUrl) { + const errorStatus = 400; + const errorMessage = `Formio form URL does not exist for ${rebateYear} CRF.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + checkFormSubmissionPeriodAndBapStatus({ + rebateYear, + formType: "crf", + mongoId, + comboKey, + req, + }) + .then(() => { + if (!bapComboKeys.includes(comboKey)) { + const logMessage = + `User with email '${mail}' attempted to update ${rebateYear} CRF ` + + `submission '${rebateId}' without a matching BAP combo key.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + /** NOTE: verifyMongoObjectId */ + if (mongoId && !ObjectId.isValid(mongoId)) { + const errorStatus = 400; + const errorMessage = `MongoDB ObjectId validation error for: '${mongoId}'.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + + /** Add custom metadata to track formio submissions from wrapper. */ + submission.metadata = { + ...submission.metadata, + ...formioCSBMetadata, + }; + + axiosFormio(req) + .put(`${formioFormUrl}/submission/${mongoId}`, submission) + .then((axiosRes) => axiosRes.data) + .then((submission) => res.json(submission)) + .catch((error) => { + // NOTE: error is logged in axiosFormio response interceptor + const errorStatus = error.response?.status || 500; + const errorMessage = `Error updating Formio ${rebateYear} Close Out form submission '${rebateId}'.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); + }) + .catch((error) => { + const logMessage = + `User with email '${mail}' attempted to update ${rebateYear} CRF ` + + `submission '${rebateId}' when the CSB CRF enrollment period was closed.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 400; + const errorMessage = `${rebateYear} CSB Close Out form enrollment period is closed.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); +} + /** * @param {Object} param * @param {'2022' | '2023'} param.rebateYear @@ -1750,6 +1839,7 @@ module.exports = { fetchCRFSubmissions, createCRFSubmission, fetchCRFSubmission, + updateCRFSubmission, // fetchChangeRequests, fetchChangeRequestSchema, From f04fcd4d78ee5054c1850c65132f8bb71ff73128 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 17 May 2024 15:07:22 -0400 Subject: [PATCH 5/7] Add commented out CRF code to Formio 2023 API route file (just preparing for the future when the 2023 CRF is developed) --- app/server/app/routes/formio2023.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/server/app/routes/formio2023.js b/app/server/app/routes/formio2023.js index 77375317..d4aa959f 100644 --- a/app/server/app/routes/formio2023.js +++ b/app/server/app/routes/formio2023.js @@ -1,10 +1,5 @@ const express = require("express"); // --- -const { - formioExampleMongoId, - formioExampleRebateId, - formioNoUserAccess, -} = require("../config/formio"); const { ensureAuthenticated, storeBapComboKeys, @@ -25,6 +20,11 @@ const { updatePRFSubmission, deletePRFSubmission, // + // fetchCRFSubmissions, + // createCRFSubmission, + // fetchCRFSubmission, + // updateCRFSubmission, + // fetchChangeRequests, fetchChangeRequestSchema, createChangeRequest, @@ -111,15 +111,23 @@ router.post("/delete-prf-submission", storeBapComboKeys, (req, res) => { // --- get user's 2023 CRF submissions from Formio router.get("/crf-submissions", storeBapComboKeys, (req, res) => { - // TODO - res.json([]); + res.json([]); // TODO: replace with `fetchCRFSubmissions({ rebateYear, req, res })` when CRF is ready }); // --- post a new 2023 CRF submission to Formio +// router.post("/crf-submission", storeBapComboKeys, (req, res) => { +// createCRFSubmission({ rebateYear, req, res }); +// }); // --- get an existing 2023 CRF's schema and submission data from Formio +// router.get("/crf-submission/:rebateId", storeBapComboKeys, async (req, res) => { +// fetchCRFSubmission({ rebateYear, req, res }); +// }); // --- post an update to an existing draft 2023 CRF submission to Formio +// router.post("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { +// updateCRFSubmission({ rebateYear, req, res }); +// }); // --- get user's 2023 Change Request form submissions from Formio router.get("/changes", storeBapComboKeys, (req, res) => { From f20ce318aa4d6b3aa9a9c53309e46e9b47503408 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 17 May 2024 15:49:08 -0400 Subject: [PATCH 6/7] Fix incorrect formioFormUrl in updateCRFSubmission() --- app/server/app/utilities/formio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 149380ae..58698416 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -1579,7 +1579,7 @@ function updateCRFSubmission({ rebateYear, req, res }) { const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); const comboKey = submission.data?.[comboKeyFieldName]; - const formioFormUrl = formUrl[rebateYear].prf; + const formioFormUrl = formUrl[rebateYear].crf; if (!formioFormUrl) { const errorStatus = 400; From 1651ca37494d9a650b4ade7fc866d9f609c0402e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 6 Jun 2024 11:46:42 -0400 Subject: [PATCH 7/7] Fix CRF formType typo in catch block in uploadS3FileMetadata() --- app/server/app/utilities/formio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 58698416..ffd8ed09 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -713,7 +713,7 @@ function uploadS3FileMetadata({ rebateYear, req, res }) { ? "CSB Application" : formType === "prf" ? "CSB Payment Request" - : formType === "cof" + : formType === "crf" ? "CSB Close Out" : "CSB";