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 new file mode 100644 index 0000000..dd3fbd7 --- /dev/null +++ b/backend/src/lib/pharmacyConfig.js @@ -0,0 +1,56 @@ + +// Configuration state +let config = { + useIntermediary: process.env.USE_INTERMEDIARY, + intermediaryUrl: process.env.INTERMEDIARY_URL, + remsAdminUrl: process.env.REMS_ADMIN_NCPDP, + ehrUrl: process.env.EHR_NCPDP_URL +}; + + + +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}/ncpdp/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}/ncpdp/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..cd23700 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, rxFill, { + 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