From 3af50e2b6d8621a4df0d661db0f070ffc805de34 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Date: Fri, 30 Oct 2026 18:47:36 +0530 Subject: [PATCH 1/2] changes for the data sync and fetching sync data --- .../nikshay/NikshaySyncStatusController.java | 163 +++++ .../NikshayBeneficiaryPhaseStatus.java | 75 +++ .../nikshay/NikshayBeneficiarySyncStatus.java | 72 +++ .../nikshay/NikshayMissingBeneficiaryDTO.java | 27 + .../nikshay/NikshayMissingBeneficiaryRow.java | 15 + .../nikshay/NikshaySyncStatusUpdateDTO.java | 13 + .../nikshay/RMNCHBeneficiaryDetailsRmnch.java | 583 ++++++++++++++++++ .../data/nikshay/RMNCHMBeneficiarydetail.java | 231 +++++++ .../mmu/repo/nikshay/BeneficiaryRepo.java | 28 + .../NikshayBeneficiaryPhaseStatusRepo.java | 34 + .../NikshayBeneficiarySyncStatusRepo.java | 97 +++ .../nikshay/NikshaySyncStatusService.java | 18 + .../impl/NikshaySyncStatusServiceImpl.java | 363 +++++++++++ .../com/iemr/mmu/utils/NikshayConstants.java | 16 + 14 files changed, 1735 insertions(+) create mode 100644 src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiaryPhaseStatus.java create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiarySyncStatus.java create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryDTO.java create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryRow.java create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/NikshaySyncStatusUpdateDTO.java create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/RMNCHMBeneficiarydetail.java create mode 100644 src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java create mode 100644 src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java create mode 100644 src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java create mode 100644 src/main/java/com/iemr/mmu/service/nikshay/NikshaySyncStatusService.java create mode 100644 src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java create mode 100644 src/main/java/com/iemr/mmu/utils/NikshayConstants.java diff --git a/src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java b/src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java new file mode 100644 index 00000000..593d2e28 --- /dev/null +++ b/src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java @@ -0,0 +1,163 @@ +package com.iemr.mmu.controller.nikshay; + +import com.google.gson.Gson; +import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryDTO; +import com.iemr.mmu.data.nikshay.NikshaySyncStatusUpdateDTO; +import com.iemr.mmu.service.nikshay.NikshaySyncStatusService; +import com.iemr.mmu.utils.NikshayConstants; +import com.iemr.mmu.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.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +@RestController +@RequestMapping(NikshayConstants.NIKSHAY_PATH) +public class NikshaySyncStatusController { + + private static final int MAX_PAGE_SIZE = 100; + + private final Logger logger = LoggerFactory.getLogger(NikshaySyncStatusController.class); + + private final Gson gson = new Gson(); + + @Autowired + private NikshaySyncStatusService nikshaySyncStatusService; + + @PostMapping("/syncStatus/syncFailed") + @Operation(summary = "Copy all FAILED rows from nikshay_beneficiary_phase_status into nikshay_beneficiary_sync_status") + public String syncFailedPhaseStatuses(@RequestHeader(value = "Authorization") String authorization, + @RequestHeader(value = "ServerAuthorization") String serverAuthorization) { + OutputResponse response = new OutputResponse(); + try { + Map result = nikshaySyncStatusService.syncFailedPhaseStatuses(serverAuthorization); + response.setResponse(gson.toJson(result)); + } catch (Exception e) { + logger.error("Error in syncFailedPhaseStatuses: " + e); + response.setError(5000, "Error syncing failed Nikshay phase statuses: " + e.getMessage()); + } + return response.toString(); + } + + @GetMapping("/syncStatus/missingBeneficiaries") + @Operation(summary = "Get beneficiaries whose Ni-kshay sync failed, filtered by provider service map, village or both (paged)") + public ResponseEntity> getMissingBeneficiaries( + @RequestParam(value = "providerServiceMapID", required = false) Integer providerServiceMapID, + @RequestParam(value = "villageID", required = false) Integer villageID, + @RequestParam(value = "pageNo", defaultValue = "0") Integer pageNo, + @RequestParam(value = "pageSize", defaultValue = "30") Integer pageSize) { + logger.info("getMissingBeneficiaries request: providerServiceMapID={}, villageID={}, pageNo={}, pageSize={}", + providerServiceMapID, villageID, pageNo, pageSize); + try { + if (providerServiceMapID == null && villageID == null) { + return errorResponse(HttpStatus.BAD_REQUEST, "providerServiceMapID or villageID is required"); + } + if (pageNo < 0) { + return errorResponse(HttpStatus.BAD_REQUEST, "pageNo must be 0 or greater"); + } + if (pageSize < 1 || pageSize > MAX_PAGE_SIZE) { + return errorResponse(HttpStatus.BAD_REQUEST, "pageSize must be between 1 and " + MAX_PAGE_SIZE); + } + + Page page = nikshaySyncStatusService.getMissingBeneficiaries( + providerServiceMapID, villageID, pageNo, pageSize); + + Map pagination = new LinkedHashMap<>(); + pagination.put("page", page.getNumber()); + pagination.put("size", page.getSize()); + pagination.put("totalElements", page.getTotalElements()); + pagination.put("totalPages", page.getTotalPages()); + pagination.put("hasNext", page.hasNext()); + + Map body = new LinkedHashMap<>(); + body.put("status", "SUCCESS"); + body.put("message", page.isEmpty() + ? "No missing beneficiary data found" + : "Missing beneficiary data fetched successfully"); + body.put("data", page.getContent()); + body.put("pagination", pagination); + return ResponseEntity.ok(body); + } catch (Exception e) { + logger.error("Error in getMissingBeneficiaries for providerServiceMapID={}, villageID={}", + providerServiceMapID, villageID, e); + return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, + "Error fetching missing beneficiary data: " + e.getMessage()); + } + } + + @GetMapping(NikshayConstants.FAILED_BENEFICIARIES_PATH) + @Operation(summary = "Get every beneficiary with a FAILED phase in nikshay_beneficiary_phase_status, with all its phases (not paged)") + public ResponseEntity> getFailedBeneficiaries(@RequestHeader(value = "ServerAuthorization") String serverAuthorization) { + try { + return ResponseEntity.ok(nikshaySyncStatusService.getFailedBeneficiaries()); + } catch (Exception e) { + logger.error("Error in getFailedBeneficiaries", e); + return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, + "Error fetching failed Nikshay beneficiaries: " + e.getMessage()); + } + } + + @PostMapping("/syncStatus/update") + @Operation(summary = "Set isUpdated on all nikshay_beneficiary_sync_status rows of a beneficiary") + public ResponseEntity> updateSyncStatus(@RequestBody NikshaySyncStatusUpdateDTO request) { + logger.info("updateSyncStatus request: beneficiaryId={}, isUpdated={}", + request != null ? request.getBeneficiaryId() : null, + request != null ? request.getIsUpdated() : null); + try { + if (request == null || request.getBeneficiaryId() == null) { + return errorResponse(HttpStatus.BAD_REQUEST, "beneficiaryId is required"); + } + if (request.getIsUpdated() == null) { + return errorResponse(HttpStatus.BAD_REQUEST, "isUpdated is required"); + } + + int rows = nikshaySyncStatusService.updateIsUpdatedByBeneficiaryId( + request.getBeneficiaryId(), request.getIsUpdated()); + if (rows == 0) { + return errorResponse(HttpStatus.NOT_FOUND, + "No sync status record found for beneficiaryId " + request.getBeneficiaryId()); + } + + Map data = new LinkedHashMap<>(); + data.put("beneficiaryId", request.getBeneficiaryId()); + data.put("isUpdated", request.getIsUpdated()); + data.put("recordsUpdated", rows); + + Map body = new LinkedHashMap<>(); + body.put("status", "SUCCESS"); + body.put("message", "Update done for beneficiaryId " + request.getBeneficiaryId()); + body.put("data", data); + return ResponseEntity.ok(body); + } catch (Exception e) { + logger.error("Error in updateSyncStatus for beneficiaryId={}", + request != null ? request.getBeneficiaryId() : null, e); + return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, + "Error updating Nikshay sync status: " + e.getMessage()); + } + } + + private ResponseEntity> errorResponse(HttpStatus httpStatus, String message) { + if (httpStatus.is4xxClientError()) { + logger.warn("Nikshay sync status request rejected ({}): {}", httpStatus.value(), message); + } + Map body = new LinkedHashMap<>(); + body.put("status", "FAILED"); + body.put("message", message); + body.put("data", new ArrayList<>()); + return ResponseEntity.status(httpStatus).body(body); + } +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiaryPhaseStatus.java b/src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiaryPhaseStatus.java new file mode 100644 index 00000000..b5b8a48f --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiaryPhaseStatus.java @@ -0,0 +1,75 @@ +package com.iemr.mmu.data.nikshay; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.sql.Timestamp; + +@Entity +@Table(name = "nikshay_beneficiary_phase_status", schema = "db_iemr") +@Data +public class NikshayBeneficiaryPhaseStatus { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "ben_reg_id", nullable = false) + private Long benRegID; + + // BeneficiaryID from db_identity.m_beneficiaryregidmapping; null if not found + @Column(name = "beneficiary_id") + private Long beneficiaryID; + + // INDEX | CONTACT | ALL_NEGATIVE + @Column(name = "beneficiary_type", length = 20, nullable = false) + private String beneficiaryType; + + // For a CONTACT: BenRegId of their index patient + @Column(name = "index_ben_reg_id") + private Long indexBenRegID; + + // ENROLMENT | CONTACTS | XRAY_UPLOAD | NAAT_UPLOAD | TRANSFER_OUT + @Column(name = "phase", length = 20, nullable = false) + private String phase; + + // PENDING | IN_PROGRESS | DONE | FAILED | NOT_APPLICABLE + @Column(name = "status", length = 20, nullable = false) + private String status; + + @Column(name = "failure_reason", length = 50) + private String failureReason; + + @Column(name = "error_detail", length = 2000) + private String errorDetail; + + @Column(name = "attempts", nullable = false) + private Integer attempts = 0; + + @Column(name = "nikshay_id", length = 50) + private String nikshayId; + + @Column(name = "episode_id", length = 50) + private String episodeId; + + @Column(name = "last_attempt_at") + private Timestamp lastAttemptAt; + + @Column(name = "completed_at") + private Timestamp completedAt; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private Timestamp createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private Timestamp updatedAt; +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiarySyncStatus.java b/src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiarySyncStatus.java new file mode 100644 index 00000000..ff72cd23 --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/NikshayBeneficiarySyncStatus.java @@ -0,0 +1,72 @@ +package com.iemr.mmu.data.nikshay; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.sql.Timestamp; + +@Entity +@Table(name = "nikshay_beneficiary_sync_status", schema = "db_iemr") +@Data +public class NikshayBeneficiarySyncStatus { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "ben_reg_id", nullable = false) + private Long benRegID; + + // BeneficiaryID from db_identity.m_beneficiaryregidmapping; null if not found + @Column(name = "beneficiary_id") + private Long beneficiaryID; + + // INDEX | CONTACT | ALL_NEGATIVE + @Column(name = "beneficiary_type", length = 20, nullable = false) + private String beneficiaryType; + + // For a CONTACT: BenRegId of their index patient + @Column(name = "index_ben_reg_id") + private Long indexBenRegID; + + // ENROLMENT | CONTACTS | XRAY_UPLOAD | NAAT_UPLOAD | TRANSFER_OUT + @Column(name = "phase", length = 20, nullable = false) + private String phase; + + // PENDING | IN_PROGRESS | DONE | FAILED | NOT_APPLICABLE + @Column(name = "status", length = 20, nullable = false) + private String status; + + @Column(name = "failure_reason", length = 50) + private String failureReason; + + @Column(name = "error_detail", length = 2000) + private String errorDetail; + + @Column(name = "nikshay_id", length = 50) + private String nikshayId; + + @Column(name = "episode_id", length = 50) + private String episodeId; + + @Column(name = "last_attempt_at") + private Timestamp lastAttemptAt; + + @Column(name = "isUpdated", nullable = false) + private Boolean isUpdated = false; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private Timestamp createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private Timestamp updatedAt; +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryDTO.java b/src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryDTO.java new file mode 100644 index 00000000..037cd37f --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryDTO.java @@ -0,0 +1,27 @@ +package com.iemr.mmu.data.nikshay; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NikshayMissingBeneficiaryDTO { + + private Long beneficiaryId; + + @JsonProperty("isUpdated") + private Boolean isUpdated; + + private List reason; + + private String name; + + private Integer villageID; + + private String villageName; +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryRow.java b/src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryRow.java new file mode 100644 index 00000000..345c8e4f --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/NikshayMissingBeneficiaryRow.java @@ -0,0 +1,15 @@ +package com.iemr.mmu.data.nikshay; + +// One row of NikshayBeneficiarySyncStatusRepo.findMissingBeneficiaries; column aliases match the getters +public interface NikshayMissingBeneficiaryRow { + + Number getBenRegID(); + + Number getBeneficiaryID(); + + String getName(); + + Number getVillageID(); + + String getVillageName(); +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/NikshaySyncStatusUpdateDTO.java b/src/main/java/com/iemr/mmu/data/nikshay/NikshaySyncStatusUpdateDTO.java new file mode 100644 index 00000000..4ae0ce0a --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/NikshaySyncStatusUpdateDTO.java @@ -0,0 +1,13 @@ +package com.iemr.mmu.data.nikshay; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class NikshaySyncStatusUpdateDTO { + + private Long beneficiaryId; + + @JsonProperty("isUpdated") + private Boolean isUpdated; +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java new file mode 100644 index 00000000..4d203467 --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java @@ -0,0 +1,583 @@ +package com.iemr.mmu.data.nikshay; + +import com.google.gson.annotations.Expose; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import lombok.Data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; + +@Entity +@Table(name = "i_beneficiarydetails_rmnch",schema = "db_identity") +@Data +public class RMNCHBeneficiaryDetailsRmnch { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Expose + @Column(name = "beneficiaryDetails_RmnchId", insertable = false, updatable = false) + private Long beneficiaryDetails_RmnchId; + + @Expose + @Column(name = "BeneficiaryRegID") + private Long BenRegId; + + @Expose + @Column(name = "aadhaNo") + private String aadhaNo; + + @Expose + @Column(name = "aadha_no") + private String aadha_no; + + @Expose + @Column(name = "aadha_noId") + private Integer aadha_noId; + + @Expose + @Column(name = "age") + private Integer age; + + @Expose + @Column(name = "ageAtMarriage") + private Integer ageAtMarriage; + + @Expose + @Column(name = "age_unit") + private String age_unit; + + @Expose + @Column(name = "age_unitId") + private Integer age_unitId; + + @Expose + @Column(name = "childRegisteredAWCID") + private Long childRegisteredAWCID; + + @Expose + @Column(name = "childRegisteredSchoolID") + private Integer childRegisteredSchoolID; + + @Expose + @Column(name = "dateofdelivey") + private Timestamp dateofdelivey; + + @Expose + @Column(name = "expectedDateofDelivery") + private Timestamp expectedDateOfDelivery; + + @Expose + @Column(name = "facilitySectionID") + private Integer facilitySectionID; + + @Expose + @Column(name = "guidelineId") + private String guidelineId; + + @Expose + @Column(name = "houseoldId") + private Long houseoldId; + + @Expose + @Column(name = "lastDeliveryConductedID") + private Integer lastDeliveryConductedID; + + @Expose + @Column(name = "lastMenstrualPeriod") + private String lastMenstrualPeriod; + + @Expose + @Column(name = "latitude") + private BigDecimal latitude; + + @Expose + @Column(name = "lengthofMenstrualCycleId") + private Integer lengthofMenstrualCycleId; + + @Expose + @Column(name = "literacyId") + private Integer literacyId; + + @Expose + @Column(name = "longitude") + private BigDecimal longitude; + + @Expose + @Column(name = "digipin") + private String digipin; + + @Expose + @Column(name = "gpsTimestamp") + private Timestamp gpsTimestamp; + + @Expose + @Column(name = "isGpsUnavailable", nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0") + private Boolean isGpsUnavailable = false; + + @Expose + @Column(name = "gpsUnavailableReason") + private String gpsUnavailableReason; + + @Expose + @Column(name = "menstrualBFDId") + private Integer menstrualBFDId; + + @Expose + @Column(name = "menstrualProblemId") + private Integer menstrualProblemId; + + @Expose + @Column(name = "menstrualStatusId") + private Integer menstrualStatusId; + + @Expose + @Column(name = "mobileOthers") + private String mobileOthers; + + @Expose + @Column(name = "mobilenoofRelation") + private String mobilenoofRelation; + + @Expose + @Column(name = "mobilenoofRelationId") + private Integer mobilenoofRelationId; + + @Expose + @Column(name = "motherName") + private String motherName; + + @Expose + @Column(name = "ncd_priority") + private Integer ncd_priority; + + @Expose + @Column(name = "need_opcareId") + private Integer need_opcareId; + + @Expose + @Column(name = "previousLiveBirth") + private String previousLiveBirth; + + @Expose + @Column(name = "rchid") + private String rchid; + + @Expose + @Column(name = "registrationDate") + private Timestamp registrationDate; + + @Expose + @Column(name = "registrationType") + private String registrationType; + + @Expose + @Column(name = "regularityofMenstrualCycleId") + private Integer regularityofMenstrualCycleId; + + @Expose + @Column(name = "religionOthers") + private String religionOthers; + + @Expose + @Column(name = "reproductiveStatus") + private String reproductiveStatus; + + @Expose + @Column(name = "reproductiveStatusId") + private Integer reproductiveStatusId; + + @Expose + @Column(name = "serverUpdatedStatus") + private Integer serverUpdatedStatus; + + @Expose + @Column(name = "typeofSchoolID") + private Integer typeofSchoolID; + + @Expose + @Column(name = "whoConductedDeliveryID") + private Integer whoConductedDeliveryID; + + @Expose + @Column(name = "Deleted") + private Boolean deleted; + + @Expose + @Column(name = "Processed") + private String Processed = "N"; + + @Expose + @Column(name = "CreatedBy") + private String createdBy; + + @Expose + @Column(name = "CreatedDate") + private Timestamp createdDate; + + @Expose + @Column(name = "Reserved") + private Boolean reserved; + + @Expose + @Column(name = "ReservedFor") + private String reservedFor; + + @Expose + @Column(name = "ReservedOn") + private String reservedOn; + + @Expose + @Column(name = "ReservedById") + private Integer reservedById; + + @Expose + @Column(name = "ModifiedBy") + private String updatedBy; + + @Expose + @Column(name = "LastModDate") + private Timestamp updatedDate; + + @Expose + @Column(name = "VanSerialNo") + private Long id; + + @Expose + @Column(name = "VanID") + private Integer VanID; + + @Expose + @Column(name = "VehicalNo") + private String vehicalNo; + + @Expose + @Column(name = "ParkingPlaceID") + private Integer parkingPlaceID; + + @Expose + @Column(name = "SyncedBy") + private String syncedBy; + + @Expose + @Column(name = "SyncedDate") + private Timestamp syncedDate; + + @Expose + @Column(name = "ProviderServiceMapID") + private Integer ProviderServiceMapID; + + @Expose + @Column(name = "deviceId") + private Integer deviceId; + + @Expose + @Column(name = "beneficiaryId") + private Long benficieryid; + + // new fields 30-06-2021 + @Expose + @Column(name = "RelatedBeneficiaryIds") + private String relatedBeneficiaryIdsDB; + @Expose + @Column(name = "HRPStatus") + private Boolean hrpStatus; + @Expose + @Column(name = "ImmunizationStatus") + private Boolean immunizationStatus; + @Expose + @Column(name = "NishchayPregnancyStatus") + private String nishchayPregnancyStatus; + @Expose + @Column(name = "NishchayPregnancyStatusPosition") + private Integer nishchayPregnancyStatusPosition; + @Expose + @Column(name = "NishchayDeliveryStatus") + private String nishchayDeliveryStatus; + @Expose + @Column(name = "NishchayDeliveryStatusPosition") + private Integer nishchayDeliveryStatusPosition; + + @Expose + @Column(name = "FamilyHeadRelation") + private String familyHeadRelation; + @Expose + @Column(name = "FamilyHeadRelationPosition") + private Integer familyHeadRelationPosition; + + @Expose + @Column(name = "MenstrualStatus") + private String menstrualStatus; + @Expose + @Column(name = "ComplicationsOthers") + private String complicationsOthers; + + @Expose + @Transient + private Long[] relatedBeneficiaryIds; + + @Expose + @Transient + private String literacyStatus; + + @Expose + @Transient + private String branchName; + + // location + @Expose + @Transient + private Integer countryId; + @Expose + @Transient + private String countryName; + @Expose + @Transient + private Integer stateId; + @Expose + @Transient + private String stateName; +// @Expose +// @Transient +// private Integer districtId; +// @Expose +// @Transient +// private String districtName; + @Expose + @Transient + private Integer blockId; + @Expose + @Transient + private String blockName; + @Expose + @Transient + private Integer villageId; + @Expose + @Transient + private String villageName; + + + @Expose + private Boolean isDeath; + + @Expose + private String isDeathValue; + + @Expose + private String dateOfDeath; + + @Expose + private String timeOfDeath; + + @Expose + private String reasonOfDeath; + + @Expose + private Integer reasonOfDeathId; + + @Expose + private String placeOfDeath; + + @Expose + private Integer placeOfDeathId; + + @Expose + private String otherPlaceOfDeath; + + @Expose + private Boolean isSpouseAdded; + + + @Expose + private Boolean isChildrenAdded; + + @Expose + private Boolean isMarried; + + @Expose + private Boolean doYouHavechildren; + + @Expose + private Integer noOfchildren; + + @Expose + private Integer noofAlivechildren; + + @Expose + private Boolean isDeactivate; + + @Expose + @Transient + private Integer servicePointID; + @Expose + @Transient + private String servicePointName; + + @Expose + @Transient + private Integer zoneID; + @Expose + @Transient + private String zoneName; + + @Expose + @Transient + private String addressLine1; + @Expose + @Transient + private String addressLine2; + @Expose + @Transient + private String addressLine3; + @Expose + @Transient + private String pinCode; + + // ---------------------------------------------- + + // Extra data from platform + @Expose + @Transient + private String bankAccount; + @Expose + @Transient + private String community; + @Expose + @Transient + private Integer communityId; + @Expose + @Transient + private String contact_number; + @Expose + @Transient + private Integer currSubDistrictId; + @Expose + @Transient + private Integer districtid; + @Expose + @Transient + private String districtname; + @Expose + @Transient + private Timestamp dob; + @Expose + @Transient + private String fatherName; + @Expose + @Transient + private String firstName; + @Expose + @Transient + private String gender; + @Expose + @Transient + private Integer genderId; + @Expose + @Transient + private String ifscCode; + @Expose + @Transient + private String lastName; + @Expose + @Transient + private String maritalstatus; + @Expose + @Transient + private Integer maritalstatusId; + @Expose + @Transient + private Timestamp marriageDate; + @Expose + @Transient + private String nameOfBank; + @Expose + @Transient + private String religion; + @Expose + @Transient + private BigInteger religionID; + @Expose + @Transient + private String spousename; + @Expose + @Transient + private String user_image; + + // 08102020 + @Expose + @Transient + private String ageFull; + + // 07-07-2021 + @Expose + @Column(name = "WhoConductedDelivery") + private String whoConductedDelivery; + @Expose + @Column(name = "LastDeliveryConducted") + private String lastDeliveryConducted; + @Expose + @Column(name = "FacilitySelection") + private String facilitySelection; + @Expose + @Column(name = "DeliveryDate") + private String deliveryDate; + @Expose + @Column(name = "ChildRegisteredSchool") + private String childRegisteredSchool; + @Expose + @Column(name = "TypeOfSchool") + private String typeOfSchool; +// 13-04-2022 + @Expose + @Column(name = "dateMarriage") + private Timestamp dateMarriage; + @Expose + @Column(name = "nayiPahalDeliveryStatus") + private String nayiPahalDeliveryStatus; + @Expose + @Column(name = "nayiPahalDeliveryStatusPosition") + private Integer nayiPahalDeliveryStatusPosition; + @Expose + @Column(name = "noOfDaysForDelivery") + private Integer noOfDaysForDelivery; + + @Expose + @Transient + private String occupation; + @Expose + @Transient + private String economicStatus; + @Expose + @Transient + private Integer economicStatusId; + @Expose + @Transient + private String residentialArea; + @Expose + @Transient + private Integer residentialAreaId; + + @Expose + private String placeOfCurrentLiving; + @Expose + private String otherPlaceOfCurrentLiving; + @Expose + private String institutionName; + + public String getOccupation() { return occupation; } + public void setOccupation(String occupation) { this.occupation = occupation; } + public String getEconomicStatus() { return economicStatus; } + public void setEconomicStatus(String economicStatus) { this.economicStatus = economicStatus; } + public Integer getEconomicStatusId() { return economicStatusId; } + public void setEconomicStatusId(Integer economicStatusId) { this.economicStatusId = economicStatusId; } + public String getResidentialArea() { return residentialArea; } + public void setResidentialArea(String residentialArea) { this.residentialArea = residentialArea; } + public Integer getResidentialAreaId() { return residentialAreaId; } + public void setResidentialAreaId(Integer residentialAreaId) { this.residentialAreaId = residentialAreaId; } + +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/RMNCHMBeneficiarydetail.java b/src/main/java/com/iemr/mmu/data/nikshay/RMNCHMBeneficiarydetail.java new file mode 100644 index 00000000..44fb6f80 --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/RMNCHMBeneficiarydetail.java @@ -0,0 +1,231 @@ +package com.iemr.mmu.data.nikshay; + +import com.google.gson.annotations.Expose; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import lombok.Data; + +import java.math.BigInteger; +import java.sql.Timestamp; + +@Entity +@Table(name = "i_beneficiarydetails", schema = "db_identity") +@Data +public class RMNCHMBeneficiarydetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(unique = true, nullable = false) + private BigInteger beneficiaryDetailsId; + + private Integer areaId; + + @Column(name = "beneficiaryRegID") + private BigInteger BenRegId; + + @Column(length = 45) + private String community; + + @Column(nullable = false, length = 50, updatable = false) + private String createdBy; + + @Column(name = "CreatedDate") + private Timestamp createdDate; + + @Column(name = "dob") + private Timestamp dob; + + private Boolean deleted = false; + + @Column(length = 45) + private String education; + + private Boolean emergencyRegistration; + + @Column(length = 50) + private String fatherName; + + @Column(length = 20) + private String firstName; + + @Column(length = 50) + private String motherName; + + private String gender; + + @Column(name = "incomeStatusId") + private Integer incomeStatusId; + + @Column(length = 45) + private String incomeStatus; + + private Timestamp lastModDate; + + @Column(length = 20) + private String lastName; + + @Column(length = 20) + private String maritalstatus; + + @Column(name = "marriageDate") + private Timestamp marriageDate; + + @Transient + private Integer ageAtMarriage; + + @Column(name = "MiddleName", length = 20) + private String middleName; + + @Column(length = 50) + private String modifiedBy; + + private Integer occupationId; + + @Column(length = 45) + private String occupation; + + private Integer phcId; + + @Column(length = 30) + private String placeOfWork; + + private String literacyStatus; + + private Integer preferredLanguageId; + + @Column(length = 45) + private String preferredLanguage; + + @Column(nullable = false, length = 4) + private String processed = "N"; + + @Column(length = 45) + private BigInteger religionID; + + @Column(length = 45) + private String religion; + + @Column(length = 300) + private String remarks; + + private Boolean reserved; + + private Integer reservedById; + + @Column(length = 45) + private String reservedFor; + + private Timestamp reservedOn; + + private BigInteger servicePointId; + + @Column(length = 45) + private String sourceOfInfo; + + @Column(length = 50) + private String spousename; + + @Column(length = 10) + private String status; + + @Column(length = 10) + private String title; + + private Integer zoneId; + + @Column(name = "GenderID") + private Integer genderId; + + @Column(name = "MaritalStatusID") + private Integer maritalstatusId; + + @Column(name = "TitleID") + private Integer titleId; + + @Column(name = "CommunityID") + private Integer communityId; + + @Column(name = "EducationID") + private Integer educationId; + + @Column(name = "SexualOrientationID") + private Integer sexualOrientationID; + + @Column(name = "SexualOrientationType") + private String sexualOrientationType; + + @Column(name = "HIVStatus") + private Integer isHIVPositive; + + @Column(name = "HealthCareWorkerId") + private Integer healthCareWorkerId; + + private String healthCareWorker; + + @Expose + @Column(name = "vanID", updatable = false) + private Integer VanID; + @Expose + @Column(name = "parkingPlaceID", updatable = false) + private Integer parkingPlaceID; + + @Expose + @Column(name = "VanSerialNo", updatable = false) + private BigInteger id; + + @Expose + @Column(name = "SyncedBy") + private String SyncedBy; + + @Expose + @Column(name = "SyncedDate") + private Timestamp SyncedDate; + + @Transient + private BigInteger benficieryid; + + @Expose + @Transient + private Integer ProviderServiceMapID; + + @Expose + @Column(name = "familyid") + private String familyId; + + @Column(name = "ExtraFields") + private String otherFields; + + @Expose + @Column(name = "economicStatus") + private String economicStatus; + + @Expose + @Column(name = "economicStatusId") + private Integer economicStatusId; + + @Expose + @Column(name = "residentialArea") + private String residentialArea; + + @Expose + @Column(name = "residentialAreaId") + private Integer residentialAreaId; + + @Expose + @Column(name = "address") + private String address; + + @Expose + private String placeOfCurrentLiving; + + @Expose + private String otherPlaceOfCurrentLiving; + + @Expose + private String institutionName; + +} diff --git a/src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java b/src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java new file mode 100644 index 00000000..9a2d92fd --- /dev/null +++ b/src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java @@ -0,0 +1,28 @@ +package com.iemr.mmu.repo.nikshay; + + +import com.iemr.mmu.data.nikshay.RMNCHBeneficiaryDetailsRmnch; +import com.iemr.mmu.data.nikshay.RMNCHMBeneficiarydetail; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.math.BigInteger; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@Repository +public interface BeneficiaryRepo extends JpaRepository { + + Optional findById(Long benID); + + + @Query("SELECT r FROM RMNCHBeneficiaryDetailsRmnch r WHERE r.BenRegId IN :benRegIDs") + List findRmnchByBenRegIDIn(@Param("benRegIDs") Collection benRegIDs); + + @Query("SELECT d FROM RMNCHMBeneficiarydetail d WHERE d.BenRegId IN :benRegIDs") + List findDetailsByBenRegIDIn(@Param("benRegIDs") Collection benRegIDs); + +} diff --git a/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java new file mode 100644 index 00000000..f94e5f6e --- /dev/null +++ b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java @@ -0,0 +1,34 @@ +package com.iemr.mmu.repo.nikshay; + +import com.iemr.mmu.data.nikshay.NikshayBeneficiaryPhaseStatus; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@Repository +public interface NikshayBeneficiaryPhaseStatusRepo extends JpaRepository { + + Optional findByBenRegIDAndPhase(Long benRegID, String phase); + + List findByBenRegID(Long benRegID); + + List findByStatus(String status); + + List findByPhaseAndStatus(String phase, String status); + + List findByStatusAndFailureReason(String status, String failureReason); + + List findByIndexBenRegID(Long indexBenRegID); + + List findByBenRegIDIn(Collection benRegIDs); + + // One entry per beneficiary that has at least one phase in the given status + @Query("SELECT DISTINCT p.benRegID FROM NikshayBeneficiaryPhaseStatus p WHERE p.status = :status " + + "ORDER BY p.benRegID DESC") + List findDistinctBenRegIDsByStatus(@Param("status") String status); +} diff --git a/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java new file mode 100644 index 00000000..b33944e3 --- /dev/null +++ b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java @@ -0,0 +1,97 @@ +package com.iemr.mmu.repo.nikshay; + +import com.iemr.mmu.data.nikshay.NikshayBeneficiarySyncStatus; +import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryRow; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +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.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.sql.Timestamp; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@Repository +public interface NikshayBeneficiarySyncStatusRepo extends JpaRepository { + + Optional findByBenRegIDAndPhase(Long benRegID, String phase); + + + List findByBenRegIDIn(Collection benRegIDs); + + // updated_at is refreshed by the column's ON UPDATE CURRENT_TIMESTAMP + @Transactional + @Modifying + @Query("UPDATE NikshayBeneficiarySyncStatus s SET s.isUpdated = :isUpdated WHERE s.beneficiaryID = :beneficiaryID") + int updateIsUpdatedByBeneficiaryID(@Param("beneficiaryID") Long beneficiaryID, @Param("isUpdated") Boolean isUpdated); + + + // Purges rows already marked updated whose last change is older than the cutoff + @Transactional + @Modifying + @Query("DELETE FROM NikshayBeneficiarySyncStatus s WHERE s.isUpdated = true " + + "AND COALESCE(s.updatedAt, s.createdAt) < :cutoff") + int deleteUpdatedOlderThan(@Param("cutoff") Timestamp cutoff); + + // Shared parts of findMissingBeneficiaries and its count query (annotation values must be constants) + String MISSING_BENEFICIARIES_FROM = + "FROM (SELECT DISTINCT ben_reg_id FROM db_iemr.nikshay_beneficiary_sync_status) s " + + "JOIN db_identity.i_beneficiarymapping bm ON bm.BenRegId = s.ben_reg_id AND bm.Deleted = 0 " + + "LEFT JOIN db_identity.m_beneficiaryregidmapping rg ON rg.BenRegId = bm.BenRegId " + + "LEFT JOIN db_identity.i_beneficiarydetails d ON d.BeneficiaryDetailsId = bm.BenDetailsId " + + "AND (d.Deleted IS NULL OR d.Deleted = 0) " + + "LEFT JOIN db_identity.i_beneficiaryaddress a ON a.BenAddressID = bm.BenAddressId " + + "WHERE (:psmId IS NULL " + + "OR EXISTS (SELECT 1 FROM db_identity.i_beneficiarydetails_rmnch r " + + "WHERE r.BeneficiaryRegID = bm.BenRegId AND r.ProviderServiceMapID = :psmId " + + "AND (r.Deleted IS NULL OR r.Deleted = 0)) " + + "OR EXISTS (SELECT 1 FROM db_identity.i_beneficiaryservicemapping sm " + + "WHERE sm.BenMapID = bm.BenMapId AND sm.ProviderServiceMapId = :psmId AND sm.Deleted = 0))"; + + // the RMNCH household's village, else the beneficiary's own address + String MISSING_BENEFICIARIES_VILLAGE_ID = + "COALESCE((SELECT h.villageid FROM db_identity.i_beneficiarydetails_rmnch r " + + "JOIN db_identity.i_householddetails h ON h.houseoldId = r.houseoldId " + + "AND (h.Deleted IS NULL OR h.Deleted = 0) " + + "WHERE r.BeneficiaryRegID = bm.BenRegId AND (r.Deleted IS NULL OR r.Deleted = 0) LIMIT 1)" + + ", a.CurrVillageId, a.PermVillageId)"; + + String MISSING_BENEFICIARIES_VILLAGE_NAME = + "COALESCE((SELECT h.village FROM db_identity.i_beneficiarydetails_rmnch r " + + "JOIN db_identity.i_householddetails h ON h.houseoldId = r.houseoldId " + + "AND (h.Deleted IS NULL OR h.Deleted = 0) " + + "WHERE r.BeneficiaryRegID = bm.BenRegId AND (r.Deleted IS NULL OR r.Deleted = 0) LIMIT 1)" + + ", a.CurrVillage, a.PermVillage)"; + + // Beneficiaries that have a row in nikshay_beneficiary_sync_status, filtered by provider service map + // and/or village (a null filter is ignored), one entry per beneficiary even with a row per phase. + // Same AMRIT identity rules as DataSyncRepository.createMissingWorklistRows: + // - beneficiary: i_beneficiarymapping (BenRegId) -> i_beneficiarydetails (name), + // m_beneficiaryregidmapping (BeneficiaryID) + // - village: the RMNCH household's village, else the beneficiary's own address (Curr, then Perm) + // - provider service map: the RMNCH record's, or any i_beneficiaryservicemapping row + // (registrar-flow beneficiaries have no RMNCH/household record) + // Driven from the sync-status table, which is far smaller than the identity tables. + // Native SQL because the identity tables live in db_identity, outside this persistence unit. + @Query(nativeQuery = true, + value = "SELECT x.benRegID, x.beneficiaryID, x.name, x.villageID, x.villageName FROM (" + + "SELECT bm.BenRegId AS benRegID, rg.BeneficiaryID AS beneficiaryID, " + + "NULLIF(TRIM(CONCAT_WS(' ', d.FirstName, d.LastName)), '') AS name, " + + MISSING_BENEFICIARIES_VILLAGE_ID + " AS villageID, " + + MISSING_BENEFICIARIES_VILLAGE_NAME + " AS villageName " + + MISSING_BENEFICIARIES_FROM + ") x " + + "WHERE (:villageId IS NULL OR x.villageID = :villageId) " + + "ORDER BY x.benRegID DESC", + countQuery = "SELECT COUNT(*) FROM (" + + "SELECT " + MISSING_BENEFICIARIES_VILLAGE_ID + " AS villageID " + + MISSING_BENEFICIARIES_FROM + ") x " + + "WHERE (:villageId IS NULL OR x.villageID = :villageId)") + Page findMissingBeneficiaries(@Param("psmId") Integer psmId, + @Param("villageId") Integer villageId, + Pageable pageable); +} diff --git a/src/main/java/com/iemr/mmu/service/nikshay/NikshaySyncStatusService.java b/src/main/java/com/iemr/mmu/service/nikshay/NikshaySyncStatusService.java new file mode 100644 index 00000000..4d7e223b --- /dev/null +++ b/src/main/java/com/iemr/mmu/service/nikshay/NikshaySyncStatusService.java @@ -0,0 +1,18 @@ +package com.iemr.mmu.service.nikshay; + +import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryDTO; +import org.springframework.data.domain.Page; + +import java.util.Map; + +public interface NikshaySyncStatusService { + + Map syncFailedPhaseStatuses(String serverAuthorization) throws Exception; + + Page getMissingBeneficiaries(Integer providerServiceMapID, Integer villageID, + int pageNo, int pageSize); + + Map getFailedBeneficiaries(); + + int updateIsUpdatedByBeneficiaryId(Long beneficiaryId, Boolean isUpdated); +} diff --git a/src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java b/src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java new file mode 100644 index 00000000..054db024 --- /dev/null +++ b/src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java @@ -0,0 +1,363 @@ +package com.iemr.mmu.service.nikshay.impl; + + +import com.iemr.mmu.data.nikshay.NikshayBeneficiaryPhaseStatus; +import com.iemr.mmu.data.nikshay.NikshayBeneficiarySyncStatus; +import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryDTO; +import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryRow; +import com.iemr.mmu.data.nikshay.RMNCHMBeneficiarydetail; +import com.iemr.mmu.repo.nikshay.BeneficiaryRepo; +import com.iemr.mmu.repo.nikshay.NikshayBeneficiaryPhaseStatusRepo; +import com.iemr.mmu.repo.nikshay.NikshayBeneficiarySyncStatusRepo; +import com.iemr.mmu.service.nikshay.NikshaySyncStatusService; +import com.iemr.mmu.utils.NikshayConstants; +import jakarta.annotation.PostConstruct; +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.math.BigInteger; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class NikshaySyncStatusServiceImpl implements NikshaySyncStatusService { + + private static final String STATUS_FAILED = "FAILED"; + + private static final String STATUS_NOT_STARTED = "NOT_STARTED"; + + private static final String PHASE_ENROLMENT = "ENROLMENT"; + + // Every beneficiary is reported with all phases, in this order + private static final List PHASES = + List.of(PHASE_ENROLMENT, "NAAT_UPLOAD", "XRAY_UPLOAD", "CONTACTS", "TRANSFER_OUT"); + + private final Logger logger = LoggerFactory.getLogger(NikshaySyncStatusServiceImpl.class); + + @Autowired + private NikshayBeneficiaryPhaseStatusRepo phaseStatusRepo; + + @Autowired + private NikshayBeneficiarySyncStatusRepo syncStatusRepo; + + @Autowired + private BeneficiaryRepo beneficiaryRepo; + + @Value("${nikshay.http.connectTimeoutMs:15000}") + private int connectTimeoutMs; + + // the failedBeneficiaries API is not paged, so allow it time on a large backlog + @Value("${nikshay.http.readTimeoutMs:120000}") + private int readTimeoutMs; + + private RestTemplate restTemplate; + + @PostConstruct + public void init() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(connectTimeoutMs); + factory.setReadTimeout(readTimeoutMs); + restTemplate = new RestTemplate(factory); + } + + // Fetches the FAILED phases from the failedBeneficiaries API (deployed on another server), then purges + // rows already marked updated more than a month ago and stores one row per FAILED phase. + // (ben_reg_id, phase) is unique, so a surviving row is refreshed in place and keeps its isUpdated + // flag; a new row starts with isUpdated = false. + @Override + @Transactional + public Map syncFailedPhaseStatuses(String serverAuthorization) throws Exception { + // fetched before touching the table, so an unreachable server leaves it as it was + List failed = fetchFailedPhases(serverAuthorization); + + Timestamp cutoff = Timestamp.valueOf(LocalDateTime.now().minusMonths(1)); + int deleted = syncStatusRepo.deleteUpdatedOlderThan(cutoff); + logger.info("Nikshay sync: deleted {} rows with isUpdated = true last changed before {}", deleted, cutoff); + + int inserted = 0; + int updated = 0; + List toSave = new ArrayList<>(); + for (NikshayBeneficiarySyncStatus remote : failed) { + NikshayBeneficiarySyncStatus sync = syncStatusRepo + .findByBenRegIDAndPhase(remote.getBenRegID(), remote.getPhase()) + .orElse(null); + if (sync == null) { + sync = new NikshayBeneficiarySyncStatus(); + inserted++; + } else { + updated++; + } + sync.setBenRegID(remote.getBenRegID()); + sync.setBeneficiaryID(remote.getBeneficiaryID()); + sync.setBeneficiaryType(remote.getBeneficiaryType()); + sync.setIndexBenRegID(remote.getIndexBenRegID()); + sync.setPhase(remote.getPhase()); + sync.setStatus(remote.getStatus()); + sync.setFailureReason(remote.getFailureReason()); + sync.setErrorDetail(remote.getErrorDetail()); + sync.setNikshayId(remote.getNikshayId()); + sync.setEpisodeId(remote.getEpisodeId()); + sync.setLastAttemptAt(remote.getLastAttemptAt()); + toSave.add(sync); + } + syncStatusRepo.saveAll(toSave); + + logger.info("Nikshay sync: {} FAILED phase rows received, {} inserted, {} updated", + failed.size(), inserted, updated); + + Map result = new HashMap<>(); + result.put("failedRecordsFound", failed.size()); + result.put("deleted", deleted); + result.put("inserted", inserted); + result.put("updated", updated); + return result; + } + + @Override + @Transactional(readOnly = true) + public Page getMissingBeneficiaries(Integer providerServiceMapID, Integer villageID, + int pageNo, int pageSize) { + // beneficiary, name and village come from the AMRIT identity tables (see the repo query) + Page benPage = syncStatusRepo.findMissingBeneficiaries( + providerServiceMapID, villageID, PageRequest.of(pageNo, pageSize)); + List benRegIDs = benPage.getContent().stream() + .map(row -> row.getBenRegID().longValue()) + .collect(Collectors.toList()); + logger.info("Nikshay missing beneficiaries: psmId={}, villageId={}, page={}, size={} -> {} on page, {} total", + providerServiceMapID, villageID, pageNo, pageSize, benRegIDs.size(), benPage.getTotalElements()); + if (benRegIDs.isEmpty()) { + return new PageImpl<>(new ArrayList<>(), benPage.getPageable(), benPage.getTotalElements()); + } + + Map> syncRowsByBen = new HashMap<>(); + for (NikshayBeneficiarySyncStatus row : syncStatusRepo.findByBenRegIDIn(benRegIDs)) { + syncRowsByBen.computeIfAbsent(row.getBenRegID(), k -> new ArrayList<>()).add(row); + } + + List data = new ArrayList<>(); + for (NikshayMissingBeneficiaryRow ben : benPage.getContent()) { + Long benRegID = ben.getBenRegID().longValue(); + List rows = syncRowsByBen.getOrDefault(benRegID, new ArrayList<>()); + + Long beneficiaryID = null; + boolean isUpdated = !rows.isEmpty(); + Set reasons = new LinkedHashSet<>(); + for (NikshayBeneficiarySyncStatus row : rows) { + if (beneficiaryID == null) beneficiaryID = row.getBeneficiaryID(); + // a beneficiary counts as updated only once every failed phase has been updated + if (!Boolean.TRUE.equals(row.getIsUpdated())) isUpdated = false; + reasons.add(row.getFailureReason() != null + ? row.getFailureReason() + : row.getPhase() + " sync failed"); + } + if (beneficiaryID == null && ben.getBeneficiaryID() != null) { + beneficiaryID = ben.getBeneficiaryID().longValue(); + } + + data.add(new NikshayMissingBeneficiaryDTO(beneficiaryID, isUpdated, new ArrayList<>(reasons), + ben.getName(), + ben.getVillageID() != null ? ben.getVillageID().intValue() : null, + ben.getVillageName())); + } + return new PageImpl<>(data, benPage.getPageable(), benPage.getTotalElements()); + } + + // Every beneficiary with at least one FAILED phase, with only its FAILED phases. Not paged. + @Override + @Transactional(readOnly = true) + public Map getFailedBeneficiaries() { + List benRegIDs = phaseStatusRepo.findDistinctBenRegIDsByStatus(STATUS_FAILED); + logger.info("Nikshay failed beneficiaries: {} beneficiaries with a FAILED phase", benRegIDs.size()); + + List> beneficiaries = new ArrayList<>(); + if (!benRegIDs.isEmpty()) { + Map> phasesByBen = new HashMap<>(); + Set nameBenRegIDs = new LinkedHashSet<>(benRegIDs); + for (NikshayBeneficiaryPhaseStatus row : phaseStatusRepo.findByBenRegIDIn(benRegIDs)) { + phasesByBen.computeIfAbsent(row.getBenRegID(), k -> new HashMap<>()).put(row.getPhase(), row); + if (row.getIndexBenRegID() != null) nameBenRegIDs.add(row.getIndexBenRegID()); + } + + // Names of the beneficiaries and of their index patients, from i_beneficiarydetails + List nameBenRegIDsBig = nameBenRegIDs.stream() + .map(BigInteger::valueOf) + .collect(Collectors.toList()); + Map nameByBen = new HashMap<>(); + for (RMNCHMBeneficiarydetail detail : beneficiaryRepo.findDetailsByBenRegIDIn(nameBenRegIDsBig)) { + if (detail.getBenRegId() == null || Boolean.TRUE.equals(detail.getDeleted())) continue; + nameByBen.putIfAbsent(detail.getBenRegId().longValue(), buildName(detail)); + } + + for (Long benRegID : benRegIDs) { + Map rows = phasesByBen.getOrDefault(benRegID, new HashMap<>()); + // beneficiary-level fields are the same on every phase row; prefer the ENROLMENT row + NikshayBeneficiaryPhaseStatus first = rows.containsKey(PHASE_ENROLMENT) + ? rows.get(PHASE_ENROLMENT) + : rows.values().stream().findFirst().orElse(null); + Long indexBenRegID = first != null ? first.getIndexBenRegID() : null; + NikshayBeneficiaryPhaseStatus enrolment = rows.get(PHASE_ENROLMENT); + + Map phases = new LinkedHashMap<>(); + for (String phase : PHASES) { + NikshayBeneficiaryPhaseStatus row = rows.get(phase); + if (row != null && STATUS_FAILED.equals(row.getStatus())) { + phases.put(phase, toPhaseMap(row)); + } + } + + Map ben = new LinkedHashMap<>(); + ben.put("benRegId", benRegID); + ben.put("beneficiaryId", first != null ? first.getBeneficiaryID() : null); + ben.put("name", nameByBen.get(benRegID)); + ben.put("beneficiaryType", first != null ? first.getBeneficiaryType() : null); + ben.put("indexBenRegId", indexBenRegID); + ben.put("indexName", indexBenRegID != null ? nameByBen.get(indexBenRegID) : null); + ben.put("nikshayId", enrolment != null ? enrolment.getNikshayId() : null); + ben.put("phases", phases); + beneficiaries.add(ben); + } + } + + Map result = new LinkedHashMap<>(); + result.put("total", beneficiaries.size()); + result.put("beneficiaries", beneficiaries); + return result; + } + + // Updates every phase row of the beneficiary. Rows synced before beneficiary_id could be resolved + // hold it as null, so when nothing matches on beneficiary_id, fall back to the BenRegId. + @Override + public int updateIsUpdatedByBeneficiaryId(Long beneficiaryId, Boolean isUpdated) { + int rows = syncStatusRepo.updateIsUpdatedByBeneficiaryID(beneficiaryId, isUpdated); +// if (rows == 0) { +// Long benRegID = beneficiaryRepo.getBenRegIdFromBenId(beneficiaryId); +// if (benRegID != null) { +// logger.info("Nikshay sync status: no rows on beneficiaryId={}, retrying with benRegID={}", +// beneficiaryId, benRegID); +// rows = syncStatusRepo.updateIsUpdatedByBenRegID(benRegID, isUpdated); +// } +// } + logger.info("Nikshay sync status: set isUpdated={} for beneficiaryId={} on {} rows", + isUpdated, beneficiaryId, rows); + return rows; + } + + private Map toPhaseMap(NikshayBeneficiaryPhaseStatus row) { + Map phase = new LinkedHashMap<>(); + phase.put("status", row != null ? row.getStatus() : STATUS_NOT_STARTED); + phase.put("failureReason", row != null ? row.getFailureReason() : null); + phase.put("errorDetail", row != null ? row.getErrorDetail() : null); + phase.put("attempts", row != null && row.getAttempts() != null ? row.getAttempts() : 0); + phase.put("nikshayId", row != null ? row.getNikshayId() : null); + phase.put("episodeId", row != null ? row.getEpisodeId() : null); + phase.put("lastAttemptAt", row != null ? row.getLastAttemptAt() : null); + phase.put("completedAt", row != null ? row.getCompletedAt() : null); + return phase; + } + + // Calls the failedBeneficiaries API and flattens its response to one row per FAILED phase + private List fetchFailedPhases(String serverAuthorization) throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + // the JWT filter on the other server lets a java/ client through on its Authorization header; + // the controller itself requires ServerAuthorization + headers.set(HttpHeaders.AUTHORIZATION, serverAuthorization); + headers.set("ServerAuthorization", serverAuthorization); + + ResponseEntity response; + try { + response = restTemplate.exchange(NikshayConstants.FAILED_BENEFICIARIES_URL, HttpMethod.GET, new HttpEntity<>(headers), + String.class); + } catch (HttpStatusCodeException e) { + throw new Exception("failedBeneficiaries API returned " + e.getStatusCode().value() + ": " + + e.getResponseBodyAsString()); + } + if (!response.hasBody()) + throw new Exception("Empty response from failedBeneficiaries API"); + + JSONObject body = new JSONObject(response.getBody()); + if (!body.has("beneficiaries")) + // a rejected session comes back from HttpInterceptor as HTTP 200 with an OutputResponse body + throw new Exception("failedBeneficiaries API failed: " + + body.optString("errorMessage", body.optString("message"))); + + List rows = new ArrayList<>(); + JSONArray beneficiaries = body.getJSONArray("beneficiaries"); + for (int i = 0; i < beneficiaries.length(); i++) { + JSONObject ben = beneficiaries.getJSONObject(i); + JSONObject phases = ben.optJSONObject("phases"); + if (phases == null) continue; + for (String phaseName : phases.keySet()) { + JSONObject phase = phases.getJSONObject(phaseName); + if (!STATUS_FAILED.equals(phase.optString("status"))) continue; + + NikshayBeneficiarySyncStatus row = new NikshayBeneficiarySyncStatus(); + row.setBenRegID(ben.getLong("benRegId")); + row.setBeneficiaryID(optLong(ben, "beneficiaryId")); + row.setBeneficiaryType(ben.optString("beneficiaryType", null)); + row.setIndexBenRegID(optLong(ben, "indexBenRegId")); + row.setPhase(phaseName); + row.setStatus(STATUS_FAILED); + row.setFailureReason(optString(phase, "failureReason")); + row.setErrorDetail(optString(phase, "errorDetail")); + // the phase's own nikshayId, else the beneficiary's (from its ENROLMENT phase) + String nikshayId = optString(phase, "nikshayId"); + row.setNikshayId(nikshayId != null ? nikshayId : optString(ben, "nikshayId")); + row.setEpisodeId(optString(phase, "episodeId")); + row.setLastAttemptAt(optTimestamp(phase, "lastAttemptAt")); + rows.add(row); + } + } + logger.info("Nikshay sync: failedBeneficiaries API returned {} beneficiaries, {} FAILED phases", + beneficiaries.length(), rows.size()); + return rows; + } + + private static String optString(JSONObject obj, String key) { + return obj.isNull(key) ? null : obj.optString(key, null); + } + + private static Long optLong(JSONObject obj, String key) { + return obj.isNull(key) ? null : obj.getLong(key); + } + + // Spring serialises a Timestamp as an ISO string or as epoch millis, depending on its Jackson settings + private static Timestamp optTimestamp(JSONObject obj, String key) { + if (obj.isNull(key)) return null; + Object value = obj.get(key); + if (value instanceof Number) return new Timestamp(((Number) value).longValue()); + return Timestamp.from(OffsetDateTime.parse(value.toString()).toInstant()); + } + + private String buildName(RMNCHMBeneficiarydetail detail) { + String name = ((detail.getFirstName() != null ? detail.getFirstName() : "") + " " + + (detail.getLastName() != null ? detail.getLastName() : "")).trim(); + return name.isEmpty() ? null : name; + } +} diff --git a/src/main/java/com/iemr/mmu/utils/NikshayConstants.java b/src/main/java/com/iemr/mmu/utils/NikshayConstants.java new file mode 100644 index 00000000..3d477579 --- /dev/null +++ b/src/main/java/com/iemr/mmu/utils/NikshayConstants.java @@ -0,0 +1,16 @@ +package com.iemr.mmu.utils; + +public final class NikshayConstants { + + private NikshayConstants() { + } + + // Server that hosts the failedBeneficiaries API (it reads nikshay_beneficiary_phase_status) + public static final String BASE_URL = "http://10.208.122.38:8080/mmu-api"; + + public static final String NIKSHAY_PATH = "/nikshay"; + + public static final String FAILED_BENEFICIARIES_PATH = "/syncStatus/failedBeneficiaries"; + + public static final String FAILED_BENEFICIARIES_URL = BASE_URL + NIKSHAY_PATH + FAILED_BENEFICIARIES_PATH; +} From 5fbf278a5b93ba02e1720e531e41597e9236e0b5 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Date: Mon, 2 Nov 2026 13:30:33 +0530 Subject: [PATCH 2/2] changes for the code review suggestions --- .../nikshay/NikshaySyncStatusController.java | 31 +- .../mmu/data/nikshay/BeneficiaryNameRow.java | 11 + ...iarydetail.java => Beneficiarydetail.java} | 2 +- .../nikshay/RMNCHBeneficiaryDetailsRmnch.java | 583 ------------------ .../mmu/repo/nikshay/BeneficiaryRepo.java | 16 +- .../NikshayBeneficiaryPhaseStatusRepo.java | 21 +- .../NikshayBeneficiarySyncStatusRepo.java | 78 +-- .../impl/NikshaySyncStatusServiceImpl.java | 35 +- 8 files changed, 97 insertions(+), 680 deletions(-) create mode 100644 src/main/java/com/iemr/mmu/data/nikshay/BeneficiaryNameRow.java rename src/main/java/com/iemr/mmu/data/nikshay/{RMNCHMBeneficiarydetail.java => Beneficiarydetail.java} (99%) delete mode 100644 src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java diff --git a/src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java b/src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java index 593d2e28..20209c7b 100644 --- a/src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java +++ b/src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java @@ -5,8 +5,11 @@ import com.iemr.mmu.data.nikshay.NikshaySyncStatusUpdateDTO; import com.iemr.mmu.service.nikshay.NikshaySyncStatusService; import com.iemr.mmu.utils.NikshayConstants; +import com.iemr.mmu.utils.exception.IEMRException; import com.iemr.mmu.utils.response.OutputResponse; +import com.iemr.mmu.utils.validator.Validator; import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +41,9 @@ public class NikshaySyncStatusController { @Autowired private NikshaySyncStatusService nikshaySyncStatusService; + @Autowired + private Validator validator; + @PostMapping("/syncStatus/syncFailed") @Operation(summary = "Copy all FAILED rows from nikshay_beneficiary_phase_status into nikshay_beneficiary_sync_status") public String syncFailedPhaseStatuses(@RequestHeader(value = "Authorization") String authorization, @@ -54,7 +60,8 @@ public String syncFailedPhaseStatuses(@RequestHeader(value = "Authorization") St } @GetMapping("/syncStatus/missingBeneficiaries") - @Operation(summary = "Get beneficiaries whose Ni-kshay sync failed, filtered by provider service map, village or both (paged)") + @Operation(summary = "Get beneficiaries whose Ni-kshay sync failed (paged); all of them with no filter, " + + "else only those matching providerServiceMapID and/or villageID") public ResponseEntity> getMissingBeneficiaries( @RequestParam(value = "providerServiceMapID", required = false) Integer providerServiceMapID, @RequestParam(value = "villageID", required = false) Integer villageID, @@ -63,9 +70,6 @@ public ResponseEntity> getMissingBeneficiaries( logger.info("getMissingBeneficiaries request: providerServiceMapID={}, villageID={}, pageNo={}, pageSize={}", providerServiceMapID, villageID, pageNo, pageSize); try { - if (providerServiceMapID == null && villageID == null) { - return errorResponse(HttpStatus.BAD_REQUEST, "providerServiceMapID or villageID is required"); - } if (pageNo < 0) { return errorResponse(HttpStatus.BAD_REQUEST, "pageNo must be 0 or greater"); } @@ -101,7 +105,24 @@ public ResponseEntity> getMissingBeneficiaries( @GetMapping(NikshayConstants.FAILED_BENEFICIARIES_PATH) @Operation(summary = "Get every beneficiary with a FAILED phase in nikshay_beneficiary_phase_status, with all its phases (not paged)") - public ResponseEntity> getFailedBeneficiaries(@RequestHeader(value = "ServerAuthorization") String serverAuthorization) { + public ResponseEntity> getFailedBeneficiaries( + @RequestHeader(value = "ServerAuthorization") String serverAuthorization, HttpServletRequest request) { + // The JWT filter lets java/ and okhttp clients through without a token, and HttpInterceptor skips + // an empty Authorization header, so the header's presence proves nothing. ServerAuthorization must + // be a live login session key (the one /syncFailed sends after the data-sync server login). + if (serverAuthorization == null || serverAuthorization.trim().isEmpty()) { + return errorResponse(HttpStatus.UNAUTHORIZED, "ServerAuthorization is required"); + } + String remoteAddress = request.getHeader("X-FORWARDED-FOR"); + if (remoteAddress == null || remoteAddress.trim().isEmpty()) { + remoteAddress = request.getRemoteAddr(); + } + try { + validator.checkKeyExists(serverAuthorization, remoteAddress); + } catch (IEMRException e) { + return errorResponse(HttpStatus.UNAUTHORIZED, "Invalid or expired ServerAuthorization"); + } + try { return ResponseEntity.ok(nikshaySyncStatusService.getFailedBeneficiaries()); } catch (Exception e) { diff --git a/src/main/java/com/iemr/mmu/data/nikshay/BeneficiaryNameRow.java b/src/main/java/com/iemr/mmu/data/nikshay/BeneficiaryNameRow.java new file mode 100644 index 00000000..3efa2059 --- /dev/null +++ b/src/main/java/com/iemr/mmu/data/nikshay/BeneficiaryNameRow.java @@ -0,0 +1,11 @@ +package com.iemr.mmu.data.nikshay; + +// One row of NikshayBeneficiarySyncStatusRepo.findBeneficiaryNames; column aliases match the getters +public interface BeneficiaryNameRow { + + Number getBenRegID(); + + String getFirstName(); + + String getLastName(); +} diff --git a/src/main/java/com/iemr/mmu/data/nikshay/RMNCHMBeneficiarydetail.java b/src/main/java/com/iemr/mmu/data/nikshay/Beneficiarydetail.java similarity index 99% rename from src/main/java/com/iemr/mmu/data/nikshay/RMNCHMBeneficiarydetail.java rename to src/main/java/com/iemr/mmu/data/nikshay/Beneficiarydetail.java index 44fb6f80..2aded129 100644 --- a/src/main/java/com/iemr/mmu/data/nikshay/RMNCHMBeneficiarydetail.java +++ b/src/main/java/com/iemr/mmu/data/nikshay/Beneficiarydetail.java @@ -16,7 +16,7 @@ @Entity @Table(name = "i_beneficiarydetails", schema = "db_identity") @Data -public class RMNCHMBeneficiarydetail { +public class Beneficiarydetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(unique = true, nullable = false) diff --git a/src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java deleted file mode 100644 index 4d203467..00000000 --- a/src/main/java/com/iemr/mmu/data/nikshay/RMNCHBeneficiaryDetailsRmnch.java +++ /dev/null @@ -1,583 +0,0 @@ -package com.iemr.mmu.data.nikshay; - -import com.google.gson.annotations.Expose; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.Transient; -import lombok.Data; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.Timestamp; - -@Entity -@Table(name = "i_beneficiarydetails_rmnch",schema = "db_identity") -@Data -public class RMNCHBeneficiaryDetailsRmnch { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Expose - @Column(name = "beneficiaryDetails_RmnchId", insertable = false, updatable = false) - private Long beneficiaryDetails_RmnchId; - - @Expose - @Column(name = "BeneficiaryRegID") - private Long BenRegId; - - @Expose - @Column(name = "aadhaNo") - private String aadhaNo; - - @Expose - @Column(name = "aadha_no") - private String aadha_no; - - @Expose - @Column(name = "aadha_noId") - private Integer aadha_noId; - - @Expose - @Column(name = "age") - private Integer age; - - @Expose - @Column(name = "ageAtMarriage") - private Integer ageAtMarriage; - - @Expose - @Column(name = "age_unit") - private String age_unit; - - @Expose - @Column(name = "age_unitId") - private Integer age_unitId; - - @Expose - @Column(name = "childRegisteredAWCID") - private Long childRegisteredAWCID; - - @Expose - @Column(name = "childRegisteredSchoolID") - private Integer childRegisteredSchoolID; - - @Expose - @Column(name = "dateofdelivey") - private Timestamp dateofdelivey; - - @Expose - @Column(name = "expectedDateofDelivery") - private Timestamp expectedDateOfDelivery; - - @Expose - @Column(name = "facilitySectionID") - private Integer facilitySectionID; - - @Expose - @Column(name = "guidelineId") - private String guidelineId; - - @Expose - @Column(name = "houseoldId") - private Long houseoldId; - - @Expose - @Column(name = "lastDeliveryConductedID") - private Integer lastDeliveryConductedID; - - @Expose - @Column(name = "lastMenstrualPeriod") - private String lastMenstrualPeriod; - - @Expose - @Column(name = "latitude") - private BigDecimal latitude; - - @Expose - @Column(name = "lengthofMenstrualCycleId") - private Integer lengthofMenstrualCycleId; - - @Expose - @Column(name = "literacyId") - private Integer literacyId; - - @Expose - @Column(name = "longitude") - private BigDecimal longitude; - - @Expose - @Column(name = "digipin") - private String digipin; - - @Expose - @Column(name = "gpsTimestamp") - private Timestamp gpsTimestamp; - - @Expose - @Column(name = "isGpsUnavailable", nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0") - private Boolean isGpsUnavailable = false; - - @Expose - @Column(name = "gpsUnavailableReason") - private String gpsUnavailableReason; - - @Expose - @Column(name = "menstrualBFDId") - private Integer menstrualBFDId; - - @Expose - @Column(name = "menstrualProblemId") - private Integer menstrualProblemId; - - @Expose - @Column(name = "menstrualStatusId") - private Integer menstrualStatusId; - - @Expose - @Column(name = "mobileOthers") - private String mobileOthers; - - @Expose - @Column(name = "mobilenoofRelation") - private String mobilenoofRelation; - - @Expose - @Column(name = "mobilenoofRelationId") - private Integer mobilenoofRelationId; - - @Expose - @Column(name = "motherName") - private String motherName; - - @Expose - @Column(name = "ncd_priority") - private Integer ncd_priority; - - @Expose - @Column(name = "need_opcareId") - private Integer need_opcareId; - - @Expose - @Column(name = "previousLiveBirth") - private String previousLiveBirth; - - @Expose - @Column(name = "rchid") - private String rchid; - - @Expose - @Column(name = "registrationDate") - private Timestamp registrationDate; - - @Expose - @Column(name = "registrationType") - private String registrationType; - - @Expose - @Column(name = "regularityofMenstrualCycleId") - private Integer regularityofMenstrualCycleId; - - @Expose - @Column(name = "religionOthers") - private String religionOthers; - - @Expose - @Column(name = "reproductiveStatus") - private String reproductiveStatus; - - @Expose - @Column(name = "reproductiveStatusId") - private Integer reproductiveStatusId; - - @Expose - @Column(name = "serverUpdatedStatus") - private Integer serverUpdatedStatus; - - @Expose - @Column(name = "typeofSchoolID") - private Integer typeofSchoolID; - - @Expose - @Column(name = "whoConductedDeliveryID") - private Integer whoConductedDeliveryID; - - @Expose - @Column(name = "Deleted") - private Boolean deleted; - - @Expose - @Column(name = "Processed") - private String Processed = "N"; - - @Expose - @Column(name = "CreatedBy") - private String createdBy; - - @Expose - @Column(name = "CreatedDate") - private Timestamp createdDate; - - @Expose - @Column(name = "Reserved") - private Boolean reserved; - - @Expose - @Column(name = "ReservedFor") - private String reservedFor; - - @Expose - @Column(name = "ReservedOn") - private String reservedOn; - - @Expose - @Column(name = "ReservedById") - private Integer reservedById; - - @Expose - @Column(name = "ModifiedBy") - private String updatedBy; - - @Expose - @Column(name = "LastModDate") - private Timestamp updatedDate; - - @Expose - @Column(name = "VanSerialNo") - private Long id; - - @Expose - @Column(name = "VanID") - private Integer VanID; - - @Expose - @Column(name = "VehicalNo") - private String vehicalNo; - - @Expose - @Column(name = "ParkingPlaceID") - private Integer parkingPlaceID; - - @Expose - @Column(name = "SyncedBy") - private String syncedBy; - - @Expose - @Column(name = "SyncedDate") - private Timestamp syncedDate; - - @Expose - @Column(name = "ProviderServiceMapID") - private Integer ProviderServiceMapID; - - @Expose - @Column(name = "deviceId") - private Integer deviceId; - - @Expose - @Column(name = "beneficiaryId") - private Long benficieryid; - - // new fields 30-06-2021 - @Expose - @Column(name = "RelatedBeneficiaryIds") - private String relatedBeneficiaryIdsDB; - @Expose - @Column(name = "HRPStatus") - private Boolean hrpStatus; - @Expose - @Column(name = "ImmunizationStatus") - private Boolean immunizationStatus; - @Expose - @Column(name = "NishchayPregnancyStatus") - private String nishchayPregnancyStatus; - @Expose - @Column(name = "NishchayPregnancyStatusPosition") - private Integer nishchayPregnancyStatusPosition; - @Expose - @Column(name = "NishchayDeliveryStatus") - private String nishchayDeliveryStatus; - @Expose - @Column(name = "NishchayDeliveryStatusPosition") - private Integer nishchayDeliveryStatusPosition; - - @Expose - @Column(name = "FamilyHeadRelation") - private String familyHeadRelation; - @Expose - @Column(name = "FamilyHeadRelationPosition") - private Integer familyHeadRelationPosition; - - @Expose - @Column(name = "MenstrualStatus") - private String menstrualStatus; - @Expose - @Column(name = "ComplicationsOthers") - private String complicationsOthers; - - @Expose - @Transient - private Long[] relatedBeneficiaryIds; - - @Expose - @Transient - private String literacyStatus; - - @Expose - @Transient - private String branchName; - - // location - @Expose - @Transient - private Integer countryId; - @Expose - @Transient - private String countryName; - @Expose - @Transient - private Integer stateId; - @Expose - @Transient - private String stateName; -// @Expose -// @Transient -// private Integer districtId; -// @Expose -// @Transient -// private String districtName; - @Expose - @Transient - private Integer blockId; - @Expose - @Transient - private String blockName; - @Expose - @Transient - private Integer villageId; - @Expose - @Transient - private String villageName; - - - @Expose - private Boolean isDeath; - - @Expose - private String isDeathValue; - - @Expose - private String dateOfDeath; - - @Expose - private String timeOfDeath; - - @Expose - private String reasonOfDeath; - - @Expose - private Integer reasonOfDeathId; - - @Expose - private String placeOfDeath; - - @Expose - private Integer placeOfDeathId; - - @Expose - private String otherPlaceOfDeath; - - @Expose - private Boolean isSpouseAdded; - - - @Expose - private Boolean isChildrenAdded; - - @Expose - private Boolean isMarried; - - @Expose - private Boolean doYouHavechildren; - - @Expose - private Integer noOfchildren; - - @Expose - private Integer noofAlivechildren; - - @Expose - private Boolean isDeactivate; - - @Expose - @Transient - private Integer servicePointID; - @Expose - @Transient - private String servicePointName; - - @Expose - @Transient - private Integer zoneID; - @Expose - @Transient - private String zoneName; - - @Expose - @Transient - private String addressLine1; - @Expose - @Transient - private String addressLine2; - @Expose - @Transient - private String addressLine3; - @Expose - @Transient - private String pinCode; - - // ---------------------------------------------- - - // Extra data from platform - @Expose - @Transient - private String bankAccount; - @Expose - @Transient - private String community; - @Expose - @Transient - private Integer communityId; - @Expose - @Transient - private String contact_number; - @Expose - @Transient - private Integer currSubDistrictId; - @Expose - @Transient - private Integer districtid; - @Expose - @Transient - private String districtname; - @Expose - @Transient - private Timestamp dob; - @Expose - @Transient - private String fatherName; - @Expose - @Transient - private String firstName; - @Expose - @Transient - private String gender; - @Expose - @Transient - private Integer genderId; - @Expose - @Transient - private String ifscCode; - @Expose - @Transient - private String lastName; - @Expose - @Transient - private String maritalstatus; - @Expose - @Transient - private Integer maritalstatusId; - @Expose - @Transient - private Timestamp marriageDate; - @Expose - @Transient - private String nameOfBank; - @Expose - @Transient - private String religion; - @Expose - @Transient - private BigInteger religionID; - @Expose - @Transient - private String spousename; - @Expose - @Transient - private String user_image; - - // 08102020 - @Expose - @Transient - private String ageFull; - - // 07-07-2021 - @Expose - @Column(name = "WhoConductedDelivery") - private String whoConductedDelivery; - @Expose - @Column(name = "LastDeliveryConducted") - private String lastDeliveryConducted; - @Expose - @Column(name = "FacilitySelection") - private String facilitySelection; - @Expose - @Column(name = "DeliveryDate") - private String deliveryDate; - @Expose - @Column(name = "ChildRegisteredSchool") - private String childRegisteredSchool; - @Expose - @Column(name = "TypeOfSchool") - private String typeOfSchool; -// 13-04-2022 - @Expose - @Column(name = "dateMarriage") - private Timestamp dateMarriage; - @Expose - @Column(name = "nayiPahalDeliveryStatus") - private String nayiPahalDeliveryStatus; - @Expose - @Column(name = "nayiPahalDeliveryStatusPosition") - private Integer nayiPahalDeliveryStatusPosition; - @Expose - @Column(name = "noOfDaysForDelivery") - private Integer noOfDaysForDelivery; - - @Expose - @Transient - private String occupation; - @Expose - @Transient - private String economicStatus; - @Expose - @Transient - private Integer economicStatusId; - @Expose - @Transient - private String residentialArea; - @Expose - @Transient - private Integer residentialAreaId; - - @Expose - private String placeOfCurrentLiving; - @Expose - private String otherPlaceOfCurrentLiving; - @Expose - private String institutionName; - - public String getOccupation() { return occupation; } - public void setOccupation(String occupation) { this.occupation = occupation; } - public String getEconomicStatus() { return economicStatus; } - public void setEconomicStatus(String economicStatus) { this.economicStatus = economicStatus; } - public Integer getEconomicStatusId() { return economicStatusId; } - public void setEconomicStatusId(Integer economicStatusId) { this.economicStatusId = economicStatusId; } - public String getResidentialArea() { return residentialArea; } - public void setResidentialArea(String residentialArea) { this.residentialArea = residentialArea; } - public Integer getResidentialAreaId() { return residentialAreaId; } - public void setResidentialAreaId(Integer residentialAreaId) { this.residentialAreaId = residentialAreaId; } - -} diff --git a/src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java b/src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java index 9a2d92fd..24bf80d8 100644 --- a/src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java +++ b/src/main/java/com/iemr/mmu/repo/nikshay/BeneficiaryRepo.java @@ -1,8 +1,7 @@ package com.iemr.mmu.repo.nikshay; -import com.iemr.mmu.data.nikshay.RMNCHBeneficiaryDetailsRmnch; -import com.iemr.mmu.data.nikshay.RMNCHMBeneficiarydetail; +import com.iemr.mmu.data.nikshay.Beneficiarydetail; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -11,18 +10,11 @@ import java.math.BigInteger; import java.util.Collection; import java.util.List; -import java.util.Optional; @Repository -public interface BeneficiaryRepo extends JpaRepository { +public interface BeneficiaryRepo extends JpaRepository { - Optional findById(Long benID); - - - @Query("SELECT r FROM RMNCHBeneficiaryDetailsRmnch r WHERE r.BenRegId IN :benRegIDs") - List findRmnchByBenRegIDIn(@Param("benRegIDs") Collection benRegIDs); - - @Query("SELECT d FROM RMNCHMBeneficiarydetail d WHERE d.BenRegId IN :benRegIDs") - List findDetailsByBenRegIDIn(@Param("benRegIDs") Collection benRegIDs); + @Query("SELECT d FROM Beneficiarydetail d WHERE d.BenRegId IN :benRegIDs") + List findDetailsByBenRegIDIn(@Param("benRegIDs") Collection benRegIDs); } diff --git a/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java index f94e5f6e..f2689842 100644 --- a/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java +++ b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiaryPhaseStatusRepo.java @@ -8,27 +8,16 @@ import java.util.Collection; import java.util.List; -import java.util.Optional; @Repository public interface NikshayBeneficiaryPhaseStatusRepo extends JpaRepository { - Optional findByBenRegIDAndPhase(Long benRegID, String phase); - - List findByBenRegID(Long benRegID); - - List findByStatus(String status); - - List findByPhaseAndStatus(String phase, String status); - - List findByStatusAndFailureReason(String status, String failureReason); - - List findByIndexBenRegID(Long indexBenRegID); - List findByBenRegIDIn(Collection benRegIDs); - // One entry per beneficiary that has at least one phase in the given status - @Query("SELECT DISTINCT p.benRegID FROM NikshayBeneficiaryPhaseStatus p WHERE p.status = :status " + + // Every phase row (any status) of every beneficiary that has at least one phase in the given status, + // newest beneficiary first + @Query("SELECT p FROM NikshayBeneficiaryPhaseStatus p WHERE p.benRegID IN " + + "(SELECT f.benRegID FROM NikshayBeneficiaryPhaseStatus f WHERE f.status = :status) " + "ORDER BY p.benRegID DESC") - List findDistinctBenRegIDsByStatus(@Param("status") String status); + List findAllPhasesOfBeneficiariesWithStatus(@Param("status") String status); } diff --git a/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java index b33944e3..236ceacf 100644 --- a/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java +++ b/src/main/java/com/iemr/mmu/repo/nikshay/NikshayBeneficiarySyncStatusRepo.java @@ -1,5 +1,6 @@ package com.iemr.mmu.repo.nikshay; +import com.iemr.mmu.data.nikshay.BeneficiaryNameRow; import com.iemr.mmu.data.nikshay.NikshayBeneficiarySyncStatus; import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryRow; import org.springframework.data.domain.Page; @@ -38,60 +39,49 @@ public interface NikshayBeneficiarySyncStatusRepo extends JpaRepository i_beneficiarydetails (name), - // m_beneficiaryregidmapping (BeneficiaryID) - // - village: the RMNCH household's village, else the beneficiary's own address (Curr, then Perm) - // - provider service map: the RMNCH record's, or any i_beneficiaryservicemapping row - // (registrar-flow beneficiaries have no RMNCH/household record) - // Driven from the sync-status table, which is far smaller than the identity tables. + // Beneficiaries that have a row in nikshay_beneficiary_sync_status, one entry per beneficiary even + // with a row per phase. No filter returns all of them; a filter keeps only the matching ones. // Native SQL because the identity tables live in db_identity, outside this persistence unit. @Query(nativeQuery = true, - value = "SELECT x.benRegID, x.beneficiaryID, x.name, x.villageID, x.villageName FROM (" + - "SELECT bm.BenRegId AS benRegID, rg.BeneficiaryID AS beneficiaryID, " + + value = "SELECT s.ben_reg_id AS benRegID, rg.BeneficiaryID AS beneficiaryID, " + "NULLIF(TRIM(CONCAT_WS(' ', d.FirstName, d.LastName)), '') AS name, " + - MISSING_BENEFICIARIES_VILLAGE_ID + " AS villageID, " + - MISSING_BENEFICIARIES_VILLAGE_NAME + " AS villageName " + - MISSING_BENEFICIARIES_FROM + ") x " + - "WHERE (:villageId IS NULL OR x.villageID = :villageId) " + - "ORDER BY x.benRegID DESC", - countQuery = "SELECT COUNT(*) FROM (" + - "SELECT " + MISSING_BENEFICIARIES_VILLAGE_ID + " AS villageID " + - MISSING_BENEFICIARIES_FROM + ") x " + - "WHERE (:villageId IS NULL OR x.villageID = :villageId)") + "a.CurrVillageId AS villageID, " + + "a.CurrVillage AS villageName " + + MISSING_BENEFICIARIES_FROM + + "LEFT JOIN db_identity.m_beneficiaryregidmapping rg ON rg.BenRegId = s.ben_reg_id " + + MISSING_BENEFICIARIES_WHERE + + " ORDER BY s.ben_reg_id DESC", + countQuery = "SELECT COUNT(*) " + MISSING_BENEFICIARIES_FROM + MISSING_BENEFICIARIES_WHERE) Page findMissingBeneficiaries(@Param("psmId") Integer psmId, @Param("villageId") Integer villageId, Pageable pageable); + + // Names of the given beneficiaries from i_beneficiarydetails, deleted records excluded. + // Native projection of only the needed columns: i_beneficiarydetails is not mapped as an entity, + // so a column added, renamed or retyped elsewhere in that table cannot break this lookup. + @Query(nativeQuery = true, + value = "SELECT d.BeneficiaryRegID AS benRegID, d.FirstName AS firstName, d.LastName AS lastName " + + "FROM db_identity.i_beneficiarydetails d " + + "WHERE d.BeneficiaryRegID IN (:benRegIDs) AND (d.Deleted IS NULL OR d.Deleted = 0)") + List findBeneficiaryNames(@Param("benRegIDs") Collection benRegIDs); } diff --git a/src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java b/src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java index 054db024..e4e155f7 100644 --- a/src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java +++ b/src/main/java/com/iemr/mmu/service/nikshay/impl/NikshaySyncStatusServiceImpl.java @@ -5,8 +5,7 @@ import com.iemr.mmu.data.nikshay.NikshayBeneficiarySyncStatus; import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryDTO; import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryRow; -import com.iemr.mmu.data.nikshay.RMNCHMBeneficiarydetail; -import com.iemr.mmu.repo.nikshay.BeneficiaryRepo; +import com.iemr.mmu.data.nikshay.BeneficiaryNameRow; import com.iemr.mmu.repo.nikshay.NikshayBeneficiaryPhaseStatusRepo; import com.iemr.mmu.repo.nikshay.NikshayBeneficiarySyncStatusRepo; import com.iemr.mmu.service.nikshay.NikshaySyncStatusService; @@ -32,7 +31,6 @@ import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; -import java.math.BigInteger; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.OffsetDateTime; @@ -66,9 +64,6 @@ public class NikshaySyncStatusServiceImpl implements NikshaySyncStatusService { @Autowired private NikshayBeneficiarySyncStatusRepo syncStatusRepo; - @Autowired - private BeneficiaryRepo beneficiaryRepo; - @Value("${nikshay.http.connectTimeoutMs:15000}") private int connectTimeoutMs; @@ -192,26 +187,28 @@ public Page getMissingBeneficiaries(Integer provid @Override @Transactional(readOnly = true) public Map getFailedBeneficiaries() { - List benRegIDs = phaseStatusRepo.findDistinctBenRegIDsByStatus(STATUS_FAILED); + // one query: all phases (the ENROLMENT row carries the nikshayId even when it did not fail), + // ordered newest beneficiary first; the LinkedHashMap keeps that order + Map> phasesByBen = new LinkedHashMap<>(); + Set indexBenRegIDs = new LinkedHashSet<>(); + for (NikshayBeneficiaryPhaseStatus row : phaseStatusRepo.findAllPhasesOfBeneficiariesWithStatus(STATUS_FAILED)) { + phasesByBen.computeIfAbsent(row.getBenRegID(), k -> new HashMap<>()).put(row.getPhase(), row); + if (row.getIndexBenRegID() != null) indexBenRegIDs.add(row.getIndexBenRegID()); + } + List benRegIDs = new ArrayList<>(phasesByBen.keySet()); logger.info("Nikshay failed beneficiaries: {} beneficiaries with a FAILED phase", benRegIDs.size()); List> beneficiaries = new ArrayList<>(); if (!benRegIDs.isEmpty()) { - Map> phasesByBen = new HashMap<>(); Set nameBenRegIDs = new LinkedHashSet<>(benRegIDs); - for (NikshayBeneficiaryPhaseStatus row : phaseStatusRepo.findByBenRegIDIn(benRegIDs)) { - phasesByBen.computeIfAbsent(row.getBenRegID(), k -> new HashMap<>()).put(row.getPhase(), row); - if (row.getIndexBenRegID() != null) nameBenRegIDs.add(row.getIndexBenRegID()); - } + nameBenRegIDs.addAll(indexBenRegIDs); // Names of the beneficiaries and of their index patients, from i_beneficiarydetails - List nameBenRegIDsBig = nameBenRegIDs.stream() - .map(BigInteger::valueOf) - .collect(Collectors.toList()); + // (deleted records are already excluded by the query) Map nameByBen = new HashMap<>(); - for (RMNCHMBeneficiarydetail detail : beneficiaryRepo.findDetailsByBenRegIDIn(nameBenRegIDsBig)) { - if (detail.getBenRegId() == null || Boolean.TRUE.equals(detail.getDeleted())) continue; - nameByBen.putIfAbsent(detail.getBenRegId().longValue(), buildName(detail)); + for (BeneficiaryNameRow detail : syncStatusRepo.findBeneficiaryNames(nameBenRegIDs)) { + if (detail.getBenRegID() == null) continue; + nameByBen.putIfAbsent(detail.getBenRegID().longValue(), buildName(detail)); } for (Long benRegID : benRegIDs) { @@ -355,7 +352,7 @@ private static Timestamp optTimestamp(JSONObject obj, String key) { return Timestamp.from(OffsetDateTime.parse(value.toString()).toInstant()); } - private String buildName(RMNCHMBeneficiarydetail detail) { + private String buildName(BeneficiaryNameRow detail) { String name = ((detail.getFirstName() != null ? detail.getFirstName() : "") + " " + (detail.getLastName() != null ? detail.getLastName() : "")).trim(); return name.isEmpty() ? null : name;