1+ import { Router , Response , Request } from 'express' ;
2+ import {
3+ medicationCollection ,
4+ remsCaseCollection ,
5+ Requirement
6+ } from '../fhir/models' ;
7+ import { Communication , Task , Patient , MedicationRequest } from 'fhir/r4' ;
8+ import axios from 'axios' ;
9+ import config from '../config' ;
10+ import { uid } from 'uid' ;
11+ import container from '../lib/winston' ;
12+ import { createQuestionnaireCompletionTask } from '../hooks/hookResources' ;
13+
14+ const router = Router ( ) ;
15+ const logger = container . get ( 'application' ) ;
16+
17+ router . post ( '/authorize' , async ( req : Request , res : Response ) => {
18+ try {
19+ const { caseNumber } = req . body ;
20+
21+ if ( ! caseNumber ) {
22+ return res . status ( 400 ) . json ( { error : 'caseNumber is required' } ) ;
23+ }
24+
25+ logger . info ( `Dispense authorization check for case: ${ caseNumber } ` ) ;
26+
27+ // Find the REMS case
28+ const remsCase = await remsCaseCollection . findOne ( { case_number : caseNumber } ) ;
29+
30+ if ( ! remsCase ) {
31+ logger . warn ( `REMS case not found: ${ caseNumber } ` ) ;
32+ return res . status ( 404 ) . json ( {
33+ approved : false ,
34+ error : 'Case not found'
35+ } ) ;
36+ }
37+
38+ // Get the medication to check requirements
39+ const medication = await medicationCollection . findOne ( {
40+ code : remsCase . drugCode ,
41+ name : remsCase . drugName
42+ } ) ;
43+
44+ if ( ! medication ) {
45+ logger . error ( `Medication not found: ${ remsCase . drugCode } ` ) ;
46+ return res . status ( 500 ) . json ( {
47+ approved : false ,
48+ error : 'Medication not found'
49+ } ) ;
50+ }
51+
52+ // Check which requirements are required for dispensing and not completed
53+ const outstandingRequirements : Requirement [ ] = [ ] ;
54+
55+ for ( const requirement of medication . requirements ) {
56+ if ( requirement . requiredToDispense ) {
57+ const metRequirement = remsCase . metRequirements . find (
58+ metReq => metReq . requirementName === requirement . name
59+ ) ;
60+
61+ if ( ! metRequirement || ! metRequirement . completed ) {
62+ outstandingRequirements . push ( requirement ) ;
63+ }
64+ }
65+ }
66+
67+ // If all required requirements are met, approve
68+ if ( outstandingRequirements . length === 0 ) {
69+ logger . info ( `All requirements met for case ${ caseNumber } . Approving.` ) ;
70+
71+ // Update dispense status
72+ remsCase . dispenseStatus = 'Approved' ;
73+ await remsCase . save ( ) ;
74+
75+ return res . status ( 200 ) . json ( { approved : true } ) ;
76+ }
77+
78+ // Outstanding requirements - deny and send Communication
79+ logger . info (
80+ `Outstanding requirements for case ${ caseNumber } : ${ outstandingRequirements
81+ . map ( r => r . name )
82+ . join ( ', ' ) } `
83+ ) ;
84+
85+ // Create patient object from REMS case
86+ const patient : Patient = {
87+ resourceType : 'Patient' ,
88+ id : `${ remsCase . patientFirstName } -${ remsCase . patientLastName } ` . replace ( / \s + / g, '-' ) ,
89+ name : [
90+ {
91+ given : [ remsCase . patientFirstName ] ,
92+ family : remsCase . patientLastName
93+ }
94+ ] ,
95+ birthDate : remsCase . patientDOB
96+ } ;
97+
98+ // Get the stored MedicationRequest reference or create a minimal one for Task context
99+ const medicationRequestRef = remsCase . medicationRequestReference ||
100+ `MedicationRequest/${ remsCase . case_number } ` ;
101+
102+ // Create a minimal MedicationRequest for task context if needed
103+ const medicationRequest : MedicationRequest = {
104+ resourceType : 'MedicationRequest' ,
105+ status : 'active' ,
106+ intent : 'order' ,
107+ medicationCodeableConcept : {
108+ coding : [
109+ {
110+ system : 'http://www.nlm.nih.gov/research/umls/rxnorm' ,
111+ code : remsCase . drugCode ,
112+ display : remsCase . drugName
113+ }
114+ ]
115+ } ,
116+ subject : {
117+ reference : `Patient/${ patient . id } `
118+ } ,
119+ requester : {
120+ reference : remsCase . metRequirements . find ( mr =>
121+ mr . requirementName ?. toLowerCase ( ) . includes ( 'prescriber' )
122+ ) ?. stakeholderId
123+ }
124+ } ;
125+
126+ // Create Tasks using the existing function
127+ const tasks : Task [ ] = [ ] ;
128+ for ( const requirement of outstandingRequirements ) {
129+ if ( requirement . appContext ) {
130+ const questionnaireUrl = requirement . appContext ;
131+ const task = createQuestionnaireCompletionTask (
132+ requirement ,
133+ patient ,
134+ questionnaireUrl ,
135+ medicationRequest
136+ ) ;
137+ task . id = `task-${ uid ( ) } ` ;
138+ tasks . push ( task ) ;
139+ }
140+ }
141+
142+ // Create Communication resource
143+ const communication : Communication = {
144+ resourceType : 'Communication' ,
145+ id : `comm-${ uid ( ) } ` ,
146+ status : 'completed' ,
147+ category : [
148+ {
149+ coding : [
150+ {
151+ system : 'http://terminology.hl7.org/CodeSystem/communication-category' ,
152+ code : 'notification' ,
153+ display : 'Notification'
154+ }
155+ ]
156+ }
157+ ] ,
158+ priority : 'urgent' ,
159+ subject : {
160+ reference : `Patient/${ patient . id } ` ,
161+ display : `${ remsCase . patientFirstName } ${ remsCase . patientLastName } `
162+ } ,
163+ topic : {
164+ coding : [
165+ {
166+ system : 'http://terminology.hl7.org/CodeSystem/communication-topic' ,
167+ code : 'progress-update' ,
168+ display : 'Progress Update'
169+ }
170+ ] ,
171+ text : 'Outstanding REMS Requirements for Medication Dispensing'
172+ } ,
173+ sent : new Date ( ) . toISOString ( ) ,
174+ recipient : [
175+ {
176+ reference : medicationRequest . requester ?. reference || ''
177+ }
178+ ] ,
179+ sender : {
180+ reference : 'Organization/rems-admin' ,
181+ display : config . server ?. name || 'REMS Administrator'
182+ } ,
183+ payload : [
184+ {
185+ contentString : `Medication dispensing authorization DENIED for ${ remsCase . drugName } .\n\n` +
186+ `The following REMS requirements must be completed:\n\n` +
187+ outstandingRequirements
188+ . map ( ( req , idx ) => `${ idx + 1 } . ${ req . name } (${ req . stakeholderType } )` )
189+ . join ( '\n' ) +
190+ `\n\nCase Number: ${ remsCase . case_number } \n` +
191+ `Patient: ${ remsCase . patientFirstName } ${ remsCase . patientLastName } (DOB: ${ remsCase . patientDOB } )`
192+ }
193+ ] ,
194+ contained : tasks ,
195+ about : [
196+ // Reference the actual MedicationRequest
197+ {
198+ reference : medicationRequestRef ,
199+ display : `Prescription for ${ remsCase . drugName } `
200+ } ,
201+ // Reference the contained Tasks
202+ ...tasks . map ( task => ( {
203+ reference : `#${ task . id } ` ,
204+ display : task . description
205+ } ) )
206+ ]
207+ } ;
208+
209+
210+ let ehrEndpoint = config . fhirServerConfig ?. auth ?. resourceServer ;
211+
212+ // Send Communication to EHR
213+ if ( ehrEndpoint ) {
214+ try {
215+ const response = await axios . post ( `${ ehrEndpoint } /Communication` , communication , {
216+ headers : {
217+ 'Content-Type' : 'application/fhir+json'
218+ }
219+ } ) ;
220+
221+ if ( response . status === 200 || response . status === 201 ) {
222+ logger . info ( `Communication sent to EHR: ${ ehrEndpoint } ` ) ;
223+ }
224+ } catch ( error : any ) {
225+ logger . error ( `Failed to send Communication to EHR: ${ error . message } ` ) ;
226+ }
227+ } else {
228+ logger . warn ( 'No EHR endpoint configured, Communication not sent' ) ;
229+ }
230+
231+ return res . status ( 200 ) . json ( { approved : false } ) ;
232+ } catch ( error : any ) {
233+ logger . error ( `Error in dispense authorization: ${ error . message } ` ) ;
234+ return res . status ( 500 ) . json ( {
235+ approved : false ,
236+ error : 'Internal server error'
237+ } ) ;
238+ }
239+ } ) ;
240+
241+ export default router ;
0 commit comments