diff --git a/api/src/main/java/org/openmrs/module/ugandaemrsync/api/UgandaEMRSyncService.java b/api/src/main/java/org/openmrs/module/ugandaemrsync/api/UgandaEMRSyncService.java
index 34cf0348..5b59d4c0 100644
--- a/api/src/main/java/org/openmrs/module/ugandaemrsync/api/UgandaEMRSyncService.java
+++ b/api/src/main/java/org/openmrs/module/ugandaemrsync/api/UgandaEMRSyncService.java
@@ -154,6 +154,27 @@ public interface UgandaEMRSyncService extends OpenmrsService {
public Encounter addVLToEncounter(String vlQualitative, String vlQuantitative, String vlDate, Encounter encounter,
Order order);
+
+ /**
+ * Saves an EID qualitative test result and return date to an encounter.
+ *
+ * If a valid POSITIVE or NEGATIVE result is provided, the method:
+ *
+ * - Voids any existing EID result and return-date observations
+ * - Adds the new EID result and result return date
+ * - Saves the encounter
+ *
+ *
+ * If EID data already exists, the associated order is discontinued
+ * and no new observations are created.
+ *
+ * @param vlQualitative qualitative EID result (POSITIVE or NEGATIVE)
+ * @param encounter encounter to update
+ * @param order associated lab order
+ * @return updated encounter, or {@code null} if the result is invalid
+ */
+ public Encounter addEIDToEncounter(String vlQualitative, Encounter encounter, Order order);
+
/**
* @param vlDate
* @return
@@ -502,10 +523,27 @@ public Encounter addVLToEncounter(String vlQualitative, String vlQuantitative, S
public Map sendSingleViralLoadOrder(Order order);
+ /**
+ * Pulls lab results from CPHL for the given order (or resolves the order from the sync task),
+ * and persists the results into the patient's encounter (VL or EID).
+ *
+ * For successful, non-pending responses, the method saves the result obs, marks the sync task
+ * as completed, logs the transaction, and completes/discontinues the order.
+ *
+ * @param order the lab order to fetch results for (may be null if syncTask is provided)
+ * @param syncTask the sync task containing the sample/accession identifier (may be null if order is provided)
+ * @return response map containing at least "responseMessage" describing the outcome
+ */
public Map requestLabResult(Order order, SyncTask syncTask);
public Date getDateFromString(String dateString, String format);
+ /**
+ * Resolves an {@link Order} using an accession/sample identifier.
+ *
+ * @param assessionNumber accession/sample identifier
+ * @return matching order, or null if none is found
+ */
public Order getOrderByAccessionNumber(String assessionNumber);
public boolean validateTestFHIRBundle(String bundleJson,String orderConceptUuid);
@@ -514,7 +552,7 @@ public Encounter addVLToEncounter(String vlQualitative, String vlQuantitative, S
public String getMissingVLFHIRCodesAsString(String bundleJson,String orderConceptUuid);
- public Concept getVLMissingCconcept(String code);
+ public Concept getVLMissingConcept(String code);
public List getReferralOrderConcepts();
diff --git a/api/src/main/java/org/openmrs/module/ugandaemrsync/api/impl/UgandaEMRSyncServiceImpl.java b/api/src/main/java/org/openmrs/module/ugandaemrsync/api/impl/UgandaEMRSyncServiceImpl.java
index 57d66e44..1a1aa399 100644
--- a/api/src/main/java/org/openmrs/module/ugandaemrsync/api/impl/UgandaEMRSyncServiceImpl.java
+++ b/api/src/main/java/org/openmrs/module/ugandaemrsync/api/impl/UgandaEMRSyncServiceImpl.java
@@ -11,12 +11,12 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
+import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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;
@@ -38,6 +38,7 @@
import org.openmrs.PersonAttribute;
import org.openmrs.PersonAddress;
import org.openmrs.Provider;
+import org.openmrs.ConceptReferenceTerm;
import org.openmrs.Person;
import org.openmrs.ProviderAttributeType;
import org.openmrs.ProviderAttribute;
@@ -294,6 +295,74 @@ public Encounter addVLToEncounter(String vlQualitative, String vlQuantitative, S
}
}
+ /**
+ * @see UgandaEMRSyncService#addEIDToEncounter(String, Encounter, Order)
+ */
+ public Encounter addEIDToEncounter(String vlQualitative, Encounter encounter, Order order) {
+ if (encounter == null) {
+ return null;
+ }
+
+ // If already saved, just discontinue the order (if applicable) and return
+ if (encounterHasVLDataAlreadySaved(encounter, order)) {
+ discontinueOrderIfActive(order);
+ return encounter;
+ }
+
+ // Concepts
+ Concept testResultConcept = Context.getConceptService().getConcept(844); // EID result concept
+ Concept returnDateConcept = Context.getConceptService().getConcept(167944); // return date concept
+
+ // Normalize incoming result string
+ String result = (vlQualitative == null) ? "" : vlQualitative.replace("\"", "").trim().toUpperCase();
+
+ Concept valueCoded = null;
+ if ("POSITIVE".equals(result)) {
+ valueCoded = Context.getConceptService().getConcept(703);
+ } else if ("NEGATIVE".equals(result)) {
+ valueCoded = Context.getConceptService().getConcept(664);
+ } else {
+ // Unknown/unhandled result -> don't save anything
+ return null;
+ }
+
+ // Void similar observations before adding new ones
+ voidObsFound(encounter, testResultConcept);
+ voidObsFound(encounter, returnDateConcept);
+
+ Obs testResultObs = createObs(encounter, order, testResultConcept, valueCoded, null, null);
+ Obs returnDateObs = createObs(encounter, order, returnDateConcept, null, new Date(), null);
+
+ encounter.addObs(testResultObs);
+ encounter.addObs(returnDateObs);
+
+ return Context.getEncounterService().saveEncounter(encounter);
+ }
+
+ /**
+ * Discontinues an active laboratory order.
+ *
+ * @param order order to discontinue
+ */
+ private void discontinueOrderIfActive(Order order) {
+ if (order == null || !order.isActive()) {
+ return;
+ }
+ try {
+ Context.getOrderService().discontinueOrder(
+ order,
+ "Completed",
+ new Date(),
+ order.getOrderer(),
+ order.getEncounter()
+ );
+ } catch (Exception e) {
+ log.error("Failed to discontinue order", e);
+ }
+ }
+
+
+
public String getDateFormat(String date) {
String dateFormat = "";
if (date.contains("-")) {
@@ -431,13 +500,34 @@ public String getHealthCenterName() {
* @see UgandaEMRSyncService#getPatientIdentifier(Patient, String)
*/
public String getPatientIdentifier(Patient patient, String patientIdentifierTypeUUID) {
- String query = "select patient_identifier.identifier from patient_identifier inner join patient_identifier_type on(patient_identifier.identifier_type=patient_identifier_type.patient_identifier_type_id) where patient_identifier_type.uuid in ('" + patientIdentifierTypeUUID + "') AND patient_id=" + patient.getPatientId() + "";
- List list = Context.getAdministrationService().executeSQL(query, true);
- String patientARTNO = "";
- if (!list.isEmpty()) {
- patientARTNO = list.get(0).toString().replace("[", "").replace("]", "");
+ if (patient == null || patient.getPatientId() == null || patientIdentifierTypeUUID == null) {
+ return "";
+ }
+
+ // Split comma-separated UUIDs
+ String[] uuids = patientIdentifierTypeUUID.split(",");
+
+ // Validate UUIDs to avoid SQL injection
+ String inClause = Arrays.stream(uuids).map(String::trim).filter(u -> u.matches("^[0-9a-fA-F\\-]{36}$")).map(u -> "'" + u + "'").collect(Collectors.joining(","));
+
+ if (inClause.isEmpty()) {
+ return "";
}
- return patientARTNO;
+
+ String sql = "select pi.identifier from patient_identifier pi inner join patient_identifier_type pit on (pi.identifier_type = pit.patient_identifier_type_id) where pit.uuid in (" + inClause + ") and pi.patient_id = " + patient.getPatientId() + " and pi.voided = 0 order by pi.preferred desc, pi.date_created desc limit 1";
+
+ List> list = Context.getAdministrationService().executeSQL(sql, true);
+
+ if (list == null || list.isEmpty() || list.get(0) == null) {
+ return "";
+ }
+
+ Object result = list.get(0);
+ if (result instanceof Object[]) {
+ return ((Object[]) result)[0].toString().replace("[", "").replace("]", "");
+ }
+
+ return result.toString().replace("[", "").replace("]", "");
}
public boolean encounterHasVLDataAlreadySaved(Encounter encounter, Order order) {
@@ -450,6 +540,7 @@ public boolean encounterHasVLDataAlreadySaved(Encounter encounter, Order order)
}
}
+
public Properties getUgandaEMRProperties() {
Properties properties = new Properties();
String appDataDir = OpenmrsUtil.getApplicationDataDirectory();
@@ -1585,6 +1676,21 @@ private List getStockOperationsByExternalReference(String extern
return stockOperations;
}
+
+ /**
+ * Records an outbound/inbound transaction related to lab result fetching, including
+ * response status and payload summary for auditing and troubleshooting.
+ *
+ * @param syncTaskType sync task type configuration (endpoint details)
+ * @param statusCode HTTP-like response code or internal error code
+ * @param statusMessage summary message to log
+ * @param logName accession/sample identifier or order reference
+ * @param status response details or server message
+ * @param date time of the transaction
+ * @param url endpoint URL
+ * @param actionRequired whether request was successful (implementation-specific)
+ * @param actionCompleted whether results were processed/saved (implementation-specific)
+ */
private void logTransaction(SyncTaskType syncTaskType, Integer statusCode, String statusMessage, String logName, String status, Date date, String url, boolean actionRequired, boolean actionCompleted) {
UgandaEMRSyncService ugandaEMRSyncService = Context.getService(UgandaEMRSyncService.class);
List syncTasks = ugandaEMRSyncService.getSyncTasksBySyncTaskId(logName).stream().filter(syncTask -> syncTask.getSyncTaskType().equals(syncTaskType)).collect(Collectors.toList());
@@ -2400,15 +2506,72 @@ public List