From 76f1120a665533e8a8302f7cf382f281e05cc263 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 5 Nov 2024 13:43:09 -0500 Subject: [PATCH 01/11] Remove async from server's formio route handlers --- app/server/app/routes/formio2022.js | 4 ++-- app/server/app/routes/formio2023.js | 6 +++--- app/server/app/routes/formio2024.js | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/server/app/routes/formio2022.js b/app/server/app/routes/formio2022.js index c079c2e9..f0459a9d 100644 --- a/app/server/app/routes/formio2022.js +++ b/app/server/app/routes/formio2022.js @@ -90,7 +90,7 @@ router.post("/prf-submission", storeBapComboKeys, (req, res) => { }); // --- get an existing 2022 PRF's schema and submission data from Formio -router.get("/prf-submission/:rebateId", storeBapComboKeys, async (req, res) => { +router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { fetchPRFSubmission({ rebateYear, req, res }); }); @@ -115,7 +115,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) => { +router.get("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { fetchCRFSubmission({ rebateYear, req, res }); }); diff --git a/app/server/app/routes/formio2023.js b/app/server/app/routes/formio2023.js index fd23da48..d5a226fb 100644 --- a/app/server/app/routes/formio2023.js +++ b/app/server/app/routes/formio2023.js @@ -102,7 +102,7 @@ router.post("/prf-submission", storeBapComboKeys, (req, res) => { }); // --- get an existing 2023 PRF's schema and submission data from Formio -router.get("/prf-submission/:rebateId", storeBapComboKeys, async (req, res) => { +router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { fetchPRFSubmission({ rebateYear, req, res }); }); @@ -127,7 +127,7 @@ router.get("/crf-submissions", storeBapComboKeys, (req, res) => { // }); // --- get an existing 2023 CRF's schema and submission data from Formio -// router.get("/crf-submission/:rebateId", storeBapComboKeys, async (req, res) => { +// router.get("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { // fetchCRFSubmission({ rebateYear, req, res }); // }); @@ -152,7 +152,7 @@ router.post("/change", storeBapComboKeys, (req, res) => { }); // --- get an existing 2023 Change Request form's schema and submission data from Formio -router.get("/change/:mongoId", storeBapComboKeys, async (req, res) => { +router.get("/change/:mongoId", storeBapComboKeys, (req, res) => { fetchChangeRequest({ rebateYear, req, res }); }); diff --git a/app/server/app/routes/formio2024.js b/app/server/app/routes/formio2024.js index 110f4d08..5b5462b6 100644 --- a/app/server/app/routes/formio2024.js +++ b/app/server/app/routes/formio2024.js @@ -102,7 +102,7 @@ router.get("/prf-submissions", storeBapComboKeys, (req, res) => { // }); // --- get an existing 2024 PRF's schema and submission data from Formio -// router.get("/prf-submission/:rebateId", storeBapComboKeys, async (req, res) => { +// router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { // fetchPRFSubmission({ rebateYear, req, res }); // }); @@ -127,7 +127,7 @@ router.get("/crf-submissions", storeBapComboKeys, (req, res) => { // }); // --- get an existing 2024 CRF's schema and submission data from Formio -// router.get("/crf-submission/:rebateId", storeBapComboKeys, async (req, res) => { +// router.get("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { // fetchCRFSubmission({ rebateYear, req, res }); // }); @@ -152,7 +152,7 @@ router.post("/change", storeBapComboKeys, (req, res) => { }); // --- get an existing 2024 Change Request form's schema and submission data from Formio -router.get("/change/:mongoId", storeBapComboKeys, async (req, res) => { +router.get("/change/:mongoId", storeBapComboKeys, (req, res) => { fetchChangeRequest({ rebateYear, req, res }); }); From 8cee5904e73e589cab4d3ff60e3a94cae8700054 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 10:52:22 -0500 Subject: [PATCH 02/11] Update server app's bap utilities file to include function for querying BAP for 2024 PRF data --- app/server/app/utilities/bap.js | 414 ++++++++++++++++++++++++++++++++ 1 file changed, 414 insertions(+) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 000bf2c5..af0878c7 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -232,6 +232,113 @@ const { submissionPeriodOpen } = require("../config/formio"); * }[]} frf2023BusRecordsContactsQueries */ +/** + * @typedef {Object} BapDataFor2024PRF + * @property {{ + * attributes: { type: "Order_Request__c", url: string } + * Id: string + * CSB_Snapshot__r: { + * attributes: { type: "Data_Staging__c", url: string } + * Id: string + * JSON_Snapshot__c: string + * } + * Applicant_Organization__r: { + * attributes: { type: "Account", url: string } + * Id: string + * County__c: string + * } + * Primary_Applicant__r: { + * attributes: { type: "Contact", url: string } + * Id: string + * FirstName: string + * LastName: string + * Title: string + * Email: string + * Phone: string + * } | null + * Alternate_Applicant__r: { + * attributes: { type: "Contact", url: string } + * Id: string + * FirstName: string + * LastName: string + * Title: string + * Email: string + * Phone: string + * } | null + * CSB_School_District__r: { + * attributes: { type: "Account", url: string } + * Id: string + * Name: string + * BillingStreet: string + * BillingCity: string + * BillingState: string + * BillingPostalCode: string + * } | null + * School_District_Contact__r: { + * attributes: { type: "Contact", url: string } + * Id: string + * FirstName: string + * LastName: string + * Title: string + * Email: string + * Phone: string + * } | null + * CSB_NCES_ID__c: string + * Org_District_Prioritized__c: string + * Self_Certification_Category__c: string + * Prioritized_as_High_Need__c: boolean + * Prioritized_as_Tribal__c: boolean + * Prioritized_as_Rural__c: boolean + * }[]} frf2024RecordQuery + * @property {{ + * attributes: { type: "Line_Item__c", url: string } + * Id: string + * Rebate_Item_num__c: number + * CSB_VIN__c: string + * CSB_Fuel_Type__c: string + * CSB_GVWR__c: number + * Old_Bus_Odometer_miles__c: number + * Old_Bus_NCES_District_ID__c: string + * CSB_Model__c: string + * CSB_Model_Year__c: string + * CSB_Manufacturer__c: string + * CSB_Manufacturer_if_Other__c: string | null + * CSB_Annual_Fuel_Consumption__c: number + * Annual_Mileage__c: number + * Old_Bus_Estimated_Remaining_Life__c: number + * Old_Bus_Annual_Idling_Hours__c: number + * New_Bus_Infra_Rebate_Requested__c: number + * New_Bus_Fuel_Type__c: string + * New_Bus_GVWR__c: number + * New_Bus_ADA_Compliant__c: boolean + * }[]} frf2024BusRecordsQuery + * @property {{ + * attributes: { type: "Line_Item__c", url: string } + * Id: string + * Related_Line_Item__c: string + * Relationship_Type__c: 'Old Bus Private Fleet Owner (if changed)' | 'New Bus Owner' + * Contact__r: { + * attributes: { type: "Contact", url: string } + * Id: string + * FirstName: string + * LastName: string + * Title: string + * Email: string + * Phone: string + * Account: { + * attributes: { type: "Account", url: string } + * Id: string + * Name: string + * BillingStreet: string + * BillingCity: string + * BillingState: string + * BillingPostalCode: string + * County__c: string | null + * } + * } + * }[]} frf2024BusRecordsContactsQueries + */ + /** * @typedef {Object} BapDataForFor2022CRF * @property {{ @@ -1180,6 +1287,298 @@ async function queryBapFor2023PRFData(req, frfReviewItemId) { }; } +/** + * Uses cached JSforce connection to query the BAP for 2024 FRF submission data, + * for use in a brand new 2024 PRF submission. + * + * @param {express.Request} req + * @param {string} frfReviewItemId CSB Rebate ID with the form/version ID (9 digits) + * @returns {Promise} 2024 FRF submission fields + */ +async function queryBapFor2024PRFData(req, frfReviewItemId) { + const logMessage = + `Querying the BAP for 2024 FRF submission associated with ` + + `FRF Review Item ID: '${frfReviewItemId}'.`; + log({ level: "info", message: logMessage, req }); + + /** @type {{ bapConnection: jsforce.Connection }} */ + const { bapConnection } = req.app.locals; + + // `SELECT + // Id + // FROM + // RecordType + // WHERE + // DeveloperName = 'CSB_Funding_Request_2024' AND + // SObjectType = 'Order_Request__c' + // LIMIT 1` + + const frf2024RecordTypeIdQuery = await bapConnection + .sobject("RecordType") + .find( + { + DeveloperName: "CSB_Funding_Request_2024", + SObjectType: "Order_Request__c", + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + }, + ) + .limit(1) + .execute(async (err, records) => ((await err) ? err : records)); + + const frf2024RecordTypeId = frf2024RecordTypeIdQuery["0"].Id; + + // `SELECT + // Id, + // CSB_Snapshot__r.Id, + // CSB_Snapshot__r.JSON_Snapshot__c + // Applicant_Organization__r.Id + // Applicant_Organization__r.County__c + // Primary_Applicant__r.Id, + // Primary_Applicant__r.FirstName, + // Primary_Applicant__r.LastName, + // Primary_Applicant__r.Title, + // Primary_Applicant__r.Email, + // Primary_Applicant__r.Phone, + // Alternate_Applicant__r.Id, + // Alternate_Applicant__r.FirstName, + // Alternate_Applicant__r.LastName, + // Alternate_Applicant__r.Title, + // Alternate_Applicant__r.Email, + // Alternate_Applicant__r.Phone, + // CSB_School_District__r.Id, + // CSB_School_District__r.Name, + // CSB_School_District__r.BillingStreet, + // CSB_School_District__r.BillingCity, + // CSB_School_District__r.BillingState, + // CSB_School_District__r.BillingPostalCode, + // School_District_Contact__r.Id, + // School_District_Contact__r.FirstName, + // School_District_Contact__r.LastName, + // School_District_Contact__r.Title, + // School_District_Contact__r.Email, + // School_District_Contact__r.Phone, + // CSB_NCES_ID__c, + // Org_District_Prioritized__c, + // Self_Certification_Category__c, + // Prioritized_as_High_Need__c, + // Prioritized_as_Tribal__c, + // Prioritized_as_Rural__c + // FROM + // Order_Request__c + // WHERE + // RecordTypeId = '${frf2024RecordTypeId}' AND + // CSB_Review_Item_ID__c = '${frfReviewItemId}' AND + // Latest_Version__c = TRUE` + + const frf2024RecordQuery = await bapConnection + .sobject("Order_Request__c") + .find( + { + RecordTypeId: frf2024RecordTypeId, + CSB_Review_Item_ID__c: frfReviewItemId, + Latest_Version__c: true, + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + "CSB_Snapshot__r.Id": 1, + "CSB_Snapshot__r.JSON_Snapshot__c": 1, + "Applicant_Organization__r.Id": 1, + "Applicant_Organization__r.County__c": 1, + "Primary_Applicant__r.Id": 1, + "Primary_Applicant__r.FirstName": 1, + "Primary_Applicant__r.LastName": 1, + "Primary_Applicant__r.Title": 1, + "Primary_Applicant__r.Email": 1, + "Primary_Applicant__r.Phone": 1, + "Alternate_Applicant__r.Id": 1, + "Alternate_Applicant__r.FirstName": 1, + "Alternate_Applicant__r.LastName": 1, + "Alternate_Applicant__r.Title": 1, + "Alternate_Applicant__r.Email": 1, + "Alternate_Applicant__r.Phone": 1, + "CSB_School_District__r.Id": 1, + "CSB_School_District__r.Name": 1, + "CSB_School_District__r.BillingStreet": 1, + "CSB_School_District__r.BillingCity": 1, + "CSB_School_District__r.BillingState": 1, + "CSB_School_District__r.BillingPostalCode": 1, + "School_District_Contact__r.Id": 1, + "School_District_Contact__r.FirstName": 1, + "School_District_Contact__r.LastName": 1, + "School_District_Contact__r.Title": 1, + "School_District_Contact__r.Email": 1, + "School_District_Contact__r.Phone": 1, + CSB_NCES_ID__c: 1, + Org_District_Prioritized__c: 1, + Self_Certification_Category__c: 1, + Prioritized_as_High_Need__c: 1, + Prioritized_as_Tribal__c: 1, + Prioritized_as_Rural__c: 1, + }, + ) + .execute(async (err, records) => ((await err) ? err : records)); + + const frf2024RecordId = frf2024RecordQuery["0"].Id; + + // `SELECT + // Id + // FROM + // RecordType + // WHERE + // DeveloperName = 'CSB_Rebate_Item' AND + // SObjectType = 'Line_Item__c' + // LIMIT 1` + + const rebateItemRecordTypeIdQuery = await bapConnection + .sobject("RecordType") + .find( + { + DeveloperName: "CSB_Rebate_Item", + SObjectType: "Line_Item__c", + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + }, + ) + .limit(1) + .execute(async (err, records) => ((await err) ? err : records)); + + const rebateItemRecordTypeId = rebateItemRecordTypeIdQuery["0"].Id; + + // `SELECT + // Id, + // Rebate_Item_num__c, + // CSB_VIN__c, + // CSB_Fuel_Type__c, + // CSB_GVWR__c, + // Old_Bus_Odometer_miles__c, + // Old_Bus_NCES_District_ID__c, + // CSB_Model__c, + // CSB_Model_Year__c, + // CSB_Manufacturer__c, + // CSB_Manufacturer_if_Other__c, + // CSB_Annual_Fuel_Consumption__c, + // Annual_Mileage__c, + // Old_Bus_Estimated_Remaining_Life__c, + // Old_Bus_Annual_Idling_Hours__c, + // New_Bus_Infra_Rebate_Requested__c, + // New_Bus_Fuel_Type__c, + // New_Bus_GVWR__c, + // New_Bus_ADA_Compliant__c + // FROM + // Line_Item__c + // WHERE + // RecordTypeId = '${rebateItemRecordTypeId}' AND + // Related_Order_Request__c = '${frf2024RecordId}' AND + // CSB_Rebate_Item_Type__c = 'Old Bus'` + + const frf2024BusRecordsQuery = await bapConnection + .sobject("Line_Item__c") + .find( + { + RecordTypeId: rebateItemRecordTypeId, + Related_Order_Request__c: frf2024RecordId, + CSB_Rebate_Item_Type__c: "Old Bus", + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + Rebate_Item_num__c: 1, + CSB_VIN__c: 1, + CSB_Fuel_Type__c: 1, + CSB_GVWR__c: 1, + Old_Bus_Odometer_miles__c: 1, + Old_Bus_NCES_District_ID__c: 1, + CSB_Model__c: 1, + CSB_Model_Year__c: 1, + CSB_Manufacturer__c: 1, + CSB_Manufacturer_if_Other__c: 1, + CSB_Annual_Fuel_Consumption__c: 1, + Annual_Mileage__c: 1, + Old_Bus_Estimated_Remaining_Life__c: 1, + Old_Bus_Annual_Idling_Hours__c: 1, + New_Bus_Infra_Rebate_Requested__c: 1, + New_Bus_Fuel_Type__c: 1, + New_Bus_GVWR__c: 1, + New_Bus_ADA_Compliant__c: 1, + }, + ) + .execute(async (err, records) => ((await err) ? err : records)); + + const frf2024BusRecordsContactsQueries = ( + await Promise.all( + frf2024BusRecordsQuery.map(async (frf2024BusRecord) => { + const frf2024BusRecordId = frf2024BusRecord.Id; + + // `SELECT + // Id, + // Related_Line_Item__c, + // Relationship_Type__c, + // Contact__r.Id, + // Contact__r.FirstName, + // Contact__r.LastName + // Contact__r.Title, + // Contact__r.Email, + // Contact__r.Phone, + // Contact__r.Account.Id, + // Contact__r.Account.Name, + // Contact__r.Account.BillingStreet, + // Contact__r.Account.BillingCity, + // Contact__r.Account.BillingState, + // Contact__r.Account.BillingPostalCode, + // Contact__r.Account.County__c, + // FROM + // Line_Item__c + // WHERE + // RecordTypeId = '${rebateItemRecordTypeId}' AND + // Related_Line_Item__c = '${frf2024BusRecordId}' AND + // CSB_Rebate_Item_Type__c = 'COF Relationship'` + + return await bapConnection + .sobject("Line_Item__c") + .find( + { + RecordTypeId: rebateItemRecordTypeId, + Related_Line_Item__c: frf2024BusRecordId, + CSB_Rebate_Item_Type__c: "COF Relationship", + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + Related_Line_Item__c: 1, + Relationship_Type__c: 1, + "Contact__r.Id": 1, + "Contact__r.FirstName": 1, + "Contact__r.LastName": 1, + "Contact__r.Title": 1, + "Contact__r.Email": 1, + "Contact__r.Phone": 1, + "Contact__r.Account.Id": 1, + "Contact__r.Account.Name": 1, + "Contact__r.Account.BillingStreet": 1, + "Contact__r.Account.BillingCity": 1, + "Contact__r.Account.BillingState": 1, + "Contact__r.Account.BillingPostalCode": 1, + "Contact__r.Account.County__c": 1, + }, + ) + .execute(async (err, records) => ((await err) ? err : records)); + }), + ) + ).flat(); + + return { + frf2024RecordQuery, + frf2024BusRecordsQuery, + frf2024BusRecordsContactsQueries, + }; +} + /** * Uses cached JSforce connection to query the BAP for 2022 FRF submission data * and 2022 PRF submission data, for use in a brand new 2022 CRF submission. @@ -1628,6 +2027,20 @@ function getBapDataFor2023PRF(req, frfReviewItemId) { }); } +/** + * Fetches 2024 FRF submission data associated with a FRF Review Item ID. + * + * @param {express.Request} req + * @param {string} frfReviewItemId + * @returns {ReturnType} + */ +function getBapDataFor2024PRF(req, frfReviewItemId) { + return verifyBapConnection(req, { + name: queryBapFor2024PRFData, + args: [req, frfReviewItemId], + }); +} + /** * Fetches 2022 FRF submission data and 2022 PRF submission data associated with * a FRF Review Item ID and a PRF Review Item ID. @@ -1708,6 +2121,7 @@ module.exports = { getBapFormSubmissionsStatuses, getBapDataFor2022PRF, getBapDataFor2023PRF, + getBapDataFor2024PRF, getBapDataFor2022CRF, checkForBapDuplicates, checkFormSubmissionPeriodAndBapStatus, From c82d06f3cffc39ac1b6b9609f5c0c3e86d809921 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 15:09:29 -0500 Subject: [PATCH 03/11] Update fetchDataForPRFSubmission() to support 2024 rebate year and call getBapDataFor2024PRF() --- app/server/app/utilities/formio.js | 252 ++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 6 deletions(-) diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 54e7281c..975146fb 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -15,6 +15,7 @@ const { getBapFormSubmissionsStatuses, getBapDataFor2022PRF, getBapDataFor2023PRF, + getBapDataFor2024PRF, getBapDataFor2022CRF, checkFormSubmissionPeriodAndBapStatus, } = require("../utilities/bap"); @@ -327,7 +328,6 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { array.push({ org_number: jsonOrg.org_number, org_type: jsonOrg.org_type, - // _org_typeCombined: "", // NOTE: 'Existing Bus Owner, New Bus Owner' _org_id: orgId, org_name: orgName, _org_contact_id: contactId, @@ -340,10 +340,7 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { org_address_2: orgStreetAddress2, org_county: County__c, org_city: BillingCity, - org_state: { - name: BillingState, - // abbreviation: "", - }, + org_state: { name: BillingState }, org_zip: BillingPostalCode, }); } @@ -499,7 +496,250 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { } if (rebateYear === "2024") { - // TODO + return getBapDataFor2024PRF(req, frfReviewItemId) + .then((results) => { + const { + frf2024RecordQuery, + frf2024BusRecordsQuery, + frf2024BusRecordsContactsQueries, + } = results; + + const existingBusOwnerType = "Old Bus Private Fleet Owner (if changed)"; + const newBusOwnerType = "New Bus Owner"; + + const { + CSB_Snapshot__r, + Applicant_Organization__r, + Primary_Applicant__r, + Alternate_Applicant__r, + CSB_School_District__r, + School_District_Contact__r, + CSB_NCES_ID__c, + Org_District_Prioritized__c, + Self_Certification_Category__c, + Prioritized_as_High_Need__c, + Prioritized_as_Tribal__c, + Prioritized_as_Rural__c, + } = frf2024RecordQuery[0]; + + const frf2024RecordJson = JSON.parse(CSB_Snapshot__r.JSON_Snapshot__c); + + const [schoolDistrictStreetAddress1, schoolDistrictStreetAddress2] = ( + CSB_School_District__r?.BillingStreet ?? "\n" + ).split("\n"); + + const org_organizations = frf2024BusRecordsContactsQueries.reduce( + (array, frf2024BusRecordsContact) => { + const { Contact__r } = frf2024BusRecordsContact; + + const { + Id: contactId, + FirstName, + LastName, + Title, + Email, + Phone, + Account, + } = Contact__r; + + const { + Id: orgId, + Name: orgName, + BillingStreet, + BillingCity, + BillingState, + BillingPostalCode, + County__c, + } = Account || {}; + + const jsonOrg = frf2024RecordJson.data.organizations.find((org) => { + const matchedName = org?.org_orgName?.trim() === orgName?.trim(); + const matchedEmail = + org.org_contactEmail?.trim()?.toLowerCase() === + Email?.trim()?.toLowerCase(); + + return matchedName && matchedEmail; + }); + + const orgAlreadyAdded = array.some((org) => org._org_id === orgId); + + /** + * Ensure the org exists in the 2024 FRF submission's + * "organizations" array, and it hasn't already been added. + */ + if (jsonOrg && !orgAlreadyAdded) { + const [orgStreetAddress1, orgStreetAddress2] = ( + BillingStreet ?? "\n" + ).split("\n"); + + array.push({ + org_number: jsonOrg.org_number, + org_type: jsonOrg.org_type, + _org_id: orgId, + org_name: orgName, + _org_contact_id: contactId, + org_contact_fname: FirstName, + org_contact_lname: LastName, + org_contact_title: Title, + org_contact_email: Email, + org_contact_phone: Phone, + org_address_1: orgStreetAddress1, + org_address_2: orgStreetAddress2, + org_county: County__c, + org_city: BillingCity, + org_state: { name: BillingState }, + org_zip: BillingPostalCode, + }); + } + + return array; + }, + [], + ); + + const bus_buses = frf2024BusRecordsQuery.map((frf2024BusRecord) => { + const { + Id: busRecordId, + Rebate_Item_num__c, + CSB_VIN__c, + CSB_Fuel_Type__c, + CSB_GVWR__c, + Old_Bus_Odometer_miles__c, + Old_Bus_NCES_District_ID__c, + CSB_Model__c, + CSB_Model_Year__c, + CSB_Manufacturer__c, + CSB_Manufacturer_if_Other__c, + CSB_Annual_Fuel_Consumption__c, + Annual_Mileage__c, + Old_Bus_Estimated_Remaining_Life__c, + Old_Bus_Annual_Idling_Hours__c, + New_Bus_Infra_Rebate_Requested__c, + New_Bus_Fuel_Type__c, + New_Bus_GVWR__c, + New_Bus_ADA_Compliant__c, + } = frf2024BusRecord; + + const existingOwnerRecord = frf2024BusRecordsContactsQueries.find( + (item) => + item.Related_Line_Item__c === busRecordId && + item.Relationship_Type__c === existingBusOwnerType, + ); + + const newOwnerRecord = frf2024BusRecordsContactsQueries.find( + (item) => + item.Related_Line_Item__c === busRecordId && + item.Relationship_Type__c === newBusOwnerType, + ); + + return { + bus_busNumber: Rebate_Item_num__c, + bus_existingOwner: { + org_id: existingOwnerRecord?.Contact__r?.Account?.Id, + org_name: existingOwnerRecord?.Contact__r?.Account?.Name, + org_contact_id: existingOwnerRecord?.Contact__r?.Id, + org_contact_fname: existingOwnerRecord?.Contact__r?.FirstName, + org_contact_lname: existingOwnerRecord?.Contact__r?.LastName, + }, + bus_existingVin: CSB_VIN__c, + bus_existingFuelType: CSB_Fuel_Type__c, + bus_existingGvwr: CSB_GVWR__c, + bus_existingOdometer: Old_Bus_Odometer_miles__c, + bus_existingModel: CSB_Model__c, + bus_existingModelYear: CSB_Model_Year__c, + bus_existingNcesId: Old_Bus_NCES_District_ID__c, + bus_existingManufacturer: CSB_Manufacturer__c, + bus_existingManufacturerOther: CSB_Manufacturer_if_Other__c, + bus_existingAnnualFuelConsumption: CSB_Annual_Fuel_Consumption__c, + bus_existingAnnualMileage: Annual_Mileage__c, + bus_existingRemainingLife: Old_Bus_Estimated_Remaining_Life__c, + bus_existingIdlingHours: Old_Bus_Annual_Idling_Hours__c, + bus_newOwner: { + org_id: newOwnerRecord?.Contact__r?.Account?.Id, + org_name: newOwnerRecord?.Contact__r?.Account?.Name, + org_contact_id: newOwnerRecord?.Contact__r?.Id, + org_contact_fname: newOwnerRecord?.Contact__r?.FirstName, + org_contact_lname: newOwnerRecord?.Contact__r?.LastName, + }, + bus_newFuelType: New_Bus_Fuel_Type__c, + bus_newGvwr: New_Bus_GVWR__c, + _bus_maxRebate: New_Bus_Infra_Rebate_Requested__c, + _bus_newADAfromFRF: New_Bus_ADA_Compliant__c, + }; + }); + + return { + data: { + _application_form_modified: frfModified, + _bap_entity_combo_key: comboKey, + _bap_rebate_id: rebateId, + _user_email: email, + _user_title: title, + _user_name: name, + _bap_applicant_email: email, + _bap_applicant_title: title, + _bap_applicant_name: name, + _bap_applicant_efti: ENTITY_EFT_INDICATOR__c || "0000", + _bap_applicant_uei: UNIQUE_ENTITY_ID__c, + _bap_applicant_organization_id: Applicant_Organization__r?.Id, + _bap_applicant_organization_name: LEGAL_BUSINESS_NAME__c, + _bap_applicant_street_address_1: PHYSICAL_ADDRESS_LINE_1__c, + _bap_applicant_street_address_2: PHYSICAL_ADDRESS_LINE_2__c, + _bap_applicant_county: Applicant_Organization__r?.County__c, + _bap_applicant_city: PHYSICAL_ADDRESS_CITY__c, + _bap_applicant_state: PHYSICAL_ADDRESS_PROVINCE_OR_STATE__c, + _bap_applicant_zip: PHYSICAL_ADDRESS_ZIPPOSTAL_CODE__c, + _bap_elec_bus_poc_email: ELEC_BUS_POC_EMAIL__c, + _bap_alt_elec_bus_poc_email: ALT_ELEC_BUS_POC_EMAIL__c, + _bap_govt_bus_poc_email: GOVT_BUS_POC_EMAIL__c, + _bap_alt_govt_bus_poc_email: ALT_GOVT_BUS_POC_EMAIL__c, + _bap_primary_id: Primary_Applicant__r?.Id, + _bap_primary_fname: Primary_Applicant__r?.FirstName, + _bap_primary_lname: Primary_Applicant__r?.LastName, + _bap_primary_title: Primary_Applicant__r?.Title, + _bap_primary_email: Primary_Applicant__r?.Email, + _bap_primary_phone: Primary_Applicant__r?.Phone, + _bap_alternate_id: Alternate_Applicant__r?.Id, + _bap_alternate_fname: Alternate_Applicant__r?.FirstName, + _bap_alternate_lname: Alternate_Applicant__r?.LastName, + _bap_alternate_title: Alternate_Applicant__r?.Title, + _bap_alternate_email: Alternate_Applicant__r?.Email, + _bap_alternate_phone: Alternate_Applicant__r?.Phone, + _bap_district_id: CSB_School_District__r?.Id, + _bap_district_nces_id: CSB_NCES_ID__c, + _bap_district_name: CSB_School_District__r?.Name, + _bap_district_address_1: schoolDistrictStreetAddress1 || "", + _bap_district_address_2: schoolDistrictStreetAddress2 || "", + _bap_district_city: CSB_School_District__r?.BillingCity, + _bap_district_state: CSB_School_District__r?.BillingState, + _bap_district_zip: CSB_School_District__r?.BillingPostalCode, + _bap_district_priority: Org_District_Prioritized__c, + _bap_district_priority_reason: { + highNeed: Prioritized_as_High_Need__c, + tribal: Prioritized_as_Tribal__c, + rural: Prioritized_as_Rural__c, + }, + _bap_district_self_certify: Self_Certification_Category__c, + _bap_district_contact_id: School_District_Contact__r?.Id, + _bap_district_contact_fname: School_District_Contact__r?.FirstName, + _bap_district_contact_lname: School_District_Contact__r?.LastName, + _bap_district_contact_title: School_District_Contact__r?.Title, + _bap_district_contact_email: School_District_Contact__r?.Email, + _bap_district_contact_phone: School_District_Contact__r?.Phone, + org_organizations, + bus_buses, + }, + /** 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 2024 Payment Request form submission from the BAP.`; + return res.status(errorStatus).json({ message: errorMessage }); + }); } } From 1a342721eb3e753d3d1ab8bed62e73f3f545ab4d Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 15:18:20 -0500 Subject: [PATCH 04/11] Add 2024 formio prf server api routes --- app/server/app/routes/formio2024.js | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/server/app/routes/formio2024.js b/app/server/app/routes/formio2024.js index 5b5462b6..e4ab6cf8 100644 --- a/app/server/app/routes/formio2024.js +++ b/app/server/app/routes/formio2024.js @@ -16,11 +16,11 @@ const { fetchFRFSubmission, updateFRFSubmission, // - // fetchPRFSubmissions, - // createPRFSubmission, - // fetchPRFSubmission, - // updatePRFSubmission, - // deletePRFSubmission, + fetchPRFSubmissions, + createPRFSubmission, + fetchPRFSubmission, + updatePRFSubmission, + deletePRFSubmission, // // fetchCRFSubmissions, // createCRFSubmission, @@ -93,28 +93,28 @@ router.post( // --- get user's 2024 PRF submissions from Formio router.get("/prf-submissions", storeBapComboKeys, (req, res) => { - res.json([]); // TODO: replace with `fetchPRFSubmissions({ rebateYear, req, res })` when PRF is ready + fetchPRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2024 PRF submission to Formio -// router.post("/prf-submission", storeBapComboKeys, (req, res) => { -// createPRFSubmission({ rebateYear, req, res }); -// }); +router.post("/prf-submission", storeBapComboKeys, (req, res) => { + createPRFSubmission({ rebateYear, req, res }); +}); // --- get an existing 2024 PRF's schema and submission data from Formio -// router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { -// fetchPRFSubmission({ rebateYear, req, res }); -// }); +router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { + fetchPRFSubmission({ rebateYear, req, res }); +}); // --- post an update to an existing draft 2024 PRF submission to Formio -// router.post("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { -// updatePRFSubmission({ rebateYear, req, res }); -// }); +router.post("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { + updatePRFSubmission({ rebateYear, req, res }); +}); // --- delete an existing 2024 PRF submission from Formio -// router.post("/delete-prf-submission", storeBapComboKeys, (req, res) => { -// deletePRFSubmission({ rebateYear, req, res }); -// }); +router.post("/delete-prf-submission", storeBapComboKeys, (req, res) => { + deletePRFSubmission({ rebateYear, req, res }); +}); // --- get user's 2024 CRF submissions from Formio router.get("/crf-submissions", storeBapComboKeys, (req, res) => { From 537f1c614121edb06b47d400d000f2e9f2942785 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 15:28:10 -0500 Subject: [PATCH 05/11] Add PRF2024Submission component --- app/client/src/routes/submissions.tsx | 228 +++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 4 deletions(-) diff --git a/app/client/src/routes/submissions.tsx b/app/client/src/routes/submissions.tsx index 401006fc..3ac9321e 100644 --- a/app/client/src/routes/submissions.tsx +++ b/app/client/src/routes/submissions.tsx @@ -1887,9 +1887,229 @@ function FRF2024Submission(props: { rebate: Rebate2024 }) { ); } -// function PRF2024Submission(props: { rebate: Rebate2024 }) { -// // -// } +function PRF2024Submission(props: { rebate: Rebate2024 }) { + const { rebate } = props; + const { frf, prf, crf } = rebate; + + const navigate = useNavigate(); + const { email } = useOutletContext<{ email: string }>(); + + const configData = useConfigData(); + const bapSamData = useBapSamData(); + const { displayErrorNotification } = useNotificationsActions(); + + /** + * Stores when data is being posted to the server, so a loading indicator can + * be rendered inside the "New Payment Request" button, and we can prevent + * double submits/creations of new PRF submissions. + */ + const [dataIsPosting, setDataIsPosting] = useState(false); + + if (!configData || !bapSamData) return null; + + /** matched SAM.gov entity for the FRF submission */ + const entity = bapSamData.entities.find((entity) => { + const comboKey = frf.formio.data._bap_entity_combo_key; + return entityIsActive(entity) && entity.ENTITY_COMBO_KEY__c === comboKey; + }); + + if (!entity) return null; + + const { title, name } = getUserInfo(email, entity); + + const prfSubmissionPeriodOpen = configData.submissionPeriodOpen["2024"].prf; + + const frfSelected = frf.bap?.status === "Accepted"; + const frfSelectedButNoPRF = frfSelected && !Boolean(prf.formio); + + if (frfSelectedButNoPRF) { + return ( + + + + + + ); + } + + // return if a Payment Request submission has not been created for this rebate + if (!prf.formio) return null; + + const { _user_email, _bap_entity_combo_key, _bap_rebate_id } = + prf.formio.data; + + const date = new Date(prf.formio.modified).toLocaleDateString(); + const time = new Date(prf.formio.modified).toLocaleTimeString(); + + const frfNeedsEdits = submissionNeedsEdits({ + formio: frf.formio, + bap: frf.bap, + }); + + const prfNeedsEdits = submissionNeedsEdits({ + formio: prf.formio, + bap: prf.bap, + }); + + const prfBapInternalStatus = prf.bap?.status || ""; + const prfBapStatus = bapStatusMap["2024"].prf.get(prfBapInternalStatus); + const prfFormioStatus = formioStatusMap.get(prf.formio.state); + + const prfStatus = prfNeedsEdits + ? "Edits Requested" + : prfBapStatus || prfFormioStatus || ""; + + const prfApproved = prfStatus === "Funding Approved"; + const prfApprovedButNoCRF = prfApproved && !Boolean(crf.formio); + + const statusTableCellClassNames = + prf.formio.state === "submitted" || !prfSubmissionPeriodOpen + ? "text-italic" + : ""; + + const prfUrl = `/prf/2024/${_bap_rebate_id}`; + + return ( + + + {frfNeedsEdits ? ( + + ) : prfNeedsEdits ? ( + + ) : prf.formio.state === "submitted" || !prfSubmissionPeriodOpen ? ( + + ) : prf.formio.state === "draft" ? ( + + ) : null} + + +   + + + Payment Request +
+ + {prfStatus === "Needs Clarification" ? ( + + ) : ( + <> + + {prfStatus} + + )} + + + +   + +   + + + {_user_email} +
+ {date} + + + + + + + ); +} // function CRF2024Submission(props: { rebate: Rebate2024 }) { // // @@ -1945,7 +2165,7 @@ function Submissions2024() { return rebate.rebateYear === "2024" ? ( - {/* */} + {/* */} {/* blank row after all submissions but the last one */} {index !== submissions.length - 1 && ( From ab714899c9b42f5bb3a525be704252f562dc6e00 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 15:51:39 -0500 Subject: [PATCH 06/11] Move use of bapStatusMap into variables in Submissions components --- app/client/src/routes/submissions.tsx | 30 +++++++++++---------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/app/client/src/routes/submissions.tsx b/app/client/src/routes/submissions.tsx index 3ac9321e..b2017c3e 100644 --- a/app/client/src/routes/submissions.tsx +++ b/app/client/src/routes/submissions.tsx @@ -196,13 +196,12 @@ function FRF2022Submission(props: { rebate: Rebate2022 }) { }); const frfBapInternalStatus = frf.bap?.status || ""; + const frfBapStatus = bapStatusMap["2022"].frf.get(frfBapInternalStatus); const frfFormioStatus = formioStatusMap.get(frf.formio.state); const frfStatus = frfNeedsEdits ? "Edits Requested" - : bapStatusMap["2022"].frf.get(frfBapInternalStatus) || - frfFormioStatus || - ""; + : frfBapStatus || frfFormioStatus || ""; const frfSelected = frfStatus === "Selected"; const frfSelectedButNoPRF = frfSelected && !Boolean(prf.formio); @@ -512,13 +511,12 @@ function PRF2022Submission(props: { rebate: Rebate2022 }) { }); const prfBapInternalStatus = prf.bap?.status || ""; + const prfBapStatus = bapStatusMap["2022"].prf.get(prfBapInternalStatus); const prfFormioStatus = formioStatusMap.get(prf.formio.state); const prfStatus = prfNeedsEdits ? "Edits Requested" - : bapStatusMap["2022"].prf.get(prfBapInternalStatus) || - prfFormioStatus || - ""; + : prfBapStatus || prfFormioStatus || ""; const prfApproved = prfStatus === "Funding Approved"; const prfApprovedButNoCRF = prfApproved && !Boolean(crf.formio); @@ -716,6 +714,7 @@ function CRF2022Submission(props: { rebate: Rebate2022 }) { }); const crfBapInternalStatus = crf.bap?.status || ""; + const crfBapStatus = bapStatusMap["2022"].crf.get(crfBapInternalStatus); const crfFormioStatus = formioStatusMap.get(crf.formio.state); const crfBapReimbursementNeeded = crf.bap?.reimbursementNeeded || false; @@ -728,9 +727,7 @@ function CRF2022Submission(props: { rebate: Rebate2022 }) { ? "Edits Requested" : crfNeedsReimbursement ? "Reimbursement Needed" - : bapStatusMap["2022"].crf.get(crfBapInternalStatus) || - crfFormioStatus || - ""; + : crfBapStatus || crfFormioStatus || ""; const crfApproved = crfStatus === "Close Out Approved"; @@ -1060,13 +1057,12 @@ function FRF2023Submission(props: { rebate: Rebate2023 }) { }); const frfBapInternalStatus = frf.bap?.status || ""; + const frfBapStatus = bapStatusMap["2023"].frf.get(frfBapInternalStatus); const frfFormioStatus = formioStatusMap.get(frf.formio.state); const frfStatus = frfNeedsEdits ? "Edits Requested" - : bapStatusMap["2023"].frf.get(frfBapInternalStatus) || - frfFormioStatus || - ""; + : frfBapStatus || frfFormioStatus || ""; const frfSelected = frfStatus === "Selected"; const frfSelectedButNoPRF = frfSelected && !Boolean(prf.formio); @@ -1366,13 +1362,12 @@ function PRF2023Submission(props: { rebate: Rebate2023 }) { }); const prfBapInternalStatus = prf.bap?.status || ""; + const prfBapStatus = bapStatusMap["2023"].prf.get(prfBapInternalStatus); const prfFormioStatus = formioStatusMap.get(prf.formio.state); const prfStatus = prfNeedsEdits ? "Edits Requested" - : bapStatusMap["2023"].prf.get(prfBapInternalStatus) || - prfFormioStatus || - ""; + : prfBapStatus || prfFormioStatus || ""; const prfApproved = prfStatus === "Funding Approved"; const prfApprovedButNoCRF = prfApproved && !Boolean(crf.formio); @@ -1727,13 +1722,12 @@ function FRF2024Submission(props: { rebate: Rebate2024 }) { }); const frfBapInternalStatus = frf.bap?.status || ""; + const frfBapStatus = bapStatusMap["2024"].frf.get(frfBapInternalStatus); const frfFormioStatus = formioStatusMap.get(frf.formio.state); const frfStatus = frfNeedsEdits ? "Edits Requested" - : bapStatusMap["2024"].frf.get(frfBapInternalStatus) || - frfFormioStatus || - ""; + : frfBapStatus || frfFormioStatus || ""; const frfSelected = frfStatus === "Selected"; const frfSelectedButNoPRF = frfSelected && !Boolean(prf.formio); From e9dffdcf0cdb13a2f0171d8c22b41f4639a99ffd Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 16:43:03 -0500 Subject: [PATCH 07/11] Update client app types for Formio and BAP data --- app/client/src/types.ts | 347 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 328 insertions(+), 19 deletions(-) diff --git a/app/client/src/types.ts b/app/client/src/types.ts index 3a4a807c..cccea6aa 100644 --- a/app/client/src/types.ts +++ b/app/client/src/types.ts @@ -34,6 +34,7 @@ export type ConfigData = { }; export type BapSamEntity = { + attributes: { type: "Data_Staging__c"; url: string }; Id: string; ENTITY_COMBO_KEY__c: string; UNIQUE_ENTITY_ID__c: string; @@ -48,24 +49,18 @@ export type BapSamEntity = { PHYSICAL_ADDRESS_PROVINCE_OR_STATE__c: string; PHYSICAL_ADDRESS_ZIPPOSTAL_CODE__c: string; PHYSICAL_ADDRESS_ZIP_CODE_4__c: string; - // contacts ELEC_BUS_POC_EMAIL__c: string | null; ELEC_BUS_POC_NAME__c: string | null; ELEC_BUS_POC_TITLE__c: string | null; - // ALT_ELEC_BUS_POC_EMAIL__c: string | null; ALT_ELEC_BUS_POC_NAME__c: string | null; ALT_ELEC_BUS_POC_TITLE__c: string | null; - // GOVT_BUS_POC_EMAIL__c: string | null; GOVT_BUS_POC_NAME__c: string | null; GOVT_BUS_POC_TITLE__c: string | null; - // ALT_GOVT_BUS_POC_EMAIL__c: string | null; ALT_GOVT_BUS_POC_NAME__c: string | null; ALT_GOVT_BUS_POC_TITLE__c: string | null; - // - attributes: { type: string; url: string }; }; export type BapSamData = @@ -73,6 +68,8 @@ export type BapSamData = | { results: true; entities: BapSamEntity[] }; export type BapFormSubmission = { + attributes: { type: "Order_Request__c"; url: string }; + Id: string; UEI_EFTI_Combo_Key__c: string; // UEI + EFTI combo key CSB_Form_ID__c: string; // MongoDB ObjectId string CSB_Modified_Full_String__c: string; // ISO 8601 date time string @@ -91,9 +88,9 @@ export type BapFormSubmission = { | "CSB Funding Request 2023" | "CSB Payment Request 2023" | "CSB Close Out Request 2023" - | "CSB Funding Request 2024" // TODO: confirm with BAP team - | "CSB Payment Request 2024" // TODO: confirm with BAP team - | "CSB Close Out Request 2024"; // TODO: confirm with BAP team + | "CSB Funding Request 2024" + | "CSB Payment Request 2024" + | "CSB Close Out Request 2024"; Rebate_Program_Year__c: null | RebateYear; Parent_CSB_Rebate__r: { CSB_Funding_Request_Status__c: string; @@ -102,7 +99,6 @@ export type BapFormSubmission = { Reimbursement_Needed__c: boolean; attributes: { type: string; url: string }; }; - attributes: { type: string; url: string }; }; export type BapFormSubmissions = { @@ -178,11 +174,42 @@ type FormioPRF2022Data = { [field: string]: unknown; // fields injected upon a new draft PRF submission creation: bap_hidden_entity_combo_key: string; - hidden_application_form_modified: string; // ISO 8601 date time string + hidden_application_form_modified: string; // ISO 8601 date time string, hidden_current_user_email: string; hidden_current_user_title: string; hidden_current_user_name: string; + hidden_sam_uei: string; + hidden_sam_efti: string; + hidden_sam_elec_bus_poc_email: string | null; + hidden_sam_alt_elec_bus_poc_email: string | null; + hidden_sam_govt_bus_poc_email: string | null; + hidden_sam_alt_govt_bus_poc_email: string | null; hidden_bap_rebate_id: string; + hidden_bap_district_id: string; + hidden_bap_primary_name: string; + hidden_bap_primary_title: string; + hidden_bap_primary_phone_number: string; + hidden_bap_primary_email: string; + hidden_bap_alternate_name: string; + hidden_bap_alternate_title: string; + hidden_bap_alternate_phone_number: string; + hidden_bap_alternate_email: string; + hidden_bap_org_name: string; + hidden_bap_district_name: string; + hidden_bap_fleet_name: string | null; + hidden_bap_prioritized: boolean; + hidden_bap_requested_funds: number; + hidden_bap_infra_max_rebate: number; + busInfo: { + busNum: number; + oldBusNcesDistrictId: string; + oldBusVin: string; + oldBusModelYear: string; + oldBusFuelType: string; + newBusFuelType: string; + hidden_bap_max_rebate: number; + }[]; + purchaseOrders: []; // fields set by form definition (among others): applicantName: string; }; @@ -196,6 +223,74 @@ type FormioCRF2022Data = { hidden_current_user_title: string; hidden_current_user_name: string; hidden_bap_rebate_id: string; + hidden_sam_uei: string; + hidden_sam_efti: string; + hidden_sam_elec_bus_poc_email: string | null; + hidden_sam_alt_elec_bus_poc_email: string | null; + hidden_sam_govt_bus_poc_email: string | null; + hidden_sam_alt_govt_bus_poc_email: string | null; + hidden_bap_district_id: string; + hidden_bap_district_name: string; + hidden_bap_primary_fname: string; + hidden_bap_primary_lname: string; + hidden_bap_primary_title: string; + hidden_bap_primary_phone_number: string; + hidden_bap_primary_email: string; + hidden_bap_alternate_fname: string; + hidden_bap_alternate_lname: string; + hidden_bap_alternate_title: string; + hidden_bap_alternate_phone_number: string; + hidden_bap_alternate_email: string; + hidden_bap_org_name: string; + hidden_bap_fleet_name: string; + hidden_bap_fleet_address: string; + hidden_bap_fleet_city: string; + hidden_bap_fleet_state: string; + hidden_bap_fleet_zip: string; + hidden_bap_fleet_contact_name: string; + hidden_bap_fleet_contact_title: string; + hidden_bap_fleet_phone: string; + hidden_bap_fleet_email: string; + hidden_bap_prioritized: boolean; + hidden_bap_requested_funds: number; + hidden_bap_received_funds: number; + hidden_bap_prf_infra_max_rebate: number | null; + hidden_bap_buses_requested_app: number; + hidden_bap_total_bus_costs_prf: number; + hidden_bap_total_bus_rebate_received: number; + hidden_bap_total_infra_costs_prf: number | null; + hidden_bap_total_infra_rebate_received: number | null; + hidden_bap_total_infra_level2_charger: number | null; + hidden_bap_total_infra_dc_fast_charger: number | null; + hidden_bap_total_infra_other_costs: number | null; + hidden_bap_district_contact_fname: string; + hidden_bap_district_contact_lname: string; + busInfo: { + busNum: number; + oldBusNcesDistrictId: string; + oldBusVin: string; + oldBusModelYear: string; + oldBusFuelType: string; + oldBusEstimatedRemainingLife: number; + oldBusExclude: boolean; + hidden_prf_oldBusExclude: boolean; + newBusDealer: string; + newBusFuelType: string; + hidden_prf_newBusFuelType: string; + newBusMake: string; + hidden_prf_newBusMake: string; + newBusMakeOther: string | null; + hidden_prf_newBusMakeOther: string | null; + newBusModel: string; + hidden_prf_newBusModel: string; + newBusModelYear: string; + hidden_prf_newBusModelYear: string; + newBusGvwr: number; + hidden_prf_newBusGvwr: number; + newBusPurchasePrice: number; + hidden_prf_newBusPurchasePrice: number; + hidden_prf_rebate: number; + }[]; // fields set by form definition (among others): signatureName: string; }; @@ -227,17 +322,124 @@ type FormioFRF2023Data = { type FormioPRF2023Data = { [field: string]: unknown; - // fields injected upon a new draft FRF submission creation: + // fields injected upon a new draft PRF submission creation: + _application_form_modified: string; + _bap_entity_combo_key: string; + _bap_rebate_id: string; _user_email: string; _user_title: string; _user_name: string; - _bap_entity_combo_key: string; - _bap_rebate_id: string; + _bap_applicant_email: string; + _bap_applicant_title: string; + _bap_applicant_name: string; + _bap_applicant_efti: string; + _bap_applicant_uei: string; + _bap_applicant_organization_id: string; + _bap_applicant_organization_name: string; + _bap_applicant_street_address_1: string; + _bap_applicant_street_address_2: string; + _bap_applicant_county: string; + _bap_applicant_city: string; + _bap_applicant_state: string; + _bap_applicant_zip: string; + _bap_elec_bus_poc_email: string | null; + _bap_alt_elec_bus_poc_email: string | null; + _bap_govt_bus_poc_email: string | null; + _bap_alt_govt_bus_poc_email: string | null; + _bap_primary_id: string; + _bap_primary_fname: string; + _bap_primary_lname: string; + _bap_primary_title: string; + _bap_primary_email: string; + _bap_primary_phone: string; + _bap_alternate_id: string | null; + _bap_alternate_fname: string | null; + _bap_alternate_lname: string | null; + _bap_alternate_title: string | null; + _bap_alternate_email: string | null; + _bap_alternate_phone: string | null; + _bap_district_id: string; + _bap_district_nces_id: string; + _bap_district_name: string; + _bap_district_address_1: string; + _bap_district_address_2: string; + _bap_district_city: string; + _bap_district_state: string; + _bap_district_zip: string; + _bap_district_priority: string; + _bap_district_priority_reason: { + highNeed: boolean; + tribal: boolean; + rural: boolean; + }; + _bap_district_self_certify: string; + _bap_district_contact_id: string; + _bap_district_contact_fname: string; + _bap_district_contact_lname: string; + _bap_district_contact_title: string; + _bap_district_contact_email: string; + _bap_district_contact_phone: string; + org_organizations: { + org_number: number; + org_type: { + existingBusOwner: boolean; + newBusOwner: boolean; + privateFleet: boolean; + }; + _org_id: string; + org_name: string; + _org_contact_id: string; + org_contact_fname: string; + org_contact_lname: string; + org_contact_title: string; + org_contact_email: string; + org_contact_phone: string; + org_address_1: string; + org_address_2: string; + org_county: string; + org_city: string; + org_state: { name: string }; + org_zip: string; + }[]; + bus_buses: { + bus_busNumber: number; + bus_existingOwner: { + org_id: string; + org_name: string; + org_contact_id: string; + org_contact_fname: string; + org_contact_lname: string; + }; + bus_existingVin: string; + bus_existingFuelType: string; + bus_existingGvwr: number; + bus_existingOdometer: number; + bus_existingModel: string; + bus_existingModelYear: string; + bus_existingNcesId: string; + bus_existingManufacturer: string; + bus_existingManufacturerOther: string | null; + bus_existingAnnualFuelConsumption: number; + bus_existingAnnualMileage: number; + bus_existingRemainingLife: number; + bus_existingIdlingHours: number; + bus_newOwner: { + org_id: string; + org_name: string; + org_contact_id: string; + org_contact_fname: string; + org_contact_lname: string; + }; + bus_newFuelType: string; + bus_newGvwr: number; + _bus_maxRebate: number; + _bus_newADAfromFRF: boolean; + }[]; }; type FormioCRF2023Data = { [field: string]: unknown; - // fields injected upon a new draft FRF submission creation: + // fields injected upon a new draft CRF submission creation: _user_email: string; _user_title: string; _user_name: string; @@ -286,17 +488,124 @@ type FormioFRF2024Data = { type FormioPRF2024Data = { [field: string]: unknown; - // fields injected upon a new draft FRF submission creation: + // fields injected upon a new draft PRF submission creation: + _application_form_modified: string; + _bap_entity_combo_key: string; + _bap_rebate_id: string; _user_email: string; _user_title: string; _user_name: string; - _bap_entity_combo_key: string; - _bap_rebate_id: string; + _bap_applicant_email: string; + _bap_applicant_title: string; + _bap_applicant_name: string; + _bap_applicant_efti: string; + _bap_applicant_uei: string; + _bap_applicant_organization_id: string; + _bap_applicant_organization_name: string; + _bap_applicant_street_address_1: string; + _bap_applicant_street_address_2: string; + _bap_applicant_county: string; + _bap_applicant_city: string; + _bap_applicant_state: string; + _bap_applicant_zip: string; + _bap_elec_bus_poc_email: string | null; + _bap_alt_elec_bus_poc_email: string | null; + _bap_govt_bus_poc_email: string | null; + _bap_alt_govt_bus_poc_email: string | null; + _bap_primary_id: string; + _bap_primary_fname: string; + _bap_primary_lname: string; + _bap_primary_title: string; + _bap_primary_email: string; + _bap_primary_phone: string; + _bap_alternate_id: string | null; + _bap_alternate_fname: string | null; + _bap_alternate_lname: string | null; + _bap_alternate_title: string | null; + _bap_alternate_email: string | null; + _bap_alternate_phone: string | null; + _bap_district_id: string; + _bap_district_nces_id: string; + _bap_district_name: string; + _bap_district_address_1: string; + _bap_district_address_2: string; + _bap_district_city: string; + _bap_district_state: string; + _bap_district_zip: string; + _bap_district_priority: string; + _bap_district_priority_reason: { + highNeed: boolean; + tribal: boolean; + rural: boolean; + }; + _bap_district_self_certify: string; + _bap_district_contact_id: string; + _bap_district_contact_fname: string; + _bap_district_contact_lname: string; + _bap_district_contact_title: string; + _bap_district_contact_email: string; + _bap_district_contact_phone: string; + org_organizations: { + org_number: number; + org_type: { + existingBusOwner: boolean; + newBusOwner: boolean; + privateFleet: boolean; + }; + _org_id: string; + org_name: string; + _org_contact_id: string; + org_contact_fname: string; + org_contact_lname: string; + org_contact_title: string; + org_contact_email: string; + org_contact_phone: string; + org_address_1: string; + org_address_2: string; + org_county: string; + org_city: string; + org_state: { name: string }; + org_zip: string; + }[]; + bus_buses: { + bus_busNumber: number; + bus_existingOwner: { + org_id: string; + org_name: string; + org_contact_id: string; + org_contact_fname: string; + org_contact_lname: string; + }; + bus_existingVin: string; + bus_existingFuelType: string; + bus_existingGvwr: number; + bus_existingOdometer: number; + bus_existingModel: string; + bus_existingModelYear: string; + bus_existingNcesId: string; + bus_existingManufacturer: string; + bus_existingManufacturerOther: string | null; + bus_existingAnnualFuelConsumption: number; + bus_existingAnnualMileage: number; + bus_existingRemainingLife: number; + bus_existingIdlingHours: number; + bus_newOwner: { + org_id: string; + org_name: string; + org_contact_id: string; + org_contact_fname: string; + org_contact_lname: string; + }; + bus_newFuelType: string; + bus_newGvwr: number; + _bus_maxRebate: number; + _bus_newADAfromFRF: boolean; + }[]; }; type FormioCRF2024Data = { [field: string]: unknown; - // fields injected upon a new draft FRF submission creation: + // fields injected upon a new draft CRF submission creation: _user_email: string; _user_title: string; _user_name: string; From e9bc049d16cdaba5bc4a6ccced231aae8ddd6a1d Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 16:43:34 -0500 Subject: [PATCH 08/11] Update bapStatusMap for 2024 PRF submission statuses --- app/client/src/config.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index 50faddae..2ec1f993 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -121,7 +121,11 @@ export const bapStatusMap = { .set("Withdrawn", "Withdrawn") .set("Coordinator Denied", "Not Selected") .set("Accepted", "Selected"), - prf: new Map(), // TODO + prf: new Map() + .set("Needs Clarification", "Needs Clarification") + .set("Withdrawn", "Withdrawn") + .set("Coordinator Denied", "Funding Denied") + .set("Accepted", "Funding Approved"), crf: new Map(), // TODO }, }; From cb9636bd23371f0b528475cd62bc813752e79498 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 16:45:43 -0500 Subject: [PATCH 09/11] Update types for server app's BapDataFor2022CRF --- app/server/app/utilities/bap.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index af0878c7..35022b90 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -340,7 +340,7 @@ const { submissionPeriodOpen } = require("../config/formio"); */ /** - * @typedef {Object} BapDataForFor2022CRF + * @typedef {Object} BapDataFor2022CRF * @property {{ * attributes: { type: "Order_Request__c", url: string } * Id: string @@ -398,8 +398,8 @@ const { submissionPeriodOpen } = require("../config/formio"); * Total_Bus_And_Infrastructure_Rebate__c: number * Total_Infrastructure_Funds__c: number | null * Num_Of_Buses_Requested_From_Application__c: number - * Total_Price_All_Buses__c: string - * Total_Bus_Rebate_Amount__c: string + * Total_Price_All_Buses__c: number + * Total_Bus_Rebate_Amount__c: number * Total_All_Eligible_Infrastructure_Costs__c: number | null * Total_Infrastructure_Rebate__c: number | null * Total_Level_2_Charger_Costs__c: number | null @@ -1586,7 +1586,7 @@ async function queryBapFor2024PRFData(req, frfReviewItemId) { * @param {express.Request} req * @param {string} frfReviewItemId CSB Rebate ID with the form/version ID (9 digits) * @param {string} prfReviewItemId CSB Rebate ID with the form/version ID (9 digits) - * @returns {Promise} 2022 FRF and 2022 PRF submission fields + * @returns {Promise} 2022 FRF and 2022 PRF submission fields */ async function queryBapFor2022CRFData(req, frfReviewItemId, prfReviewItemId) { const logMessage = From c1eb1c2b016df26b5e31937148f8260822540a52 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 6 Nov 2024 17:03:19 -0500 Subject: [PATCH 10/11] Add PRF2024 component and add it to the app's routes --- app/client/src/components/app.tsx | 4 +- app/client/src/routes/prf2024.tsx | 484 ++++++++++++++++++++++++++++++ 2 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 app/client/src/routes/prf2024.tsx diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index b92141f4..aa7619f2 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -43,7 +43,7 @@ import { PRF2023 } from "@/routes/prf2023"; // import { CRF2023 } from "@/routes/crf2023"; import { Change2024 } from "@/routes/change2024"; import { FRF2024 } from "@/routes/frf2024"; -// import { PRF2024 } from "@/routes/prf2024"; +import { PRF2024 } from "@/routes/prf2024"; // import { CRF2024 } from "@/routes/crf2024"; import { useDialogState, useDialogActions } from "@/contexts/dialog"; @@ -263,7 +263,7 @@ export function App() { } /> } /> - {/* } /> */} + } /> {/* } /> */} } /> diff --git a/app/client/src/routes/prf2024.tsx b/app/client/src/routes/prf2024.tsx new file mode 100644 index 00000000..52e9e251 --- /dev/null +++ b/app/client/src/routes/prf2024.tsx @@ -0,0 +1,484 @@ +import { useEffect, useMemo, useRef } from "react"; +import { useNavigate, useOutletContext, useParams } from "react-router-dom"; +import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query"; +import { Dialog } from "@headlessui/react"; +import { Formio, Form } from "@formio/react"; +import s3 from "formiojs/providers/storage/s3"; +import clsx from "clsx"; +import { cloneDeep, isEqual } from "lodash"; +import icons from "uswds/img/sprite.svg"; +// --- +import { + type FormioSchemaAndSubmission, + type FormioPRF2024Submission, +} from "@/types"; +import { serverUrl, messages } from "@/config"; +import { + getData, + postData, + useContentData, + useConfigData, + useBapSamData, + useSubmissionsQueries, + useSubmissions, + submissionNeedsEdits, + entityIsActive, + entityHasExclusionStatus, + entityHasDebtSubjectToOffset, + getUserInfo, +} from "@/utilities"; +import { Loading } from "@/components/loading"; +import { Message } from "@/components/message"; +import { MarkdownContent } from "@/components/markdownContent"; +import { useNotificationsActions } from "@/contexts/notifications"; + +type Response = FormioSchemaAndSubmission; + +/** Custom hook to fetch and update Formio submission data */ +function useFormioSubmissionQueryAndMutation(rebateId: string | undefined) { + const queryClient = useQueryClient(); + + useEffect(() => { + queryClient.resetQueries({ queryKey: ["formio/2024/prf-submission"] }); + }, [queryClient]); + + const url = `${serverUrl}/api/formio/2024/prf-submission/${rebateId}`; + + const query = useQuery({ + queryKey: ["formio/2024/prf-submission", { id: rebateId }], + queryFn: () => { + return getData(url).then((res) => { + const mongoId = res.submission?._id; + const comboKey = res.submission?.data._bap_entity_combo_key; + + /** + * Change the formUrl the File component's `uploadFile` uses, so the s3 + * upload PUT request is routed through the server app. + * + * https://github.com/formio/formio.js/blob/master/src/components/file/File.js#L760 + * https://github.com/formio/formio.js/blob/master/src/providers/storage/s3.js#L5 + * https://github.com/formio/formio.js/blob/master/src/providers/storage/xhr.js#L90 + */ + Formio.Providers.providers.storage.s3 = function (formio: { + formUrl: string; + [field: string]: unknown; + }) { + const s3Formio = cloneDeep(formio); + s3Formio.formUrl = `${serverUrl}/api/formio/2024/s3/prf/${mongoId}/${comboKey}`; + return s3(s3Formio); + }; + + return Promise.resolve(res); + }); + }, + refetchOnWindowFocus: false, + }); + + const mutation = useMutation({ + mutationFn: (updatedSubmission: { + mongoId: string; + submission: { + data: { [field: string]: unknown }; + metadata: { [field: string]: unknown }; + state: "submitted" | "draft"; + }; + }) => { + return postData(url, updatedSubmission); + }, + onSuccess: (res) => { + return queryClient.setQueryData( + ["formio/2024/prf-submission", { id: rebateId }], + (prevData) => { + return prevData?.submission + ? { ...prevData, submission: res } + : prevData; + }, + ); + }, + }); + + return { query, mutation }; +} + +export function PRF2024() { + const { email } = useOutletContext<{ email: string }>(); + /* ensure user verification (JWT refresh) doesn't cause form to re-render */ + return useMemo(() => { + return ; + }, [email]); +} + +function PaymentRequestForm(props: { email: string }) { + const { email } = props; + + const navigate = useNavigate(); + const { id: rebateId } = useParams<"id">(); // CSB Rebate ID (6 digits) + + const content = useContentData(); + const configData = useConfigData(); + const bapSamData = useBapSamData(); + const { + displaySuccessNotification, + displayErrorNotification, + dismissNotification, + } = useNotificationsActions(); + + const submissionsQueries = useSubmissionsQueries("2024"); + const submissions = useSubmissions("2024"); + + const { query, mutation } = useFormioSubmissionQueryAndMutation(rebateId); + const { userAccess, formSchema, submission } = query.data ?? {}; + + /** + * Stores when data is being posted to the server, so a loading overlay can + * be rendered over the form, preventing the user from losing input data when + * the form is re-rendered with data returned from the server's successful + * post response. + */ + const dataIsPosting = useRef(false); + + /** + * Stores when the form is being submitted, so it can be referenced in the + * Form component's `onSubmit` event prop to prevent double submits. + */ + const formIsBeingSubmitted = useRef(false); + + /** + * Stores the form data's state right after the user clicks the Save, Submit, + * or Next button. As soon as a post request to update the data succeeds, this + * pending submission data is reset to an empty object. This pending data, + * along with the submission data returned from the server is passed into the + * Form component's `submission` prop. + */ + const pendingSubmissionData = useRef<{ [field: string]: unknown }>({}); + + /** + * Stores the last succesfully submitted data, so it can be used in the Form + * component's `onNextPage` event prop's "dirty check" which determines if + * posting of updated data is needed (so we don't make needless requests if no + * field data in the form has changed). + */ + const lastSuccesfullySubmittedData = useRef<{ [field: string]: unknown }>({}); + + if (!configData || !bapSamData) { + return ; + } + + if (submissionsQueries.some((query) => query.isFetching)) { + return ; + } + + if (submissionsQueries.some((query) => query.isError)) { + return ; + } + + if (query.isInitialLoading) { + return ; + } + + if (query.isError || !userAccess || !formSchema || !submission) { + return ; + } + + const rebate = submissions.find((r) => r.rebateId === rebateId); + + const frfNeedsEdits = !rebate + ? false + : submissionNeedsEdits({ + formio: rebate.frf.formio, + bap: rebate.frf.bap, + }); + + const prfNeedsEdits = !rebate + ? false + : submissionNeedsEdits({ + formio: rebate.prf.formio, + bap: rebate.prf.bap, + }); + + const prfSubmissionPeriodOpen = configData.submissionPeriodOpen["2024"].prf; + + const formIsReadOnly = + frfNeedsEdits || + ((submission.state === "submitted" || !prfSubmissionPeriodOpen) && + !prfNeedsEdits); + + /** matched SAM.gov entity for the Payment Request submission */ + const entity = bapSamData.entities.find((entity) => { + const { ENTITY_COMBO_KEY__c } = entity; + return ENTITY_COMBO_KEY__c === submission.data._bap_entity_combo_key; + }); + + if (!entity) { + return ; + } + + const isActive = entityIsActive(entity); + const hasExclusionStatus = entityHasExclusionStatus(entity); + const hasDebtSubjectToOffset = entityHasDebtSubjectToOffset(entity); + + if (!isActive || hasExclusionStatus || hasDebtSubjectToOffset) { + return ; + } + + const { + ELEC_BUS_POC_EMAIL__c, + ALT_ELEC_BUS_POC_EMAIL__c, + GOVT_BUS_POC_EMAIL__c, + ALT_GOVT_BUS_POC_EMAIL__c, + } = entity; + + const { title, name } = getUserInfo(email, entity); + + return ( +
+ {content && ( + + )} + + {frfNeedsEdits && ( + + )} + +
    +
  • +
    + +
    +
    + Rebate ID: {rebateId} +
    +
  • +
+ + {}}> +
+
+
+ + + +
+
+
+ +
+
{ + if (formIsReadOnly) return; + + // account for when form is being submitted to prevent double submits + if (formIsBeingSubmitted.current) return; + if (onSubmitSubmission.state === "submitted") { + formIsBeingSubmitted.current = true; + } + + const data = { ...onSubmitSubmission.data }; + + const updatedSubmission = { + mongoId: submission._id, + submission: { + ...onSubmitSubmission, + data, + }, + }; + + dismissNotification({ id: 0 }); + dataIsPosting.current = true; + pendingSubmissionData.current = data; + + mutation.mutate(updatedSubmission, { + onSuccess: (res, _payload, _context) => { + pendingSubmissionData.current = {}; + lastSuccesfullySubmittedData.current = cloneDeep(res.data); + + /** success notification id */ + const id = Date.now(); + + displaySuccessNotification({ + id, + body: ( +

+ {onSubmitSubmission.state === "submitted" ? ( + <> + Payment Request {rebateId} submitted + successfully. + + ) : ( + <>Draft saved successfully. + )} +

+ ), + }); + + if (onSubmitSubmission.state === "submitted") { + /** + * NOTE: we'll keep the success notification displayed and + * redirect the user to their dashboard + */ + navigate("/"); + } + + if (onSubmitSubmission.state === "draft") { + setTimeout(() => dismissNotification({ id }), 5000); + } + }, + onError: (_error, _payload, _context) => { + displayErrorNotification({ + id: Date.now(), + body: ( +

+ {onSubmitSubmission.state === "submitted" ? ( + <>Error submitting Payment Request form. + ) : ( + <>Error saving draft. + )} +

+ ), + }); + }, + onSettled: (_data, _error, _payload, _context) => { + dataIsPosting.current = false; + formIsBeingSubmitted.current = false; + }, + }); + }} + onNextPage={(onNextPageParam: { + page: number; + submission: { + data: { [field: string]: unknown }; + metadata: { [field: string]: unknown }; + }; + }) => { + if (formIsReadOnly) return; + + const data = { ...onNextPageParam.submission.data }; + + // "dirty check" – don't post an update if no changes have been made + // to the form (ignoring current user fields) + const currentData = { ...data }; + const submittedData = { ...lastSuccesfullySubmittedData.current }; + + delete currentData._user_email; + delete currentData._user_title; + delete currentData._user_name; + delete submittedData._user_email; + delete submittedData._user_title; + delete submittedData._user_name; + if (isEqual(currentData, submittedData)) return; + + const updatedSubmission = { + mongoId: submission._id, + submission: { + ...onNextPageParam.submission, + data, + state: "draft" as const, + }, + }; + + dismissNotification({ id: 0 }); + dataIsPosting.current = true; + pendingSubmissionData.current = data; + + mutation.mutate(updatedSubmission, { + onSuccess: (res, _payload, _context) => { + pendingSubmissionData.current = {}; + lastSuccesfullySubmittedData.current = cloneDeep(res.data); + + /** success notification id */ + const id = Date.now(); + + displaySuccessNotification({ + id, + body: ( +

+ Draft saved successfully. +

+ ), + }); + + setTimeout(() => dismissNotification({ id }), 5000); + }, + onError: (_error, _payload, _context) => { + displayErrorNotification({ + id: Date.now(), + body: ( +

+ Error saving draft. +

+ ), + }); + }, + onSettled: (_data, _error, _payload, _context) => { + dataIsPosting.current = false; + }, + }); + }} + /> +
+ + {frfNeedsEdits && ( + + )} +
+ ); +} From 0f5c1147ec8d0a28707ec634e2009b3816a9acc4 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 7 Nov 2024 11:42:26 -0500 Subject: [PATCH 11/11] Update field names of new fields used in a brand new 2024 PRF submission --- app/client/src/types.ts | 77 +++++++++++++++--------------- app/server/app/utilities/formio.js | 77 ++++++++++++++++-------------- 2 files changed, 80 insertions(+), 74 deletions(-) diff --git a/app/client/src/types.ts b/app/client/src/types.ts index cccea6aa..c14baff6 100644 --- a/app/client/src/types.ts +++ b/app/client/src/types.ts @@ -489,7 +489,7 @@ type FormioFRF2024Data = { type FormioPRF2024Data = { [field: string]: unknown; // fields injected upon a new draft PRF submission creation: - _application_form_modified: string; + _frf_modified: string; _bap_entity_combo_key: string; _bap_rebate_id: string; _user_email: string; @@ -546,60 +546,61 @@ type FormioPRF2024Data = { _bap_district_contact_email: string; _bap_district_contact_phone: string; org_organizations: { + _bap_org_frf: boolean; org_number: number; org_type: { - existingBusOwner: boolean; - newBusOwner: boolean; - privateFleet: boolean; + existing_bus_owner: boolean; + new_bus_owner: boolean; + private_fleet: boolean; }; - _org_id: string; - org_name: string; - _org_contact_id: string; - org_contact_fname: string; - org_contact_lname: string; - org_contact_title: string; - org_contact_email: string; - org_contact_phone: string; - org_address_1: string; - org_address_2: string; - org_county: string; - org_city: string; - org_state: { name: string }; - org_zip: string; + _bap_org_id: string; + _bap_org_name: string; + _bap_org_contact_id: string; + _bap_org_contact_fname: string; + _bap_org_contact_lname: string; + _bap_org_contact_title: string; + _bap_org_contact_email: string; + _bap_org_contact_phone: string; + _bap_org_address_1: string; + _bap_org_address_2: string; + _bap_org_county: string; + _bap_org_city: string; + _bap_org_state: { name: string }; + _bap_org_zip: string; }[]; bus_buses: { - bus_busNumber: number; - bus_existingOwner: { + bus_number: number; + bus_existing_owner: { org_id: string; org_name: string; org_contact_id: string; org_contact_fname: string; org_contact_lname: string; }; - bus_existingVin: string; - bus_existingFuelType: string; - bus_existingGvwr: number; - bus_existingOdometer: number; - bus_existingModel: string; - bus_existingModelYear: string; - bus_existingNcesId: string; - bus_existingManufacturer: string; - bus_existingManufacturerOther: string | null; - bus_existingAnnualFuelConsumption: number; - bus_existingAnnualMileage: number; - bus_existingRemainingLife: number; - bus_existingIdlingHours: number; - bus_newOwner: { + bus_existing_vin: string; + bus_existing_fuel_type: string; + bus_existing_gvwr: number; + bus_existing_odometer: number; + bus_existing_model: string; + bus_existing_model_year: string; + bus_existing_nces_id: string; + bus_existing_manufacturer: string; + bus_existing_manufacturer_other: string | null; + bus_existing_annual_fuel_consumption: number; + bus_existing_annual_mileage: number; + bus_existing_remaining_life: number; + bus_existing_idling_hours: number; + bus_new_owner: { org_id: string; org_name: string; org_contact_id: string; org_contact_fname: string; org_contact_lname: string; }; - bus_newFuelType: string; - bus_newGvwr: number; - _bus_maxRebate: number; - _bus_newADAfromFRF: boolean; + bus_new_fuel_type: string; + bus_new_gvwr: number; + _bus_new_max_rebate: number; + _bus_new_ada_from_frf: boolean; }[]; }; diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 975146fb..a722d8e2 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -573,22 +573,27 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { ).split("\n"); array.push({ + _bap_org_frf: true, org_number: jsonOrg.org_number, - org_type: jsonOrg.org_type, - _org_id: orgId, - org_name: orgName, - _org_contact_id: contactId, - org_contact_fname: FirstName, - org_contact_lname: LastName, - org_contact_title: Title, - org_contact_email: Email, - org_contact_phone: Phone, - org_address_1: orgStreetAddress1, - org_address_2: orgStreetAddress2, - org_county: County__c, - org_city: BillingCity, - org_state: { name: BillingState }, - org_zip: BillingPostalCode, + org_type: { + existing_bus_owner: jsonOrg.org_type.existingBusOwner, + new_bus_owner: jsonOrg.org_type.newBusOwner, + private_fleet: jsonOrg.org_type.privateFleet, + }, + _bap_org_id: orgId, + _bap_org_name: orgName, + _bap_org_contact_id: contactId, + _bap_org_contact_fname: FirstName, + _bap_org_contact_lname: LastName, + _bap_org_contact_title: Title, + _bap_org_contact_email: Email, + _bap_org_contact_phone: Phone, + _bap_org_address_1: orgStreetAddress1, + _bap_org_address_2: orgStreetAddress2, + _bap_org_county: County__c, + _bap_org_city: BillingCity, + _bap_org_state: { name: BillingState }, + _bap_org_zip: BillingPostalCode, }); } @@ -633,44 +638,44 @@ function fetchDataForPRFSubmission({ rebateYear, req, res }) { ); return { - bus_busNumber: Rebate_Item_num__c, - bus_existingOwner: { + bus_number: Rebate_Item_num__c, + bus_existing_owner: { org_id: existingOwnerRecord?.Contact__r?.Account?.Id, org_name: existingOwnerRecord?.Contact__r?.Account?.Name, org_contact_id: existingOwnerRecord?.Contact__r?.Id, org_contact_fname: existingOwnerRecord?.Contact__r?.FirstName, org_contact_lname: existingOwnerRecord?.Contact__r?.LastName, }, - bus_existingVin: CSB_VIN__c, - bus_existingFuelType: CSB_Fuel_Type__c, - bus_existingGvwr: CSB_GVWR__c, - bus_existingOdometer: Old_Bus_Odometer_miles__c, - bus_existingModel: CSB_Model__c, - bus_existingModelYear: CSB_Model_Year__c, - bus_existingNcesId: Old_Bus_NCES_District_ID__c, - bus_existingManufacturer: CSB_Manufacturer__c, - bus_existingManufacturerOther: CSB_Manufacturer_if_Other__c, - bus_existingAnnualFuelConsumption: CSB_Annual_Fuel_Consumption__c, - bus_existingAnnualMileage: Annual_Mileage__c, - bus_existingRemainingLife: Old_Bus_Estimated_Remaining_Life__c, - bus_existingIdlingHours: Old_Bus_Annual_Idling_Hours__c, - bus_newOwner: { + bus_existing_vin: CSB_VIN__c, + bus_existing_fuel_type: CSB_Fuel_Type__c, + bus_existing_gvwr: CSB_GVWR__c, + bus_existing_odometer: Old_Bus_Odometer_miles__c, + bus_existing_model: CSB_Model__c, + bus_existing_model_year: CSB_Model_Year__c, + bus_existing_nces_id: Old_Bus_NCES_District_ID__c, + bus_existing_manufacturer: CSB_Manufacturer__c, + bus_existing_manufacturer_other: CSB_Manufacturer_if_Other__c, + bus_existing_annual_fuel_consumption: CSB_Annual_Fuel_Consumption__c, // prettier-ignore + bus_existing_annual_mileage: Annual_Mileage__c, + bus_existing_remaining_life: Old_Bus_Estimated_Remaining_Life__c, + bus_existing_idling_hours: Old_Bus_Annual_Idling_Hours__c, + bus_new_owner: { org_id: newOwnerRecord?.Contact__r?.Account?.Id, org_name: newOwnerRecord?.Contact__r?.Account?.Name, org_contact_id: newOwnerRecord?.Contact__r?.Id, org_contact_fname: newOwnerRecord?.Contact__r?.FirstName, org_contact_lname: newOwnerRecord?.Contact__r?.LastName, }, - bus_newFuelType: New_Bus_Fuel_Type__c, - bus_newGvwr: New_Bus_GVWR__c, - _bus_maxRebate: New_Bus_Infra_Rebate_Requested__c, - _bus_newADAfromFRF: New_Bus_ADA_Compliant__c, + bus_new_fuel_type: New_Bus_Fuel_Type__c, + bus_new_gvwr: New_Bus_GVWR__c, + _bus_new_max_rebate: New_Bus_Infra_Rebate_Requested__c, + _bus_new_ada_from_frf: New_Bus_ADA_Compliant__c, }; }); return { data: { - _application_form_modified: frfModified, + _frf_modified: frfModified, _bap_entity_combo_key: comboKey, _bap_rebate_id: rebateId, _user_email: email,