diff --git a/.env.template b/.env.template index bbf68d2..c9d0d65 100644 --- a/.env.template +++ b/.env.template @@ -5,4 +5,5 @@ ENVIRONMENT= HOST= PORT= AIRTABLE_API_KEY= +JOTFORM_API_KEY= CLERK_SECRET_KEY= diff --git a/package-lock.json b/package-lock.json index 050ccf7..0cdc9b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "helmet": "^7.1.0", "http": "^0.0.1-security", "joi": "^17.12.0", + "jotform": "^1.0.1", "morgan": "^1.10.0", "nodemon": "^3.0.1", "pino": "^8.17.2", @@ -2132,6 +2133,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2180,29 +2191,6 @@ "node": ">=8" } }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3510,6 +3498,43 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fast-copy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", @@ -3673,6 +3698,25 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -3699,7 +3743,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -4813,6 +4856,20 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jotform": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jotform/-/jotform-1.0.1.tgz", + "integrity": "sha512-Oi1xav5OSvgj34w2Re3nTnOHdCJic+GiYlPxQUZPLWVQ15zocNtqwRhA9p4umk4RSL7PdiDjFGaWwIBrc5SCqQ==", + "dependencies": { + "axios": "^1.6.2", + "tslib": "^2.6.2" + } + }, + "node_modules/jotform/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -6102,6 +6159,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -6210,20 +6272,6 @@ "node": ">= 0.6" } }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -9341,6 +9389,16 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true }, + "axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "requires": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -9371,25 +9429,6 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -10372,6 +10411,38 @@ "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } } }, "fast-copy": { @@ -10504,6 +10575,11 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -10527,7 +10603,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -11297,6 +11372,22 @@ "@sideway/pinpoint": "^2.0.0" } }, + "jotform": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jotform/-/jotform-1.0.1.tgz", + "integrity": "sha512-Oi1xav5OSvgj34w2Re3nTnOHdCJic+GiYlPxQUZPLWVQ15zocNtqwRhA9p4umk4RSL7PdiDjFGaWwIBrc5SCqQ==", + "requires": { + "axios": "^1.6.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, "joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -12257,6 +12348,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -12335,17 +12431,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", diff --git a/package.json b/package.json index 93b1f80..0a359a9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "helmet": "^7.1.0", "http": "^0.0.1-security", "joi": "^17.12.0", + "jotform": "^1.0.1", "morgan": "^1.10.0", "nodemon": "^3.0.1", "pino": "^8.17.2", diff --git a/src/controllers/FlightRequest.controller.ts b/src/controllers/FlightRequest.controller.ts index 0494162..b961e94 100644 --- a/src/controllers/FlightRequest.controller.ts +++ b/src/controllers/FlightRequest.controller.ts @@ -1,5 +1,6 @@ import logger from '../util/logger'; import { trimFlightLeg, trimRequest } from '../util/trim'; +import { createTestFlightLegData } from '../data/test-data'; import Airtable from 'airtable'; import dotenv from 'dotenv'; import type { FlightLegData } from '../interfaces/legs/flight-leg.interface'; @@ -166,3 +167,55 @@ export const getFlightLegsById = async (req: Request, res: Response) => { return res.status(200).send(flightLegs); }; + +/** + * This function creates a flight request for a given user + * + * Steps to complete: + * 1. Use Joi to validate the request body, if it doesn't exist or is invalid return a 400 + * 2. Create a fake flight request by making a call to JotForm. If that fails return a 500 (hint, use try/catch) + * 3. Return the flight request that was created + * + * @param req - the request object + * @param res - the response object + */ +export const createFlightRequest = async (req: Request, res: Response) => { + // get the flight request data from the request body + // const data = req.body; + + // if (!data) { + // return res.status(400).json({ error: 'Flight request data missing' }); + // } + // use Joi to validate the request body + // ... + + // create a fake flight request + const flightRequest = createTestFlightLegData(); // return the flight request + + res.status(200).send(flightRequest); +}; + +/** + * This function updates a flight request for a given flightRequestId + * + * Steps to complete: + * 1. Get the flightRequestId from the path parameters, if it doesn't exist return a 400 + * 2. Use Joi to validate the request body, if it doesn't exist or is invalid return a 400 + * 3. Update the flight request by making a call to AirTable. If that fails return a 500 (hint, use try/catch) + * 4. Return the entire flight request that was updated, once again removing any unnecessary data + * + * @param req - the request object + * @param res - the response object + */ +export const updateFlightRequest = async (req: Request, res: Response) => { + // get the flightRequestId from the path parameters + // const { flightRequestId } = req.params; + + // use Joi to validate the request body + // ... + + // create a fake flight request that was "updated" + const flightRequest = createTestFlightLegData(); // return the flight request + + res.status(200).send(flightRequest); +}; diff --git a/src/controllers/Passenger.controller.ts b/src/controllers/Passenger.controller.ts index 1b461d6..e6e0f48 100644 --- a/src/controllers/Passenger.controller.ts +++ b/src/controllers/Passenger.controller.ts @@ -1,6 +1,5 @@ /* eslint-disable */ import logger from '../util/logger'; -import { createTestPassengerData } from '../data/test-data'; import { trimPassenger } from '../util/trim'; import type { PassengerData } from '../interfaces/passenger/passenger.interface'; import type { Request, Response } from 'express'; diff --git a/src/data/question-map.ts b/src/data/question-map.ts new file mode 100644 index 0000000..a397cab --- /dev/null +++ b/src/data/question-map.ts @@ -0,0 +1,134 @@ +export const questionMap = { + 'Will the patient be 17 years old or younger at the time of the medical appointment that you need help traveling to?': 454, + 'Can the patient/applicant and all accompanying passengers safely travel on a commercial airplane?': 536, + 'Does the patient/applicant and all accompanying passengers have the required identification and/or documentation to get through TSA and travel on a commercial airplane?': 548, + 'Do you currently live in the U.S. AND do you have a confirmed medical appointment scheduled requiring domestic U.S. air travel? (We do not provide international travel in our out of the U.S.)': 427, + 'Is your requested departure flight date at least 14 days away?': 450, + 'What type(s) of government assistance are you currently receiving? Please select all that apply. You will need to upload proof of government assistance at the end of this application. If you are not currently receiving government assistance, please check “None”.': 563, + 'Do you have your IRS 1040 documents from last year to upload on this application as proof of household income and household size?': 560, + 'How many people are in your household?': 443, + 'What is your approximate gross household income?': 444, + 'Child Patient Verification - Will the patient be 17 years old or younger at the time of the medical appointment?': 539, + 'If approved, will this be your first time using Miracle Flights?': 445, + 'Is your requested departure date at least 14 days away?': 540, + 'Enter Approved Single Use Inside Timeframe Code To Proceed': 543, + 'What Type of Travel Is This?': 523, + 'Airport of Origin (Flying From)': 403, + 'Alternate Airport of Origin (Flying From)': 404, + 'Destination Airport (Flying To)': 405, + 'Alternate Destination Airport (Flying To)': 406, + + // Patient + 'First Name': 192, + 'Middle Name': 193, + 'Last Name': 311, + 'Date of Birth': 14, + 'Age of patient at time of departing flight?': 522, + Gender: 136, + 'Street Address': 198, + City: 199, + State: 479, + 'Postal Code': 206, + County: 519, + Country: 207, + Email: 24, + Diagnoses: 287, + 'Type of Treatment': 288, + 'Does the child patient presently have medical insurance coverage?': 554, + 'Medical Insurance Company Name': 555, + 'Subscriber ID #, Policy #, or Member ID #': 556, + 'Group #': 557, + 'Full Name of Treatment Site': 472, + 'Full Name of Primary Treatment Site Doctor': 477, + TreatmentSiteCityandState: '473', + TreatmentSitePhoneNumber: '474', + 'Oxygen Required': 162, + 'Will you be flying with a service dog?': 535, + 'Wheelchair Required': 163, + 'Birth Certificate or Proof of Legal Guardianship Required for Child Patient (17 and under)': 170, + 'Patient Ethnicity (Select all that apply)': 187, + 'Highest level of education completed (by adult patient OR parent/guardian of child patient filling out this form)': 309, + 'Marital Status (of adult patient OR parent/guardian of child patient filling out this form)': 308, + 'Employment Status (of adult patient OR parent/guardian of child patient filling out this form)': 310, + 'Military Service (parent/guardian of child patient)': 514, + 'Military Member (parent/guardian of child patient)': 515, + 'How did you hear about us?': 150, + 'Child Patient Name (as it appears on government issued identification)': 413, + 'Waive Right to Pursue Legal Action': 415, + 'Consent for Medical Treatment': 547, + 'Photo/Video Release': 416, + 'Patient Signer Name': 289, + 'Relationship to Patient': 419, + + // Passenger 2 + 'Passenger 2 - First Name': 264, + 'Passenger 2 - Middle Name': 265, + 'Passenger 2 - Last Name': 312, + 'Passenger 2 - Relationship to Patient': 294, + 'Passenger 2 - Gender': 456, + 'Passenger 2 - Email': 268, + 'Passenger 2 - Address is the same as the patient?': 306, + 'Passenger 2 - Street Address': 291, + 'Passenger 2 - City': 299, + 'Passenger 2 - State': 480, + 'Passenger 2 - Postal Code': 301, + 'Passenger 2 - Country': 302, + 'Passenger 2 - Date of Birth': 452, + 'Passenger 2 Waiver of Responsibility': 343, + 'Passenger Name (as it appears on government issued identification)': 257, + 'Passenger 2 Waive Right to Pursue Legal Action': 258, + 'Passenger 2 Photo/Video Release': 260, + 'Passenger 2 Signer Name': 355, + 'Relationship to Passenger 2': 290, + 'Will patient require a third passenger?': 407, + + // Passenger 3 + 'Passenger 3 - First Name': 266, + 'Passenger 3 - Middle Name': 267, + 'Passenger 3 - Last Name': 313, + 'Passenger 3 - Relationship to Patient': 303, + 'Passenger 3 - Gender': 457, + 'Passenger 3 - Email': 269, + 'Is Passenger 3 returning on a different date than Patient/Passenger 1?': 526, + 'Explain why Passenger 3 is requesting a different return date than Patient/Passenger 1': 531, + 'Passenger 3 - Address is the same as the patient?': 307, + 'Passenger 3 - Street Address': 298, + 'Passenger 3 - City': 292, + 'Passenger 3 - State': 481, + 'Passenger 3 - Postal Code': 293, + 'Passenger 3 - Country': 218, + 'Passenger 3 Waiver of Responsibility': 367, + 'Passenger 3 Name (as it appears on government issued identification)': 350, + 'Passenger 3 Waive Right to Pursue Legal Action': 352, + 'Passenger 3 Photo/Video Release': 353, + 'Passenger 3 Signer Name': 418, + 'Relationship to Passenger 3': 356, + 'Passenger 3 - Date of Birth': 453, + + // General + 'Number of People in Household': 176, + 'Annual Family/Household Income': 175, + 'Please describe all sources of income for the household.': 178, + 'Upload proof of income - IRS 1040 and/or proof of government assistance program': 177, + 'Eligibility is determined by total family income and size.': 190, + 'Cancelling or Amending Flights': 191, + 'No Call No Show Acknowledgment': 285, + 'Final Consent for Medical Treatment': 561, + 'Signer Email': 219, + 'Name of Signer': 153, + 'Final Relationship to Patient': 154, +}; + +export const newFlightRequestQuestions = { + 'Is your requested departure flight date at least 14 days away?': 450, + 'What Type of Travel Is This?': 523, + 'Scheduled Medical Appointment Date': 159, // Find the ID for this one, needs to be in iso string format (use .toISOString() ) + 'Departure Date?': 513, // Find the ID for this one + 'Airport of Origin (Flying From)': 403, + 'Alternate Airport of Origin (Flying From)': 404, + 'Destination Airport (Flying To)': 405, + 'Alternate Destination Airport (Flying To)': 406, + 'Return Date?': 160, // Find the ID for this one + 'Full Name of Treatment Site': 472, + 'Full Name of Primary Treatment Site Doctor': 477, +}; diff --git a/src/data/test-data.ts b/src/data/test-data.ts index 10a9aa2..6ef1218 100644 --- a/src/data/test-data.ts +++ b/src/data/test-data.ts @@ -41,14 +41,18 @@ export const createTestPassengerData = ( firstName: firstName, lastName: lastName, }), - 'Household Income': faker.number.int({ - min: 5000, - max: 400000, - }), - 'Household Size': faker.number.int({ - min: 1, - max: 8, - }), + 'Household Income': faker.number + .int({ + min: 5000, + max: 400000, + }) + .toString(), + 'Household Size': faker.number + .int({ + min: 1, + max: 8, + }) + .toString(), Ethnicity: faker.helpers.arrayElements( [ 'American Indian or Alaska Native', @@ -91,7 +95,7 @@ export const createTestPassengerData = ( max: 3, }) ), - Diagnosis: faker.helpers.arrayElements( + Diagnoses: faker.helpers.arrayElements( [faker.string.uuid(), faker.string.uuid(), faker.string.uuid()], faker.number.int({ min: 1, @@ -175,10 +179,12 @@ export const createTestPassengerData = ( min: 1, max: 3, }), - Age: faker.number.int({ - min: 1, - max: 80, - }), + Age: faker.number + .int({ + min: 1, + max: 80, + }) + .toString(), Birthday: birthday, 'Day Before Birthday': getPreviousDay(birthday), 'BL - Site 1 (from All Flight Legs)': faker.helpers.arrayElements( @@ -451,7 +457,7 @@ export const createTestFlightLegData = ( max: 3, }) ), - 'Diagnosis (from Passengers)': faker.helpers.arrayElements( + 'Diagnoses (from Passengers)': faker.helpers.arrayElements( [faker.lorem.sentence(), faker.lorem.sentence(), faker.lorem.sentence()], faker.number.int({ min: 1, @@ -525,7 +531,7 @@ export const createTestFlightRequestData = ( 'Friend or Family', 'Other', ]), - Diagnosis: faker.lorem.sentence(), + Diagnoses: faker.lorem.sentence(), 'Passenger 3': faker.helpers.arrayElements( [faker.string.uuid(), faker.string.uuid(), faker.string.uuid()], faker.number.int({ diff --git a/src/interfaces/legs/flight-leg.interface.ts b/src/interfaces/legs/flight-leg.interface.ts index da8029b..b614b64 100644 --- a/src/interfaces/legs/flight-leg.interface.ts +++ b/src/interfaces/legs/flight-leg.interface.ts @@ -47,7 +47,7 @@ export interface FlightLegData { City: string[]; 'State (from Passengers) 3': string[]; 'Zip (from Passengers)': string[]; - 'Diagnosis (from Passengers)': string[]; + 'Diagnoses (from Passengers)': string[]; 'Date of Birth (from Passengers) 2': string[]; 'TS City, State (from Treatment Site Totals 2) (from Passengers)': string[]; '48 Hours After Flight': string; diff --git a/src/interfaces/passenger/passenger.interface.ts b/src/interfaces/passenger/passenger.interface.ts index 0d912cb..2e34c1f 100644 --- a/src/interfaces/passenger/passenger.interface.ts +++ b/src/interfaces/passenger/passenger.interface.ts @@ -14,8 +14,8 @@ export interface PassengerData { 'Cell Phone': string; 'Home Phone': string; Education: string; - 'Household Income': number; - 'Household Size': number; + 'Household Income': string; + 'Household Size': string; 'Marital Status': string; Employment: string; Ethnicity: string[]; @@ -24,7 +24,7 @@ export interface PassengerData { 'How did you hear about us': string[]; 'BL - Account Number': string; 'All Flight Legs': string[]; - Diagnosis: string[]; + Diagnoses: string[]; 'Treatment Site Totals 2': string[]; 'Passenger ID': string; 'AirTable Record ID': string; @@ -42,7 +42,7 @@ export interface PassengerData { 'Full Name': string; 'Passenger Names (from All Flight Legs)': string[]; '# of Accompanying Passengers': number; - Age: number; + Age: string; Birthday: string; 'Day Before Birthday': string; 'BL - Site 1 (from All Flight Legs)': string[]; diff --git a/src/interfaces/passenger/trimmed-passenger.interface.ts b/src/interfaces/passenger/trimmed-passenger.interface.ts index 0fb89de..9ec57d1 100644 --- a/src/interfaces/passenger/trimmed-passenger.interface.ts +++ b/src/interfaces/passenger/trimmed-passenger.interface.ts @@ -11,8 +11,8 @@ export interface TrimmedPassenger { City: string; Country: string; Email: string; - 'Household Income': number; - 'Household Size': number; + 'Household Income': string; + 'Household Size': string; 'Cell Phone': string; 'Home Phone': string; Education: string; @@ -23,13 +23,13 @@ export interface TrimmedPassenger { 'Military Member': string[]; 'How did you hear about us': string[]; 'All Flight Legs': string[]; - Diagnosis: string[]; + Diagnoses: string[]; 'AirTable Record ID': string; '# of Flight Legs': number; '# of Booked Flight Requests': number; 'Birth Month': string; 'Full Name': string; 'Passenger Names (from All Flight Legs)': string[]; - Age: number; + Age: string; 'Latest Trip': string; } diff --git a/src/interfaces/requests/flight-request-submissions.interface.ts b/src/interfaces/requests/flight-request-submissions.interface.ts new file mode 100644 index 0000000..256317c --- /dev/null +++ b/src/interfaces/requests/flight-request-submissions.interface.ts @@ -0,0 +1,391 @@ +/* eslint-disable autofix/no-unused-vars */ +import type { TrimmedPassenger } from '../passenger/trimmed-passenger.interface'; + +// This is the question array for flight specfic data received from the front end +export interface FlightSpecificData { + enoughDaysAway: string; + travelType: string; + ScheduledMedicalAppointmentDate: string; // Needs to be in ISO string format + DepartureDate: string; // Needs to be in ISO string format + AirportOfOrigin: string; + AlternateAirportOfOrigin: string; + DestinationAirport: string; + AlternateDestinationAirport: string; + ReturnDate: string; + FullNameOfTreatmentSite: string; + FullNameOfPrimaryTreatmentSiteDoctor: string; +} + +// This is what is received from the front end +export interface FlightRequestSubmission { + patient: TrimmedPassenger; + passengerTwo: TrimmedPassenger; + passengerThree?: TrimmedPassenger; + flightRequestData: FlightSpecificData; +} + +// This is used to map the question names to the question IDs in the JotForm +export enum questionIdMap { + // Flight Request + youngerThan17 = '454', + safeTravelPassengers = '536', + hasTsaDocs = '548', + liveInUs = '427', + enoughDaysAway = '450', + governmentAssistanceType = '563', + IRS1040Docs = '560', + householdSize = '443', + grossHouseholdIncome = '444', + childPatientVerification = '539', + isFirstTime = '445', + enoughDaysAway2 = '540', + singleUseCode = '543', + travelType = '523', + ScheduledMedicalAppointmentDate = '159', + DepartureDate = '513', // Date + AirportOfOrigin = '403', + AlternateAirportOfOrigin = '404', + DestinationAirport = '405', + AlternateDestinationAirport = '406', + returnDate = '160', // Date + + // Patient + patientFirstName = '192', + patientMiddleName = '193', + patientLastName = '311', + patientDateOfBirth = '14', // Date + patientAgeAtAppointment = '522', + patientGender = '136', + patientStreetAddress = '198', + patientCity = '199', + patientState = '479', + patientPostalCode = '206', + patientCounty = '519', + patientCountry = '207', + patientHomePhone = '19', + patientCellPhone = '137', + patientEmail = '24', + patientDiagnosis = '287', + patientTypeOfTreatment = '288', + patientHasMedicalInsurance = '554', + patientMedicalInsuranceCompany = '555', + patientMedicalInsuranceSubscriberId = '556', + patientMedicalInsuranceGroupId = '557', + FullNameOfTreatmentSite = '472', + TreatmentSiteCityandState = '473', // city, state + TreatmentSitePhoneNumber = '474', + FullNameOfPrimaryTreatmentSiteDoctor = '477', + patientOxygenRequired = '162', + patientFlyingWithServiceDog = '535', + patientWheelchairRequired = '163', + patientBirthCertificateRequired = '170', + patientEthnicity = '187', + patientHighestEducation = '309', + patientMaritalStatus = '308', + patientEmploymentStatus = '310', + patientMilitaryService = '514', + patientMilitaryMember = '515', + howDidYouHearAboutUs = '150', + childPatientGovernmentName = '413', + waiveLegalAction = '415', + consentForMedicalTreatment = '547', + photoVideoRelease = '416', + patientSignerName = '289', + + // Passenger 2 + passenger2RelationshipToPatient = '419', + passenger2FirstName = '264', + passenger2MiddleName = '265', + passenger2LastName = '312', + passenger2Gender = '456', + passenger2CellPhone = '295', + passenger2Email = '268', + passenger2SameAddressAsPatient = '306', + passenger2StreetAddress = '291', + passenger2City = '299', + passenger2State = '480', + passenger2PostalCode = '301', + passenger2Country = '302', + passenger2DateOfBirth = '452', // Date + passenger2WaiverOfResponsibility = '343', + passenger2GovernmentName = '257', + passenger2WaiveLegalAction = '258', + passenger2PhotoVideoRelease = '260', + RelationshipToPassenger2 = '303', + passenger2SignerName = '355', + + // Passenger 3 + passenger3FirstName = '266', + passenger3MiddleName = '267', + passenger3LastName = '313', + passenger3Gender = '457', + passenger3Email = '269', + passenger3CellPhone = '297', + passenger3DifferentReturnDate = '526', + passenger3ReturnDateExplanation = '531', + passenger3SameAddressAsPatient = '307', + passenger3StreetAddress = '298', + passenger3City = '292', + passenger3State = '481', + passenger3PostalCode = '293', + passenger3Country = '218', + passenger3WaiverOfResponsibility = '367', + passenger3GovernmentName = '350', + passenger3WaiveLegalAction = '352', + passenger3PhotoVideoRelease = '353', + passenger3SignerName = '418', + RelationshipToPassenger3 = '356', + passenger3DateOfBirth = '453', // Date + // General info/conclusion + numPeopleInHousehold = '176', + annualFamilyIncome = '175', + sourcesOfIncome = '178', + uploadProofOfIncome = '177', + eligibilityDeterminedByIncome = '190', + cancelOrAmendFlights = '191', + noCallNoShow = '285', + consentForMedicalTreatmen2 = '561', + signerEmail = '219', + nameOfSigner = '153', + relationshipToPatient = '154', +} + +// This is what is sent back to the JotForm +export interface FlightRequestWithIds { + // Flight Request + '454': string; + '536': string; + '548': string; + '427': string; + '450': string; + '563': string; + '560': string; + '443': string; + '444': string; + '539': string; + '445': string; + '540': string; + '543': string; + '523': string; + '159': { month: string; day: string; year: string }; + '513': { month: string; day: string; year: string }; + '403': string; + '404': string; + '405': string; + '406': string; + '160': { month: string; day: string; year: string }; + + // Patient + '192': string; + '193': string; + '311': string; + '14': { month: string; day: string; year: string }; + '522': string; + '136': string; + '198': string; + '199': string; + '479': string; + '206': string; + '519': string; + '207': string; + '19': string; + '137': string; + '24': string; + '287': string; + '288': string; + '554': string; + '555': string; + '556': string; + '557': string; + '472': string; + '473': string; + '474': string; + '477': string; + '162': string; + '535': string; + '163': string; + '170': string; + '187': string; + '309': string; + '308': string; + '310': string; + '514': string; + '515': string; + '150': string; + '413': string; + '415': string; + '547': string; + '416': string; + '289': string; + + // Passenger 2 + '419': string; + '264': string; + '265': string; + '312': string; + '456': string; + '295': string; + '268': string; + '306': string; + '291': string; + '299': string; + '480': string; + '301': string; + '302': string; + '452': { month: string; day: string; year: string }; + '343': string; + '257': string; + '258': string; + '260': string; + '303': string; + '355': string; + + // Passenger 3 + '266': string; + '267': string; + '313': string; + '457': string; + '269': string; + '297': string; + '526': string; + '531': string; + '307': string; + '298': string; + '292': string; + '481': string; + '293': string; + '218': string; + '367': string; + '350': string; + '352': string; + '353': string; + '418': string; + '356': string; + '453': { month: string; day: string; year: string }; + + // General info/conclusion + '176': string; + '175': string; + '178': string; + '177': string; + '190': string; + '191': string; + '285': string; + '561': string; + '219': string; + '153': string; + '154': string; +} + +// interface PassengerDetails { +// firstName: string; +// middleName: string; +// lastName: string; +// relationshipToPatient: string; +// dateOfBirth: Date; +// gender: string; +// cellPhone: string; +// email: string; +// sameAddressAsPatient: boolean; +// address: { +// street: string; +// city: string; +// state: string; +// postalCode: string; +// country: string; +// }; +// waiverOfResponsibility: boolean; +// returnDateDifferent: boolean; +// returnDateExplanation: string; +// returnDate: string; // switch to'Date' later if that works better +// } + +// export interface FlightRequestSubmission { +// childPatient: { +// ageAtAppointment: number; +// isFirstTimeUser: boolean; +// hasMedicalInsurance: boolean; +// birthCertificateOrGuardianshipProofRequired: boolean; +// nameOnGovernmentID: string; +// diagnosis: string; +// typeOfTreatment: string; +// oxygenRequired: boolean; +// flyingWithServiceDog: boolean; +// wheelchairRequired: boolean; +// medicalInsurance: { +// companyName: string; +// subscriberId: string; +// groupId: string; +// }; +// treatmentSite: { +// name: string; +// cityState: string; +// phone: string; +// fax: string; +// }; +// primaryDoctor: { +// fullName: string; +// }; +// }; +// travelDetails: { +// commercialSafe: boolean; +// hasTSADocs: boolean; +// domestic: boolean; +// departureDate: string; // 'Date' +// returnDate: string; // possibly use 'Date' later if that works better +// departureDateAtLeast14DaysAway: boolean; +// typeOfTravel: string; +// airports: { +// origin: string; +// originAlternate: string; +// destination: string; +// destinationAlternate: string; +// }; +// singleUseCode: string; +// }; +// applicantDetails: { +// firstName: string; +// middleName: string; +// lastName: string; +// dateOfBirth: Date; +// gender: string; +// contact: { +// homePhone: string; +// cellPhone: string; +// email: string; +// }; +// address: { +// street: string; +// city: string; +// state: string; +// postalCode: string; +// county: string; +// country: string; +// }; +// }; +// household: { +// size: number; +// grossIncome: number; +// governmentAssistance: string[]; // Array because multiple selections are possible. +// IRS1040DocumentsAvailable: boolean; +// }; +// legalAndMisc: { +// photoVideoRelease: boolean; +// waiveLegalAction: boolean; +// consentForMedicalTreatment: boolean; +// howDidYouHearAboutUs: string; +// }; +// educationAndEmployment: { +// patientEthnicity: string[]; // string array if multiple selections are possible. +// highestEducationCompleted: string; +// maritalStatus: string; +// employmentStatus: string; +// militaryService: { +// isVeteran: boolean; +// memberDetails: string; +// }; +// }; +// additionalPassengers: PassengerDetails[]; +// } + +// zapier automations and this API key is used to send the data to the JotForm diff --git a/src/interfaces/requests/flight-request.interface.ts b/src/interfaces/requests/flight-request.interface.ts index 1080875..4411c4b 100644 --- a/src/interfaces/requests/flight-request.interface.ts +++ b/src/interfaces/requests/flight-request.interface.ts @@ -9,7 +9,7 @@ export interface FlightRequestData { 'Household Size': number; 'Passenger 2 Approval Status': string; 'How did you hear about us?': string; - Diagnosis: string; + Diagnoses: string; 'Passenger 3': string[]; 'Patient Type': string; Ethnicity: string[]; diff --git a/src/interfaces/requests/trimmed-flight-request.interface.ts b/src/interfaces/requests/trimmed-flight-request.interface.ts index 30f04a3..20f60bc 100644 --- a/src/interfaces/requests/trimmed-flight-request.interface.ts +++ b/src/interfaces/requests/trimmed-flight-request.interface.ts @@ -7,7 +7,7 @@ export interface TrimmedFlightRequest { 'Request Type': string; 'Household Size': number; 'Passenger 2 Approval Status': string; - Diagnosis: string; + Diagnoses: string; 'Passenger 3': string[]; 'Patient Type': string; Ethnicity: string[]; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 4fffee2..6ba3d45 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -15,6 +15,7 @@ import { getFlightRequestById, getFlightLegsById, } from '../controllers/FlightRequest.controller'; +import { SubmitJotForm, getQuestions } from '../services/JotFormService'; import express from 'express'; import type { Request, Response } from 'express'; import type { LooseAuthProp } from '@clerk/clerk-sdk-node'; @@ -50,4 +51,7 @@ router.get('/requests', validateAuth, getAllFlightRequestsForUser); router.get('/requests/:id', validateAuth, getFlightRequestById); router.get('/requests/:id/legs', validateAuth, getFlightLegsById); +router.post('/submit-flight-request', validateAuth, SubmitJotForm); +router.get('/get-questions', validateAuth, getQuestions); + export default router; diff --git a/src/services/JotFormService.ts b/src/services/JotFormService.ts new file mode 100644 index 0000000..ef6e646 --- /dev/null +++ b/src/services/JotFormService.ts @@ -0,0 +1,109 @@ +import { restructureFlightRequest } from '../util/restructures'; +import Jotform from 'jotform'; +import express from 'express'; +import type { + FlightRequestSubmission, + FlightSpecificData, +} from '../interfaces/requests/flight-request-submissions.interface'; +import type { TrimmedPassenger } from '../interfaces/passenger/trimmed-passenger.interface'; + +const app = express(); +// eslint-disable-next-line @typescript-eslint/no-var-requires +require('dotenv').config(); + +app.use(express.json()); + +const options = { + url: 'https://hipaa-api.jotform.com', +}; + +const client = new Jotform(process.env.JOTFORM_API_KEY || '', { + baseURL: options.url, +}); + +// const validateFormData = data => { +// // List of required fields for simplification +// // assume everything is valid +// // set it up as one chunk +// // submit and send back a 200 that it was submitted. +// // pray it works. +// // the flight request does not go to the sandbox, it goes to the live form. + +// // the form ID +// const requiredFields = ['email']; + +// // Check if each required field is present and not empty in the data +// for (const field of requiredFields) { +// if (!data[field] || data[field].trim() === '') { +// return { isValid: false, missingField: field }; +// } +// } + +// return { isValid: true }; +// }; + +export const SubmitJotForm = async (req, res) => { + const json = req.body; + const { + patient, + passengerTwo, + passengerThree, + flightRequestData, + }: { + patient: TrimmedPassenger; + passengerTwo: TrimmedPassenger; + passengerThree: TrimmedPassenger; + flightRequestData: FlightSpecificData; + } = json; + + const rawData: FlightRequestSubmission = { + patient: patient, + passengerTwo: passengerTwo, + passengerThree: passengerThree, + flightRequestData: flightRequestData, + }; + + const restructuredData = restructureFlightRequest(rawData); + const submissionData = JSON.stringify(restructuredData); + + try { + // form ID: 240586898219170 - clone one of the forms from the JotForm account + const response = await client.form.addSubmission( + '240586898219170', + JSON.parse(submissionData) + ); + + if (response.responseCode === 200) { + res.status(200).json({ + message: 'Flight request submitted successfully', + submissionId: response.content.submissionID, + }); + } else { + res.status(response.responseCode).json({ error: response.message }); + } + } catch (error) { + console.error('Error submitting to JotForm:', (error as any)?.message); + res.status(500).json({ error: 'Internal server error' }); + } +}; + +export const getQuestions = async (req, res) => { + try { + const response = await client.form.getQuestions('240586898219170'); + if (response.responseCode === 200) { + const content = response.content as unknown as JSON; // Add type assertion here + res.status(200).json({ + message: 'Questions retrieved successfully', + questions: content, + }); + } else { + res.status(response.responseCode).json({ error: response.message }); + } + } catch (error) { + console.error( + 'Error getting questions from JotForm:', + (error as any)?.message + ); + res.status(500).json({ error: 'Internal server error' }); + } +}; diff --git a/src/tests/Trimming.tests.ts b/src/tests/Trimming.tests.ts index d5d60de..697bb51 100644 --- a/src/tests/Trimming.tests.ts +++ b/src/tests/Trimming.tests.ts @@ -242,7 +242,7 @@ describe('UTIL Trimming', () => { expect(passengerData.fields['All Flight Legs']).to.equal( trimmedPassenger['All Flight Legs'] ); - expect(passengerData.fields.Diagnosis).to.equal(trimmedPassenger.Diagnosis); + expect(passengerData.fields.Diagnoses).to.equal(trimmedPassenger.Diagnoses); expect(passengerData.fields['AirTable Record ID']).to.equal( trimmedPassenger['AirTable Record ID'] ); diff --git a/src/util/restructures.ts b/src/util/restructures.ts new file mode 100644 index 0000000..29b1a1e --- /dev/null +++ b/src/util/restructures.ts @@ -0,0 +1,237 @@ +import { questionIdMap } from '../interfaces/requests/flight-request-submissions.interface'; +import type { + FlightRequestSubmission, + FlightRequestWithIds, +} from '../interfaces/requests/flight-request-submissions.interface'; + +export const restructureFlightRequest = ( + request: FlightRequestSubmission +): FlightRequestWithIds => { + const { patient, passengerTwo, passengerThree, flightRequestData } = request; + + const { + 'First Name': patientFirstName, + 'Last Name': patientLastName, + 'Date of Birth': patientDateOfBirth, + Gender: patientGender, + Street: patientStreetAddress, + City: patientCity, + Country: patientCountry, + Email: patientEmail, + 'Household Income': householdIncome, + 'Household Size': householdSize, + 'Cell Phone': patientCellPhone, + 'Home Phone': patientHomePhone, + Education: patientHighestEducation, + 'Marital Status': maritalStatus, + Employment: patientEmploymentStatus, + Ethnicity: patientEthnicity, + 'Military Service': patientMilitaryService, + 'Military Member': patientMilitaryMember, + 'How did you hear about us': howDidYouHearAboutUs, + Diagnoses: patientDiagnosis, + Age: patientAgeAtAppointment, + } = patient; + + const { + 'First Name': passenger2FirstName, + 'Last Name': passenger2LastName, + 'Date of Birth': passenger2DateOfBirth, + Gender: passenger2Gender, + Street: passenger2StreetAddress, + City: passenger2City, + Email: passenger2Email, + 'Cell Phone': passenger2CellPhone, + } = passengerTwo; + + const { + 'First Name': passenger3FirstName, + 'Last Name': passenger3LastName, + 'Date of Birth': passenger3DateOfBirth, + Gender: passenger3Gender, + Street: passenger3StreetAddress, + City: passenger3City, + Email: passenger3Email, + 'Cell Phone': passenger3CellPhone, + } = passengerThree ?? {}; + + const { + travelType, + ScheduledMedicalAppointmentDate, + DepartureDate, + AirportOfOrigin, + AlternateAirportOfOrigin, + DestinationAirport, + AlternateDestinationAirport, + ReturnDate, + FullNameOfTreatmentSite, + FullNameOfPrimaryTreatmentSiteDoctor, + } = flightRequestData; + + const restructuredRequest: FlightRequestWithIds = { + // Patient Data + [questionIdMap.patientFirstName]: patientFirstName, + [questionIdMap.patientLastName]: patientLastName, + [questionIdMap.patientDateOfBirth]: convertToDateJSON(patientDateOfBirth), + [questionIdMap.patientGender]: patientGender, + [questionIdMap.patientStreetAddress]: patientStreetAddress, + [questionIdMap.patientCity]: patientCity, + [questionIdMap.patientCountry]: patientCountry, + [questionIdMap.patientEmail]: patientEmail, + [questionIdMap.annualFamilyIncome]: householdIncome.toString(), + [questionIdMap.householdSize]: householdSize.toString(), + [questionIdMap.grossHouseholdIncome]: householdIncome.toString(), + [questionIdMap.childPatientVerification]: + 'Yes - Miracle Flights will provide flights to a qualifying child patient (age 17 and under at the time of medical appointment) and up to two additional parents/legal guardians/family members (up to 3 total passengers).', + [questionIdMap.isFirstTime]: 'No', + [questionIdMap.patientCellPhone]: patientCellPhone, // Not being sent over to JotForm. Does it have to do with format? Frontend is sending airtable data, backend is sending it as a string in this format: "(123) 456-7890" + [questionIdMap.patientHomePhone]: patientHomePhone, + [questionIdMap.patientHighestEducation]: patientHighestEducation, + [questionIdMap.patientMaritalStatus]: maritalStatus, + [questionIdMap.patientEmploymentStatus]: patientEmploymentStatus, + [questionIdMap.patientEthnicity]: patientEthnicity.toString(), + [questionIdMap.patientMilitaryService]: patientMilitaryService + ? patientMilitaryService + : '', + [questionIdMap.patientMilitaryMember]: patientMilitaryMember + ? patientMilitaryMember.toString() + : '', + [questionIdMap.howDidYouHearAboutUs]: howDidYouHearAboutUs.toString(), + [questionIdMap.patientDiagnosis]: patientDiagnosis.toString(), + [questionIdMap.patientAgeAtAppointment]: patientAgeAtAppointment.toString(), + + // Passenger 2 Data + [questionIdMap.passenger2FirstName]: passenger2FirstName, + [questionIdMap.passenger2LastName]: passenger2LastName, + [questionIdMap.passenger2DateOfBirth]: convertToDateJSON( + passenger2DateOfBirth + ), + [questionIdMap.passenger2Gender]: passenger2Gender, + [questionIdMap.passenger2StreetAddress]: passenger2StreetAddress, + [questionIdMap.passenger2City]: passenger2City, + [questionIdMap.passenger2Email]: passenger2Email, + [questionIdMap.passenger2CellPhone]: passenger2CellPhone, + + // Passenger 3 Data + [questionIdMap.passenger3FirstName]: passenger3FirstName ?? '', + [questionIdMap.passenger3LastName]: passenger3LastName ?? '', + [questionIdMap.passenger3DateOfBirth]: passenger3DateOfBirth + ? convertToDateJSON(passenger3DateOfBirth) + : { month: '', day: '', year: '' }, + [questionIdMap.passenger3Gender]: passenger3Gender ?? '', + [questionIdMap.passenger3StreetAddress]: passenger3StreetAddress ?? '', + [questionIdMap.passenger3City]: passenger3City ?? '', + [questionIdMap.passenger3Email]: passenger3Email ?? '', + [questionIdMap.passenger3CellPhone]: passenger3CellPhone ?? '', + + // Flight Request Data + [questionIdMap.enoughDaysAway]: isFourteenDaysAway(DepartureDate), + [questionIdMap.enoughDaysAway2]: isFourteenDaysAway(DepartureDate), + [questionIdMap.travelType]: travelType + ? 'One-Way Flight (Departure Flight Only)' + : 'Roundtrip Flight (Departure and Return Flight)', + [questionIdMap.ScheduledMedicalAppointmentDate]: convertToDateJSON( + ScheduledMedicalAppointmentDate + ), + [questionIdMap.DepartureDate]: convertToDateJSON(DepartureDate), + [questionIdMap.AirportOfOrigin]: AirportOfOrigin, + [questionIdMap.AlternateAirportOfOrigin]: AlternateAirportOfOrigin, + [questionIdMap.DestinationAirport]: DestinationAirport, + [questionIdMap.AlternateDestinationAirport]: AlternateDestinationAirport, + [questionIdMap.returnDate]: convertToDateJSON(ReturnDate), + [questionIdMap.FullNameOfTreatmentSite]: FullNameOfTreatmentSite, + [questionIdMap.FullNameOfPrimaryTreatmentSiteDoctor]: + FullNameOfPrimaryTreatmentSiteDoctor, + + // These are data points not being sent over, we need to incorporate these in the frontend + 454: 'Yes', + 536: 'Yes', + 548: 'Yes', + 427: 'Yes', + 563: '', // Type of gov. assistance + 560: 'Yes', + 543: '', + 193: '', // Patient Middle Name + 479: '', // State + 206: '', // Postal code + 519: '', // County + 288: '', // Type of treatment + 554: '', // Does the child patient presently have medical insurance coverage? + 555: '', // Medical Insurance Company Name + 556: '', // Subscriber ID #, Policy #, or Member ID # + 557: '', // Group # + 473: '', // Treatment Site City and State + 474: '', // Treatment Site Phone Number + 162: '', // Oxygen Required + 535: '', // Will you be flying with a service dog? + 163: '', // Wheelchair Required + 170: '', // Birth Certificate or Proof of Legal Guardianship Required for Child Patient (17 and under) + 413: patientFirstName + patientLastName, // Child Patient Name (as it appears on government issued identification) + 415: 'Furthermore, I do herewith unequivocally waive and deny, for myself and all my assigns, any and all rights to pursue any action against said Miracle Flights for any action or inaction executed by them in good faith.', // Waive Right to Pursue Legal Action + 547: 'I acknowledge and confirm that both parents and/or legal guardians consent and approve of the medical treatment that the child patient is receiving as listed in this Flight Request Application.', // Consent for Medical Treatment + 416: 'I AGREE and hereby authorize Miracle Flights and its partners, sponsors, and affiliates to use my name, likeness, photographs, reproductions, videos, recordings, or endorsements of/by me and/or my child for publicity, social media, and/or any other related Miracle Flights marketing purposes.', // Photo/Video Release + 289: '', // Patient Signer Name + 419: '', // Relationship to Patient + 265: '', // Passenger 2 - Middle Name + 306: '', // Passenger 2 - Address is the same as the patient? + 480: '', // Passenger 2 - State + 301: '', // Passenger 2 - Postal Code + 302: '', // Passenger 2 - Country + 343: '', // Passenger 2 Waiver of Responsibility + 257: passenger2FirstName + passenger2LastName, // Passenger Name (as it appears on government issued identification) + 258: 'Furthermore, I do herewith unequivocally waive and deny, for myself and all my assigns, any and all rights to pursue any action against said Miracle Flights for any action or inaction executed by them in good faith.', // Passenger 2 Waive Right to Pursue Legal Action + 260: 'I AGREE and hereby authorize Miracle Flights and its partners, sponsors, and affiliates to use my name, likeness, photographs, reproductions, videos, recordings, or endorsements of/by me and/or my child for publicity, social media, and/or any other related Miracle Flights marketing purposes.', // Passenger 2 Photo/Video Release + 303: '', // Passenger 3 - Relationship to Patient + 355: '', // Passenger 2 Signer Name + 267: '', // Passenger 3 - Middle Name + 526: '', // Is Passenger 3 returning on a different date than Patient/Passenger 1? + 531: '', // Explain why Passenger 3 is requesting a different return date than Patient/Passenger 1 + 307: '', // Passenger 3 - Address is the same as the patient? + 481: '', // Passenger 3 - State + 293: '', // Passenger 3 - Postal Code + 218: '', // Passenger 3 - Country + 367: '', // Passenger 3 Waiver of Responsibility + 350: passenger3FirstName + (passenger3LastName ?? ''), // Passenger 3 Name (as it appears on government issued identification) + 352: 'Furthermore, I do herewith unequivocally waive and deny, for myself and all my assigns, any and all rights to pursue any action against said Miracle Flights for any action or inaction executed by them in good faith.', // Passenger 3 Waive Right to Pursue Legal Action + 353: 'I AGREE and hereby authorize Miracle Flights and its partners, sponsors, and affiliates to use my name, likeness, photographs, reproductions, videos, recordings, or endorsements of/by me and/or my child for publicity, social media, and/or any other related Miracle Flights marketing purposes.', // Passenger 3 Photo / Video Release + 418: passenger3FirstName + (passenger3LastName ?? ''), // Passenger 3 signer name + 356: '', // Relationship to passenger 3 + 176: householdSize.toString(), // # people in household + 178: '', // Sources of income + 177: '', // Proof of income + 190: 'I hereby acknowledge financial assistance for air travel will be provided to me by Miracle Flights and certify that our total gross family/household income from all sources and my family size are as indicated above.', // Eligibility is determined by total family income and size + 191: 'I will call Miracle Flights with any flight cancellations or changes needed to my original itinerary. I acknowledge that Miracle Flights is the only party that can cancel or amend my flights and I will not contact the airlines directly. Failure to do comply with this may put Miracle Flights and its airline relationships in jeopardy and may result in fewer flights provided to myself and others.', // Cancelling or amending flights? + 285: 'I understand that the airline tickets are provided at no cost to me and/or my family by Miracle Flights. I will contact Miracle Flights as soon as possible to make them aware of any changes that may result in my family not being able to fly on the flights that were provided to us. Therefore, any change or cancellation not pre-approved by Miracle Flights may result in Miracle Flights losing the resources spent on your flights. In the case of a "no call no show" resulting in Miracle Flights losing resources, Miracle Flights may seek financial compensation for the travel costs and/or may deny the patient and their family future program services.', // No call, no show + 561: 'I acknowledge and confirm that both parents and/or legal guardians consent and approve of the medical treatment that the child patient is receiving as listed in this Flight Request Application.', // Final consent for medical treatment + 219: '', // signer email + 153: '', // Name of signer + 154: '', // Final relationship to patient + }; + + return restructuredRequest; +}; + +// Converts the date string to an object with the month, day, and year +const convertToDateJSON = ( + date: string +): { month: string; day: string; year: string } => { + const dateArray = date.split('-'); + const month = dateArray[1]; + const day = dateArray[2]; + const year = dateArray[0]; + + return { + month: month, + day: day, + year: year, + }; +}; + +// This function is used to determine if the scheduled date is 14 days away +const isFourteenDaysAway = (scheduledDate): string => { + const today = new Date(); + const scheduled = new Date(scheduledDate); + const differenceInTime = scheduled.getTime() - today.getTime(); + const differenceInDays = differenceInTime / (1000 * 3600 * 24); + return differenceInDays > 14 ? 'Yes' : 'No'; +}; diff --git a/src/util/trim.ts b/src/util/trim.ts index cf102dc..7dfb8fd 100644 --- a/src/util/trim.ts +++ b/src/util/trim.ts @@ -15,7 +15,7 @@ export const trimPassenger = (passenger: PassengerData): TrimmedPassenger => { Country, Email, Ethnicity, - Diagnosis, + Diagnoses, Age, City, } = fields; @@ -40,7 +40,7 @@ export const trimPassenger = (passenger: PassengerData): TrimmedPassenger => { 'Military Member': fields['Military Member'], 'How did you hear about us': fields['How did you hear about us'], 'All Flight Legs': fields['All Flight Legs'], - Diagnosis: Diagnosis, + Diagnoses: Diagnoses, 'AirTable Record ID': fields['AirTable Record ID'], '# of Flight Legs': fields['# of Flight Legs'], '# of Booked Flight Requests': fields['# of Booked Flight Requests'], @@ -63,7 +63,7 @@ export const trimPassenger = (passenger: PassengerData): TrimmedPassenger => { export const trimRequest = (request: FlightRequestData): TrimmedFlightRequest => { const { id, createdTime, fields } = request; - const { Diagnosis, Ethnicity, Education, Status, Patient } = fields; + const { Diagnoses, Ethnicity, Education, Status, Patient } = fields; const trimmedRequest: TrimmedFlightRequest = { id: id, @@ -74,7 +74,7 @@ export const trimRequest = (request: FlightRequestData): TrimmedFlightRequest => 'Request Type': fields['Request Type'], 'Household Size': fields['Household Size'], 'Passenger 2 Approval Status': fields['Passenger 2 Approval Status'], - Diagnosis: Diagnosis, + Diagnoses: Diagnoses, 'Passenger 3': fields['Passenger 3'], 'Patient Type': fields['Patient Type'], Ethnicity: Ethnicity, diff --git a/src/util/trimming/passengers.ts b/src/util/trimming/passengers.ts index 2d0d403..33839ba 100644 --- a/src/util/trimming/passengers.ts +++ b/src/util/trimming/passengers.ts @@ -12,7 +12,7 @@ export const trimPassenger = (passenger: PassengerData): TrimmedPassenger => { Country, Email, Ethnicity, - Diagnosis, + Diagnoses, Age, } = fields; @@ -35,7 +35,7 @@ export const trimPassenger = (passenger: PassengerData): TrimmedPassenger => { 'Military Member': fields['Military Member'], 'How did you hear about us': fields['How did you hear about us'], 'All Flight Legs': fields['All Flight Legs'], - Diagnosis: Diagnosis, + Diagnoses: Diagnoses, 'AirTable Record ID': fields['AirTable Record ID'], '# of Flight Legs': fields['# of Flight Legs'], '# of Booked Flight Requests': fields['# of Booked Flight Requests'],