From 96594af01a6868ca65a836bca873bf57b2ba7da6 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Thu, 12 Feb 2026 10:22:52 -0500 Subject: [PATCH 1/5] integration with ncpdp intermediary --- backend/src/lib/pharmacyConfig.js | 55 ++++++++++++ backend/src/routes/doctorOrders.js | 102 ++++++++++++++++------- frontend/src/App.tsx | 4 +- frontend/src/components/ConfigToggle.tsx | 85 +++++++++++++++++++ 4 files changed, 214 insertions(+), 32 deletions(-) create mode 100644 backend/src/lib/pharmacyConfig.js create mode 100644 frontend/src/components/ConfigToggle.tsx diff --git a/backend/src/lib/pharmacyConfig.js b/backend/src/lib/pharmacyConfig.js new file mode 100644 index 0000000..3cf88c5 --- /dev/null +++ b/backend/src/lib/pharmacyConfig.js @@ -0,0 +1,55 @@ + +// Configuration state +let config = { + useIntermediary: false, + intermediaryUrl: process.env.INTERMEDIARY_URL || 'http://localhost:3003', + remsAdminUrl: process.env.REMS_ADMIN_NCPDP || 'http://localhost:8090/ncpdp', + ehrUrl: process.env.EHR_NCPDP_URL || 'http://localhost:8080/ncpdp/script' +}; + + +export function getConfig() { + return { ...config }; +} + + +export function updateConfig(newConfig) { + config = { ...config, ...newConfig }; + console.log('Configuration updated:', config); + return { ...config }; +} + +/** + * Get the endpoint for NCPDP messages (REMS) + */ +export function getNCPDPEndpoint() { + if (config.useIntermediary) { + return `${config.intermediaryUrl}/script`; + } + return config.remsAdminUrl; +} + +/** + * Get the endpoint for ETASU requests + */ +export function getETASUEndpoint() { + if (config.useIntermediary) { + return `${config.intermediaryUrl}/etasu`; + } + // Direct ETASU endpoint to REMS Admin + return config.remsAdminUrl.replace('/ncpdp', '/4_0_0/GuidanceResponse/$rems-etasu'); +} + +/** + * Get the endpoint for RxFill messages (to EHR) + * RxFill is sent to both EHR and REMS Admin + * If using intermediary, send to intermediary (it forwards to both) + * If not using intermediary, return EHR endpoint (caller must also send to REMS) + */ +export function getRxFillEndpoint() { + if (config.useIntermediary) { + // Intermediary handles forwarding to both EHR and REMS Admin + return `${config.intermediaryUrl}/script`; + } + return config.ehrUrl; +} \ No newline at end of file diff --git a/backend/src/routes/doctorOrders.js b/backend/src/routes/doctorOrders.js index 841ac08..b6c77af 100644 --- a/backend/src/routes/doctorOrders.js +++ b/backend/src/routes/doctorOrders.js @@ -16,6 +16,7 @@ import { } from '../ncpdpScriptBuilder/buildScript.v2017071.js'; import { NewRx } from '../database/schemas/newRx.js'; import { medicationRequestToRemsAdmins } from '../database/data.js'; +import { getConfig, updateConfig, getNCPDPEndpoint, getETASUEndpoint, getRxFillEndpoint } from '../lib/pharmacyConfig.js'; bpx(bodyParser); router.use( @@ -276,37 +277,48 @@ router.patch('/api/updateRx/:id/pickedUp', async (req, res) => { const rxFill = buildRxFill(newRx); console.log('Sending RxFill per NCPDP workflow'); - - // Send to EHR - try { - const ehrStatus = await axios.post(env.EHR_RXFILL_URL, rxFill, { - headers: { - Accept: 'application/xml', - 'Content-Type': 'application/xml' - } + + const config = getConfig(); + + if (config.useIntermediary) { + // Send to intermediary - it will forward to both EHR and REMS Admin + const endpoint = getRxFillEndpoint(); + console.log(`Sending RxFill to intermediary: ${endpoint}`); + await axios.post(endpoint, rxFillStr, { + headers: { 'Content-Type': 'application/xml' } }); - console.log('Sent RxFill to EHR, received status:', ehrStatus.data); - } catch (ehrError) { - console.log('Failed to send RxFill to EHR:', ehrError.message); - } - - // Send to REMS Admin (required by NCPDP spec for REMS drugs) - const order = await doctorOrder.findOne({ prescriberOrderNumber }); - if (isRemsDrug(order)) { + } else { + // Send to EHR try { - const remsAdminStatus = await axios.post( - env.REMS_ADMIN_NCPDP, - rxFill, - { - headers: { - Accept: 'application/xml', - 'Content-Type': 'application/xml' - } + const ehrStatus = await axios.post(env.EHR_RXFILL_URL, rxFill, { + headers: { + Accept: 'application/xml', + 'Content-Type': 'application/xml' } - ); - console.log('Sent RxFill to REMS Admin, received status:', remsAdminStatus.data); - } catch (remsError) { - console.log('Failed to send RxFill to REMS Admin:', remsError.message); + }); + console.log('Sent RxFill to EHR, received status:', ehrStatus.data); + } catch (ehrError) { + console.log('Failed to send RxFill to EHR:', ehrError.message); + } + + // Send to REMS Admin (required by NCPDP spec for REMS drugs) + const order = await doctorOrder.findOne({ prescriberOrderNumber }); + if (isRemsDrug(order)) { + try { + const remsAdminStatus = await axios.post( + env.REMS_ADMIN_NCPDP, + rxFill, + { + headers: { + Accept: 'application/xml', + 'Content-Type': 'application/xml' + } + } + ); + console.log('Sent RxFill to REMS Admin, received status:', remsAdminStatus.data); + } catch (remsError) { + console.log('Failed to send RxFill to REMS Admin:', remsError.message); + } } } } catch (error) { @@ -482,7 +494,7 @@ const getGuidanceResponse = async order => { try { const response = await axios.post(etasuUrl, body, { - headers: { + headers: { 'content-type': 'application/json' } }); @@ -516,8 +528,11 @@ const sendREMSInitiationRequest = async order => { console.log(initiationRequest) + const endpoint = getNCPDPEndpoint(); + console.log(`Sending REMSInitiationRequest to: ${endpoint}`); + const response = await axios.post( - env.REMS_ADMIN_NCPDP, + endpoint, initiationRequest, { headers: { @@ -527,6 +542,7 @@ const sendREMSInitiationRequest = async order => { } ); + const parsedResponse = await parseStringPromise(response.data, XML2JS_OPTS); console.log('Received REMSInitiationResponse'); @@ -563,8 +579,11 @@ const sendREMSRequest = async order => { console.log('Sending REMSRequest to REMS Admin for case:', order.caseNumber); console.log(remsRequest) + const endpoint = getNCPDPEndpoint(); + console.log(`Sending REMSRequest to: ${endpoint}`); + const response = await axios.post( - env.REMS_ADMIN_NCPDP, + endpoint, remsRequest, { headers: { @@ -774,4 +793,25 @@ async function parseNCPDPScript(newRx) { return order; } +/** + * Route: 'doctorOrders/api/config' + * Description: 'Get current pharmacy configuration' + */ +router.get('/api/config', async (_req, res) => { + const config = getConfig(); + console.log('Returning configuration:', config); + res.json(config); +}); + +/** + * Route: 'doctorOrders/api/config' + * Description: 'Update pharmacy configuration' + */ +router.post('/api/config', async (req, res) => { + const newConfig = updateConfig(req.body); + console.log('Configuration updated:', newConfig); + res.json(newConfig); +}); + + export default router; \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3ee0b3d..bc188bc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,6 +8,7 @@ import DoctorOrders from './views/DoctorOrders/DoctorOrders'; import Login from './views/Login/Login'; import ProtectedRoute from './components/ProtectedRoute'; import { AuthProvider } from './contexts/AuthContext'; +import ConfigToggle from './components/ConfigToggle'; import axios from 'axios'; axios.defaults.baseURL = process.env.REACT_APP_PIMS_BACKEND_URL @@ -38,6 +39,7 @@ function App() { + @@ -60,4 +62,4 @@ function App() { ); } -export default App; +export default App; \ No newline at end of file diff --git a/frontend/src/components/ConfigToggle.tsx b/frontend/src/components/ConfigToggle.tsx new file mode 100644 index 0000000..70598c4 --- /dev/null +++ b/frontend/src/components/ConfigToggle.tsx @@ -0,0 +1,85 @@ +import { IconButton, Menu, MenuItem, Switch, Typography, Box, Divider } from '@mui/material'; +import SettingsIcon from '@mui/icons-material/Settings'; +import { useState, useEffect } from 'react'; +import axios from 'axios'; + +export default function ConfigToggle() { + const [anchorEl, setAnchorEl] = useState(null); + const [useIntermediary, setUseIntermediary] = useState(false); + const open = Boolean(anchorEl); + + // Load config on mount + useEffect(() => { + const saved = localStorage.getItem('useIntermediary'); + if (saved !== null) { + setUseIntermediary(saved === 'true'); + } + }, []); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleToggle = async () => { + const newValue = !useIntermediary; + setUseIntermediary(newValue); + localStorage.setItem('useIntermediary', String(newValue)); + + // Update backend + try { + await axios.post('/doctorOrders/api/config', { useIntermediary: newValue }); + console.log('Configuration updated:', newValue ? 'Using Intermediary' : 'Direct Connection'); + } catch (error) { + console.error('Failed to update backend config:', error); + } + }; + + return ( + <> + + + + + + + + NCPDP Routing + + + + + + + + + Use Intermediary + + + {useIntermediary + ? 'Routing via intermediary' + : 'Direct to REMS Admin'} + + + + + + + ); +} \ No newline at end of file From be8a6b981ec03ba8d5e20643b666962ee408d3c1 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Thu, 12 Feb 2026 10:34:14 -0500 Subject: [PATCH 2/5] update url path --- backend/src/lib/pharmacyConfig.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/lib/pharmacyConfig.js b/backend/src/lib/pharmacyConfig.js index 3cf88c5..545f976 100644 --- a/backend/src/lib/pharmacyConfig.js +++ b/backend/src/lib/pharmacyConfig.js @@ -1,13 +1,14 @@ // Configuration state let config = { - useIntermediary: false, + useIntermediary: true, intermediaryUrl: process.env.INTERMEDIARY_URL || 'http://localhost:3003', - remsAdminUrl: process.env.REMS_ADMIN_NCPDP || 'http://localhost:8090/ncpdp', + remsAdminUrl: process.env.REMS_ADMIN_NCPDP || 'http://localhost:8090', ehrUrl: process.env.EHR_NCPDP_URL || 'http://localhost:8080/ncpdp/script' }; + export function getConfig() { return { ...config }; } @@ -24,7 +25,7 @@ export function updateConfig(newConfig) { */ export function getNCPDPEndpoint() { if (config.useIntermediary) { - return `${config.intermediaryUrl}/script`; + return `${config.intermediaryUrl}/ncpdp/script`; } return config.remsAdminUrl; } @@ -49,7 +50,7 @@ export function getETASUEndpoint() { export function getRxFillEndpoint() { if (config.useIntermediary) { // Intermediary handles forwarding to both EHR and REMS Admin - return `${config.intermediaryUrl}/script`; + return `${config.intermediaryUrl}/ncpdp/script`; } return config.ehrUrl; } \ No newline at end of file From 41045767ff0bae62aa44af7cf0afde653b50f9b5 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Thu, 12 Feb 2026 13:37:38 -0500 Subject: [PATCH 3/5] rxfill error fix --- backend/src/routes/doctorOrders.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/routes/doctorOrders.js b/backend/src/routes/doctorOrders.js index b6c77af..cd23700 100644 --- a/backend/src/routes/doctorOrders.js +++ b/backend/src/routes/doctorOrders.js @@ -284,7 +284,7 @@ router.patch('/api/updateRx/:id/pickedUp', async (req, res) => { // Send to intermediary - it will forward to both EHR and REMS Admin const endpoint = getRxFillEndpoint(); console.log(`Sending RxFill to intermediary: ${endpoint}`); - await axios.post(endpoint, rxFillStr, { + await axios.post(endpoint, rxFill, { headers: { 'Content-Type': 'application/xml' } }); } else { From 507314f026395a4623d21417088f463a12f27574 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Thu, 12 Feb 2026 13:52:37 -0500 Subject: [PATCH 4/5] update pharmacy config --- backend/src/lib/pharmacyConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/lib/pharmacyConfig.js b/backend/src/lib/pharmacyConfig.js index 545f976..3eaf5a6 100644 --- a/backend/src/lib/pharmacyConfig.js +++ b/backend/src/lib/pharmacyConfig.js @@ -1,7 +1,7 @@ // Configuration state let config = { - useIntermediary: true, + useIntermediary: process.env.USE_INTERMEDIARY || true, intermediaryUrl: process.env.INTERMEDIARY_URL || 'http://localhost:3003', remsAdminUrl: process.env.REMS_ADMIN_NCPDP || 'http://localhost:8090', ehrUrl: process.env.EHR_NCPDP_URL || 'http://localhost:8080/ncpdp/script' From af8637736cb9678a9d28c1ad7d021d8eb4f9d585 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Thu, 12 Feb 2026 13:56:47 -0500 Subject: [PATCH 5/5] update pharmacy config --- backend/env.json | 8 ++++++++ backend/src/lib/pharmacyConfig.js | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/env.json b/backend/env.json index 30e25f1..18462d2 100644 --- a/backend/env.json +++ b/backend/env.json @@ -54,5 +54,13 @@ "REMS_ADMIN_NCPDP": { "type": "string", "default": "http://localhost:8090/ncpdp/script" + }, + "INTERMEDIARY_URL": { + "type": "string", + "default": "http://localhost:8090/ncpdp/script" + }, + "EHR_NCPDP_URL": { + "type": "string", + "default": "|| 'http://localhost:8080/ncpdp/script'" } } diff --git a/backend/src/lib/pharmacyConfig.js b/backend/src/lib/pharmacyConfig.js index 3eaf5a6..dd3fbd7 100644 --- a/backend/src/lib/pharmacyConfig.js +++ b/backend/src/lib/pharmacyConfig.js @@ -1,10 +1,10 @@ // Configuration state let config = { - useIntermediary: process.env.USE_INTERMEDIARY || true, - intermediaryUrl: process.env.INTERMEDIARY_URL || 'http://localhost:3003', - remsAdminUrl: process.env.REMS_ADMIN_NCPDP || 'http://localhost:8090', - ehrUrl: process.env.EHR_NCPDP_URL || 'http://localhost:8080/ncpdp/script' + useIntermediary: process.env.USE_INTERMEDIARY, + intermediaryUrl: process.env.INTERMEDIARY_URL, + remsAdminUrl: process.env.REMS_ADMIN_NCPDP, + ehrUrl: process.env.EHR_NCPDP_URL };