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
2 changes: 1 addition & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.openmrs.module</groupId>
<artifactId>ugandaemrsync</artifactId>
<version>2.0.1-SNAPSHOT</version>
<version>2.0.2-SNAPSHOT</version>
</parent>

<artifactId>ugandaemrsync-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,10 +508,11 @@ public Encounter addVLToEncounter(String vlQualitative, String vlQuantitative, S

public Order getOrderByAccessionNumber(String assessionNumber);

public boolean validateVLFHIRBundle(String bundleJson);
public boolean validateTestFHIRBundle(String bundleJson,String orderConceptUuid);

public boolean isValidCPHLBarCode(String accessionNumber);
public String getMissingVLFHIRCodesAsString(String bundleJson);

public String getMissingVLFHIRCodesAsString(String bundleJson,String orderConceptUuid);

public Concept getVLMissingCconcept(String code);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
Expand Down Expand Up @@ -2325,7 +2326,6 @@ public void sendPrescription() {
List<String> drugOrders = generateDrugOrderToOtherSystem(concepts);



try {
for (String drugOrderString : drugOrders) {
// Send prescription via POST
Expand All @@ -2339,7 +2339,6 @@ public void sendPrescription() {
);



int responseCode = response.get("responseCode") instanceof Integer
? (int) response.get("responseCode")
: Integer.parseInt(response.get("responseCode").toString());
Expand Down Expand Up @@ -2433,11 +2432,11 @@ public Map sendSingleViralLoadOrder(Order order) {
String payload = processResourceFromOrder(order);

if (payload != null) {
if (!validateVLFHIRBundle(payload)) {
if (!validateTestFHIRBundle(payload,order.getConcept().getUuid())) {
String missingObsInPayload = String.format(
"Order: %s is not valid due to missing %s in the required field",
order.getAccessionNumber(),
getMissingVLFHIRCodesAsString(payload)
getMissingVLFHIRCodesAsString(payload,order.getConcept().getUuid())
);
logTransaction(syncTaskType, 500, missingObsInPayload, order.getAccessionNumber(),
missingObsInPayload,
Expand Down Expand Up @@ -2604,14 +2603,14 @@ private Map sendViralLoadToCPHL(SyncTaskType syncTaskType, String payload, Ugand
);

if (syncResponse != null) {
String responseFromCPHLServer=String.format("Response From CPHL Server: %s",syncResponse.get("responseMessage"));
String responseFromCPHLServer = String.format("Response From CPHL Server: %s", syncResponse.get("responseMessage"));
Map responseType = handleReturnedResponses(order, syncResponse);
int responseCode = Integer.parseInt(syncResponse.getOrDefault("responseCode", "500").toString());

// Handle duplicate case with a 400 response
if (responseCode == 400 && "Duplicate".equalsIgnoreCase(String.valueOf(responseType.get("responseType")))) {
responseCode = 200;
responseFromCPHLServer = String.format("Response From CPHL Server: %s",responseType.get("responseMessage"));
responseFromCPHLServer = String.format("Response From CPHL Server: %s", responseType.get("responseMessage"));
}

boolean isSuccess = responseCode == 200 || responseCode == 201 || responseCode == 202 || responseCode == 208;
Expand Down Expand Up @@ -2807,8 +2806,8 @@ private JSONArray getProductCatalogFromEAFYA(SyncTaskType syncTaskType) {
return eAFYAProductList;
}

public boolean validateVLFHIRBundle(String bundleJson) {
List<String> targetCodes = Arrays.asList(Context.getAdministrationService().getGlobalProperty("ugandaemrsync.viralloadRequiredProgramData").split(","));
public boolean validateTestFHIRBundle(String bundleJson,String orderConceptUuid) {
List<String> targetCodes = getTargetCodes(orderConceptUuid);
Set<String> foundCodes = new HashSet<>();

FhirContext ctx = FhirContext.forR4();
Expand All @@ -2835,12 +2834,23 @@ public boolean validateVLFHIRBundle(String bundleJson) {
return foundCodes.containsAll(targetCodes);
}

public String getMissingVLFHIRCodesAsString(String bundleJson) {
List<String> targetCodes = Arrays.asList(
Context.getAdministrationService()
.getGlobalProperty("ugandaemrsync.viralloadRequiredProgramData")
.split(",")
);
private List<String> getTargetCodes(String orderConceptUuid) {
List<String> targetCodes = new ArrayList<>();
String testReferralValidators = Context.getAdministrationService().getGlobalProperty("ugandaemrsync.testReferralValidators");
JSONObject testReferralValidatorsObject = new JSONObject(testReferralValidators);

try {
targetCodes = Arrays.asList(testReferralValidatorsObject.getJSONObject("testValidators").getString(orderConceptUuid).split(","));
} catch (Exception exception) {
log.error(exception.getMessage());
}

return targetCodes;
}

public String getMissingVLFHIRCodesAsString(String bundleJson,String orderConceptUuid) {
List<String> targetCodes = getTargetCodes(orderConceptUuid);

Set<String> foundCodes = new HashSet<>();

FhirContext ctx = FhirContext.forR4();
Expand All @@ -2850,30 +2860,30 @@ public String getMissingVLFHIRCodesAsString(String bundleJson) {
try {
bundle = parser.parseResource(Bundle.class, bundleJson);

for (Bundle.BundleEntryComponent entry : bundle.getEntry()) {
if (entry.getResource() instanceof Observation) {
Observation obs = (Observation) entry.getResource();
for (Coding coding : obs.getCode().getCoding()) {
if (targetCodes.contains(coding.getCode())) {
foundCodes.add(coding.getCode());
for (Bundle.BundleEntryComponent entry : bundle.getEntry()) {
if (entry.getResource() instanceof Observation) {
Observation obs = (Observation) entry.getResource();
for (Coding coding : obs.getCode().getCoding()) {
if (targetCodes.contains(coding.getCode())) {
foundCodes.add(coding.getCode());
}
}
}
}
}

// Identify missing codes
// Identify missing codes

for (String code : targetCodes) {
if (!foundCodes.contains(code)) {
for (String code : targetCodes) {
if (!foundCodes.contains(code)) {

Concept concept = getVLMissingCconcept(code);
if (concept != null) {
missingCodes.add(concept.getName().getName());
} else {
missingCodes.add(code);
Concept concept = getVLMissingCconcept(code);
if (concept != null) {
missingCodes.add(concept.getName().getName());
} else {
missingCodes.add(code);
}
}
}
}
} catch (Exception exception) {
log.error(exception);
}
Expand Down
18 changes: 18 additions & 0 deletions api/src/main/resources/liquibase.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2048,4 +2048,22 @@
SELECT patient_identifier_type_id, name, 'http://health.go.ug/cr/nationalid' as url, 1, '2025-04-28 08:43:43', 0, '5e8a626e-3d70-11f0-bcbd-bf35c84f3004' FROM patient_identifier_type WHERE uuid = 'f0c16a6d-dc5f-4118-a803-616d0075d282';
</sql>
</changeSet>

<changeSet id="ugandaemrsync-2025-11-15-1940" author="slubwama">
<addColumn tableName="sync_task_type">
<column name="token_expiry_date" type="DATETIME"/>
<column name="token_refresh_key" type="VARCHAR(255)"/>
<column name="token_type" type="VARCHAR(255)"/>
</addColumn>
</changeSet>

<changeSet id="ugandaemrsync-2025-11-15-1945" author="slubwama">
<addColumn tableName="sync_fhir_profile">
<column name="token_expiry_date" type="DATETIME"/>
<column name="token_refresh_key" type="VARCHAR(255)"/>
<column name="token_type" type="VARCHAR(255)"/>
<column name="searchable" type="TINYINT(3)"/>
<column name="search_url" type="VARCHAR(255)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ public void testValidateVLFHIRBundle_withAllRequiredCodes() {
bundle.addEntry().setResource(createObservation("202501021"));

String json = parser.encodeResourceToString(bundle);
boolean result = ugandaEMRSyncService.validateVLFHIRBundle(json);
boolean result = ugandaEMRSyncService.validateTestFHIRBundle(json,"1eb05918-f50c-4cad-a827-3c78f296a10a");

Assert.assertTrue(result);
}

Expand All @@ -110,7 +111,8 @@ public void testValidateVLFHIRBundle_missingSomeCodes() {
// Missing "868642"

String json = parser.encodeResourceToString(bundle);
boolean result = ugandaEMRSyncService.validateVLFHIRBundle(json);
boolean result = ugandaEMRSyncService.validateTestFHIRBundle(json,"1eb05918-f50c-4cad-a827-3c78f296a10a");

Assert.assertFalse("Payload Missing some data", result);
}

Expand All @@ -135,7 +137,7 @@ public void testGetMissingVLFHIRCodesAsString_withOutMissingCodes() {
String json = parser.encodeResourceToString(bundle);


String result = service.getMissingVLFHIRCodesAsString(json);
String result = service.getMissingVLFHIRCodesAsString(json,"1eb05918-f50c-4cad-a827-3c78f296a10a");
Assert.assertEquals("", result);
}

Expand All @@ -159,7 +161,7 @@ public void testGetMissingVLFHIRCodesAsString_withTwoMissingCodes() {
String missingConceptName1 = service.getVLMissingCconcept("202501020").getName().getName();
String missingConceptName2 = service.getVLMissingCconcept("202501021").getName().getName();

String result = service.getMissingVLFHIRCodesAsString(json);
String result = service.getMissingVLFHIRCodesAsString(json,"1eb05918-f50c-4cad-a827-3c78f296a10a");
Assert.assertEquals(String.format("%s,%s", missingConceptName1, missingConceptName2), result);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
<global_property property="ugandaemrsync.viralloadRequiredProgramData" property_value="413946009,385354005,202501002,LL5723-3,202501009,202501016,202501020,33882-2,202501021" description="The Viral load required fields codes " uuid="b1b706ca-3080-11f0-be8b-d08c4bd23cb6"/>
<global_property property="ugandaemrsync.minimumCPHLBarCodeLength" property_value="9" description="The minimum Length of a CPHL Barcode." uuid="42d82a3e-309b-11f0-be8b-d08c4bd23cb6"/>
<global_property property="ugandaemrsync.cphlReferralOrderConceptIds" property_value="165412" description="The Concept ID for allowable referral orders to be synced to CPHL" uuid="86154180-3198-11f0-9bf9-e786e6f00235"/>
<global_property property="ugandaemrsync.testReferralValidators" property_value='{"validatableTest":["1eb05918-f50c-4cad-a827-3c78f296a10a","163610AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","dc8d4af2-30ab-102d-86b0-7a5022ba4115"],"testValidators":{"1eb05918-f50c-4cad-a827-3c78f296a10a":"413946009,385354005,202501002,LL5723-3,202501009,202501016,202501020,33882-2,202501021","163610AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA":"413946009,385354005,202501002,LL5723-3,202501009,202501016,202501020,33882-2,202501021","dc8d4af2-30ab-102d-86b0-7a5022ba4115":""}}' description="The validator of required fields a test by another system" uuid="9807cd6a-4060-45e7-ad69-0ef7370a01d4"/>
</dataset>

2 changes: 1 addition & 1 deletion omod/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>org.openmrs.module</groupId>
<artifactId>ugandaemrsync</artifactId>
<version>2.0.1-SNAPSHOT</version>
<version>2.0.2-SNAPSHOT</version>
</parent>

<artifactId>ugandaemrsync-omod</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.StringProperty;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.*;
import org.openmrs.api.ConceptService;
import org.openmrs.api.OrderService;
import org.openmrs.api.context.Context;
import org.openmrs.module.ugandaemrsync.api.UgandaEMRSyncService;
import org.openmrs.module.ugandaemrsync.api.impl.UgandaEMRSyncServiceImpl;
import org.openmrs.module.ugandaemrsync.model.SyncTask;
import org.openmrs.module.ugandaemrsync.model.SyncTaskType;
import org.openmrs.module.ugandaemrsync.web.resource.DTO.ReferralOrder;
Expand Down Expand Up @@ -36,6 +40,7 @@
@Resource(name = RestConstants.VERSION_1 + "/referredorders", supportedClass = ReferralOrder.class, supportedOpenmrsVersions = {"1.9.* - 9.*"})
public class ReferralOrderResource extends DelegatingCrudResource<ReferralOrder> {


@Override
public ReferralOrder newDelegate() {
throw new ResourceDoesNotSupportOperationException("Operation not supported");
Expand Down Expand Up @@ -66,6 +71,8 @@ public ReferralOrder getByUniqueId(String uniqueId) {

@Override
public NeedsPaging<ReferralOrder> doGetAll(RequestContext context) throws ResponseException {

List<Concept> concepts = getConceptsFromSyncTaskType();
OrderService orderService = Context.getOrderService();
CareSetting careSetting = orderService.getCareSettingByUuid(CARE_SETTING_UUID_OPD);

Expand All @@ -74,7 +81,7 @@ public NeedsPaging<ReferralOrder> doGetAll(RequestContext context) throws Respon
OrderSearchCriteria orderSearchCriteria = new OrderSearchCriteria(
null,
careSetting,
Collections.singletonList(Context.getConceptService().getConcept(165412)),
concepts,
Collections.singletonList(orderType),
null,
null,
Expand Down Expand Up @@ -279,4 +286,58 @@ private Order.FulfillerStatus parseFulfillerStatus(String status) {
return Order.FulfillerStatus.IN_PROGRESS;
}
}

/**
* Builds a list of Concepts from the SyncTaskType configured for Viral Load sync.
* The SyncTaskType.getDataType() must contain a comma-separated list of Concept IDs, UUIDs, or Names.
*
* @return List of Concepts resolved from the SyncTaskType dataType.
*/
public static List<Concept> getConceptsFromSyncTaskType() {

UgandaEMRSyncService syncService = Context.getService(UgandaEMRSyncService.class);
SyncTaskType syncTaskType = syncService.getSyncTaskTypeByUUID(VIRAL_LOAD_SYNC_TYPE_UUID);

List<Concept> concepts = new ArrayList<>();

// Early exit if syncTaskType or dataType is missing
if (syncTaskType == null) {
//log.warning("SyncTaskType not found for UUID: " + VIRAL_LOAD_SYNC_TYPE_UUID);
return concepts;
}

String dataType = syncTaskType.getDataType();
if (StringUtils.isBlank(dataType)) {
//log.warning("SyncTaskType dataType is blank for UUID: " + VIRAL_LOAD_SYNC_TYPE_UUID);
return concepts;
}

// Parse and resolve each token
for (String token : dataType.split(",")) {
token = token.trim();
if (token.isEmpty()) {
continue;
}

Concept concept = null;
if (StringUtils.isNumeric(token)) {
// Concept ID
concept = Context.getConceptService().getConcept(Integer.parseInt(token));
} else {
// Try UUID, then Name
concept = Context.getConceptService().getConceptByUuid(token);
if (concept == null) {
concept = Context.getConceptService().getConceptByName(token);
}
}

if (concept != null) {
concepts.add(concept);
} else {
//log.warning("No concept found for token: " + token);
}
}

return concepts;
}
}
8 changes: 8 additions & 0 deletions omod/src/main/resources/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,14 @@
The Concept ID for allowable referral orders to be synced to CPHL
</description>
</globalProperty>

<globalProperty>
<property>ugandaemrsync.testReferralValidators</property>
<defaultValue>{"validatableTest":["1eb05918-f50c-4cad-a827-3c78f296a10a","163610AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","dc8d4af2-30ab-102d-86b0-7a5022ba4115"],"testValidators":{"1eb05918-f50c-4cad-a827-3c78f296a10a":"413946009,385354005,202501002,LL5723-3,202501009,202501016,202501020,33882-2","163610AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA":"413946009,385354005,202501002,LL5723-3,202501009,202501016,202501020,33882-2","dc8d4af2-30ab-102d-86b0-7a5022ba4115":""}}</defaultValue>
<description>
The allowable test order to be referred and their data requirements.
</description>
</globalProperty>

<!-- Internationalization -->
<!-- All message codes should start with ugandaemrsync.* -->
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<groupId>org.openmrs.module</groupId>
<artifactId>ugandaemrsync</artifactId>
<version>2.0.1-SNAPSHOT</version>
<version>2.0.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>UgandaemrSync</name>
<description>Provides data sharing capabilities from the facility level installation with other systems</description>
Expand Down