Skip to content

Commit

Permalink
Merge pull request #329 from ojwanganto/billing-lims-workflow
Browse files Browse the repository at this point in the history
Add bill-check precondition before submitting lab requests to LIMS
  • Loading branch information
patryllus authored Jan 6, 2025
2 parents 7f914e1 + 068cb8e commit 94d84df
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 65 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ labware_eid_server_url <host>/api/eid-exchange
labware_eid_server_result_url <host>/api/eid-exchange
labware_eid_server_api_token xyz

Enabling facility-based LIMS integration
-------------
Create a scheduled task using the following:

insert into scheduler_task_config(name,description,schedulable_class,start_time,repeat_interval,created_by,start_time_pattern,uuid)
values ('Facility based LIMS-EMR Integration Task','Facility based LIMS-EMR Integration Task','org.openmrs.module.kenyaemrorderentry.task.FacilityBasedLimsIntegrationTask','2024-12-22 23:59:59',180,1,'MM/dd/yyyy HH:mm:ss',uuid());

Accreditation
-------------
* Highcharts graphing library by Highsoft used under Creative Commons Licence 3.0 (http://www.highcharts.com/)
Expand Down
4 changes: 4 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<dependency>
<groupId>org.openmrs.module</groupId>
<artifactId>kenyaemr.cashier-api</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,9 @@ public class ModuleConstants {
public static final String PULL_SCHEDULER_UUID = "a78ff983-defc-4b90-a8bf-7f7e6b16f5b1";

public static final String PUSH_SCHEDULER_UUID = "67b980ec-dbf3-4662-95da-d9dba9d356d2";
public static final String GP_EXPRESS_PAYMENT_METHODS = "kenyaemrorderentry.facilitywidelims.expressPaymentMethods";
public static final String VISIT_ATTRIBUTE_PAYMENT_METHOD_UUID = "e6cb0c3b-04b0-4117-9bc6-ce24adbda802";



}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.openmrs.module.kenyaemrorderentry.ModuleConstants;
import org.openmrs.module.kenyaemrorderentry.api.service.KenyaemrOrdersService;
import org.openmrs.module.kenyaemrorderentry.labDataExchange.LimsSystemWebRequest;
import org.openmrs.module.kenyaemrorderentry.labDataExchange.labsUtils;
import org.openmrs.module.kenyaemrorderentry.queue.LimsQueue;
import org.openmrs.module.kenyaemrorderentry.queue.LimsQueueStatus;
import org.springframework.aop.AfterReturningAdvice;
Expand All @@ -31,61 +32,67 @@
*/
public class LimsIntegration implements AfterReturningAdvice {

private Log log = LogFactory.getLog(this.getClass());
private Log log = LogFactory.getLog(this.getClass());

@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
String limsIntegrationEnabled = "";
GlobalProperty enableLimsIntegration = Context.getAdministrationService().getGlobalPropertyObject(ModuleConstants.GP_ENABLE_LIMS_INTEGRATION);
limsIntegrationEnabled = enableLimsIntegration.getPropertyValue().trim();
if (limsIntegrationEnabled.equalsIgnoreCase("false")) {
return;
} else if (limsIntegrationEnabled.equalsIgnoreCase("true")) {
try {
// Extract the Order object from the arguments
if (method.getName().equals("saveOrder") && args.length > 0 && args[0] instanceof Order) {
Order order = (Order) args[0];
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
String limsIntegrationEnabled = "";
GlobalProperty enableLimsIntegration = Context.getAdministrationService().getGlobalPropertyObject(ModuleConstants.GP_ENABLE_LIMS_INTEGRATION);
limsIntegrationEnabled = enableLimsIntegration.getPropertyValue().trim();
if (limsIntegrationEnabled.equalsIgnoreCase("false")) {
return;
} else if (limsIntegrationEnabled.equalsIgnoreCase("true")) {
try {
// Extract the Order object from the arguments
if (method.getName().equals("saveOrder") && args.length > 0 && args[0] instanceof Order) {
Order order = (Order) args[0];

if (order == null) {
return;
}
if (order == null) {
return;
}

if (order instanceof TestOrder) {
// Exclude discontinuation orders as well
if (order.getAction().equals(Order.Action.DISCONTINUE)
|| order.getAction().equals(Order.Action.REVISE)
|| order.getAction().equals(Order.Action.RENEW)) {
return;
}
if (order instanceof TestOrder) {
// Exclude discontinuation orders as well
if (order.getAction().equals(Order.Action.DISCONTINUE)
|| order.getAction().equals(Order.Action.REVISE)
|| order.getAction().equals(Order.Action.RENEW)) {
return;
}

LimsSystemWebRequest limsSystemWebRequest = new LimsSystemWebRequest();
JSONObject limsPayload = limsSystemWebRequest.generateLIMSpostPayload(order);
LimsSystemWebRequest limsSystemWebRequest = new LimsSystemWebRequest();
JSONObject limsPayload = limsSystemWebRequest.generateLIMSpostPayload(order);

if (!limsPayload.isEmpty()) {
KenyaemrOrdersService service = Context.getService(KenyaemrOrdersService.class);
LimsQueue limsQueue = new LimsQueue();
limsQueue.setDateSent(new Date());
limsQueue.setOrder(order);
limsQueue.setPayload(limsPayload.toJSONString());
if (!limsPayload.isEmpty()) {

try {
LimsSystemWebRequest.postLabOrderRequestToLims(limsPayload.toJSONString());
limsQueue.setStatus(LimsQueueStatus.SUBMITTED);
service.saveLimsQueue(limsQueue);
} catch (Exception e) {
limsQueue.setStatus(LimsQueueStatus.QUEUED);
service.saveLimsQueue(limsQueue);
System.out.println(e.getMessage());
}
} else {
System.err.println("LIMS-EMR integration: Could not generate the payload for LIMS data exchange");
}
}
}
} catch (Exception e) {
System.err.println("Error intercepting order before creation: " + e.getMessage());
e.printStackTrace();
}
}
}
KenyaemrOrdersService service = Context.getService(KenyaemrOrdersService.class);
LimsQueue limsQueue = new LimsQueue();
limsQueue.setDateSent(new Date());
limsQueue.setOrder(order);
limsQueue.setPayload(limsPayload.toJSONString());

try {
if (labsUtils.isOrderForExpressPatient(order)) { // send to LIMS only if order is for express patient
LimsSystemWebRequest.postLabOrderRequestToLims(limsPayload.toJSONString());
limsQueue.setStatus(LimsQueueStatus.SUBMITTED);
service.saveLimsQueue(limsQueue);
} else { // queue for additional billing check before submission
limsQueue.setStatus(LimsQueueStatus.QUEUED);
service.saveLimsQueue(limsQueue);
}
} catch (Exception e) {
limsQueue.setStatus(LimsQueueStatus.QUEUED);
service.saveLimsQueue(limsQueue);
System.out.println(e.getMessage());
}
} else {
System.err.println("LIMS-EMR integration: Could not generate the payload for LIMS data exchange");
}
}
}
} catch (Exception e) {
System.err.println("Error intercepting order before creation: " + e.getMessage());
e.printStackTrace();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@
package org.openmrs.module.kenyaemrorderentry.labDataExchange;

import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import org.openmrs.Concept;
import org.apache.commons.lang3.StringUtils;
import org.openmrs.GlobalProperty;
import org.openmrs.Order;
import org.openmrs.api.ConceptService;
import org.openmrs.Visit;
import org.openmrs.VisitAttribute;
import org.openmrs.api.VisitService;
import org.openmrs.api.context.Context;
import org.openmrs.module.kenyaemrorderentry.api.service.KenyaemrOrdersService;
import org.openmrs.module.kenyaemrorderentry.manifest.LabManifest;
import org.openmrs.module.kenyaemrorderentry.util.Utils;
import org.openmrs.ui.framework.SimpleObject;
import org.openmrs.module.kenyaemr.cashier.api.BillLineItemService;
import org.openmrs.module.kenyaemr.cashier.api.model.BillLineItem;
import org.openmrs.module.kenyaemr.cashier.api.model.BillStatus;
import org.openmrs.module.kenyaemr.cashier.api.search.BillItemSearch;
import org.openmrs.module.kenyaemrorderentry.ModuleConstants;
import org.openmrs.util.PrivilegeConstants;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class labsUtils {
static ConceptService conceptService = Context.getConceptService();
public static String INPATIENT = "a73e2ac6-263b-47fc-99fc-e0f2c09fc914";
/**
* Format gender
*
Expand Down Expand Up @@ -77,4 +83,52 @@ public static List<Integer> getOrderIdsForActiveOrders(String fetchDate) {
System.out.println("Active Labs For LIMS: " + activeOrders.size());
return activeLabs;
}

/**
* Checks if an order has a bill, and checks the bill payment status.
* If bill status is PENDING then the order is not submitted to LIMS
* @param order
* @return
*/
public static boolean orderHasUnsettledBill(Order order) {
BillLineItem billItemSearch = new BillLineItem();
billItemSearch.setOrder(order);
BillLineItemService billLineItemService = Context.getService(BillLineItemService.class);
List<BillLineItem> result = billLineItemService.fetchBillItemByOrder(new BillItemSearch(billItemSearch, false));
BillLineItem lineItem = result != null && !result.isEmpty() ? result.get(0) : null;// default to the first item
if (lineItem != null && lineItem.getPaymentStatus().equals(BillStatus.PENDING)) {// all other statuses should be interpreted as PAID
return true;
}
return false;
}

/**
* Checks the express status of a patient based on check-in details.
* Express payment methods should be a configurable global property.
* @should return true if a patient is checked in with an express payment method
* @should return true if an order's visit is inpatient
* @param order
* @return
*/
public static boolean isOrderForExpressPatient(Order order) {
Visit activeVisit = order.getEncounter().getVisit();
if (activeVisit != null && activeVisit.getVisitType().getUuid().equals(INPATIENT)) {
return true;
}

VisitAttribute visitPaymentMethod = activeVisit.getActiveAttributes().stream().filter(attr -> attr.getAttributeType().getUuid().equalsIgnoreCase(ModuleConstants.VISIT_ATTRIBUTE_PAYMENT_METHOD_UUID)).findFirst().orElse(null);
GlobalProperty expressPaymentMethodsConfig = Context.getAdministrationService().getGlobalPropertyObject(ModuleConstants.GP_EXPRESS_PAYMENT_METHODS);
String expressPaymentMethodsString = expressPaymentMethodsConfig.getPropertyValue();

if (visitPaymentMethod != null && !StringUtils.isBlank(expressPaymentMethodsString)) {
String [] expressPaymentMethodUuids = expressPaymentMethodsString.split(",");
if (expressPaymentMethodUuids.length > 0) {
boolean isExpressPatient = Arrays.stream(expressPaymentMethodUuids).anyMatch(visitPaymentMethod.getValueReference().trim()::equals);
if (isExpressPatient) {
return true;
}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.openmrs.module.kenyaemrorderentry.task;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.GlobalProperty;
import org.openmrs.api.context.Context;
import org.openmrs.module.kenyaemrorderentry.ModuleConstants;
import org.openmrs.module.kenyaemrorderentry.api.service.KenyaemrOrdersService;
import org.openmrs.module.kenyaemrorderentry.labDataExchange.LimsSystemWebRequest;
import org.openmrs.module.kenyaemrorderentry.labDataExchange.labsUtils;
import org.openmrs.module.kenyaemrorderentry.queue.LimsQueue;
import org.openmrs.module.kenyaemrorderentry.queue.LimsQueueStatus;
import org.openmrs.scheduler.tasks.AbstractTask;
import org.openmrs.ui.framework.SimpleObject;
import org.openmrs.util.OpenmrsUtil;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
* This task sends lab tests to a facility-based lab system.
*/
public class FacilityBasedLimsIntegrationTask extends AbstractTask {
private Log log = LogFactory.getLog(getClass());
/**
* @see AbstractTask#execute()
*/
public void execute() {
System.out.println("Facility based LIMS-EMR integration: PUSH TASK Starting");
Context.openSession();
String limsIntegrationEnabled = "";
GlobalProperty enableLimsIntegration = Context.getAdministrationService().getGlobalPropertyObject(ModuleConstants.GP_ENABLE_LIMS_INTEGRATION);
limsIntegrationEnabled = enableLimsIntegration != null ? enableLimsIntegration.getPropertyValue().trim() : null;

if (limsIntegrationEnabled == null || limsIntegrationEnabled.equals("false")) {
return;
}
KenyaemrOrdersService kenyaemrOrdersService = Context.getService(KenyaemrOrdersService.class);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE, -2); // upto two minutes ago to ensure billing information (created through AOP) is carefully evaluated
Date effectiveDate = cal.getTime();
List<LimsQueue> queuedLabTests = kenyaemrOrdersService.getLimsQueueEntriesByStatus(LimsQueueStatus.QUEUED, null, effectiveDate, false);

if (queuedLabTests.isEmpty()) {
System.out.println("Facility based LIMS-EMR integration PUSH: There are no tests to send to LIMS");
return;
}
int counter = 0;
for (LimsQueue limsQueue : queuedLabTests) {
try {
if (labsUtils.isOrderForExpressPatient(limsQueue.getOrder()) || !labsUtils.orderHasUnsettledBill(limsQueue.getOrder())) {
LimsSystemWebRequest.postLabOrderRequestToLims(limsQueue.getPayload());
limsQueue.setStatus(LimsQueueStatus.SUBMITTED);
limsQueue.setDateLastChecked(new Date());
kenyaemrOrdersService.saveLimsQueue(limsQueue);
counter++;
}
} catch (Exception e) {
System.out.println("Facility based LIMS-EMR integration PUSH:" + e.getMessage());
}
}
Context.closeSession();
System.out.println("Facility based LIMS-EMR integration PUSH: Number of pushed requests = " + counter);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.openmrs.module.kenyaemrorderentry.labDataExchange.LabOrderDataExchange;
import org.openmrs.module.kenyaemrorderentry.labDataExchange.LabwareFacilityWideResultsMapper;
import org.openmrs.module.kenyaemrorderentry.labDataExchange.LimsSystemWebRequest;
import org.openmrs.module.kenyaemrorderentry.labDataExchange.labsUtils;
import org.openmrs.module.kenyaemrorderentry.manifest.LabManifest;
import org.openmrs.module.kenyaemrorderentry.manifest.LabManifestOrder;
import org.openmrs.module.kenyaemrorderentry.metadata.KenyaemrorderentryAdminSecurityMetadata;
Expand All @@ -47,6 +48,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -451,7 +453,10 @@ public Object sendQueuedLabTestsToLims(HttpServletRequest request) {
KenyaemrOrdersService kenyaemrOrdersService = Context.getService(KenyaemrOrdersService.class);
SimpleObject model = new SimpleObject();

List<LimsQueue> queuedLabTests = kenyaemrOrdersService.getLimsQueueEntriesByStatus(LimsQueueStatus.QUEUED, null, null, false);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE, -2); // upto two minutes ago to ensure billing information (created through AOP) is carefully evaluated
Date effectiveDate = cal.getTime();
List<LimsQueue> queuedLabTests = kenyaemrOrdersService.getLimsQueueEntriesByStatus(LimsQueueStatus.QUEUED, null, effectiveDate, false);

if (queuedLabTests.isEmpty()) {
model.put("response", "There are no tests to send to LIMS");
Expand All @@ -460,11 +465,13 @@ public Object sendQueuedLabTestsToLims(HttpServletRequest request) {
int counter = 0;
for (LimsQueue limsQueue : queuedLabTests) {
try {
LimsSystemWebRequest.postLabOrderRequestToLims(limsQueue.getPayload());
limsQueue.setStatus(LimsQueueStatus.SUBMITTED);
limsQueue.setDateLastChecked(new Date());
kenyaemrOrdersService.saveLimsQueue(limsQueue);
counter++;
if (labsUtils.isOrderForExpressPatient(limsQueue.getOrder()) || !labsUtils.orderHasUnsettledBill(limsQueue.getOrder())) {
LimsSystemWebRequest.postLabOrderRequestToLims(limsQueue.getPayload());
limsQueue.setStatus(LimsQueueStatus.SUBMITTED);
limsQueue.setDateLastChecked(new Date());
kenyaemrOrdersService.saveLimsQueue(limsQueue);
counter++;
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
Expand Down
10 changes: 10 additions & 0 deletions omod/src/main/resources/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
<require_module version="${kenyacoreVersion}">
org.openmrs.module.kenyacore
</require_module>
<require_module version="${cashierVersion}">
org.openmrs.module.kenyaemr.cashier
</require_module>
</require_modules>

<!-- Module Activator -->
Expand Down Expand Up @@ -416,5 +419,12 @@
</description>
</globalProperty>

<globalProperty>
<property>kenyaemrorderentry.facilitywidelims.expressPaymentMethods</property>
<defaultValue></defaultValue>
<description>
A comma separated list of express payment methods' UUID. Example: xxxxx,yyyy,zzzz
</description>
</globalProperty>
</module>

Loading

0 comments on commit 94d84df

Please sign in to comment.