Skip to content
Closed
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: 2 additions & 0 deletions src/main/environment/common_ci.properties
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ diagnostic.emrlite.xray.refresh-url=${diagnostic.emrlite.xray.base-url}/api/v1/i
diagnostic.emrlite.xray.order-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/
diagnostic.emrlite.xray.result-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/result/
diagnostic.emrlite.xray.ping-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/ping/
diagnostic.emrlite.xray.cancel-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/cancel/
diagnostic.emrlite.xray.username=@env.DIAGNOSTIC_EMRLITE_XRAY_USERNAME@
diagnostic.emrlite.xray.password=@env.DIAGNOSTIC_EMRLITE_XRAY_PASSWORD@

Expand All @@ -78,6 +79,7 @@ diagnostic.emrlite.truenat.refresh-url=${diagnostic.emrlite.truenat.base-url}/ap
diagnostic.emrlite.truenat.order-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/
diagnostic.emrlite.truenat.result-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/result/
diagnostic.emrlite.truenat.ping-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/ping/
diagnostic.emrlite.truenat.cancel-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/cancel/
diagnostic.emrlite.truenat.username=@env.DIAGNOSTIC_EMRLITE_TRUENAT_USERNAME@
diagnostic.emrlite.truenat.password=@env.DIAGNOSTIC_EMRLITE_TRUENAT_PASSWORD@

Expand Down
2 changes: 2 additions & 0 deletions src/main/environment/common_docker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ diagnostic.emrlite.xray.refresh-url=${diagnostic.emrlite.xray.base-url}/api/v1/i
diagnostic.emrlite.xray.order-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/
diagnostic.emrlite.xray.result-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/result/
diagnostic.emrlite.xray.ping-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/ping/
diagnostic.emrlite.xray.cancel-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/cancel/
diagnostic.emrlite.xray.username=${DIAGNOSTIC_EMRLITE_XRAY_USERNAME}
diagnostic.emrlite.xray.password=${DIAGNOSTIC_EMRLITE_XRAY_PASSWORD}

Expand All @@ -77,6 +78,7 @@ diagnostic.emrlite.truenat.refresh-url=${diagnostic.emrlite.truenat.base-url}/ap
diagnostic.emrlite.truenat.order-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/
diagnostic.emrlite.truenat.result-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/result/
diagnostic.emrlite.truenat.ping-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/ping/
diagnostic.emrlite.truenat.cancel-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/cancel/
diagnostic.emrlite.truenat.username=${DIAGNOSTIC_EMRLITE_TRUENAT_USERNAME}
diagnostic.emrlite.truenat.password=${DIAGNOSTIC_EMRLITE_TRUENAT_PASSWORD}

Expand Down
6 changes: 6 additions & 0 deletions src/main/environment/common_example.properties
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ abha.client.secret=<your-abha-client-secret>
abha.token.url=<abha-token-url>
abha.xcmid=<sbx-or-prod>

# GovThealth API Config
govthealth.user.details.url={$GOVTHEALTH_USER_DETAILS_URL}
govthealth.user.id={$GOVTHEALTH_USER_ID}
govthealth.password={$GOVTHEALTH_PASSWORD}

# ── Diagnostic Device Integration ────────────────────────────────────────────
# diagnostic.provider.xray / diagnostic.provider.truenat select the vendor per orderType family
Expand All @@ -64,6 +68,7 @@ diagnostic.emrlite.xray.refresh-url=${diagnostic.emrlite.xray.base-url}/api/v1/i
diagnostic.emrlite.xray.order-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/
diagnostic.emrlite.xray.result-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/result/
diagnostic.emrlite.xray.ping-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/ping/
diagnostic.emrlite.xray.cancel-url=${diagnostic.emrlite.xray.base-url}/api/v1/integrations/orders/cancel/
diagnostic.emrlite.xray.username=${DIAGNOSTIC_EMRLITE_XRAY_USERNAME}
diagnostic.emrlite.xray.password=${DIAGNOSTIC_EMRLITE_XRAY_PASSWORD}

Expand All @@ -74,6 +79,7 @@ diagnostic.emrlite.truenat.refresh-url=${diagnostic.emrlite.truenat.base-url}/ap
diagnostic.emrlite.truenat.order-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/
diagnostic.emrlite.truenat.result-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/result/
diagnostic.emrlite.truenat.ping-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/ping/
diagnostic.emrlite.truenat.cancel-url=${diagnostic.emrlite.truenat.base-url}/api/v1/integrations/orders/cancel/
diagnostic.emrlite.truenat.username=${DIAGNOSTIC_EMRLITE_TRUENAT_USERNAME}
diagnostic.emrlite.truenat.password=${DIAGNOSTIC_EMRLITE_TRUENAT_PASSWORD}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ public DiagnosticOrderController(DiagnosticOrderService diagnosticOrderService)

@PostMapping("/order/push")
@Operation(summary = "Push a diagnostic order to the configured provider")
public String pushOrder(@RequestBody @Valid DiagnosticOrderRequestDto request) {
public String pushOrder(@RequestBody @Valid DiagnosticOrderRequestDto request,
@RequestHeader("JwtToken") String jwtToken) {
OutputResponse response = new OutputResponse();
try {
DiagnosticOrder order = diagnosticOrderService.createAndPushOrder(request);
DiagnosticOrder order = diagnosticOrderService.createAndPushOrderByUser(request, jwtToken);
response.setResponse(new Gson().toJson(order));
} catch (Exception e) {
logger.error("Error in pushOrder: {}", e.getMessage());
Expand Down Expand Up @@ -128,14 +129,17 @@ public String pollOrder(@RequestParam Long beneficiaryId, @RequestParam String o
}

@PostMapping("/order/manualResult")
@Operation(summary = "Manually submit a diagnostic result for a beneficiary's latest order of the given "
+ "type, for use when no vendor device is integrated. Stored as-is; tbPresence, tbConfidence, and "
+ "drugResistancePresence are left null since nothing derives them. Rejected if that order is "
+ "already COMPLETED.")
public String submitManualResult(@RequestBody @Valid ManualDiagnosticResultRequestDto request) {
@Operation(summary = "Exactly one of resultSummary/reasonToClose is required. resultSummary: manually submit "
+ "a diagnostic result for a beneficiary's latest order of the given type, for use when no vendor "
+ "device is integrated. Stored as-is; tbPresence, tbConfidence, and drugResistancePresence are left "
+ "null since nothing derives them. Rejected if that order is already COMPLETED. reasonToClose: close "
+ "the order instead (same behavior as reasonToClose on /order/push), sourcing patient/orderEvent "
+ "from the beneficiary's existing order for this type.")
public String submitManualResult(@RequestBody @Valid ManualDiagnosticResultRequestDto request,
@RequestHeader("JwtToken") String jwtToken) {
OutputResponse response = new OutputResponse();
try {
DiagnosticOrderResultDto result = diagnosticOrderService.submitManualResult(request);
DiagnosticOrderResultDto result = diagnosticOrderService.submitManualResult(request, jwtToken);
response.setResponse(new GsonBuilder().serializeNulls().create().toJson(result));
} catch (Exception e) {
logger.error("Error in submitManualResult: {}", e.getMessage());
Expand Down
13 changes: 9 additions & 4 deletions src/main/java/com/iemr/flw/domain/iemr/DiagnosticOrder.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import java.sql.Timestamp;

@Entity
@Table(name = "tb_diagnostic_order", schema = "db_iemr",
uniqueConstraints = @UniqueConstraint(columnNames = {"beneficiary_id", "visitCode", "order_type"}))
@Table(name = "tb_diagnostic_order", schema = "db_iemr")
@Data
public class DiagnosticOrder {

Expand Down Expand Up @@ -53,12 +52,15 @@ public class DiagnosticOrder {
@Column(name = "error_message", columnDefinition = "TEXT")
private String errorMessage;

@Column(name = "reason_for_refusal", columnDefinition = "TEXT")
private String reasonForRefusal;
@Column(name = "reason_to_close", columnDefinition = "TEXT")
private String reasonToClose;

@Column(name = "push_response_json", columnDefinition = "LONGTEXT")
private String pushResponseJson;

@Column(name = "cancel_response_json", columnDefinition = "LONGTEXT")
private String cancelResponseJson;

@Column(name = "patient_first_name", length = 100)
private String patientFirstName;

Expand All @@ -82,6 +84,9 @@ public class DiagnosticOrder {
@Column(name = "modified_by")
private String modifiedBy;

@Column(name = "manually_entered_by")
private String manuallyEnteredBy;

@UpdateTimestamp
@Column(name = "last_mod_date")
private Timestamp lastModDate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,10 @@ public class StopTBGeneralExamination {
@Column(name = "clubbing")
private String clubbing;

// JSON arrays serialised from mobile
@Column(name = "key_population_risk_factor_ids", columnDefinition = "TEXT")
private String keyPopulationRiskFactorIds;

@Column(name = "key_population_risk_factors", columnDefinition = "TEXT")
private String keyPopulationRiskFactors;

@Column(name = "hiv_status_id")
private Integer hivStatusId;

// "Positive" | "Reactive" | "Negative" | "Unknown"
@Column(name = "hiv_status")
private String hivStatus;
// key_population_risk_factor_ids/_factors and hiv_status_id/hiv_status were removed from
// this table — TB Screening (TBScreening) is the single source of truth for these fields
// (see StopTBServiceImpl#examToMap, and the AMRIT-DB migration that backfilled tb_screening
// from this table's historical data before dropping the columns here).

@Column(name = "referral_to_hwc_needed_id")
private Integer referralToHWCNeededId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@ public class DiagnosticOrderRequestDto {
@NotNull
private Long beneficiaryId;

@NotNull
private Long visitCode;

@NotNull
private String orderType;

private String orderEvent;

private String reasonForRefusal;
private String reasonToClose;

@NotNull
@Valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class DiagnosticOrderResultDto {
private String providerStatus;
private String resultSummary;
private String errorMessage;
private String reasonForRefusal;
private String reasonToClose;
private Boolean tbPresence;
private Double tbConfidence;
private Boolean drugResistancePresence;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
public class DiagnosticOrderStatusSummaryDto {
private List<Long> awaitingProviderResult;
private List<Long> completed;
private List<Long> pollingTimedOut;
private List<Long> failed;
private List<Long> refused;
private List<Long> closed;
private List<Long> awaitingManualEntry;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.iemr.flw.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
Expand All @@ -15,6 +14,7 @@ public class ManualDiagnosticResultRequestDto {
@NotNull
private String orderType;

@NotBlank
private String resultSummary;

private String reasonToClose;
}
2 changes: 1 addition & 1 deletion src/main/java/com/iemr/flw/dto/iemr/FormSectionDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class FormSectionDTO {

private Boolean isRequired = true;

@NotNull(message = "displayOrder is required")
/** Omit to auto-append after existing siblings; set explicitly to insert at that position (existing siblings shift up). */
private Integer displayOrder;

private Boolean hasSubmitButton = false;
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/com/iemr/flw/dto/iemr/QuestionOptionDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -51,7 +50,7 @@ public class QuestionOptionDTO {

private String optionValueHindi;

@NotNull(message = "displayOrder is required")
/** Omit to auto-append after existing siblings; set explicitly to insert at that position (existing siblings shift up). */
private Integer displayOrder;

@Valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class SectionQuestionDTO {

private Boolean isMandatory = true;

@NotNull(message = "displayOrder is required")
/** Omit to auto-append after existing siblings; set explicitly to insert at (or move to) that position (existing siblings shift). */
private Integer displayOrder;

private Integer maxLength;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.iemr.flw.integration.provider;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class DiagnosticCancelResult {
private boolean success;
private String rawResponseJson;
private String errorMessage;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ public interface DiagnosticProvider {

DiagnosticPollResult pollResult(DiagnosticOrder order, boolean includeAssets) throws Exception;

/**
* Notifies the vendor that this order is being closed out without (or regardless of) a result,
* so it stops tracking/processing it on their end. Only call for an order actually known to the
* vendor (i.e. previously pushed) — calling this for an order that was never pushed is meaningless.
* The raw response is returned (on both success and a provider-level rejection) so the caller can
* persist it; only a transport-level failure (unreachable, timeout) throws instead, since there's
* no response body to return in that case.
*/
DiagnosticCancelResult cancelOrder(DiagnosticOrder order, String reason) throws Exception;

/**
* Lightweight liveness check against the vendor group serving this orderType. Never throws —
* returns false on any failure (unreachable, non-2xx, unparsable response, unconfigured URL).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.iemr.flw.domain.iemr.DiagnosticOrder;
import com.iemr.flw.integration.provider.DiagnosticCancelResult;
import com.iemr.flw.integration.provider.DiagnosticDocumentAsset;
import com.iemr.flw.integration.provider.DiagnosticPollResult;
import com.iemr.flw.integration.provider.DiagnosticProvider;
Expand Down Expand Up @@ -60,6 +61,12 @@ public class EmrLiteProvider implements DiagnosticProvider {
@Value("${diagnostic.emrlite.truenat.ping-url}")
private String truenatPingUrl;

@Value("${diagnostic.emrlite.xray.cancel-url}")
private String xrayCancelUrl;

@Value("${diagnostic.emrlite.truenat.cancel-url}")
private String truenatCancelUrl;

@Autowired
@Qualifier("emrLiteRestTemplate")
private RestTemplate restTemplate;
Expand Down Expand Up @@ -158,6 +165,34 @@ public DiagnosticPollResult pollResult(DiagnosticOrder order, boolean includeAss
tbPresence, tbConfidence, drugResistancePresence);
}

@Override
public DiagnosticCancelResult cancelOrder(DiagnosticOrder order, String reason) throws Exception {
EmrLiteCancelRequest request = new EmrLiteCancelRequest(order.getExternalOrderId(), reason);
String cancelUrl = DiagnosticOrderType.XRAY_CHEST.name().equals(order.getOrderType())
? xrayCancelUrl : truenatCancelUrl;
String responseBody;
try {
responseBody = doPost(cancelUrl, gson.toJson(request));
} catch (HttpStatusCodeException e) {
String body = e.getResponseBodyAsString();
logger.warn("Provider HTTP error on order cancel: externalOrderId={}, status={}",
order.getExternalOrderId(), e.getStatusCode());
return new DiagnosticCancelResult(false, body, "HTTP " + e.getStatusCode());
}

EmrLiteProviderResponse envelope = gson.fromJson(responseBody, EmrLiteProviderResponse.class);
if (envelope == null || !envelope.isSuccess()) {
logger.warn("Provider rejected order cancel: externalOrderId={}, message={}",
order.getExternalOrderId(), envelope != null ? envelope.getMessage() : null);
return new DiagnosticCancelResult(false, responseBody,
envelope != null ? envelope.getMessage() : "empty response");
}

logger.info("Order cancelled on provider: externalOrderId={}, reason={}",
order.getExternalOrderId(), reason);
return new DiagnosticCancelResult(true, responseBody, null);
}

@Override
public boolean checkHealth(DiagnosticOrderType orderType) {
String pingUrl = DiagnosticOrderType.XRAY_CHEST.equals(orderType) ? xrayPingUrl : truenatPingUrl;
Expand Down Expand Up @@ -236,8 +271,8 @@ private DiagnosticOrderStatus deriveOrderStatus(EmrLiteResultResponse result) {
DiagnosticOrderStatus topLevelStatus = DiagnosticOrderStatus.fromString(result.getStatus());
if (topLevelStatus == DiagnosticOrderStatus.COMPLETED || topLevelStatus == DiagnosticOrderStatus.FAILED) {
// Components contradict a terminal top-level status — only the components map may
// resolve to a terminal state; treat this as still in progress.
return DiagnosticOrderStatus.IN_PROGRESS;
// resolve to a terminal state; treat this as still pending.
return DiagnosticOrderStatus.PENDING;
}
return topLevelStatus;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.iemr.flw.integration.provider.emrlite.dto;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmrLiteCancelRequest {
private String externalOrderId;
private String reason;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ public enum DiagnosticOrderStatus {
IN_PROGRESS,
COMPLETED,
FAILED,
CANCELLED,
EXPIRED,
REFUSED,
CLOSED,
MANUAL_ENTRY;

public static DiagnosticOrderStatus fromString(String value) {
if (value == null) return PENDING;
// IN_PROGRESS is kept only as a legacy/historical value (old rows, backward-compat reporting
// queries) — it is never freshly assigned. A vendor reporting "IN_PROGRESS" is just PENDING.
if (IN_PROGRESS.name().equalsIgnoreCase(value)) return PENDING;
for (DiagnosticOrderStatus status : values()) {
if (status.name().equalsIgnoreCase(value)) {
return status;
Expand Down
Loading
Loading