Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HSC-379: Add support for imaging, procedure and medical supply orders #68

Merged
merged 23 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 22 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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public final class Constants {

public static final String TEST_ORDER_TYPE_UUID = "52a447d3-a64a-11e3-9aeb-50e549534c5e";

public static final String IMAGING_ORDER_TYPE_UUID = "8d2aff07-55e6-4a4a-8878-72b9eb36a3b8";

public static final String PROCEDURE_ORDER_TYPE_UUID = "67a92e56-0f88-11ea-8d71-362b9e155667";

public static final String SUPPLY_REQUEST_ORDER_TYPE_UUID = "67a92bd6-0f88-11ea-8d71-362b9e155667";

public static final String DRUG_ORDER_TYPE_UUID = "131168f4-15f5-102d-96e4-000c29c2a5d7";

Comment on lines 23 to 32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, hardcoding UUIDs is not ideal, as different implementations may require different sets of UUIDs. I would prefer making these configurable with default values, rather than embedding them directly into the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you are right. But let's take this up separately as this is an existing practice for this repo.

Have created a ticket here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this issue is becoming more prominent as it is going to be consumed by existing distributions with pre-existing data. The sooner this is addressed, the better. I believe this can be resolved in the current PR, as it already includes the majority of the referenced UUIDs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ruhanga I would not want to include the changes you suggested in this PR. I have created a new PR for the above ticket. Will need to fix test cases to make it work.

I would request @rbuisson to please merge this PR as this is blocking HSC deployment.

private Constants() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public enum FhirResource {

SERVICEREQUEST("direct:fhir-handler-servicerequest", "direct:fhir-servicerequest", "orders", "test_order"),

PROCEDURE("direct:fhir-handler-procedure", "direct:fhir-procedure", "orders"),

SUPPLYREQUEST("direct:fhir-handler-supplyrequest", "direct:fhir-supplyrequest", "orders"),

TASK("direct:fhir-handler-task", "direct:fhir-task", "fhir_task");

private final String[] tables;
Expand Down
VaishSiddharth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.openmrs.eip.fhir.routes.resources;

import static org.openmrs.eip.fhir.Constants.HEADER_FHIR_EVENT_TYPE;
import static org.openmrs.eip.fhir.Constants.PROCEDURE_ORDER_TYPE_UUID;
import static org.openmrs.eip.fhir.Constants.PROP_EVENT_OPERATION;

import java.util.Date;

import org.apache.camel.LoggingLevel;
import org.apache.camel.component.http.HttpComponent;
import org.apache.camel.model.dataformat.JsonLibrary;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.openmrs.eip.fhir.FhirResource;
import org.openmrs.eip.fhir.routes.resources.dto.Order;
import org.openmrs.eip.fhir.spring.OpenmrsRestConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import lombok.Setter;

@Setter
@Component
public class ProcedureRouter extends BaseFhirResourceRouter {

@Value("${openmrs.baseUrl}")
private String openmrsBaseUrl;

@Autowired
private OpenmrsRestConfiguration openmrsRestConfiguration;

ProcedureRouter() {
super(FhirResource.PROCEDURE);
}

@Override
public void configure() throws Exception {
getCamelContext().getComponent("http", HttpComponent.class)
.setHttpClientConfigurer(openmrsRestConfiguration.createHttpClientConfigurer());

from(FhirResource.PROCEDURE.incomingUrl()).routeId("fhir-procedure-router").filter(isSupportedTable()).toD(
"sql:SELECT ot.uuid as uuid from order_type ot join orders o on o.order_type_id = ot.order_type_id where o.uuid ='${exchangeProperty.event.identifier}'?dataSource=#openmrsDataSource")
.filter(simple("${body[0]['uuid']} == '" + PROCEDURE_ORDER_TYPE_UUID + "'"))
.log(LoggingLevel.INFO, "Processing ProcedureRouter ${exchangeProperty.event.tableName}")
.toD(
"sql:SELECT voided, order_action, previous_order_id FROM orders WHERE uuid = '${exchangeProperty.event.identifier}'?dataSource=#openmrsDataSource")
.choice().when(simple("${exchangeProperty.event.operation} == 'd' || ${body[0]['voided']} == 1"))
.setHeader(HEADER_FHIR_EVENT_TYPE, constant("d")).setBody(simple("${exchangeProperty.event.identifier}"))
.to(FhirResource.PROCEDURE.outgoingUrl()).when(simple("${body[0]['order_action']} == 'DISCONTINUE'"))
.toD(
"sql:SELECT uuid FROM orders WHERE order_id = ${body[0]['previous_order_id']}?dataSource=#openmrsDataSource")
.setHeader("CamelHttpMethod", constant("GET")).toD(openmrsBaseUrl + "/ws/rest/v1/order/${body[0]['uuid']}")
.unmarshal().json(JsonLibrary.Jackson, Order.class).process(exchange -> {
Order order = exchange.getIn().getBody(Order.class);
exchange.getMessage().setBody(mapOrderToServiceRequest(order));
}).setHeader(HEADER_FHIR_EVENT_TYPE, constant("d")).to(FhirResource.PROCEDURE.outgoingUrl()).otherwise()
.setHeader("CamelHttpMethod", constant("GET"))
.toD(openmrsBaseUrl + "/ws/rest/v1/order/${exchangeProperty.event.identifier}").unmarshal()
.json(JsonLibrary.Jackson, Order.class).process(exchange -> {
Order order = exchange.getIn().getBody(Order.class);
exchange.getMessage().setBody(mapOrderToServiceRequest(order));
}).setHeader(HEADER_FHIR_EVENT_TYPE, simple("${exchangeProperty." + PROP_EVENT_OPERATION + "}"))
.to(FhirResource.PROCEDURE.outgoingUrl()).endChoice().end();
}

private ServiceRequest mapOrderToServiceRequest(Order order) {
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setStatus(determineServiceRequestStatus(order));
serviceRequest.setIntent(ServiceRequest.ServiceRequestIntent.ORDER);
serviceRequest.setCode(new CodeableConcept(
new Coding().setCode(order.getConcept().getUuid()).setDisplay(order.getConcept().getDisplay()))
.setText(order.getConcept().getDisplay()));
serviceRequest.setSubject(new Reference().setReference("Patient/" + order.getPatient().getUuid()).setType("Patient")
.setDisplay(order.getPatient().getDisplay()));
serviceRequest.setEncounter(
new Reference().setReference("Encounter/" + order.getEncounter().getUuid()).setType("Encounter"));
serviceRequest.setRequester(new Reference().setReference("Practitioner/" + order.getOrderer().getUuid())
.setType("Practitioner").setDisplay(order.getOrderer().getDisplay()));

return serviceRequest;
}

private ServiceRequest.ServiceRequestStatus determineServiceRequestStatus(Order order) {
Date currentDate = new Date();
boolean isCompeted = order.isActivated()
&& ((order.getDateStopped() != null && currentDate.after(order.getDateStopped()))
|| (order.getAutoExpireDate() != null && currentDate.after(order.getAutoExpireDate())));
boolean isDiscontinued = order.isActivated() && order.getAction().equals("DISCONTINUE");

if ((isCompeted && isDiscontinued)) {
return ServiceRequest.ServiceRequestStatus.UNKNOWN;
} else if (isDiscontinued) {
return ServiceRequest.ServiceRequestStatus.REVOKED;
} else if (isCompeted) {
return ServiceRequest.ServiceRequestStatus.COMPLETED;
} else {
return ServiceRequest.ServiceRequestStatus.ACTIVE;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.openmrs.eip.fhir.routes.resources;

import static org.openmrs.eip.fhir.Constants.HEADER_FHIR_EVENT_TYPE;
import static org.openmrs.eip.fhir.Constants.IMAGING_ORDER_TYPE_UUID;
import static org.openmrs.eip.fhir.Constants.PROP_EVENT_OPERATION;
import static org.openmrs.eip.fhir.Constants.TEST_ORDER_TYPE_UUID;

Expand All @@ -19,7 +20,8 @@ public class ServiceRequestRouter extends BaseFhirResourceRouter {
public void configure() throws Exception {
from(FhirResource.SERVICEREQUEST.incomingUrl()).routeId("fhir-servicerequest-router").filter(isSupportedTable()).toD(
"sql:SELECT ot.uuid as uuid from order_type ot join orders o on o.order_type_id = ot.order_type_id where o.uuid ='${exchangeProperty.event.identifier}'?dataSource=#openmrsDataSource")
.filter(simple("${body[0]['uuid']} == '" + TEST_ORDER_TYPE_UUID + "'"))
.filter(simple("${body[0]['uuid']} == '" + TEST_ORDER_TYPE_UUID + "' || ${body[0]['uuid']} == '"
+ IMAGING_ORDER_TYPE_UUID + "'"))
.log(LoggingLevel.INFO, "Processing ${exchangeProperty.event.tableName} message")
.toD(
"sql:SELECT voided, order_action, previous_order_id FROM orders WHERE uuid = '${exchangeProperty.event.identifier}'?dataSource=#openmrsDataSource")
Expand Down
VaishSiddharth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.openmrs.eip.fhir.routes.resources;

import static org.openmrs.eip.fhir.Constants.HEADER_FHIR_EVENT_TYPE;
import static org.openmrs.eip.fhir.Constants.PROP_EVENT_OPERATION;
import static org.openmrs.eip.fhir.Constants.SUPPLY_REQUEST_ORDER_TYPE_UUID;

import java.util.Collections;
import java.util.Date;

import org.apache.camel.LoggingLevel;
import org.apache.camel.component.http.HttpComponent;
import org.apache.camel.model.dataformat.JsonLibrary;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.hl7.fhir.r4.model.SupplyRequest;
import org.openmrs.eip.fhir.FhirResource;
import org.openmrs.eip.fhir.routes.resources.dto.Order;
import org.openmrs.eip.fhir.spring.OpenmrsRestConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import lombok.Setter;

@Setter
@Component
public class SupplyRequestRouter extends BaseFhirResourceRouter {

@Value("${openmrs.baseUrl}")
private String openmrsBaseUrl;

@Autowired
private OpenmrsRestConfiguration openmrsRestConfiguration;

SupplyRequestRouter() {
super(FhirResource.SUPPLYREQUEST);
}

@Override
public void configure() throws Exception {
getCamelContext().getComponent("http", HttpComponent.class)
.setHttpClientConfigurer(openmrsRestConfiguration.createHttpClientConfigurer());

from(FhirResource.SUPPLYREQUEST.incomingUrl()).routeId("fhir-supplyrequest-router").filter(isSupportedTable()).toD(
"sql:SELECT ot.uuid as uuid from order_type ot join orders o on o.order_type_id = ot.order_type_id where o.uuid ='${exchangeProperty.event.identifier}'?dataSource=#openmrsDataSource")
.filter(simple("${body[0]['uuid']} == '" + SUPPLY_REQUEST_ORDER_TYPE_UUID + "'"))
.log(LoggingLevel.INFO, "Processing SupplyRequestRouter ${exchangeProperty.event.tableName}")
.toD(
"sql:SELECT voided, order_action, previous_order_id FROM orders WHERE uuid = '${exchangeProperty.event.identifier}'?dataSource=#openmrsDataSource")
.choice().when(simple("${exchangeProperty.event.operation} == 'd' || ${body[0]['voided']} == 1"))
.setHeader(HEADER_FHIR_EVENT_TYPE, constant("d")).setBody(simple("${exchangeProperty.event.identifier}"))
.to(FhirResource.SUPPLYREQUEST.outgoingUrl()).when(simple("${body[0]['order_action']} == 'DISCONTINUE'"))
.toD(
"sql:SELECT uuid FROM orders WHERE order_id = ${body[0]['previous_order_id']}?dataSource=#openmrsDataSource")
.setHeader("CamelHttpMethod", constant("GET")).toD(openmrsBaseUrl + "/ws/rest/v1/order/${body[0]['uuid']}")
.unmarshal().json(JsonLibrary.Jackson, Order.class).process(exchange -> {
Order order = exchange.getIn().getBody(Order.class);
exchange.getMessage().setBody(mapOrderToSupplyRequest(order));
}).setHeader(HEADER_FHIR_EVENT_TYPE, constant("d")).to(FhirResource.SUPPLYREQUEST.outgoingUrl()).otherwise()
.setHeader("CamelHttpMethod", constant("GET"))
.toD(openmrsBaseUrl + "/ws/rest/v1/order/${exchangeProperty.event.identifier}").unmarshal()
.json(JsonLibrary.Jackson, Order.class).process(exchange -> {
Order order = exchange.getIn().getBody(Order.class);
exchange.getMessage().setBody(mapOrderToSupplyRequest(order));
}).setHeader(HEADER_FHIR_EVENT_TYPE, simple("${exchangeProperty." + PROP_EVENT_OPERATION + "}"))
.to(FhirResource.SUPPLYREQUEST.outgoingUrl()).endChoice().end();
}

private SupplyRequest mapOrderToSupplyRequest(Order order) {
SupplyRequest supplyRequest = new SupplyRequest();
supplyRequest.setId(order.getUuid());
supplyRequest.setItem(new Reference().setReference("MedicalSupply/" + order.getConcept().getUuid())
.setDisplay(order.getConcept().getDisplay()));
supplyRequest.setReasonReference(Collections.singletonList(
new Reference().setType("Encounter").setReference("Encounter/" + order.getEncounter().getUuid())));
supplyRequest.setQuantity(new Quantity().setValue(order.getQuantity()).setCode(order.getQuantityUnits().getUuid()));
supplyRequest.setRequester(
new Reference().setReference(order.getOrderer().getUuid()).setDisplay(order.getOrderer().getDisplay()));
supplyRequest.setDeliverTo(new Reference().setReference("Patient/" + order.getPatient().getUuid())
.setDisplay(order.getPatient().getDisplay()));
supplyRequest.setStatus(determineSupplyRequestStatus(order));

return supplyRequest;
}

private SupplyRequest.SupplyRequestStatus determineSupplyRequestStatus(Order order) {
Date currentDate = new Date();
boolean isCompeted = order.isActivated()
&& ((order.getDateStopped() != null && currentDate.after(order.getDateStopped()))
|| (order.getAutoExpireDate() != null && currentDate.after(order.getAutoExpireDate())));
boolean isDiscontinued = order.isActivated() && order.getAction().equals("DISCONTINUE");

if ((isCompeted && isDiscontinued)) {
return SupplyRequest.SupplyRequestStatus.UNKNOWN;
} else if (isDiscontinued) {
return SupplyRequest.SupplyRequestStatus.CANCELLED;
} else if (isCompeted) {
return SupplyRequest.SupplyRequestStatus.COMPLETED;
} else {
return SupplyRequest.SupplyRequestStatus.ACTIVE;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.openmrs.eip.fhir.routes.resources.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Concept {

public String uuid;

public String display;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.openmrs.eip.fhir.routes.resources.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Encounter {

public String uuid;

public String display;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.openmrs.eip.fhir.routes.resources.dto;

import java.sql.Timestamp;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Order {

public String uuid;

public String orderNumber;

public String accessionNumber;

public Patient patient;

public Concept concept;

public String action;

public Object previousOrder;

public Date dateActivated;

public Object scheduledDate;

public Date dateStopped;

public Date autoExpireDate;

public Encounter encounter;

public Orderer orderer;

public Object orderReason;

public Object orderReasonNonCoded;

public String urgency;

public Object instructions;

public Object commentToFulfiller;

public String display;

public String type;

public String resourceVersion;

public float quantity;

public QuantityUnits quantityUnits;

public boolean isActivated() {
return this.isActivated(new Date());
}

public boolean isActivated(Date checkDate) {
if (this.dateActivated == null) {
return false;
} else {
if (checkDate == null) {
checkDate = new Date();
}

return compare(this.dateActivated, checkDate) <= 0;
}
}

public static int compare(Date d1, Date d2) {
if (d1 instanceof Timestamp && d2 instanceof Timestamp) {
return d1.compareTo(d2);
} else {
if (d1 instanceof Timestamp) {
d1 = new Date(d1.getTime());
}

if (d2 instanceof Timestamp) {
d2 = new Date(d2.getTime());
}

return d1.compareTo(d2);
}
}
}
Loading
Loading