Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ The backend environment variables are configured in `backend/env.json`:
| USE_INTERMEDIARY | `true` | Set to `true` to route ETASU checks through the REMS intermediary instead of directly to REMS admin. |
| INTERMEDIARY_FHIR_URL | `http://localhost:3003/4_0_0` | Base URL of the REMS intermediary FHIR server. Used when `USE_INTERMEDIARY` is true. |
| REMS_ADMIN_NCPDP | `http://localhost:8090/ncpdp/script` | URL endpoint for sending NCPDP Script messages directly to REMS admin. |
| INTERMEDIARY_URL | `http://localhost:3003` | Base URL of the REMS intermediary. Used when `USE_INTERMEDIARY` is true to route NCPDP Script and RxFill messages. |
| EHR_NCPDP_URL | `http://localhost:8080/ncpdp/script` | URL endpoint for sending NCPDP Script messages directly to the EHR system. Used when `USE_INTERMEDIARY` is false. |

## Setup

Expand Down Expand Up @@ -119,5 +121,4 @@ Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

limitations under the License.
6 changes: 3 additions & 3 deletions backend/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@
},
"INTERMEDIARY_URL": {
"type": "string",
"default": "http://localhost:8090/ncpdp/script"
"default": "http://localhost:3003"
},
"EHR_NCPDP_URL": {
"type": "string",
"default": "|| 'http://localhost:8080/ncpdp/script'"
"default": "http://localhost:8080/ncpdp/script"
}
}
}
15 changes: 15 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 13 additions & 7 deletions backend/src/lib/pharmacyConfig.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import env from 'var';

// 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
useIntermediary: env.USE_INTERMEDIARY,
intermediaryUrl: env.INTERMEDIARY_URL,
remsAdminUrl: env.REMS_ADMIN_NCPDP,
ehrUrl: env.EHR_NCPDP_URL
};



export function getConfig() {
return { ...config };
}


export function updateConfig(newConfig) {
config = { ...config, ...newConfig };
const sanitized = { ...newConfig };

if ('useIntermediary' in sanitized) {
const val = sanitized.useIntermediary;
sanitized.useIntermediary = val === true || val === 'true';
}

config = { ...config, ...sanitized };
console.log('Configuration updated:', config);
return { ...config };
}
Expand Down
6 changes: 3 additions & 3 deletions backend/src/routes/doctorOrders.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// XML Parsing Middleware used for NCPDP SCRIPT
import bodyParser from 'body-parser';
import bpx from 'body-parser-xml';
import { parseStringPromise } from "xml2js";
import { parseStringPromise } from 'xml2js';
import env from 'var';
import {
buildRxStatus,
Expand All @@ -16,7 +16,7 @@
} 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';

Check warning on line 19 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

'getETASUEndpoint' is defined but never used

Check warning on line 19 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

'getETASUEndpoint' is defined but never used

bpx(bodyParser);
router.use(
Expand Down Expand Up @@ -385,7 +385,7 @@
const getEtasuUrl = order => {
let baseUrl;

if (env.USE_INTERMEDIARY) {
if (getConfig().useIntermediary) {
baseUrl = env.INTERMEDIARY_FHIR_URL;
} else {
const remsDrug = medicationRequestToRemsAdmins.find(entry => {
Expand Down Expand Up @@ -419,7 +419,7 @@

// Make the etasu call with the case number if it exists, if not call with patient and medication
let body = {};
if (order.caseNumber && !env.USE_INTERMEDIARY) {
if (order.caseNumber && !getConfig().useIntermediary) {
body = {
resourceType: 'Parameters',
parameter: [
Expand Down Expand Up @@ -526,7 +526,7 @@
const initiationRequest = buildREMSInitiationRequest(newRx);
console.log('Sending REMSInitiationRequest to REMS Admin');

console.log(initiationRequest)

Check failure on line 529 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

Missing semicolon

Check failure on line 529 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

Missing semicolon

const endpoint = getNCPDPEndpoint();
console.log(`Sending REMSInitiationRequest to: ${endpoint}`);
Expand Down Expand Up @@ -577,7 +577,7 @@

const remsRequest = buildREMSRequest(newRx, order.caseNumber);
console.log('Sending REMSRequest to REMS Admin for case:', order.caseNumber);
console.log(remsRequest)

Check failure on line 580 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

Missing semicolon

Check failure on line 580 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

Missing semicolon

const endpoint = getNCPDPEndpoint();
console.log(`Sending REMSRequest to: ${endpoint}`);
Expand Down Expand Up @@ -673,7 +673,7 @@
return null;
}

const request = remsResponse.request;

Check warning on line 676 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

'request' is assigned a value but never used

Check warning on line 676 in backend/src/routes/doctorOrders.js

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier on back end

'request' is assigned a value but never used

const response = remsResponse.response;
const responseStatus = response?.responsestatus;
Expand Down
4 changes: 2 additions & 2 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import env from 'var';
import https from 'https';
import fs from 'fs';

//middleware and configurations
import bodyParser from 'body-parser';

main().catch(err => console.log(err));
Expand All @@ -23,6 +22,7 @@ async function main() {

console.log('CORS OPTIONS: ' + JSON.stringify(options));

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors(options));
app.use('/doctorOrders', doctorOrders);
Expand All @@ -48,4 +48,4 @@ async function main() {
user: env.MONGO_USERNAME,
pass: env.MONGO_PASSWORD
});
}
}
7 changes: 4 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 55 additions & 19 deletions frontend/src/components/ConfigToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { IconButton, Menu, MenuItem, Switch, Typography, Box, Divider } from '@mui/material';
import { IconButton, Menu, MenuItem, Switch, Typography, Box, Divider, TextField, Button } from '@mui/material';
import SettingsIcon from '@mui/icons-material/Settings';
import { useState, useEffect } from 'react';
import axios from 'axios';

interface PharmacyConfig {
useIntermediary: boolean;
intermediaryUrl: string;
remsAdminUrl: string;
ehrUrl: string;
}

export default function ConfigToggle() {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [useIntermediary, setUseIntermediary] = useState(false);
const [config, setConfig] = useState<PharmacyConfig>({
useIntermediary: false,
intermediaryUrl: '',
remsAdminUrl: '',
ehrUrl: '',
});
const open = Boolean(anchorEl);

// Load config on mount
// Load config from backend on mount
useEffect(() => {
const saved = localStorage.getItem('useIntermediary');
if (saved !== null) {
setUseIntermediary(saved === 'true');
}
axios.get<PharmacyConfig>('/doctorOrders/api/config')
.then(({ data }) => setConfig(data))
.catch(() => console.error('Failed to load config'));
}, []);

const handleClick = (event: React.MouseEvent<HTMLElement>) => {
Expand All @@ -24,15 +35,15 @@ export default function ConfigToggle() {
setAnchorEl(null);
};

const handleToggle = async () => {
const newValue = !useIntermediary;
setUseIntermediary(newValue);
localStorage.setItem('useIntermediary', String(newValue));
const handleToggle = () => {
setConfig(prev => ({ ...prev, useIntermediary: !prev.useIntermediary }));
};

// Update backend
const handleSave = async () => {
try {
await axios.post('/doctorOrders/api/config', { useIntermediary: newValue });
console.log('Configuration updated:', newValue ? 'Using Intermediary' : 'Direct Connection');
await axios.post('/doctorOrders/api/config', config);
console.log('Configuration updated:', config);
handleClose();
} catch (error) {
console.error('Failed to update backend config:', error);
}
Expand All @@ -55,7 +66,7 @@ export default function ConfigToggle() {
open={open}
onClose={handleClose}
PaperProps={{
sx: { minWidth: 280, p: 1 }
sx: { minWidth: 300, p: 1 }
}}
>
<Box sx={{ px: 2, py: 1 }}>
Expand All @@ -66,19 +77,44 @@ export default function ConfigToggle() {
<Divider />
<MenuItem onClick={handleToggle} sx={{ py: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, width: '100%' }}>
<Switch checked={useIntermediary} size="small" />
<Switch checked={config.useIntermediary} size="small" />
<Box>
<Typography variant="body2" fontWeight="medium">
Use Intermediary
</Typography>
<Typography variant="caption" color="text.secondary">
{useIntermediary
? 'Routing via intermediary'
: 'Direct to REMS Admin'}
{config.useIntermediary ? 'Routing via intermediary' : 'Direct to REMS Admin'}
</Typography>
</Box>
</Box>
</MenuItem>
<Divider />
<Box sx={{ px: 2, py: 1.5, display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<TextField
label="Intermediary URL"
size="small"
fullWidth
value={config.intermediaryUrl}
onChange={e => setConfig(prev => ({ ...prev, intermediaryUrl: e.target.value }))}
/>
<TextField
label="REMS Admin URL"
size="small"
fullWidth
value={config.remsAdminUrl}
onChange={e => setConfig(prev => ({ ...prev, remsAdminUrl: e.target.value }))}
/>
<TextField
label="EHR URL"
size="small"
fullWidth
value={config.ehrUrl}
onChange={e => setConfig(prev => ({ ...prev, ehrUrl: e.target.value }))}
/>
<Button variant="contained" size="small" onClick={handleSave} sx={{ alignSelf: 'flex-end' }}>
Save
</Button>
</Box>
</Menu>
</>
);
Expand Down
Loading