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
8 changes: 4 additions & 4 deletions src/main/java/com/iemr/flw/controller/StopTBController.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public String getAllGeneralExaminations(@RequestBody Map<String, Object> body) {
if (raw == null) throw new Exception("providerServiceMapID is required");
Integer villageID = body.get("villageID") != null ? Integer.parseInt(body.get("villageID").toString()) : null;
Map<String, Object> data = stopTBService.getAllGeneralExaminations(Integer.parseInt(raw.toString()), villageID);
response.setResponse(gson.toJson(data));
response.setResponse(gson.toJsonTree(data));
} catch (Exception e) {
logger.error("Error in getAllGeneralExaminations: " + e);
response.setError(5000, "Error fetching general examinations: " + e.getMessage());
Expand Down Expand Up @@ -85,7 +85,7 @@ public String getAllNurseTBScreenings(@RequestBody Map<String, Object> body) {
if (raw == null) throw new Exception("providerServiceMapID is required");
Integer villageID = body.get("villageID") != null ? Integer.parseInt(body.get("villageID").toString()) : null;
Map<String, Object> data = stopTBService.getAllNurseTBScreenings(Integer.parseInt(raw.toString()), villageID);
response.setResponse(gson.toJson(data));
response.setResponse(gson.toJsonTree(data));
} catch (Exception e) {
logger.error("Error in getAllNurseTBScreenings: " + e);
response.setError(5000, "Error fetching TB screenings: " + e.getMessage());
Expand Down Expand Up @@ -122,7 +122,7 @@ public String getAllGeneralOpd(@RequestBody Map<String, Object> body) {
if (raw == null) throw new Exception("providerServiceMapID is required");
Integer villageID = body.get("villageID") != null ? Integer.parseInt(body.get("villageID").toString()) : null;
Map<String, Object> data = stopTBService.getAllGeneralOpd(Integer.parseInt(raw.toString()), villageID);
response.setResponse(gson.toJson(data));
response.setResponse(gson.toJsonTree(data));
} catch (Exception e) {
logger.error("Error in getAllGeneralOpd: " + e);
response.setError(5000, "Error fetching general OPD records: " + e.getMessage());
Expand Down Expand Up @@ -155,7 +155,7 @@ public String getAllDiagnostics(@RequestBody Map<String, Object> body) {
if (raw == null) throw new Exception("providerServiceMapID is required");
Integer villageID = body.get("villageID") != null ? Integer.parseInt(body.get("villageID").toString()) : null;
Map<String, Object> data = stopTBService.getAllDiagnostics(Integer.parseInt(raw.toString()), villageID);
response.setResponse(gson.toJson(data));
response.setResponse(gson.toJsonTree(data));
} catch (Exception e) {
logger.error("Error in getAllDiagnostics: " + e);
response.setError(5000, "Error fetching diagnostics records: " + e.getMessage());
Expand Down
77 changes: 77 additions & 0 deletions src/main/java/com/iemr/flw/controller/StopTBSyncController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.iemr.flw.controller;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.iemr.flw.service.StopTBSyncService;
import com.iemr.flw.utils.JwtUtil;
import com.iemr.flw.utils.response.OutputResponse;
import io.swagger.v3.oas.annotations.Operation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/stoptb/sync")
public class StopTBSyncController {

private final Logger logger = LoggerFactory.getLogger(StopTBSyncController.class);

private final Gson gson = new GsonBuilder().serializeNulls().create();

@Autowired
private StopTBSyncService stopTBSyncService;

@Autowired
private JwtUtil jwtUtil;

/**
* Headers: JwtToken (required; the user's role mappings decide the allowed service and villages).
* Body: { token: string|null, providerServiceMapID: int, villageIds: [int] (empty = all of the user's
* villages), types: [string], limit: int }.
* types: the record types the app applies (all when omitted); changing it requires a new snapshot.
* Response: { supported, serverId, snapshot, items: [{type, op, key, version, data}], nextToken, hasMore }.
* Call again with nextToken while hasMore is true. On a server without the change log (AMRIT-DB before V132) the
* response is { supported: false } and the app keeps using the getAll endpoints.
*/
@PostMapping("/changes")
@Operation(summary = "STOP-TB records changed since the token (first call: everything in the villages)")
public String getChanges(@RequestBody Map<String, Object> body,
@RequestHeader(value = "Authorization", required = false) String authorization,
@RequestHeader(value = "JwtToken", required = false) String jwtToken) {
OutputResponse response = new OutputResponse();
try {
// The user decides which villages may be synced, so the JWT is required here even
// where the request filter would let a mobile request through without one.
if (jwtToken == null || jwtToken.isBlank()) throw new IllegalArgumentException("JwtToken header is required");
Integer userId = jwtUtil.extractUserId(jwtToken);
Object token = body.get("token");
Integer psm = body.get("providerServiceMapID") != null
? Integer.parseInt(body.get("providerServiceMapID").toString()) : null;
List<Integer> villageIds = new ArrayList<>();
if (body.get("villageIds") instanceof List) {
for (Object v : (List<?>) body.get("villageIds")) {
if (v != null) villageIds.add((int) Double.parseDouble(v.toString()));
}
}
List<String> types = new ArrayList<>();
if (body.get("types") instanceof List) {
for (Object t : (List<?>) body.get("types")) {
if (t != null) types.add(t.toString());
}
}
Integer limit = body.get("limit") != null ? (int) Double.parseDouble(body.get("limit").toString()) : null;
Map<String, Object> data = stopTBSyncService.getChanges(
userId, token != null ? token.toString() : null, psm, villageIds, types, limit, authorization);
response.setResponse(gson.toJson(data));
} catch (Exception e) {
logger.error("Error in sync/changes: " + e);
response.setError(5000, "Error fetching changes: " + e.getMessage());
}
return response.toString();
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/iemr/flw/repo/identity/BeneficiaryRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ Page<RMNCHMBeneficiaryaddress> getBenDataWithinDates(
@Query(nativeQuery = true, value = " SELECT HealthID,HealthIdNumber,isNewAbha FROM db_iemr.t_healthid WHERE HealthIdNumber = :healthIdNumber ")
ArrayList<Object[]> getBenHealthDetails(@Param("healthIdNumber") String healthIdNumber);

// Same rows FHIR-API getBenhealthID reads, so the worklist can fill healthId/healthIdNumber without an HTTP call
@Query(nativeQuery = true, value = " SELECT HealthID, HealthIdNumber FROM db_iemr.m_benhealthidmapping WHERE BeneficiaryRegID = :benRegId ")
List<Object[]> getHealthIdMappingRows(@Param("benRegId") Long benRegId);

@Query(nativeQuery = true, value = " SELECT HealthId FROM db_iemr.t_healthid WHERE HealthIdNumber = :healthIdNumber ")
List<String> getHealthIdByNumber(@Param("healthIdNumber") String healthIdNumber);

// benRegId has no unique constraint on i_beneficiarymapping — a beneficiary can have
// more than one mapping row, so this must return a list, not assume a single result.
@Query("SELECT b FROM RMNCHMBeneficiarymapping b WHERE b.benRegId = :benRegId ORDER BY b.benMapId DESC")
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/iemr/flw/repo/iemr/DiagnosticOrderRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ Optional<DiagnosticOrder> findFirstByBeneficiaryIdAndOrderTypeAndDeletedFalseOrd
@Query("UPDATE DiagnosticOrder o SET o.vanSerialNo = o.id WHERE o.id = :id")
void updateVanSerialNo(@Param("id") Long id);

// A poll that changed nothing only records when it ran (and the retry count), leaving processed,
// modified_by and the rest of the row alone: no re-upload to central, no change-log line.
@Transactional
@Modifying
@Query("UPDATE DiagnosticOrder o SET o.lastPolledAt = :polledAt, o.retryCount = :retryCount WHERE o.id = :id")
int recordUnchangedPoll(@Param("id") Long id, @Param("polledAt") Timestamp polledAt,
@Param("retryCount") Integer retryCount);

// Beneficiary-status-summary queries, filtered the same way as
// FormResponseRepo.findBeneficiaryIdsByFormIdAndStatusFiltered - null-safe optional
// villageId/providerServiceMapId via a correlated BenFlowStatus subquery.
Expand Down Expand Up @@ -103,4 +111,16 @@ List<Long> findBeneficiaryIdsClosed(@Param("orderType") String orderType,
"AND (:providerServiceMapId IS NULL OR b.providerServiceMapId = :providerServiceMapId))")
List<Long> findBeneficiaryIdsAwaitingManualEntry(@Param("orderType") String orderType,
@Param("villageId") Integer villageId, @Param("providerServiceMapId") Integer providerServiceMapId);

// The five queries above in one pass: beneficiaryId + status of each beneficiary's latest order,
// same filters, for the caller to bucket by status.
@Query("SELECT o.beneficiaryId, o.status FROM DiagnosticOrder o WHERE o.orderType = :orderType AND o.deleted = false " +
"AND o.status IN ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'FAILED', 'CLOSED', 'CANCELLED', 'EXPIRED', 'REFUSED', 'MANUAL_ENTRY') " +
"AND o.id = (SELECT MAX(o2.id) FROM DiagnosticOrder o2 " +
"WHERE o2.beneficiaryId = o.beneficiaryId AND o2.orderType = :orderType AND o2.deleted = false) " +
"AND o.beneficiaryId IN (SELECT b.beneficiaryID FROM BenFlowStatus b WHERE b.deleted = false " +
"AND (:villageId IS NULL OR b.villageID = :villageId) " +
"AND (:providerServiceMapId IS NULL OR b.providerServiceMapId = :providerServiceMapId))")
List<Object[]> findLatestOrderStatusByBeneficiary(@Param("orderType") String orderType,
@Param("villageId") Integer villageId, @Param("providerServiceMapId") Integer providerServiceMapId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import jakarta.persistence.QueryHint;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -19,6 +21,8 @@ public interface StopTBDiagnosticsRepo extends JpaRepository<StopTBDiagnostics,
@Query("SELECT d FROM StopTBDiagnostics d WHERE d.providerServiceMapID = :psmId AND d.deleted = false ORDER BY d.visitDate DESC")
List<StopTBDiagnostics> findAllByProviderServiceMapID(@Param("psmId") Integer psmId);

// Read-only: getAll only reads these rows, so Hibernate keeps no change-tracking snapshot per row
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))
@Query("SELECT d FROM StopTBDiagnostics d WHERE d.benRegID IN " +
"(SELECT b.beneficiaryRegID FROM BenFlowStatus b WHERE b.providerServiceMapId = :psmId AND b.villageID = :villageId) " +
"AND d.deleted = false ORDER BY d.visitDate DESC")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import jakarta.persistence.QueryHint;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -32,6 +34,8 @@ StopTBGeneralExamination findByBeneficiaryRegIDAndVisitCode(@Param("beneficiaryR
@Query("SELECT e FROM StopTBGeneralExamination e WHERE e.providerServiceMapID = :psmId AND e.deleted = false ORDER BY e.createdDate DESC")
List<StopTBGeneralExamination> findAllByProviderServiceMapID(@Param("psmId") Integer psmId);

// Read-only: getAll only reads these rows, so Hibernate keeps no change-tracking snapshot per row
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))
@Query("SELECT e FROM StopTBGeneralExamination e WHERE e.beneficiaryRegID IN " +
"(SELECT b.beneficiaryRegID FROM BenFlowStatus b WHERE b.providerServiceMapId = :psmId AND b.villageID = :villageId) " +
"AND e.deleted = false ORDER BY e.createdDate DESC")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import jakarta.persistence.QueryHint;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -30,6 +32,8 @@ public interface StopTBGeneralOpdRepo extends JpaRepository<StopTBGeneralOpd, Lo
@Query("SELECT o FROM StopTBGeneralOpd o WHERE o.providerServiceMapID = :psmId AND o.deleted = false ORDER BY o.createdDate DESC")
List<StopTBGeneralOpd> findAllByProviderServiceMapID(@Param("psmId") Integer psmId);

// Read-only: getAll only reads these rows, so Hibernate keeps no change-tracking snapshot per row
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))
@Query("SELECT o FROM StopTBGeneralOpd o WHERE o.benRegID IN " +
"(SELECT b.beneficiaryRegID FROM BenFlowStatus b WHERE b.providerServiceMapId = :psmId AND b.villageID = :villageId) " +
"AND o.deleted = false ORDER BY o.createdDate DESC")
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/iemr/flw/repo/iemr/TBScreeningRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import jakarta.persistence.QueryHint;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -37,6 +39,8 @@ public interface TBScreeningRepo extends JpaRepository<TBScreening, Long> {
@Query("SELECT tbs FROM TBScreening tbs WHERE tbs.providerServiceMapID = :psmId AND tbs.deleted = false ORDER BY tbs.visitDate DESC")
List<TBScreening> findAllByProviderServiceMapID(@Param("psmId") Integer psmId);

// Read-only: getAll only reads these rows, so Hibernate keeps no change-tracking snapshot per row
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))
@Query("SELECT tbs FROM TBScreening tbs WHERE tbs.benRegID IN " +
"(SELECT b.beneficiaryRegID FROM BenFlowStatus b WHERE b.providerServiceMapId = :psmId AND b.villageID = :villageId) " +
"AND tbs.deleted = false ORDER BY tbs.visitDate DESC")
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/com/iemr/flw/repo/iemr/TBSuspectedRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ TBSuspected getByUserIdAndBenIdAndVisitCode(@Param("benId") Long benId, @Param("
@Query("SELECT tbs FROM TBSuspected tbs WHERE tbs.userId = :userId and tbs.visitDate >= :fromDate and tbs.visitDate <= :toDate")
List<TBSuspected> getByUserId(@Param("userId") Integer userId, @Param("fromDate") Timestamp fromDate, @Param("toDate") Timestamp toDate);

@Query("SELECT tbs FROM TBSuspected tbs WHERE tbs.benId IN " +
"(SELECT b.beneficiaryID FROM BenFlowStatus b WHERE b.providerServiceMapId = :psmId AND b.villageID = :villageId)")
// Same query as before in SQL, with an optimizer hint so the village subquery reads
// idx_flow_psm_village_reg instead of the PSM-only index (ignored if that index is missing).
@Query(nativeQuery = true, value = "SELECT t.* FROM db_iemr.tb_suspected t WHERE t.ben_id IN " +
"(SELECT /*+ INDEX(b idx_flow_psm_village_reg) */ b.beneficiary_id FROM db_iemr.i_ben_flow_outreach b " +
"WHERE b.providerServiceMapID = :psmId AND b.villageID = :villageId)")
List<TBSuspected> getByProviderServiceMapIdAndVillageId(@Param("psmId") Integer psmId, @Param("villageId") Integer villageId);

@Transactional
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/iemr/flw/repo/iemr/UserServiceRoleRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public interface UserServiceRoleRepo extends JpaRepository<UserServiceRole, Inte
List<Object[]> getNikshayMappingRows(@Param("userId") Integer userId,
@Param("providerServiceMapId") Integer providerServiceMapId);

// Every active mapping row's service and village list, for the STOP-TB changes feed's scope check.
@Query(value = "SELECT usrm.ProviderServiceMapID, usrm.Villageid FROM m_userservicerolemapping usrm " +
"WHERE usrm.UserID = :userId AND usrm.Deleted = false", nativeQuery = true)
List<Object[]> getServiceVillageRows(@Param("userId") Integer userId);

@Query(value = "SELECT NikshayTUID, TUName FROM m_nikshay_tu WHERE NikshayTUID IN (:ids)", nativeQuery = true)
List<Object[]> findNikshayTuNames(@Param("ids") java.util.Collection<Integer> ids);

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/iemr/flw/service/BeneficiaryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
import com.iemr.flw.dto.iemr.SAMResponseDTO;

import java.util.List;
import java.util.Map;

public interface BeneficiaryService {

String getBenData(GetBenRequestHandler requestDTO, String authorisation) throws Exception;

Map<Long, String> getBeneficiaryRecordsByRegIds(List<Long> benRegIds, String authorisation);

String saveEyeCheckupVsit(List<EyeCheckupRequestDTO> eyeCheckupRequestDTOS,String token);

List<EyeCheckupRequestDTO> getEyeCheckUpVisit(GetBenRequestHandler request,String token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ public interface DiagnosticOrderService {

DiagnosticOrderResultDto getOrderResult(Long beneficiaryId, String orderType, Long visitCode);

/**
* Orders with their result, keyed by order id (STOP-TB changes feed). Deleted orders are left
* out. Each record carries what getBeneficiariesByStatus and getOrderResult give today: the
* beneficiary, order type, visit code and status (the app buckets by the latest order per
* beneficiary and type), plus the result fields.
*/
java.util.Map<Long, java.util.Map<String, Object>> getOrderRecordsByIds(java.util.Collection<Long> ids);

DiagnosticOrder getOrder(Long beneficiaryId, String orderType, Long visitCode) throws Exception;

List<DiagnosticOrder> getOrdersByBeneficiaryId(Long beneficiaryId) throws Exception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ List<FormResponseDTO> getResponsesByBeneficiary(Long beneficiaryId, Integer vill
/** Single response with all nested section and question answers. */
FormResponseDTO getResponseById(Long responseId);

/**
* Responses with all nested answers, keyed by responseId, as getResponsesByBeneficiary returns
* them (STOP-TB changes feed). Ids with no response are left out.
*/
java.util.Map<Long, FormResponseDTO> getResponsesByIds(java.util.Collection<Long> responseIds);

/**
* Submit multiple form responses in one bulk transaction.
* All FormResponse rows are batch-inserted via saveAll() so responseIds are available
Expand Down
Loading
Loading