From db1c562f7970331f8be1f25c71a056e4ed40e27a Mon Sep 17 00:00:00 2001 From: Amoghavarsh <93114621+5Amogh@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:54:58 +0530 Subject: [PATCH 01/62] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dee19366..3cf9dfcc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.1.0 + 3.8.0 war From 6e0d0f705c88dd63dfebeb16235700ba4c5ac28c Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Fri, 22 May 2026 13:52:02 +0530 Subject: [PATCH 02/62] Merge Release 3.6.2 to Main (#163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Elasticsearch implementation for Beneficiary Search (#123) * fix: ES Implementation-mapping, indexing and async records * fix: add service for ES Search * fix: search implementation * fix: add additional fields as per the requirement * fix: comment extra fields * fix: rename the files, remove commented code * fix: update pom.xml * fix: revert advancesearch * fix: add properties * fix: coderabbit comments * fix: remove comment code * fix: accept numeric values for search * fix: update the env variable * fix: advance search functionality * fix: update the advance search ES functionality * fix: sync and fetch benid * fix: size limit issue * fix: improve response time * fix: updated the end point to advancedSearchES * fix: age issue while moving to nurse worklist (#130) * Optimize the Elasticsearch for better Response Time (#131) * fix: optimize the index and reduce the size * fix: align indent * fix: abha / health id issue * fix: sync optimization * fix: get abha created date * fix: state issue * fix: village issue * fix: add abha details * fix: abha fix and refresh index * fix: refresh index * fix: remove duplicate dependency * fix: fuzzy search, resume, refresh api's * fix: add middlename, maritalstatus * fix: remove refresh while bulk indexing * fix: add license * fix: remove the bean function to create the index automatically (#133) * Fix ES Issue in the Query (#137) * fix: ES Exception * fix: ES exception * Nd/vs/fix es (#138) * fix: ES Exception * fix: ES exception * fix: enable multi-word fuzzy search requirement (#139) * fix: enable multi-word fuzzy search requirement * Downgrade version from 3.6.2 to 3.6.1 * fix: multi-word search (#140) * Fix the column mismatch issue in beneficiary search (#142) * fix: column mismatch issue * fix: update marital status * add new column in rmnch table for death and child record * add new column in rmnch table for death and child record * Cherry-pick health and version API enhancements to release-3.6.1 (#145) * feat(health,version): add health and version endpoints * feat(health,version): add health and version endpoints without auth * fix(health): remove unused private methods * fix(health): fix exception issue * fix(health): redact error details for unauthenticated health checks * fix code quality issues and reduce cognitive complexity * feat(health): add MySQL health endpoint * refactor(health): simplify MySQL health check and remove sensitive details * fix(health): remove unused imports and variables * refactor(health): address nitpicks (configurable ES scheme, log noise, graceful shutdown, record) * fix(health): scope PROCESSLIST lock-wait check to application DB user * refactor(health): remove unused params and reuse response/error constants * fix(health): remove unused imports and methods * chore(health): clean up unused imports, params, and dead helpers * fix(health): avoid sharing JDBC connections across threads in advanced MySQL checks * refactor(health): reuse REDIS_COMPONENT constant and extract nested try block * fix(health): avoid blocking DB I/O under write lock and restore interrupt flag * fix(health): cancel in-flight futures on generic failure * feat(health,version): add index existance, read-only detection, canary write for elasticsearch health check * refactor(health): reduce cognitive complexity, remove dead throws, and clean code smells * Rebase 3.6.2 (#150) * fix: enable multi-word fuzzy search requirement (#139) * fix: enable multi-word fuzzy search requirement * Downgrade version from 3.6.2 to 3.6.1 * fix: multi-word search (#140) * Fix the column mismatch issue in beneficiary search (#142) * fix: column mismatch issue * fix: update marital status * add new column in rmnch table for death and child record * add new column in rmnch table for death and child record * Cherry-pick health and version API enhancements to release-3.6.1 (#145) * feat(health,version): add health and version endpoints * feat(health,version): add health and version endpoints without auth * fix(health): remove unused private methods * fix(health): fix exception issue * fix(health): redact error details for unauthenticated health checks * fix code quality issues and reduce cognitive complexity * feat(health): add MySQL health endpoint * refactor(health): simplify MySQL health check and remove sensitive details * fix(health): remove unused imports and variables * refactor(health): address nitpicks (configurable ES scheme, log noise, graceful shutdown, record) * fix(health): scope PROCESSLIST lock-wait check to application DB user * refactor(health): remove unused params and reuse response/error constants * fix(health): remove unused imports and methods * chore(health): clean up unused imports, params, and dead helpers * fix(health): avoid sharing JDBC connections across threads in advanced MySQL checks * refactor(health): reuse REDIS_COMPONENT constant and extract nested try block * fix(health): avoid blocking DB I/O under write lock and restore interrupt flag * fix(health): cancel in-flight futures on generic failure * feat(health,version): add index existance, read-only detection, canary write for elasticsearch health check * refactor(health): reduce cognitive complexity, remove dead throws, and clean code smells --------- Co-authored-by: Saurav Mishra Co-authored-by: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Co-authored-by: KOPPIREDDY DURGA PRASAD <144464542+DurgaPrasad-54@users.noreply.github.com> * feat(jwt): enhance jwt validation logging and public endpoint check (#151) * fix: pom version (#152) * Add the missing properties for 1097_Preprod (#153) * fix: add the missing properties * fix: update db url * docs: add CLAUDE.md for Claude Code guidance * fix: map sexualOrientationID during beneficiary update in 1097 convertIdentityEditDTOToMBeneficiarydetail() was missing sexualOrientationID and sexualOrientationType, so the field was never persisted on update. Co-Authored-By: Claude Sonnet 4.6 * Sexual orientation data not reflecting under DB record (#157) * fix: enable multi-word fuzzy search requirement (#139) * fix: enable multi-word fuzzy search requirement * Downgrade version from 3.6.2 to 3.6.1 * fix: multi-word search (#140) * Fix the column mismatch issue in beneficiary search (#142) * fix: column mismatch issue * fix: update marital status * add new column in rmnch table for death and child record * add new column in rmnch table for death and child record * Cherry-pick health and version API enhancements to release-3.6.1 (#145) * feat(health,version): add health and version endpoints * feat(health,version): add health and version endpoints without auth * fix(health): remove unused private methods * fix(health): fix exception issue * fix(health): redact error details for unauthenticated health checks * fix code quality issues and reduce cognitive complexity * feat(health): add MySQL health endpoint * refactor(health): simplify MySQL health check and remove sensitive details * fix(health): remove unused imports and variables * refactor(health): address nitpicks (configurable ES scheme, log noise, graceful shutdown, record) * fix(health): scope PROCESSLIST lock-wait check to application DB user * refactor(health): remove unused params and reuse response/error constants * fix(health): remove unused imports and methods * chore(health): clean up unused imports, params, and dead helpers * fix(health): avoid sharing JDBC connections across threads in advanced MySQL checks * refactor(health): reuse REDIS_COMPONENT constant and extract nested try block * fix(health): avoid blocking DB I/O under write lock and restore interrupt flag * fix(health): cancel in-flight futures on generic failure * feat(health,version): add index existance, read-only detection, canary write for elasticsearch health check * refactor(health): reduce cognitive complexity, remove dead throws, and clean code smells * fix: map sexualOrientationID during beneficiary update in 1097 convertIdentityEditDTOToMBeneficiarydetail() was missing sexualOrientationID and sexualOrientationType, so the field was never persisted on update. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Co-authored-by: Saurav Mishra Co-authored-by: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Co-authored-by: KOPPIREDDY DURGA PRASAD <144464542+DurgaPrasad-54@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 * feat: add getBenFamilyDetails API and fix searchFamily queries - Add POST /family/getBenFamilyDetails endpoint that returns full family details (master record + all members) for a given beneficiaryRegId - Fix searchFamily and searchFamilyWithFamilyId queries: replace noOfmembers > 0 guard with deleted=false filter so families with zero/null member count and soft-deleted records are handled correctly; also make villageId optional and use LIKE prefix match for familyName Co-Authored-By: Claude Sonnet 4.6 * fix: preserve existing occupation and education when incoming values are null During beneficiary edit, convertIdentityEditDTOToMBeneficiarydetail creates a fresh entity and overwrites all columns on save. Added null-guards for occupationId, occupation, educationId, and education so existing DB values are preserved when the incoming DTO omits them — consistent with the existing pattern for familyId and headOfFamily_Relation. Co-Authored-By: Claude Sonnet 4.6 * fix: replace in-memory queue with SELECT FOR UPDATE SKIP LOCKED for BenRegId allocation (#159) * aam-2126 Memeberlist is not displying properly * fix: aam-2313 phone number leading with zero - removed zero (#161) * Allowing numbers with zero is search by phone number. (#162) * fix: aam-2313 phone number leading with zero - removed zero * fix: aam-2313 serach by user phone number fix for number leading with zero --------- Co-authored-by: Saurav Mishra Co-authored-by: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Co-authored-by: KOPPIREDDY DURGA PRASAD <144464542+DurgaPrasad-54@users.noreply.github.com> Co-authored-by: SnehaRH Co-authored-by: Claude Sonnet 4.6 Co-authored-by: SnehaRH <77656297+snehar-nd@users.noreply.github.com> --- CLAUDE.md | 73 +++++++++++++++ pom.xml | 2 +- src/main/environment/1097_ci.properties | 1 - src/main/environment/1097_example.properties | 1 - .../FamilyTaggingController.java | 14 +++ .../common/identity/repo/BenContactRepo.java | 10 ++- .../identity/repo/BenRegIdMappingRepo.java | 9 ++ .../repo/familyTag/FamilyTagRepo.java | 8 +- .../service/BenRegIdClaimService.java | 89 +++++++++++++++++++ .../identity/service/IdentityService.java | 60 +++++++------ .../familyTagging/FamilyTagService.java | 2 + .../familyTagging/FamilyTagServiceImpl.java | 44 ++++++++- .../utils/JwtUserIdValidationFilter.java | 7 +- 13 files changed, 279 insertions(+), 41 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/main/java/com/iemr/common/identity/service/BenRegIdClaimService.java diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..ae3f45c7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +# CLAUDE.md - Identity-API + +## Project Overview + +Identity-API is the beneficiary identity management service for the AMRIT platform. It handles beneficiary creation, search, update, and deduplication across dual database profiles (`db_iemr` main and `db_identity`). It supports RMNCH (Reproductive, Maternal, Newborn, Child, and Adolescent Health) data management, family tagging, Elasticsearch-based beneficiary search, and health ID linkage. + +## Tech Stack + +- Java 17, Spring Boot 3.2.2, Maven +- Spring Data JPA / Hibernate, MySQL 8.0 +- Elasticsearch (Spring Data Elasticsearch for beneficiary search indexing) +- Redis for session management +- Lombok (1.18.36), MapStruct +- SpringDoc OpenAPI (Swagger UI at `/swagger-ui.html`) +- ECS logging (logback-ecs-encoder) +- JaCoCo for test coverage +- Packaged as WAR for Wildfly deployment + +## Build & Run + +```bash +mvn clean install -DENV_VAR=local # Build +mvn spring-boot:run -DENV_VAR=local # Run locally +mvn -B package --file pom.xml -P # Package WAR (dev, local, test, ci, uat) +mvn test # Run tests +``` + +Environment config: `src/main/resources/common_.properties` is copied to `application.properties` at build time. + +## Key Packages (`com.iemr.common.identity`) + +- **controller/** - REST endpoints: + - `IdentityController` - Core beneficiary CRUD (create, search, update, search by phone/ID/name) + - `IdentityESController` - Elasticsearch-based beneficiary search + - `rmnch/RMNCHMobileAppController` - RMNCH mobile app data sync + - `familyTagging/FamilyTaggingController` - Family tagging and family search + - `elasticsearch/ElasticsearchSyncController` - Elasticsearch sync management + - `health/HealthController` - Health check endpoint + - `version/VersionController` - API version info +- **service/** - Business logic: + - `IdentityService` - Core identity operations + - `rmnch/` - RMNCH beneficiary management + - `familyTagging/` - Family tagging logic + - `elasticsearch/` - Elasticsearch indexing and sync + - `health/` - Health check service +- **domain/** - Core JPA entities for beneficiary data: + - `MBeneficiaryregidmapping` - Beneficiary registration ID mapping + - `MBeneficiaryaddress`, `MBeneficiarycontact`, `MBeneficiaryAccount` - Beneficiary demographics + - `MBeneficiaryfamilymapping` - Family relationships + - `MBeneficiaryconsent` - Consent management + - `VBenAdvanceSearch` - View for advanced search queries +- **data/** - Additional data models: + - `rmnch/` - RMNCH-specific entities (CBAC details, born birth details, household details, NCD/TB/HRP data) + - `elasticsearch/` - Elasticsearch document models and sync job + - `familyTagging/` - Family tagging models +- **dto/** - Data transfer objects for API requests/responses +- **repo/** - Spring Data JPA and Elasticsearch repositories +- **mapper/** - MapStruct mappers for entity-DTO conversion +- **filter/** - Servlet filters +- **security/** - Security configuration +- **utils/** - Utilities (Redis, HTTP, validation, session, gateway, email, exception handling) +- **config/** - Application configuration + +## Architecture Notes + +- Dual-profile beneficiary storage: main identity in `db_identity`, with mapping to `db_iemr` for AMRIT platform integration +- Elasticsearch integration provides fast full-text beneficiary search with background sync jobs +- RMNCH module handles field-worker mobile app data (CBAC screening, household surveys, birth details) +- Family tagging enables linking beneficiaries into family units with search by family ID +- MapStruct mappers handle complex entity-to-DTO transformations +- Health ID (ABHA) linkage stored per beneficiary for ABDM integration +- Beneficiary deduplication logic via advanced search views +- Artifact ID: `identity-api`, group: `com.iemr.common.identity`, version: 3.6.1 diff --git a/pom.xml b/pom.xml index f29361fd..c3b57f1c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.6.1 + 3.6.2 war diff --git a/src/main/environment/1097_ci.properties b/src/main/environment/1097_ci.properties index 0e8deded..514f675d 100644 --- a/src/main/environment/1097_ci.properties +++ b/src/main/environment/1097_ci.properties @@ -24,7 +24,6 @@ spring.redis.host=@env.REDIS_HOST@ cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ - # Elasticsearch Configuration elasticsearch.host=@env.ELASTICSEARCH_HOST@ elasticsearch.port=@env.ELASTICSEARCH_PORT@ diff --git a/src/main/environment/1097_example.properties b/src/main/environment/1097_example.properties index ab87d360..ba2f3211 100644 --- a/src/main/environment/1097_example.properties +++ b/src/main/environment/1097_example.properties @@ -31,4 +31,3 @@ elasticsearch.index.beneficiary=beneficiary_index # Enable/Disable ES (for gradual rollout) elasticsearch.enabled=true - diff --git a/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java b/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java index 19323611..b347bd50 100644 --- a/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java +++ b/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java @@ -116,6 +116,20 @@ public String untagFamily(@RequestBody String comingReq) { return response.toString(); } + @Operation(summary = "Get family tagging details by beneficiary ID") + @PostMapping(value = { "/getBenFamilyDetails" }, consumes = "application/json", produces = "application/json") + public String getFamilyDetailsByBeneficiaryId(@RequestBody String comingReq) { + OutputResponse response = new OutputResponse(); + try { + String s = familyTagService.getFamilyDetailsByBeneficiaryId(comingReq); + response.setResponse(s); + } catch (Exception e) { + logger.error("Error in fetching family details by beneficiary ID : " + e); + response.setError(5000, "Error in fetching family details by beneficiary ID : " + e.getLocalizedMessage()); + } + return response.toString(); + } + @Operation(summary = "Edit beneficiary family details") @PostMapping(value = { "/editFamilyTagging" }, consumes = "application/json", produces = "application/json") public String editFamilyDetails(@RequestBody String comingReq) { diff --git a/src/main/java/com/iemr/common/identity/repo/BenContactRepo.java b/src/main/java/com/iemr/common/identity/repo/BenContactRepo.java index fdbda216..02334684 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenContactRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenContactRepo.java @@ -49,8 +49,12 @@ public interface BenContactRepo extends CrudRepository findByPreferredSMSPhoneNumOrderByBenContactsIDAsc(String smsPhoneNum); - @Query("select c from MBeneficiarycontact c where c.preferredPhoneNum = :phoneNum ") - List findByAnyPhoneNum(@Param("phoneNum") String phoneNum); + // @Query("select c from MBeneficiarycontact c where c.preferredPhoneNum = :phoneNum ") + // List findByAnyPhoneNum(@Param("phoneNum") String phoneNum); + + @Query("select c from MBeneficiarycontact c where c.preferredPhoneNum IN :variants") + List findByAnyPhoneNum(@Param("variants") List variants); + @Query("select c from MBeneficiarycontact c where c.preferredPhoneNum = :phoneNum or c.phoneNum1 = :phoneNum " @@ -70,4 +74,6 @@ public interface BenContactRepo extends CrudRepository findTop10000ByProvisionedAndReserved(Boolean isProvisioned,Boolean isReserved); + /** + * Atomically selects and locks the next available registration ID row. + * SKIP LOCKED ensures concurrent servers each get a distinct row without blocking each other, + * eliminating duplicate BenRegId assignments when multiple app instances share the same database. + */ + @Transactional + @Query(value = "SELECT * FROM m_beneficiaryregidmapping WHERE Provisioned = false AND Reserved = false ORDER BY BenRegId ASC LIMIT 1 FOR UPDATE SKIP LOCKED", nativeQuery = true) + MBeneficiaryregidmapping findAndLockNextAvailable(); + } diff --git a/src/main/java/com/iemr/common/identity/repo/familyTag/FamilyTagRepo.java b/src/main/java/com/iemr/common/identity/repo/familyTag/FamilyTagRepo.java index 4451fd37..88d05388 100644 --- a/src/main/java/com/iemr/common/identity/repo/familyTag/FamilyTagRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/familyTag/FamilyTagRepo.java @@ -42,12 +42,10 @@ public interface FamilyTagRepo extends CrudRepository { public int untagFamily(@Param("benFamilyTagId") List benFamilyTagId,@Param("modifiedBy") String modifiedBy); - @Query("SELECT obj FROM BenFamilyMapping obj WHERE obj.familyName =:familyName AND obj.villageId =:villageId AND (obj.noOfmembers is not null " - + " AND obj.noOfmembers >0)") + @Query("SELECT obj FROM BenFamilyMapping obj WHERE obj.familyName LIKE CONCAT(:familyName, '%') AND (:villageId IS NULL OR obj.villageId =:villageId) AND (obj.deleted IS NULL OR obj.deleted = false)") List searchFamily(@Param("familyName") String familyName,@Param("villageId") Integer villageId); - - @Query("SELECT obj FROM BenFamilyMapping obj WHERE obj.familyName =:familyName AND obj.villageId =:villageId AND obj.familyId =:familyId AND (obj.noOfmembers is not null " - + " AND obj.noOfmembers >0)") + + @Query("SELECT obj FROM BenFamilyMapping obj WHERE obj.familyName LIKE CONCAT(:familyName, '%') AND (:villageId IS NULL OR obj.villageId =:villageId) AND obj.familyId =:familyId AND (obj.deleted IS NULL OR obj.deleted = false)") List searchFamilyWithFamilyId(@Param("familyName") String familyName,@Param("villageId") Integer villageId,@Param("familyId") String familyId); @Query("SELECT obj FROM BenFamilyMapping obj WHERE obj.familyId =:familyId") diff --git a/src/main/java/com/iemr/common/identity/service/BenRegIdClaimService.java b/src/main/java/com/iemr/common/identity/service/BenRegIdClaimService.java new file mode 100644 index 00000000..ba2c32eb --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/BenRegIdClaimService.java @@ -0,0 +1,89 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.identity.service; + +import java.sql.Timestamp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.iemr.common.identity.domain.MBeneficiaryregidmapping; +import com.iemr.common.identity.repo.BenRegIdMappingRepo; + +/** + * Handles atomic beneficiary registration ID claiming. + * + * Uses SELECT ... FOR UPDATE SKIP LOCKED so that each application instance + * obtains a distinct, exclusive row. Multiple servers sharing the same database + * will never receive the same BenRegId, preventing + * SQLIntegrityConstraintViolationException duplicate-key errors that occurred + * with the previous in-memory ArrayDeque queue approach. + * + * REQUIRES_NEW propagation ensures the SELECT + UPDATE happens in its own + * short-lived transaction, releasing the row lock immediately after the ID is + * marked reserved — keeping lock contention to a minimum. + */ +@Service +public class BenRegIdClaimService { + + private static final Logger logger = LoggerFactory.getLogger(BenRegIdClaimService.class); + + @Autowired + private BenRegIdMappingRepo regIdRepo; + + /** + * Atomically claims the next available registration ID. + * + *
    + *
  1. Opens a brand-new transaction (REQUIRES_NEW).
  2. + *
  3. Executes SELECT … FOR UPDATE SKIP LOCKED to lock exactly one row. + * Concurrent callers on other servers/threads skip the locked row and + * get the next one — no two callers ever see the same row.
  4. + *
  5. Marks the row {@code reserved = true} and flushes it within the same + * transaction so the change is visible to other connections the moment + * this method returns.
  6. + *
+ * + * @return the reserved {@link MBeneficiaryregidmapping} with {@code reserved=true} + * @throws IllegalStateException if the ID pool is exhausted + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public MBeneficiaryregidmapping claimNextAvailableRegId() { + MBeneficiaryregidmapping regMap = regIdRepo.findAndLockNextAvailable(); + if (regMap == null) { + throw new IllegalStateException( + "No available registration IDs in the pool. " + + "Please contact the system administrator to import more IDs."); + } + if (regMap.getCreatedDate() == null) { + regMap.setCreatedDate(new Timestamp(System.currentTimeMillis())); + } + regMap.setReserved(true); + regMap = regIdRepo.save(regMap); + logger.info("BenRegIdClaimService: claimed BenRegId={}", regMap.getBenRegId()); + return regMap; + } +} diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 08b9fbbe..afb193e3 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -25,8 +25,8 @@ import java.math.BigInteger; import java.sql.Timestamp; import java.text.SimpleDateFormat; -import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -154,6 +154,8 @@ private JdbcTemplate getJdbcTemplate() { @Autowired BenRegIdMappingRepo regIdRepo; @Autowired + private BenRegIdClaimService benRegIdClaimService; + @Autowired BenServiceMappingRepo serviceMapRepo; @Autowired MBeneficiaryAccountRepo accountRepo; @@ -555,7 +557,15 @@ public List getBeneficiariesByPhoneNum(String phoneNum) List list = new ArrayList<>(); try { - List benContact = contactRepo.findByAnyPhoneNum(phoneNum); + // List benContact = contactRepo.findByAnyPhoneNum(phoneNum); + + String clean = phoneNum.trim(); + if (clean.startsWith("+91")) clean = clean.substring(3); + else if (clean.startsWith("91") && clean.length() == 12) clean = clean.substring(2); + else if (clean.startsWith("0") && clean.length() == 11) clean = clean.substring(1); + + List variants = Arrays.asList(clean, "0" + clean, "91" + clean, "+91" + clean); + List benContact = contactRepo.findByAnyPhoneNum(variants); logger.info(benContact.size() + " contacts found for phone number " + phoneNum); @@ -1038,6 +1048,18 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields if (benDetails.getOther() != null) { mbDetl.setOther(benDetails.getOther()); } + if (mbDetl.getOccupationId() == null && benDetails.getOccupationId() != null) { + mbDetl.setOccupationId(benDetails.getOccupationId()); + } + if (mbDetl.getOccupation() == null && benDetails.getOccupation() != null) { + mbDetl.setOccupation(benDetails.getOccupation()); + } + if (mbDetl.getEducationId() == null && benDetails.getEducationId() != null) { + mbDetl.setEducationId(benDetails.getEducationId()); + } + if (mbDetl.getEducation() == null && benDetails.getEducation() != null) { + mbDetl.setEducation(benDetails.getEducation()); + } // Extract and set extra fields // String identityJson = new Gson().toJson(json); @@ -1337,6 +1359,8 @@ private MBeneficiarydetail convertIdentityEditDTOToMBeneficiarydetail(IdentityEd if (dto.getOtherFields() != null) { beneficiarydetail.setOtherFields(dto.getOtherFields()); } + beneficiarydetail.setSexualOrientationID(dto.getSexualOrientationID()); + beneficiarydetail.setSexualOrientationType(dto.getSexualOrientationType()); return beneficiarydetail; } @@ -1345,34 +1369,13 @@ private MBeneficiarydetail convertIdentityEditDTOToMBeneficiarydetail(IdentityEd * @param identity * @return */ - ArrayDeque queue = new ArrayDeque<>(); - public BeneficiaryCreateResp createIdentity(IdentityDTO identity) { logger.info("IdentityService.createIdentity - start"); - List list = null; - MBeneficiaryregidmapping regMap = null; - synchronized (queue) { - if (queue.isEmpty()) { - logger.info("fetching 10000 rows"); - list = regIdRepo.findTop10000ByProvisionedAndReserved(false, false); - logger.info("Adding SynchronousQueue start-- "); - for (MBeneficiaryregidmapping map : list) { - queue.add(map); - } - logger.info("Adding SynchronousQueue end-- "); - } - regMap = queue.removeFirst(); - } - regMap.setReserved(true); - if (regMap.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - regMap.setCreatedDate(ts); - } - - regIdRepo.save(regMap); + // Atomically claim the next available ID using SELECT … FOR UPDATE SKIP LOCKED. + // This is safe across multiple app servers sharing the same database — each server + // locks and reserves a distinct row, so duplicate BenRegId inserts cannot occur. + MBeneficiaryregidmapping regMap = benRegIdClaimService.claimNextAvailableRegId(); regMap.setProvisioned(true); @@ -1665,6 +1668,9 @@ private String cleanPhoneNumber(String phoneNumber) { } else if (cleaned.startsWith("91") && cleaned.length() == 12) { // Handle case where + is already removed but 91 remains cleaned = cleaned.substring(2); + } else if (cleaned.startsWith("0") && cleaned.length() == 11) { + // Handle case where number starts with 0 and is 11 digits long + cleaned = cleaned.substring(1); } return cleaned.trim(); diff --git a/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagService.java b/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagService.java index 85e8cfba..5bf22b36 100644 --- a/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagService.java +++ b/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagService.java @@ -36,4 +36,6 @@ public interface FamilyTagService { public String searchFamily(String request) throws IEMRException; public String editFamilyDetails(String request) throws IEMRException; + + public String getFamilyDetailsByBeneficiaryId(String request) throws IEMRException; } diff --git a/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java b/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java index 2a60571b..f44eecf8 100644 --- a/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java @@ -37,6 +37,7 @@ import com.google.gson.JsonParser; import com.iemr.common.identity.data.familyTagging.BenFamilyMapping; import com.iemr.common.identity.data.familyTagging.FamilyMembers; +import com.iemr.common.identity.data.familyTagging.FamilySearchResponse; import com.iemr.common.identity.domain.MBeneficiarydetail; import com.iemr.common.identity.domain.MBeneficiarymapping; import com.iemr.common.identity.exception.IEMRException; @@ -264,10 +265,49 @@ public String getFamilyDetails(String request) throws IEMRException { throw new IEMRException("Error while fetching family member details :" + e.getLocalizedMessage()); } } + @Override + public String getFamilyDetailsByBeneficiaryId(String request) throws IEMRException { + try { + BenFamilyMapping reqObj = InputMapper.gson().fromJson(request, BenFamilyMapping.class); + if (reqObj.getBeneficiaryRegId() == null) + throw new IEMRException("beneficiaryRegId is required"); + + MBeneficiarymapping mapping = benMappingRepo + .getBenDetailsId(BigInteger.valueOf(reqObj.getBeneficiaryRegId())); + if (mapping == null || mapping.getBenDetailsId() == null) + throw new IEMRException("Beneficiary not found"); + + List benDetails = benDetailRepo + .findByBeneficiaryDetailsIdOrderByBeneficiaryDetailsIdAsc(mapping.getBenDetailsId()); + if (benDetails == null || benDetails.isEmpty() || benDetails.get(0).getFamilyId() == null) + return "No family tagged to this beneficiary"; + + String familyId = benDetails.get(0).getFamilyId(); + + BenFamilyMapping familyMaster = familyTagRepo.searchFamilyByFamilyId(familyId); + List memberList = benDetailRepo.getFamilyDetails(familyId); + + FamilySearchResponse resp = new FamilySearchResponse(); + if (familyMaster != null) { + resp.setFamilyId(familyMaster.getFamilyId()); + resp.setFamilyName(familyMaster.getFamilyName()); + resp.setHeadOfTheFamily(familyMaster.getFamilyHeadName()); + resp.setNoOfMembers(familyMaster.getNoOfmembers()); + } + List memberResponseList = new ArrayList<>(); + addFamilyMembersToList(memberList, memberResponseList); + resp.setFamilyMembers(memberResponseList); + + return new Gson().toJson(resp); + } catch (Exception e) { + throw new IEMRException( + "Error while fetching family details by beneficiary ID : " + e.getLocalizedMessage()); + } + } + private void addFamilyMembersToList(List list, List responseList) { - StringBuilder name = new StringBuilder(""); for (MBeneficiarydetail obj : list) { - + StringBuilder name = new StringBuilder(""); FamilyMembers famObj = new FamilyMembers(); BigInteger benRegId = benMappingRepo.getBenRegId(obj.getBeneficiaryDetailsId(), obj.getVanID()); if (benRegId != null) diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java index af81ea34..cf959aa6 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -43,10 +43,13 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo return; } String path = request.getRequestURI(); - logger.info("JwtUserIdValidationFilter invoked for path: {}", path); + + String servletPath = request.getServletPath(); + logger.info("JwtUserIdValidationFilter invoked for requestURI: {}, servletPath: {}", path, servletPath); // Skip JWT validation for public endpoints - if (path.equals("/health") || path.equals("/version")) { + if (servletPath.equals("/health") || servletPath.equals("/version") || + path.endsWith("/health") || path.endsWith("/version")) { logger.info("Public endpoint accessed: {} - skipping JWT validation", path); filterChain.doFilter(servletRequest, servletResponse); return; From 93d43907f9fcd38723165ca5c9e896207f88f383 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 5 Jun 2026 16:15:22 +0530 Subject: [PATCH 03/62] feat: write anthropometry from rmnch/syncDataToAmrit to i_beneficiarydetails.otherFields Mobile sends height/weight/bmi/temperature in beneficiaryDetails payload. i_beneficiarydetails_rmnch has no these columns so they were lost. FLW-API getBeneficiaryData reads from otherFields (temperatureValue key). - RMNCHBeneficiaryDetailsRmnch: @Transient height/weight/bmi/temperature - BenDetailRepo: updateOtherFieldsByBenRegId query - RmnchDataSyncServiceImpl: merge anthropometry into otherFields after save Co-Authored-By: Claude Sonnet 4.6 --- .../rmnch/RMNCHBeneficiaryDetailsRmnch.java | 15 +++++++ .../common/identity/repo/BenDetailRepo.java | 5 +++ .../rmnch/RmnchDataSyncServiceImpl.java | 43 ++++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index 4cab210c..c0b5c864 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -550,4 +550,19 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose private Boolean isDeactivate; + // Anthropometry fields sent by Stop TB mobile app via beneficiaryDetails payload. + // i_beneficiarydetails_rmnch has no these columns — stored in i_beneficiarydetails.otherFields instead. + @Expose + @Transient + private Double height; + @Expose + @Transient + private Double weight; + @Expose + @Transient + private Double bmi; + @Expose + @Transient + private Double temperature; // stored as "temperatureValue" in otherFields to match getBeneficiaryData key + } diff --git a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java index 4e8bcc01..413298ba 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java @@ -148,6 +148,11 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi @Query("SELECT b FROM MBeneficiarydetail b WHERE b.familyId =:familyid ") List searchByFamilyId(@Param("familyid") String familyid); + @Transactional + @Modifying + @Query("UPDATE MBeneficiarydetail d SET d.otherFields = :otherFields WHERE d.mBeneficiarymapping.benRegId = :benRegId") + int updateOtherFieldsByBenRegId(@Param("benRegId") BigInteger benRegId, @Param("otherFields") String otherFields); + /** * Find complete beneficiary data by IDs from Elasticsearch */ diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index bd973193..43d715ee 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -72,6 +72,8 @@ import com.iemr.common.identity.repo.rmnch.RMNCHCBACDetailsRepo; import com.iemr.common.identity.repo.rmnch.RMNCHHouseHoldDetailsRepo; import com.iemr.common.identity.repo.rmnch.RMNCHMBenMappingRepo; +import com.iemr.common.identity.domain.MBeneficiarydetail; +import com.iemr.common.identity.repo.BenDetailRepo; import com.iemr.common.identity.repo.rmnch.RMNCHMBenRegIdMapRepo; import com.iemr.common.identity.utils.config.ConfigProperties; import com.iemr.common.identity.utils.exception.IEMRException; @@ -110,7 +112,8 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHBenContactRepo rMNCHBenContactRepo; @Autowired private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; - + @Autowired + private BenDetailRepo benDetailRepo; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ) throws Exception { @@ -190,6 +193,22 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { // update beneficiary data in i_beneficiarydetails table rMNCHBenDetailsRepo.saveAll(benDetailsList); + // Write anthropometry (height/weight/bmi/temperature) to i_beneficiarydetails.otherFields. + // i_beneficiarydetails_rmnch has no these columns; FLW-API getBeneficiaryData reads from otherFields. + for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsExtraList) { + if (obj.getBenRegId() != null && hasAnthropometryData(obj)) { + try { + MBeneficiarydetail benDetail = benDetailRepo.findByBenRegId(obj.getBenRegId()); + if (benDetail != null) { + String merged = mergeAnthropometry(benDetail.getOtherFields(), obj); + benDetailRepo.updateOtherFieldsByBenRegId(obj.getBenRegId(), merged); + } + } catch (Exception ex) { + logger.warn("Failed to update otherFields for benRegId: " + obj.getBenRegId() + " - " + ex.getMessage()); + } + } + } + // born birth details if (jsnOBJ != null && jsnOBJ.has("bornBirthDeatils")) { RMNCHBornBirthDetails[] objArr1 = InputMapper.gson() @@ -267,6 +286,28 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { return new Gson().toJson(resultMap); } + + private boolean hasAnthropometryData(RMNCHBeneficiaryDetailsRmnch obj) { + return obj.getHeight() != null || obj.getWeight() != null + || obj.getBmi() != null || obj.getTemperature() != null; + } + + private String mergeAnthropometry(String existingOtherFields, RMNCHBeneficiaryDetailsRmnch obj) { + JsonObject json = new JsonObject(); + if (existingOtherFields != null && !existingOtherFields.isBlank()) { + try { + json = new JsonParser().parse(existingOtherFields).getAsJsonObject(); + } catch (Exception ignored) { + } + } + if (obj.getHeight() != null) json.addProperty("height", obj.getHeight()); + if (obj.getWeight() != null) json.addProperty("weight", obj.getWeight()); + if (obj.getBmi() != null) json.addProperty("bmi", obj.getBmi()); + // mobile sends "temperature"; FLW-API getBeneficiaryData reads "temperatureValue" + if (obj.getTemperature() != null) json.addProperty("temperatureValue", obj.getTemperature()); + return new Gson().toJson(json); + } + @Override public String getBenData(String requestOBJ, String authorisation) throws Exception { String outputResponse = null; From 1f4fa23ab16d1e69aa0d394c63543c9c987e266b Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 5 Jun 2026 17:56:21 +0530 Subject: [PATCH 04/62] feat: stamp vanID from Redis on all 4 RMNCH entities in syncDataToAmrit Reads camp:vanID and camp:parkingPlaceID from Redis (written by MMU-API on van login) and stamps them onto RMNCHBeneficiaryDetailsRmnch, RMNCHBornBirthDetails, RMNCHCBACdetails, and RMNCHHouseHoldDetails when the mobile payload carries VanID=null or 0. Also writes anthropometry (height/weight/bmi/temperature) from mobile beneficiaryDetails payload into i_beneficiarydetails.otherFields so FLW-API getBeneficiaryData can return them. Gracefully skips if Redis has no camp configured. Co-Authored-By: Claude Sonnet 4.6 --- .../rmnch/RmnchDataSyncServiceImpl.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 43d715ee..2205001d 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -74,6 +74,7 @@ import com.iemr.common.identity.repo.rmnch.RMNCHMBenMappingRepo; import com.iemr.common.identity.domain.MBeneficiarydetail; import com.iemr.common.identity.repo.BenDetailRepo; +import com.iemr.common.identity.utils.redis.RedisStorage; import com.iemr.common.identity.repo.rmnch.RMNCHMBenRegIdMapRepo; import com.iemr.common.identity.utils.config.ConfigProperties; import com.iemr.common.identity.utils.exception.IEMRException; @@ -114,6 +115,8 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; @Autowired private BenDetailRepo benDetailRepo; + @Autowired + private RedisStorage redisStorage; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ) throws Exception { @@ -125,6 +128,20 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { ArrayList cBACDetailsIds = new ArrayList<>(); ArrayList houseHoldDetailsIds = new ArrayList<>(); + // Read camp vanID/parkingPlaceID from Redis (set by MMU-API on van login) + Integer campVanID = null; + Integer campParkingPlaceID = null; + try { + String vanVal = redisStorage.getObject("camp:vanID", false, 0); + String ppVal = redisStorage.getObject("camp:parkingPlaceID", false, 0); + if (vanVal != null && !vanVal.isBlank()) campVanID = Integer.parseInt(vanVal); + if (ppVal != null && !ppVal.isBlank()) campParkingPlaceID = Integer.parseInt(ppVal); + } catch (Exception ignored) { + // no camp configured — vanID stamping skipped + } + final Integer vanID = campVanID; + final Integer parkingPlaceID = campParkingPlaceID; + try { if (requestOBJ != null && !requestOBJ.isEmpty()) { JsonObject jsnOBJ = new JsonObject(); @@ -168,6 +185,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } obj.setRelatedBeneficiaryIdsDB(sb.toString()); } + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()); if (rmnchmBeneficiarydetail != null) { @@ -220,6 +241,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { RMNCHBornBirthDetails temp = rMNCHBornBirthDetailsRepo.getByRegID(benRegID); if (temp != null) obj.setBornBirthDeatilsId(temp.getBornBirthDeatilsId()); + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } bornBirthList = (ArrayList) rMNCHBornBirthDetailsRepo .saveAll(bornBirthList); @@ -243,6 +268,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { RMNCHCBACdetails temp = rMNCHCBACDetailsRepo.getByRegID(benRegID); if (temp != null) obj.setCBACDetailsid(temp.getCBACDetailsid()); + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } cbacList = (ArrayList) rMNCHCBACDetailsRepo.saveAll(cbacList); @@ -260,6 +289,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { .getByHouseHoldID(obj.getHouseoldId()); if (temp != null) obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } houseHoldList = (ArrayList) rMNCHHouseHoldDetailsRepo .saveAll(houseHoldList); From 09d88f650e38e64c4f11ff73bac982d6633d4fa7 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 5 Jun 2026 18:23:47 +0530 Subject: [PATCH 05/62] fix: preserve @Transient anthropometry fields lost after JPA saveAll merge Hibernate merge() returns new managed instances that do not carry @Transient field values (height/weight/bmi/temperature). Keep a reference to the original list before saveAll so the anthropometry loop reads from objects that still have the mobile payload values. Co-Authored-By: Claude Sonnet 4.6 --- .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 2205001d..754e574e 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -207,6 +207,9 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } + // Keep original list before saveAll — @Transient fields (height/weight/bmi/temperature) + // are lost in the JPA-managed instances returned by merge() + List benDetailsOriginalList = new ArrayList<>(benDetailsExtraList); benDetailsExtraList = (ArrayList) rMNCHBeneficiaryDetailsRmnchRepo .saveAll(benDetailsExtraList); @@ -216,7 +219,7 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { // Write anthropometry (height/weight/bmi/temperature) to i_beneficiarydetails.otherFields. // i_beneficiarydetails_rmnch has no these columns; FLW-API getBeneficiaryData reads from otherFields. - for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsExtraList) { + for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsOriginalList) { if (obj.getBenRegId() != null && hasAnthropometryData(obj)) { try { MBeneficiarydetail benDetail = benDetailRepo.findByBenRegId(obj.getBenRegId()); From 23675d23532bafdc21801ccd84b905c38cd42c55 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 12 Jun 2026 13:56:10 +0530 Subject: [PATCH 06/62] fix: prevent camp:vanID deletion on every syncDataToAmrit call getObject("camp:vanID", false, 0) was calling EXPIRE key 0 which immediately deletes the key in Redis after every sync. Replaced with getRaw() which reads the value without modifying TTL, so camp:vanID persists across multiple syncs until MMU logout. Co-Authored-By: Claude Sonnet 4.6 --- .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 4 ++-- .../com/iemr/common/identity/utils/redis/RedisStorage.java | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 754e574e..efbfc210 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -132,8 +132,8 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { Integer campVanID = null; Integer campParkingPlaceID = null; try { - String vanVal = redisStorage.getObject("camp:vanID", false, 0); - String ppVal = redisStorage.getObject("camp:parkingPlaceID", false, 0); + String vanVal = redisStorage.getRaw("camp:vanID"); + String ppVal = redisStorage.getRaw("camp:parkingPlaceID"); if (vanVal != null && !vanVal.isBlank()) campVanID = Integer.parseInt(vanVal); if (ppVal != null && !ppVal.isBlank()) campParkingPlaceID = Integer.parseInt(ppVal); } catch (Exception ignored) { diff --git a/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java b/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java index 04a3f1d8..98ced6fa 100644 --- a/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java @@ -67,6 +67,13 @@ public String getObject(String key, Boolean extendExpirationTime, int expiration return userRespFromRedis; } + public String getRaw(String key) { + RedisConnection redCon = connection.getConnection(); + byte[] data = redCon.get(key.getBytes()); + redCon.close(); + return data != null ? new String(data) : null; + } + public Long deleteObject(String key) throws RedisSessionException { RedisConnection redCon = connection.getConnection(); Long userRespFromRedis = Long.valueOf(0L); From 68df190ea20b453b8ddc7d3454e915c11e640193 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 16 Jun 2026 16:32:08 +0530 Subject: [PATCH 07/62] feat(stoptb): add stoptb.enforce.vanid flag to syncDataToAmrit The Stop TB mobile app calls this RMNCH sync endpoint directly to save household details. Same issue as elsewhere: vanID stamping is skipped silently when Redis has no camp:vanID, so household records end up with vanID=NULL. When stoptb.enforce.vanid=true, sync now fails with a clear error instead. --- src/main/environment/common_ci.properties | 3 +++ src/main/environment/common_docker.properties | 3 +++ src/main/environment/common_example.properties | 5 +++++ .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 9 +++++++++ 4 files changed, 20 insertions(+) diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 98736180..916f5712 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -22,6 +22,9 @@ fhir-url=@env.FHIR_API@ # Redis Config spring.redis.host=@env.REDIS_HOST@ +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=@env.STOPTB_ENFORCE_VANID@ + cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ # Elasticsearch Configuration diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 8f66c5b0..5aa71373 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -22,6 +22,9 @@ fhir-url=${FHIR_API} # Redis Config spring.redis.host=${REDIS_HOST} +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=${STOPTB_ENFORCE_VANID} + cors.allowed-origins=${CORS_ALLOWED_ORIGINS} # Elasticsearch Configuration diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index 28eda5ea..bbc5251b 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -17,6 +17,11 @@ fhir-url=http://localhost:8093/ # Redis Config spring.redis.host=localhost + +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not +# configured instead of silently skipping vanID stamping +stoptb.enforce.vanid=false + cors.allowed-origins=http://localhost:* # Elasticsearch Configuration diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index efbfc210..4de97f6f 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -117,6 +117,11 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private BenDetailRepo benDetailRepo; @Autowired private RedisStorage redisStorage; + + // When true, sync fails loudly if camp is not configured instead of silently + // skipping vanID stamping + @Value("${stoptb.enforce.vanid:false}") + private boolean enforceVanID; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ) throws Exception { @@ -139,6 +144,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } catch (Exception ignored) { // no camp configured — vanID stamping skipped } + if (campVanID == null && enforceVanID) { + throw new Exception( + "Camp not configured: vanID missing. Please select van/service point in MMU before syncing data."); + } final Integer vanID = campVanID; final Integer parkingPlaceID = campParkingPlaceID; From caa8e349c05b42c429fa21bec674c272823ec786 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 16 Jun 2026 16:33:49 +0530 Subject: [PATCH 08/62] chore: bump version to 3.8.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 06f2a240..613011c5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.6.2 + 3.8.2 war From 5367efaedd595cd71e978febd62cb5bd72f8fcdb Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Tue, 16 Jun 2026 17:43:42 +0530 Subject: [PATCH 09/62] fix duplicate ben id issue --- .../repo/rmnch/RMNCHBenDetailsRepo.java | 3 +- .../repo/rmnch/RMNCHHouseHoldDetailsRepo.java | 6 ++- .../rmnch/RmnchDataSyncServiceImpl.java | 50 +++++++++++-------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBenDetailsRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBenDetailsRepo.java index 104fe902..1bc948a3 100644 --- a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBenDetailsRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBenDetailsRepo.java @@ -22,6 +22,7 @@ package com.iemr.common.identity.repo.rmnch; import java.math.BigInteger; +import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; @@ -38,5 +39,5 @@ public RMNCHMBeneficiarydetail getByIdAndVanID(@Param("vanSerialNo") BigInteger @Query(" SELECT t FROM RMNCHMBeneficiarydetail t WHERE t.id = " + "(SELECT m.benDetailsId from RMNCHMBeneficiarymapping m where m.benRegId = :beneficiaryRegID)") - public RMNCHMBeneficiarydetail getByBenRegID(@Param("beneficiaryRegID") BigInteger beneficiaryRegID); + public List getByBenRegID(@Param("beneficiaryRegID") BigInteger beneficiaryRegID); } diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java index 4aed3c87..76490cbd 100644 --- a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java @@ -28,11 +28,13 @@ import com.iemr.common.identity.data.rmnch.RMNCHHouseHoldDetails; +import java.util.List; + @Repository public interface RMNCHHouseHoldDetailsRepo extends CrudRepository { @Query(" SELECT t FROM RMNCHHouseHoldDetails t WHERE t.id = :vanSerialNo AND t.VanID = :vanID") public RMNCHHouseHoldDetails getByIdAndVanID(@Param("vanSerialNo") long vanSerialNo, @Param("vanID") int vanID); - @Query(" SELECT t FROM RMNCHHouseHoldDetails t WHERE t.houseoldId =:houseoldId ") - public RMNCHHouseHoldDetails getByHouseHoldID(@Param("houseoldId") long houseoldId); + @Query("SELECT t FROM RMNCHHouseHoldDetails t WHERE t.houseoldId = :houseoldId") + List getByHouseHoldID(@Param("houseoldId") long houseoldId); } diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 568d4c1a..8b1e73da 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -167,20 +167,22 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } obj.setRelatedBeneficiaryIdsDB(sb.toString()); } - RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = - rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()); - if (rmnchmBeneficiarydetail != null) { - rmnchmBeneficiarydetail.setFirstName(obj.getFirstName()); - rmnchmBeneficiarydetail.setLastName(obj.getLastName()); - rmnchmBeneficiarydetail.setFatherName(obj.getFatherName()); - rmnchmBeneficiarydetail.setMotherName(obj.getMotherName()); - rmnchmBeneficiarydetail.setDob(obj.getDob()); - rmnchmBeneficiarydetail.setSpousename(obj.getSpousename()); - rmnchmBeneficiarydetail.setGender(obj.getGender()); - rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); - rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); - rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); - benDetailsList.add(rmnchmBeneficiarydetail); + if(!rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).isEmpty()){ + RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = + rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).get(0); + if (rmnchmBeneficiarydetail != null) { + rmnchmBeneficiarydetail.setFirstName(obj.getFirstName()); + rmnchmBeneficiarydetail.setLastName(obj.getLastName()); + rmnchmBeneficiarydetail.setFatherName(obj.getFatherName()); + rmnchmBeneficiarydetail.setMotherName(obj.getMotherName()); + rmnchmBeneficiarydetail.setDob(obj.getDob()); + rmnchmBeneficiarydetail.setSpousename(obj.getSpousename()); + rmnchmBeneficiarydetail.setGender(obj.getGender()); + rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); + rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); + rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); + benDetailsList.add(rmnchmBeneficiarydetail); + } } } @@ -239,10 +241,14 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { List houseHoldList = Arrays.asList(objArr3); for (RMNCHHouseHoldDetails obj : houseHoldList) { - RMNCHHouseHoldDetails temp = rMNCHHouseHoldDetailsRepo - .getByHouseHoldID(obj.getHouseoldId()); - if (temp != null) - obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); + if(!rMNCHHouseHoldDetailsRepo + .getByHouseHoldID(obj.getHouseoldId()).isEmpty()){ + RMNCHHouseHoldDetails temp = rMNCHHouseHoldDetailsRepo + .getByHouseHoldID(obj.getHouseoldId()).get(0); + if (temp != null) + obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); + } + } houseHoldList = (ArrayList) rMNCHHouseHoldDetailsRepo .saveAll(houseHoldList); @@ -438,8 +444,12 @@ private String getMappingsForAddressIDs(List addressLi // 20-09-2021,end if (benDetailsRMNCHOBJ != null && benDetailsRMNCHOBJ.getHouseoldId() != null) - benHouseHoldRMNCHROBJ = rMNCHHouseHoldDetailsRepo - .getByHouseHoldID(benDetailsRMNCHOBJ.getHouseoldId()); + if(!rMNCHHouseHoldDetailsRepo + .getByHouseHoldID(benDetailsRMNCHOBJ.getHouseoldId()).isEmpty()){ + benHouseHoldRMNCHROBJ = rMNCHHouseHoldDetailsRepo + .getByHouseHoldID(benDetailsRMNCHOBJ.getHouseoldId()).get(0); + } + } if (benDetailsRMNCHOBJ == null) From 8d83a9af95d3280c080d3060bdadcaf94aedae34 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 14:03:20 +0530 Subject: [PATCH 10/62] save abha id in health maping with beneficiaryID --- .../rmnch/RMNCHMobileAppController.java | 4 +- .../service/rmnch/RmnchDataSyncService.java | 2 +- .../rmnch/RmnchDataSyncServiceImpl.java | 67 +++++++++++++++++-- src/main/resources/application.properties | 1 + 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java index cbb91cce..5552e90f 100644 --- a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java +++ b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java @@ -59,11 +59,11 @@ public class RMNCHMobileAppController { @PostMapping(value = "/syncDataToAmrit", consumes = "application/json", produces = "application/json") @Operation(summary = "Sync data to AMRIT for already regestered beneficiary with AMRIT beneficiary id ") - public String syncDataToAmrit(@RequestBody String requestOBJ) { + public String syncDataToAmrit(@RequestBody String requestOBJ,@RequestHeader(value = "Authorization") String authorization) { OutputResponse response = new OutputResponse(); try { if (requestOBJ != null) { - String s = rmnchDataSyncService.syncDataToAmrit(requestOBJ); + String s = rmnchDataSyncService.syncDataToAmrit(requestOBJ,authorization); response.setResponse(s); } else response.setError(5000, "Invalid/NULL request obj"); diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncService.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncService.java index 845e79f9..7bbcb316 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncService.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncService.java @@ -22,7 +22,7 @@ package com.iemr.common.identity.service.rmnch; public interface RmnchDataSyncService { - public String syncDataToAmrit(String requestOBJ) throws Exception; + public String syncDataToAmrit(String requestOBJ, String authorization) throws Exception; public String saveBeneficiaryDetailsAfterRegistration( Long beneficiaryID, Long beneficiaryRegID, diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index a62450ed..d2284ee5 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -33,8 +33,6 @@ import java.util.Map; import java.util.regex.Pattern; -import com.iemr.common.identity.utils.OutputResponse; -import io.swagger.v3.oas.annotations.Operation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -78,8 +76,6 @@ import com.iemr.common.identity.utils.exception.IEMRException; import com.iemr.common.identity.utils.http.HttpUtils; import com.iemr.common.identity.utils.mapper.InputMapper; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; @Service @Qualifier("rmnchServiceImpl") @@ -115,7 +111,8 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override - public String syncDataToAmrit(String requestOBJ) throws Exception { + public String syncDataToAmrit(String requestOBJ, String authorization) throws Exception { + Map resultMap = new HashMap(); @@ -133,6 +130,8 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { // other tables data saving // ben details RMNCH extra fields details + logger.info("Request object of syncDataToAmrit: "+jsnOBJ); + BigInteger benRegID = null; @@ -187,6 +186,14 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); benDetailsList.add(rmnchmBeneficiarydetail); + if (jsnOBJ.has("abhaId") && !jsnOBJ.get("abhaId").isJsonNull()) { + String abhaId = jsnOBJ.get("abhaId").getAsString(); + if(!abhaId.isEmpty() || abhaId!=null){ + mapHealthIDToBeneficiary(authorization,rmnchmBeneficiarydetail.getBenRegId().longValue(),rmnchmBeneficiarydetail.getBenficieryid().longValue(),abhaId,rmnchmBeneficiarydetail.getCreatedBy(),rmnchmBeneficiarydetail.getFirstName(),rmnchmBeneficiarydetail.getLastName(),rmnchmBeneficiarydetail.getDob().toString(),rmnchmBeneficiarydetail.getProviderServiceMapID()); + + } + } + } } @@ -285,6 +292,56 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { return new Gson().toJson(resultMap); } + public String mapHealthIDToBeneficiary(String authorization, + Long benRegID, + Long beneficiaryID, + String abhaId, + String createdBy,String firstName,String lastName,String dob,Integer providerServiceMapId) { + try { + Map requestMap = new HashMap<>(); + + requestMap.put("beneficiaryRegID", benRegID); + requestMap.put("beneficiaryID", beneficiaryID); + requestMap.put("healthIdNumber", abhaId); + + requestMap.put("createdBy", createdBy); + requestMap.put("providerServiceMapId", providerServiceMapId); + requestMap.put("isNew", false); + + // ABHA Profile + Map abhaProfile = new HashMap<>(); + abhaProfile.put("ABHANumber", abhaId); + + List phrAddress = new ArrayList<>(); + phrAddress.add(abhaId + "@abdm"); + + abhaProfile.put("phrAddress", phrAddress); + abhaProfile.put("firstName", firstName); + abhaProfile.put("middleName", ""); + abhaProfile.put("lastName", lastName); + abhaProfile.put("dob", dob); + + requestMap.put("ABHAProfile", abhaProfile); + + HttpUtils utils = new HttpUtils(); + + HashMap header = new HashMap<>(); + header.put("Authorization", authorization); + + String responseStr = utils.post( + ConfigProperties.getPropertyByName("fhir-url") + + ConfigProperties.getPropertyByName("mapHealthIDToBeneficiary"), + new Gson().toJson(requestMap), + header); + logger.info("Save abha id in health mapping:"+responseStr.toString()); + return responseStr; + }catch (Exception e){ + logger.info("Error Save Health Id"); + return "Error Save Health Id: "+e.getMessage(); + } + + } + @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 12be1b44..79ba181f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -148,6 +148,7 @@ spring.jpa.properties.hibernate.show_sql=false door-to-door-page-size=2 get-HRP-Status=ANC/getHRPStatus getHealthID=healthID/getBenhealthID +mapHealthIDToBeneficiary=healthIDRecord/mapHealthIDToBeneficiary spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true From 3b7dd5200937faa4a7bf965933dcf0b7d5c6e111 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 16:27:15 +0530 Subject: [PATCH 11/62] abha id add in entity class --- .../identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java | 3 +++ .../identity/data/rmnch/RMNCHMBeneficiarydetail.java | 4 ++++ .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 7 ++----- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index 4cab210c..e403b3d7 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -550,4 +550,7 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose private Boolean isDeactivate; + @Expose + @Transient + private String abhaId; } diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java index 397c5a66..d9221675 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java @@ -214,4 +214,8 @@ public class RMNCHMBeneficiarydetail { @Expose @Transient private Integer ProviderServiceMapID; + + @Expose + @Transient + private String abhaId; } \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index d2284ee5..27ca8739 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -186,12 +186,9 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); benDetailsList.add(rmnchmBeneficiarydetail); - if (jsnOBJ.has("abhaId") && !jsnOBJ.get("abhaId").isJsonNull()) { - String abhaId = jsnOBJ.get("abhaId").getAsString(); - if(!abhaId.isEmpty() || abhaId!=null){ - mapHealthIDToBeneficiary(authorization,rmnchmBeneficiarydetail.getBenRegId().longValue(),rmnchmBeneficiarydetail.getBenficieryid().longValue(),abhaId,rmnchmBeneficiarydetail.getCreatedBy(),rmnchmBeneficiarydetail.getFirstName(),rmnchmBeneficiarydetail.getLastName(),rmnchmBeneficiarydetail.getDob().toString(),rmnchmBeneficiarydetail.getProviderServiceMapID()); + if (obj.getAbhaId()!=null && !obj.getAbhaId().isEmpty()) { + mapHealthIDToBeneficiary(authorization,obj.getBenRegId().longValue(),obj.getBenficieryid().longValue(),obj.getAbhaId(),obj.getCreatedBy(),obj.getFirstName(),obj.getLastName(),obj.getDob().toString(),obj.getProviderServiceMapID()); - } } } From 1d8dbe75ce20d33ac2f192e21cb1c399cbd35d9d Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 16:37:16 +0530 Subject: [PATCH 12/62] abha id add in entity class --- .../common/identity/service/rmnch/RmnchDataSyncServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 27ca8739..962c31c8 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -333,7 +333,7 @@ public String mapHealthIDToBeneficiary(String authorization, logger.info("Save abha id in health mapping:"+responseStr.toString()); return responseStr; }catch (Exception e){ - logger.info("Error Save Health Id"); + logger.info("Error Save Health Id"+e); return "Error Save Health Id: "+e.getMessage(); } From 1801efed23e349b400fe1da51cfafb42c565764c Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 17:31:48 +0530 Subject: [PATCH 13/62] abha id add in entity class --- .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 962c31c8..d5dd5a1b 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -109,6 +109,9 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHBenContactRepo rMNCHBenContactRepo; @Autowired private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; + + @Value("${fhir-url}") + private String fhirUrl; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ, String authorization) throws Exception { @@ -326,8 +329,7 @@ public String mapHealthIDToBeneficiary(String authorization, header.put("Authorization", authorization); String responseStr = utils.post( - ConfigProperties.getPropertyByName("fhir-url") - + ConfigProperties.getPropertyByName("mapHealthIDToBeneficiary"), + fhirUrl+ConfigProperties.getPropertyByName("mapHealthIDToBeneficiary"), new Gson().toJson(requestMap), header); logger.info("Save abha id in health mapping:"+responseStr.toString()); From 594ab594c19898d10ea6c75e8a612e99d027de6a Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 17:43:32 +0530 Subject: [PATCH 14/62] abha id add in entity class --- .../identity/controller/rmnch/RMNCHMobileAppController.java | 2 +- .../common/identity/service/rmnch/RmnchDataSyncServiceImpl.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java index 5552e90f..f8a8957f 100644 --- a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java +++ b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java @@ -59,7 +59,7 @@ public class RMNCHMobileAppController { @PostMapping(value = "/syncDataToAmrit", consumes = "application/json", produces = "application/json") @Operation(summary = "Sync data to AMRIT for already regestered beneficiary with AMRIT beneficiary id ") - public String syncDataToAmrit(@RequestBody String requestOBJ,@RequestHeader(value = "Authorization") String authorization) { + public String syncDataToAmrit(@RequestBody String requestOBJ,@RequestHeader(value = "jwttoken") String authorization) { OutputResponse response = new OutputResponse(); try { if (requestOBJ != null) { diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index d5dd5a1b..dffbf5df 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -328,6 +328,7 @@ public String mapHealthIDToBeneficiary(String authorization, HashMap header = new HashMap<>(); header.put("Authorization", authorization); + String responseStr = utils.post( fhirUrl+ConfigProperties.getPropertyByName("mapHealthIDToBeneficiary"), new Gson().toJson(requestMap), From ec021c4a27c03aa0406a141264a168af92c85b5e Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 17:48:36 +0530 Subject: [PATCH 15/62] abha id add in entity class --- .../common/identity/service/rmnch/RmnchDataSyncServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index dffbf5df..c7f9e795 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -299,6 +299,7 @@ public String mapHealthIDToBeneficiary(String authorization, String createdBy,String firstName,String lastName,String dob,Integer providerServiceMapId) { try { Map requestMap = new HashMap<>(); + logger.info("authorization:"+authorization); requestMap.put("beneficiaryRegID", benRegID); requestMap.put("beneficiaryID", beneficiaryID); From 5a83005d1895e31443170e7d47adffd24cfef1b3 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 17:59:41 +0530 Subject: [PATCH 16/62] abha id add in entity class --- .../rmnch/RmnchDataSyncServiceImpl.java | 61 +++++++++++++++---- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index c7f9e795..f73a2d98 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -40,6 +40,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -76,6 +77,8 @@ import com.iemr.common.identity.utils.exception.IEMRException; import com.iemr.common.identity.utils.http.HttpUtils; import com.iemr.common.identity.utils.mapper.InputMapper; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; @Service @Qualifier("rmnchServiceImpl") @@ -298,8 +301,11 @@ public String mapHealthIDToBeneficiary(String authorization, String abhaId, String createdBy,String firstName,String lastName,String dob,Integer providerServiceMapId) { try { + RestTemplate restTemplate = new RestTemplate(); + + logger.info("Authorization Token : {}", authorization); + Map requestMap = new HashMap<>(); - logger.info("authorization:"+authorization); requestMap.put("beneficiaryRegID", benRegID); requestMap.put("beneficiaryID", beneficiaryID); @@ -324,21 +330,50 @@ public String mapHealthIDToBeneficiary(String authorization, requestMap.put("ABHAProfile", abhaProfile); - HttpUtils utils = new HttpUtils(); + String requestBody = new Gson().toJson(requestMap); + + String url = fhirUrl + + ConfigProperties.getPropertyByName("mapHealthIDToBeneficiary"); + + logger.info("Calling URL : {}", url); + logger.info("Request Body : {}", requestBody); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // Agar token me Bearer nahi aa raha hai + if (authorization != null && !authorization.startsWith("Bearer ")) { + authorization = "Bearer " + authorization; + } + + headers.set("Authorization", authorization); + + HttpEntity entity = + new HttpEntity<>(requestBody, headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.POST, + entity, + String.class + ); + + logger.info("ABHA Mapping Response : {}", response.getBody()); + + return response.getBody(); + + } catch (HttpClientErrorException e) { + + logger.error("HTTP Error Status : {}", e.getStatusCode()); + logger.error("HTTP Error Response : {}", e.getResponseBodyAsString(), e); + + return "HTTP Error : " + e.getStatusCode(); - HashMap header = new HashMap<>(); - header.put("Authorization", authorization); + } catch (Exception e) { + logger.error("Error while saving Health ID Mapping", e); - String responseStr = utils.post( - fhirUrl+ConfigProperties.getPropertyByName("mapHealthIDToBeneficiary"), - new Gson().toJson(requestMap), - header); - logger.info("Save abha id in health mapping:"+responseStr.toString()); - return responseStr; - }catch (Exception e){ - logger.info("Error Save Health Id"+e); - return "Error Save Health Id: "+e.getMessage(); + return "Error Save Health Id : " + e.getMessage(); } } From 32db7e19a23ca42fc02d06830a8ab14d20a84452 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 18:18:37 +0530 Subject: [PATCH 17/62] abha id add in entity class --- .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index f73a2d98..131d55f4 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -340,13 +340,9 @@ public String mapHealthIDToBeneficiary(String authorization, HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - // Agar token me Bearer nahi aa raha hai - if (authorization != null && !authorization.startsWith("Bearer ")) { - authorization = "Bearer " + authorization; - } - headers.set("Authorization", authorization); + headers.set("Jwttoken", authorization); HttpEntity entity = new HttpEntity<>(requestBody, headers); From a90fe132d315f1822b9fac9e9affcb5b05acfc0c Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 18:32:16 +0530 Subject: [PATCH 18/62] abha id add in entity class --- .../service/rmnch/RmnchDataSyncServiceImpl.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 131d55f4..e11aba38 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -24,6 +24,7 @@ import java.math.BigInteger; import java.sql.Date; import java.sql.Timestamp; +import java.text.SimpleDateFormat; import java.time.Period; import java.util.ArrayList; import java.util.Arrays; @@ -302,7 +303,17 @@ public String mapHealthIDToBeneficiary(String authorization, String createdBy,String firstName,String lastName,String dob,Integer providerServiceMapId) { try { RestTemplate restTemplate = new RestTemplate(); - + String formattedDob = dob; + + try { + if (dob != null && dob.contains(" ")) { + Timestamp timestamp = Timestamp.valueOf(dob); + formattedDob = new SimpleDateFormat("dd-MM-yyyy") + .format(timestamp); + } + } catch (Exception ex) { + logger.warn("DOB format conversion failed, sending original DOB : {}", dob); + } logger.info("Authorization Token : {}", authorization); Map requestMap = new HashMap<>(); @@ -326,7 +337,8 @@ public String mapHealthIDToBeneficiary(String authorization, abhaProfile.put("firstName", firstName); abhaProfile.put("middleName", ""); abhaProfile.put("lastName", lastName); - abhaProfile.put("dob", dob); + abhaProfile.put("dob", formattedDob); + requestMap.put("ABHAProfile", abhaProfile); From 8185f5a9e1264295b0ffd08858b1382833719bc5 Mon Sep 17 00:00:00 2001 From: Sehjot Singh Pannu Date: Thu, 18 Jun 2026 17:09:32 +0530 Subject: [PATCH 19/62] feat(STOP-148): add GPS location capture support for beneficiary and RMNCH records Introduces GPS-related fields (gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable, and gpsUnavailableReason) across beneficiary address and RMNCH domain models, including MBeneficiaryaddress, Address DTO, RMNCHBeneficiaryDetailsRmnch, and RMNCHHouseHoldDetails. Enhances IdentityMapper and IdentityService to map and persist GPS information between incoming DTOs and beneficiary address entities. Updates RmnchDataSyncServiceImpl to extract and synchronize GPS details from the nested i_bendemographics payload during beneficiary sync, and to parse gpsTimestamp from household details during RMNCH household data processing. --- .../rmnch/RMNCHBeneficiaryDetailsRmnch.java | 24 ++ .../data/rmnch/RMNCHHouseHoldDetails.java | 69 +++-- .../iemr/common/identity/domain/Address.java | 8 + .../identity/domain/MBeneficiaryaddress.java | 60 ++-- .../identity/mapper/IdentityMapper.java | 84 +++--- .../identity/service/IdentityService.java | 262 +++++++++--------- .../rmnch/RmnchDataSyncServiceImpl.java | 49 +++- 7 files changed, 346 insertions(+), 210 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index 4cab210c..0477fd4a 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -550,4 +550,28 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose private Boolean isDeactivate; + @Expose + @Column(name = "gpsLatitude") + private Double gpsLatitude; + + @Expose + @Column(name = "gpsLongitude") + private Double gpsLongitude; + + @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; + } diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index 8aa8ed47..b73f9b00 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -1,24 +1,24 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ package com.iemr.common.identity.data.rmnch; import java.sql.Timestamp; @@ -31,11 +31,12 @@ import jakarta.persistence.Table; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; import lombok.Data; /** - * + * * @author de40034072 * */ @@ -359,4 +360,30 @@ public class RMNCHHouseHoldDetails { @Column(name = "mohallaName") private String mohallaName; + @Expose + @SerializedName("latitude") + @Column(name = "gpsLatitude") + private Double gpsLatitude; + + @Expose + @SerializedName("longitude") + @Column(name = "gpsLongitude") + private Double gpsLongitude; + + @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; + } diff --git a/src/main/java/com/iemr/common/identity/domain/Address.java b/src/main/java/com/iemr/common/identity/domain/Address.java index c37b4507..dfce1672 100644 --- a/src/main/java/com/iemr/common/identity/domain/Address.java +++ b/src/main/java/com/iemr/common/identity/domain/Address.java @@ -23,6 +23,8 @@ import lombok.Data; +import java.sql.Timestamp; + public @Data class Address { private String addrLine1; private String addrLine2; @@ -51,4 +53,10 @@ private Integer vanID; private Integer parkingPlaceID; + private Double gpsLatitude; + private Double gpsLongitude; + private String digipin; + private Timestamp gpsTimestamp; + private Boolean isGpsUnavailable; + private String gpsUnavailableReason; } diff --git a/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java b/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java index 0e2ab415..d5eaf7bd 100644 --- a/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java +++ b/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java @@ -1,24 +1,24 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ package com.iemr.common.identity.domain; import java.io.Serializable; @@ -39,7 +39,7 @@ /** * The persistent class for the m_beneficiaryaddress database table. - * + * */ @Entity @Table(name = "i_beneficiaryaddress") @@ -260,6 +260,24 @@ public class MBeneficiaryaddress implements Serializable { // END OF new column added for data sync + @Column(name = "gpsLatitude") + private Double gpsLatitude; + + @Column(name = "gpsLongitude") + private Double gpsLongitude; + + @Column(name = "digipin") + private String digipin; + + @Column(name = "gpsTimestamp") + private Timestamp gpsTimestamp; + + @Column(name = "isGpsUnavailable", nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0") + private Boolean isGpsUnavailable = false; + + @Column(name = "gpsUnavailableReason") + private String gpsUnavailableReason; + public MBeneficiaryaddress setCurrentAddress(Address address) { this.currAddrLine1 = address.getAddrLine1(); this.currAddrLine2 = address.getAddrLine2(); diff --git a/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java b/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java index bc3ad686..765acb97 100644 --- a/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java +++ b/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java @@ -1,24 +1,24 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ package com.iemr.common.identity.mapper; import java.sql.Timestamp; @@ -66,7 +66,7 @@ public interface IdentityMapper { MBeneficiarymapping identityDTOToMBeneficiarymapping(IdentityDTO dto); - + @Mapping(source = "defaultNo", target = "shareAnonymousWithGovt") @Mapping(source = "defaultNo", target = "shareAnonymousWithMedicalCommunity") @@ -93,7 +93,7 @@ public interface IdentityMapper { MBeneficiaryconsent identityDTOToDefaultMBeneficiaryconsent(IdentityDTO dto, Boolean defaultYes, Boolean defaultNo); - + // @Mapping(source = "dto.areaId", target = "areaId") // @Mapping(source = "dto.beneficiaryRegId", target = "beneficiaryRegID") @@ -144,7 +144,7 @@ public interface IdentityMapper { // @Mapping(source = "dto.vanID", target = "vanID") // @Mapping(source = "dto.parkingPlaceId", target = "parkingPlaceID") // MBeneficiarydetail identityDTOToMBeneficiarydetail(IdentityDTO dto); - + @Mapping(source = "benFamilyDTO.isEmergencyContact", target = "isEmergencyContact") @Mapping(source = "benFamilyDTO.relationshipToSelf", target = "relationshipToSelf") @@ -152,7 +152,7 @@ public interface IdentityMapper { @Mapping(source = "createdBy", target = "createdBy") @Mapping(source = "createdDate", target = "createdDate") MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO benFamilyDTO, String createdBy, - Timestamp createdDate); + Timestamp createdDate); List identityDTOListToMBeneficiaryfamilymappingList(List list); @@ -210,6 +210,12 @@ MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO be @Mapping(target = "currentAddress.village", source = "map.MBeneficiaryaddress.currVillage") @Mapping(target = "currentAddress.addressValue", source = "map.MBeneficiaryaddress.currAddressValue") @Mapping(target = "currentAddress.pinCode", source = "map.MBeneficiaryaddress.currPinCode") + @Mapping(target = "currentAddress.gpsLatitude", source = "map.MBeneficiaryaddress.gpsLatitude") + @Mapping(target = "currentAddress.gpsLongitude", source = "map.MBeneficiaryaddress.gpsLongitude") + @Mapping(target = "currentAddress.digipin", source = "map.MBeneficiaryaddress.digipin") + @Mapping(target = "currentAddress.gpsTimestamp", source = "map.MBeneficiaryaddress.gpsTimestamp") + @Mapping(target = "currentAddress.isGpsUnavailable", source = "map.MBeneficiaryaddress.isGpsUnavailable") + @Mapping(target = "currentAddress.gpsUnavailableReason", source = "map.MBeneficiaryaddress.gpsUnavailableReason") @Mapping(target = "emergencyAddress.addrLine1", source = "map.MBeneficiaryaddress.emerAddrLine1") @Mapping(target = "emergencyAddress.addrLine2", source = "map.MBeneficiaryaddress.emerAddrLine2") @Mapping(target = "emergencyAddress.addrLine3", source = "map.MBeneficiaryaddress.emerAddrLine3") @@ -296,13 +302,13 @@ MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO be @Mapping(target = "beneficiaryDetails.title", source = "map.MBeneficiarydetail.title") @Mapping(target = "beneficiaryDetails.zoneId", source = "map.MBeneficiarydetail.zoneId") @Mapping(target = "contacts", expression = "java( map != null && map.getMBeneficiarycontact() != null && " - + "map.getMBeneficiarydetail() != null ? " - + "Phone.createContactList(map.getMBeneficiarycontact(), " - + "(benRegId != null ? benRegId.toString() : null), " - + "(map.getMBeneficiarydetail().getFirstName() != null ? map.getMBeneficiarydetail().getFirstName() : \"\") + \" \" + " - + "(map.getMBeneficiarydetail().getMiddleName() != null ? map.getMBeneficiarydetail().getMiddleName() : \"\") + \" \" + " - + "(map.getMBeneficiarydetail().getLastName() != null ? map.getMBeneficiarydetail().getLastName() : \"\") " - + ") : null)") + + "map.getMBeneficiarydetail() != null ? " + + "Phone.createContactList(map.getMBeneficiarycontact(), " + + "(map.getBenRegId() != null ? map.getBenRegId().toString() : null), " + + "(map.getMBeneficiarydetail().getFirstName() != null ? map.getMBeneficiarydetail().getFirstName() : \"\") + \" \" + " + + "(map.getMBeneficiarydetail().getMiddleName() != null ? map.getMBeneficiarydetail().getMiddleName() : \"\") + \" \" + " + + "(map.getMBeneficiarydetail().getLastName() != null ? map.getMBeneficiarydetail().getLastName() : \"\") " + + ") : null)") @Mapping(target = "permanentAddress.zoneID", source = "map.MBeneficiaryaddress.permZoneID") @Mapping(target = "permanentAddress.zoneName", source = "map.MBeneficiaryaddress.permZone") @@ -334,13 +340,13 @@ MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO be @Mapping(target = "accountNo", source = "map.MBeneficiaryAccount.accountNo") @Mapping(target = "benAccountID", source = "map.benAccountID") @Mapping(target = "ageAtMarriage", expression = "java(map != null && map.getMBeneficiarydetail() != null ? " - + "MBeneficiarydetail.getAgeAtMarriageCalc(map.getMBeneficiarydetail().getDob(), " - + "map.getMBeneficiarydetail().getMarriageDate(), " - + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") + + "MBeneficiarydetail.getAgeAtMarriageCalc(map.getMBeneficiarydetail().getDob(), " + + "map.getMBeneficiarydetail().getMarriageDate(), " + + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") @Mapping(target = "marriageDate", expression = "java(map != null && map.getMBeneficiarydetail() != null ? " - + "MBeneficiarydetail.getMarriageDateCalc(map.getMBeneficiarydetail().getDob(), " - + "map.getMBeneficiarydetail().getMarriageDate(), " - + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") + + "MBeneficiarydetail.getMarriageDateCalc(map.getMBeneficiarydetail().getDob(), " + + "map.getMBeneficiarydetail().getMarriageDate(), " + + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") @Mapping(target = "literacyStatus", source = "map.MBeneficiarydetail.literacyStatus") @Mapping(target = "motherName", source = "map.MBeneficiarydetail.motherName") @@ -481,7 +487,7 @@ List mapToMBeneficiaryfamilymappingWithBenFamilyDTOList( @Mapping(source = "dto.createdDate", target = "createdDate") @Mapping(source = "dto.vanID", target = "vanID") @Mapping(source = "dto.parkingPlaceId", target = "parkingPlaceID") - // End + // End MBeneficiaryAccount identityDTOToMBeneficiaryAccount(IdentityDTO dto); @InheritInverseConfiguration @@ -490,7 +496,7 @@ List mapToMBeneficiaryfamilymappingWithBenFamilyDTOList( @Mapping(source = "dto.benImage", target = "benImage") @Mapping(source = "dto.agentName", target = "createdBy") @Mapping(source = "dto.createdDate", target = "createdDate") - + @Mapping(source = "dto.vanID", target = "vanID") @Mapping(source = "dto.parkingPlaceId", target = "parkingPlaceID") diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 6e8a71ef..212e8b5c 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -1,23 +1,23 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. */ package com.iemr.common.identity.service; @@ -229,7 +229,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null || list3.get(i).getBeneficiaryDetails().getFirstName() == null || !list3.get(i).getBeneficiaryDetails().getFirstName() - .equalsIgnoreCase(searchDTO.getFirstName())) { + .equalsIgnoreCase(searchDTO.getFirstName())) { list3.remove(i); i--; @@ -241,7 +241,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null || list3.get(i).getBeneficiaryDetails().getLastName() == null || !list3.get(i).getBeneficiaryDetails().getLastName() - .equalsIgnoreCase(searchDTO.getLastName())) { + .equalsIgnoreCase(searchDTO.getLastName())) { list3.remove(i); i--; @@ -286,7 +286,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null || list3.get(i).getCurrentAddress().getDistrictId() == null || !list3.get(i).getCurrentAddress().getDistrictId() - .equals(searchDTO.getCurrentAddress().getDistrictId())) { + .equals(searchDTO.getCurrentAddress().getDistrictId())) { list3.remove(i); i--; @@ -298,7 +298,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null || list3.get(i).getCurrentAddress().getVillageId() == null || !list3.get(i).getCurrentAddress().getVillageId() - .equals(searchDTO.getCurrentAddress().getVillageId())) { + .equals(searchDTO.getCurrentAddress().getVillageId())) { list3.remove(i); i--; @@ -515,7 +515,7 @@ public List getBeneficiariesByBenId(BigInteger benId) /** * - * @param BenRegId + * @param benRegId * @return */ public List getBeneficiariesByBenRegId(BigInteger benRegId) @@ -591,104 +591,104 @@ public List getBeneficiariesByPhoneNum(String phoneNum) } -/** - * Advanced search using Elasticsearch with fallback to database - */ -public Map advancedSearchBeneficiariesES( - String firstName, String middleName, String lastName, Integer genderId, java.util.Date dob, - Integer stateId, Integer districtId, Integer blockId, Integer villageId, - String fatherName, String spouseName, String maritalStatus, String phoneNumber, - String beneficiaryId, String healthId, String aadharNo, - Integer userId, String auth, Boolean is1097) throws Exception { - - try { - logger.info("IdentityService.advancedSearchBeneficiariesES - start"); - logger.info("ES enabled: {}", esEnabled); - - Map response = new HashMap<>(); - - if (esEnabled) { - logger.info("Using Elasticsearch for advanced search"); - - // Call Elasticsearch service - List> esResults = elasticsearchService.advancedSearch( - firstName, middleName, lastName, genderId, dob, stateId, districtId, - blockId, villageId, fatherName, spouseName, maritalStatus, phoneNumber, - beneficiaryId, healthId, aadharNo, userId - ); - - response.put("data", esResults); - response.put("count", esResults.size()); - response.put("source", "elasticsearch"); - - logger.info("ES returned {} results", esResults.size()); - - } else { - logger.info("ES disabled - using database for advanced search"); - - IdentitySearchDTO searchDTO = new IdentitySearchDTO(); - searchDTO.setFirstName(firstName); - searchDTO.setLastName(lastName); - searchDTO.setGenderId(genderId); - searchDTO.setDob(dob != null ? new Timestamp(dob.getTime()) : null); - searchDTO.setFatherName(fatherName); - searchDTO.setSpouseName(spouseName); - searchDTO.setContactNumber(phoneNumber); - - if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { - try { - searchDTO.setBeneficiaryId(new BigInteger(beneficiaryId)); - } catch (NumberFormatException e) { - logger.warn("Invalid beneficiaryId format: {}", beneficiaryId); + /** + * Advanced search using Elasticsearch with fallback to database + */ + public Map advancedSearchBeneficiariesES( + String firstName, String middleName, String lastName, Integer genderId, Date dob, + Integer stateId, Integer districtId, Integer blockId, Integer villageId, + String fatherName, String spouseName, String maritalStatus, String phoneNumber, + String beneficiaryId, String healthId, String aadharNo, + Integer userId, String auth, Boolean is1097) throws Exception { + + try { + logger.info("IdentityService.advancedSearchBeneficiariesES - start"); + logger.info("ES enabled: {}", esEnabled); + + Map response = new HashMap<>(); + + if (esEnabled) { + logger.info("Using Elasticsearch for advanced search"); + + // Call Elasticsearch service + List> esResults = elasticsearchService.advancedSearch( + firstName, middleName, lastName, genderId, dob, stateId, districtId, + blockId, villageId, fatherName, spouseName, maritalStatus, phoneNumber, + beneficiaryId, healthId, aadharNo, userId + ); + + response.put("data", esResults); + response.put("count", esResults.size()); + response.put("source", "elasticsearch"); + + logger.info("ES returned {} results", esResults.size()); + + } else { + logger.info("ES disabled - using database for advanced search"); + + IdentitySearchDTO searchDTO = new IdentitySearchDTO(); + searchDTO.setFirstName(firstName); + searchDTO.setLastName(lastName); + searchDTO.setGenderId(genderId); + searchDTO.setDob(dob != null ? new Timestamp(dob.getTime()) : null); + searchDTO.setFatherName(fatherName); + searchDTO.setSpouseName(spouseName); + searchDTO.setContactNumber(phoneNumber); + + if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { + try { + searchDTO.setBeneficiaryId(new BigInteger(beneficiaryId)); + } catch (NumberFormatException e) { + logger.warn("Invalid beneficiaryId format: {}", beneficiaryId); + } } + + if (stateId != null || districtId != null || blockId != null || villageId != null) { + Address addressDTO = new Address(); + addressDTO.setStateId(stateId); + addressDTO.setDistrictId(districtId); + addressDTO.setSubDistrictId(blockId); + addressDTO.setVillageId(villageId); + searchDTO.setCurrentAddress(addressDTO); + } + + List dbResults = this.getBeneficiaries(searchDTO); + + List> formattedResults = dbResults.stream() + .map(this::convertBeneficiaryDTOToMap) + .collect(Collectors.toList()); + + response.put("data", formattedResults); + response.put("count", formattedResults.size()); + response.put("source", "database"); + + logger.info("Database returned {} results", formattedResults.size()); } - - if (stateId != null || districtId != null || blockId != null || villageId != null) { - Address addressDTO = new Address(); - addressDTO.setStateId(stateId); - addressDTO.setDistrictId(districtId); - addressDTO.setSubDistrictId(blockId); - addressDTO.setVillageId(villageId); - searchDTO.setCurrentAddress(addressDTO); - } - - List dbResults = this.getBeneficiaries(searchDTO); - - List> formattedResults = dbResults.stream() - .map(this::convertBeneficiaryDTOToMap) - .collect(Collectors.toList()); - - response.put("data", formattedResults); - response.put("count", formattedResults.size()); - response.put("source", "database"); - - logger.info("Database returned {} results", formattedResults.size()); - } - - logger.info("IdentityService.advancedSearchBeneficiariesES - end"); - return response; - - } catch (Exception e) { - logger.error("Advanced search failed: {}", e.getMessage(), e); - throw new Exception("Error in advanced search: " + e.getMessage(), e); - } -}/** - * Convert BeneficiariesDTO to Map format - */ -private Map convertBeneficiaryDTOToMap(BeneficiariesDTO dto) { - try { - ObjectMapper mapper = new ObjectMapper(); - String json = mapper.writeValueAsString(dto); - return mapper.readValue(json, Map.class); - } catch (Exception e) { - logger.error("Error converting DTO to map", e); - return new HashMap<>(); + + logger.info("IdentityService.advancedSearchBeneficiariesES - end"); + return response; + + } catch (Exception e) { + logger.error("Advanced search failed: {}", e.getMessage(), e); + throw new Exception("Error in advanced search: " + e.getMessage(), e); + } + }/** + * Convert BeneficiariesDTO to Map format + */ + private Map convertBeneficiaryDTOToMap(BeneficiariesDTO dto) { + try { + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(dto); + return mapper.readValue(json, Map.class); + } catch (Exception e) { + logger.error("Error converting DTO to map", e); + return new HashMap<>(); + } } -} - /** + /** * * * * Search beneficiary by healthID / ABHA address @@ -763,7 +763,7 @@ public List searhBeneficiaryByFamilyId(String familyId) List benDetailsList = detailRepo.searchByFamilyId(familyId); if (benDetailsList == null || benDetailsList.isEmpty()) { - return beneficiaryList; + return beneficiaryList; }else { // considering as of now family creation is possible through facility modules // only @@ -789,7 +789,7 @@ public List searhBeneficiaryByFamilyId(String familyId) } public List searchBeneficiaryByVillageIdAndLastModifyDate(List villageIDs, - Timestamp lastModifiedDate) { + Timestamp lastModifiedDate) { List beneficiaryList = new ArrayList<>(); try { @@ -833,7 +833,7 @@ public List searhBeneficiaryByGovIdentity(String identity) // find benmap ids if (benIdentityList == null || benIdentityList.isEmpty()) { - return beneficiaryList; + return beneficiaryList; }else { for (MBeneficiaryidentity identityObj : benIdentityList) { benMapObjArr.addAll( @@ -1114,7 +1114,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiaryaddress().getBenAddressID(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benAddressID != null) { - mbAddr.setBenAddressID(benAddressID); + mbAddr.setBenAddressID(benAddressID); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1141,7 +1141,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiarycontact().getBenContactsID(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benContactsID != null) { - benCon.setBenContactsID(benContactsID); + benCon.setBenContactsID(benContactsID); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1259,7 +1259,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiaryAccount().getBenAccountID(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benAccountID != null) { - beneficiaryAccount.setBenAccountID(benAccountID); + beneficiaryAccount.setBenAccountID(benAccountID); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1284,7 +1284,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiaryImage().getBenImageId(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benImageId != null) { - beneficiaryImage.setBenImageId(benImageId); + beneficiaryImage.setBenImageId(benImageId); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1304,11 +1304,11 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields logger.info("Triggering Elasticsearch sync for benRegId: {}", identity.getBeneficiaryRegId()); syncService.syncBeneficiaryAsync(identity.getBeneficiaryRegId()); } - - logger.info("IdentityService.editIdentity - end. id = " + benMapping.getBenMapId()); -} - + logger.info("IdentityService.editIdentity - end. id = " + benMapping.getBenMapId()); + } + + private MBeneficiarydetail convertIdentityEditDTOToMBeneficiarydetail(IdentityEditDTO dto) { MBeneficiarydetail beneficiarydetail = new MBeneficiarydetail(); @@ -1819,6 +1819,12 @@ private MBeneficiaryaddress identityDTOToMBeneficiaryaddress(IdentityDTO dto) { beneficiaryAddress.setCurrServicePointId(dto.getCurrentAddress().getServicePointID()); beneficiaryAddress.setCurrServicePoint(dto.getCurrentAddress().getServicePointName()); beneficiaryAddress.setCurrHabitation(dto.getCurrentAddress().getHabitation()); + beneficiaryAddress.setGpsLatitude(dto.getCurrentAddress().getGpsLatitude()); + beneficiaryAddress.setGpsLongitude(dto.getCurrentAddress().getGpsLongitude()); + beneficiaryAddress.setDigipin(dto.getCurrentAddress().getDigipin()); + beneficiaryAddress.setGpsTimestamp(dto.getCurrentAddress().getGpsTimestamp()); + beneficiaryAddress.setIsGpsUnavailable(dto.getCurrentAddress().getIsGpsUnavailable()); + beneficiaryAddress.setGpsUnavailableReason(dto.getCurrentAddress().getGpsUnavailableReason()); } if (dto.getEmergencyAddress() != null) { beneficiaryAddress.setEmerAddrLine1(dto.getEmergencyAddress().getAddrLine1()); @@ -1929,7 +1935,7 @@ public String unReserveIdentity(ReserveIdentityDTO unReserve) { * Get partial details of beneficiaries (first name middle name and last * name) list on benId's list * - * @param BenRegIds + * @param benRegIds * @return {@link List} Beneficiaries */ public List getBeneficiariesPartialDeatilsByBenRegIdList(List benRegIds) { diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 8b1e73da..2470649b 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -23,6 +23,7 @@ import java.math.BigInteger; import java.sql.Date; +import java.sql.Timestamp; import java.time.Period; import java.util.ArrayList; import java.util.Arrays; @@ -42,6 +43,7 @@ import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -140,10 +142,37 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { // benRegID = rMNCHMBenRegIdMapRepo.getRegID(benDetailsExtraList.get(0).getBenficieryid()); // // if (benRegID != null) { - + + // Build GPS lookup map from i_bendemographics in raw JSON + Map benGpsMap = new HashMap<>(); + JsonArray benJsonArr = jsnOBJ.getAsJsonArray("beneficiaryDetails"); + for (JsonElement el : benJsonArr) { + JsonObject benJson = el.getAsJsonObject(); + if (benJson.has("benficieryid") && !benJson.get("benficieryid").isJsonNull() + && benJson.has("i_bendemographics") + && !benJson.get("i_bendemographics").isJsonNull()) { + benGpsMap.put(benJson.get("benficieryid").getAsBigInteger(), + benJson.getAsJsonObject("i_bendemographics")); + } + } + for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsExtraList) { benRegID = rMNCHMBenRegIdMapRepo.getRegID(obj.getBenficieryid()); obj.setBenRegId(benRegID); + // Extract GPS from i_bendemographics + JsonObject demog = benGpsMap.get(obj.getBenficieryid()); + if (demog != null) { + if (demog.has("latitude") && !demog.get("latitude").isJsonNull()) + obj.setGpsLatitude(demog.get("latitude").getAsDouble()); + if (demog.has("longitude") && !demog.get("longitude").isJsonNull()) + obj.setGpsLongitude(demog.get("longitude").getAsDouble()); + if (demog.has("digipin") && !demog.get("digipin").isJsonNull()) + obj.setDigipin(demog.get("digipin").getAsString()); + if (demog.has("gpsTimestamp") && !demog.get("gpsTimestamp").isJsonNull()) + obj.setGpsTimestamp(new Timestamp(demog.get("gpsTimestamp").getAsLong())); + if (demog.has("isGpsUnavailable") && !demog.get("isGpsUnavailable").isJsonNull()) + obj.setIsGpsUnavailable(demog.get("isGpsUnavailable").getAsBoolean()); + } if(!rMNCHBeneficiaryDetailsRmnchRepo .getByRegID(benRegID).isEmpty()){ RMNCHBeneficiaryDetailsRmnch temp = rMNCHBeneficiaryDetailsRmnchRepo @@ -240,6 +269,22 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { .fromJson(jsnOBJ.get("houseHoldDetails"), RMNCHHouseHoldDetails[].class); List houseHoldList = Arrays.asList(objArr3); + // Build gpsTimestamp map (sent as string, needs manual parse) + Map hhTimestampMap = new HashMap<>(); + JsonArray hhJsonArr = jsnOBJ.getAsJsonArray("houseHoldDetails"); + for (JsonElement el : hhJsonArr) { + JsonObject hhJson = el.getAsJsonObject(); + try { + if (hhJson.has("houseoldId") && !hhJson.get("houseoldId").isJsonNull() + && hhJson.has("gpsTimestamp") + && !hhJson.get("gpsTimestamp").isJsonNull()) { + hhTimestampMap.put( + Long.parseLong(hhJson.get("houseoldId").getAsString()), + hhJson.get("gpsTimestamp").getAsLong()); + } + } catch (NumberFormatException ignored) {} + } + for (RMNCHHouseHoldDetails obj : houseHoldList) { if(!rMNCHHouseHoldDetailsRepo .getByHouseHoldID(obj.getHouseoldId()).isEmpty()){ @@ -247,6 +292,8 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { .getByHouseHoldID(obj.getHouseoldId()).get(0); if (temp != null) obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); + if (hhTimestampMap.containsKey(obj.getHouseoldId())) + obj.setGpsTimestamp(new Timestamp(hhTimestampMap.get(obj.getHouseoldId()))); } } From c520cdcab2d6c5e2d03f1ae7f5475ea18e7ffa61 Mon Sep 17 00:00:00 2001 From: Sehjot Singh Pannu Date: Thu, 18 Jun 2026 17:09:32 +0530 Subject: [PATCH 20/62] feat(STOP-148): add GPS location capture support for beneficiary and RMNCH records Introduces GPS-related fields (gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable, and gpsUnavailableReason) across beneficiary address and RMNCH domain models, including MBeneficiaryaddress, Address DTO, RMNCHBeneficiaryDetailsRmnch, and RMNCHHouseHoldDetails. Enhances IdentityMapper and IdentityService to map and persist GPS information between incoming DTOs and beneficiary address entities. Updates RmnchDataSyncServiceImpl to extract and synchronize GPS details from the nested i_bendemographics payload during beneficiary sync, and to parse gpsTimestamp from household details during RMNCH household data processing. --- .../rmnch/RMNCHBeneficiaryDetailsRmnch.java | 39 +++++++++++++++++ .../common/identity/repo/BenDetailRepo.java | 5 +++ .../rmnch/RmnchDataSyncServiceImpl.java | 43 ++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index 0477fd4a..55de4dc1 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -574,4 +574,43 @@ public class RMNCHBeneficiaryDetailsRmnch { @Column(name = "gpsUnavailableReason") private String gpsUnavailableReason; + @Expose + @Column(name = "gpsLatitude") + private Double gpsLatitude; + + @Expose + @Column(name = "gpsLongitude") + private Double gpsLongitude; + + @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; + + // Anthropometry fields sent by Stop TB mobile app via beneficiaryDetails payload. + // i_beneficiarydetails_rmnch has no these columns — stored in i_beneficiarydetails.otherFields instead. + @Expose + @Transient + private Double height; + @Expose + @Transient + private Double weight; + @Expose + @Transient + private Double bmi; + @Expose + @Transient + private Double temperature; // stored as "temperatureValue" in otherFields to match getBeneficiaryData key + } diff --git a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java index 4e8bcc01..413298ba 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java @@ -148,6 +148,11 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi @Query("SELECT b FROM MBeneficiarydetail b WHERE b.familyId =:familyid ") List searchByFamilyId(@Param("familyid") String familyid); + @Transactional + @Modifying + @Query("UPDATE MBeneficiarydetail d SET d.otherFields = :otherFields WHERE d.mBeneficiarymapping.benRegId = :benRegId") + int updateOtherFieldsByBenRegId(@Param("benRegId") BigInteger benRegId, @Param("otherFields") String otherFields); + /** * Find complete beneficiary data by IDs from Elasticsearch */ diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 2470649b..bbcca840 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -71,6 +71,8 @@ import com.iemr.common.identity.repo.rmnch.RMNCHCBACDetailsRepo; import com.iemr.common.identity.repo.rmnch.RMNCHHouseHoldDetailsRepo; import com.iemr.common.identity.repo.rmnch.RMNCHMBenMappingRepo; +import com.iemr.common.identity.domain.MBeneficiarydetail; +import com.iemr.common.identity.repo.BenDetailRepo; import com.iemr.common.identity.repo.rmnch.RMNCHMBenRegIdMapRepo; import com.iemr.common.identity.utils.config.ConfigProperties; import com.iemr.common.identity.utils.exception.IEMRException; @@ -109,7 +111,8 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHBenContactRepo rMNCHBenContactRepo; @Autowired private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; - + @Autowired + private BenDetailRepo benDetailRepo; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ) throws Exception { @@ -223,6 +226,22 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { // update beneficiary data in i_beneficiarydetails table rMNCHBenDetailsRepo.saveAll(benDetailsList); + // Write anthropometry (height/weight/bmi/temperature) to i_beneficiarydetails.otherFields. + // i_beneficiarydetails_rmnch has no these columns; FLW-API getBeneficiaryData reads from otherFields. + for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsExtraList) { + if (obj.getBenRegId() != null && hasAnthropometryData(obj)) { + try { + MBeneficiarydetail benDetail = benDetailRepo.findByBenRegId(obj.getBenRegId()); + if (benDetail != null) { + String merged = mergeAnthropometry(benDetail.getOtherFields(), obj); + benDetailRepo.updateOtherFieldsByBenRegId(obj.getBenRegId(), merged); + } + } catch (Exception ex) { + logger.warn("Failed to update otherFields for benRegId: " + obj.getBenRegId() + " - " + ex.getMessage()); + } + } + } + // born birth details if (jsnOBJ != null && jsnOBJ.has("bornBirthDeatils")) { RMNCHBornBirthDetails[] objArr1 = InputMapper.gson() @@ -323,6 +342,28 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { return new Gson().toJson(resultMap); } + + private boolean hasAnthropometryData(RMNCHBeneficiaryDetailsRmnch obj) { + return obj.getHeight() != null || obj.getWeight() != null + || obj.getBmi() != null || obj.getTemperature() != null; + } + + private String mergeAnthropometry(String existingOtherFields, RMNCHBeneficiaryDetailsRmnch obj) { + JsonObject json = new JsonObject(); + if (existingOtherFields != null && !existingOtherFields.isBlank()) { + try { + json = new JsonParser().parse(existingOtherFields).getAsJsonObject(); + } catch (Exception ignored) { + } + } + if (obj.getHeight() != null) json.addProperty("height", obj.getHeight()); + if (obj.getWeight() != null) json.addProperty("weight", obj.getWeight()); + if (obj.getBmi() != null) json.addProperty("bmi", obj.getBmi()); + // mobile sends "temperature"; FLW-API getBeneficiaryData reads "temperatureValue" + if (obj.getTemperature() != null) json.addProperty("temperatureValue", obj.getTemperature()); + return new Gson().toJson(json); + } + @Override public String getBenData(String requestOBJ, String authorisation) throws Exception { String outputResponse = null; From e4b706711487eb7cf356f70feb438a0c83917e8c Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 5 Jun 2026 17:56:21 +0530 Subject: [PATCH 21/62] feat: stamp vanID from Redis on all 4 RMNCH entities in syncDataToAmrit Reads camp:vanID and camp:parkingPlaceID from Redis (written by MMU-API on van login) and stamps them onto RMNCHBeneficiaryDetailsRmnch, RMNCHBornBirthDetails, RMNCHCBACdetails, and RMNCHHouseHoldDetails when the mobile payload carries VanID=null or 0. Also writes anthropometry (height/weight/bmi/temperature) from mobile beneficiaryDetails payload into i_beneficiarydetails.otherFields so FLW-API getBeneficiaryData can return them. Gracefully skips if Redis has no camp configured. Co-Authored-By: Claude Sonnet 4.6 --- .../rmnch/RmnchDataSyncServiceImpl.java | 75 ++++++++++++------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index bbcca840..a35ad27d 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -73,6 +73,7 @@ import com.iemr.common.identity.repo.rmnch.RMNCHMBenMappingRepo; import com.iemr.common.identity.domain.MBeneficiarydetail; import com.iemr.common.identity.repo.BenDetailRepo; +import com.iemr.common.identity.utils.redis.RedisStorage; import com.iemr.common.identity.repo.rmnch.RMNCHMBenRegIdMapRepo; import com.iemr.common.identity.utils.config.ConfigProperties; import com.iemr.common.identity.utils.exception.IEMRException; @@ -113,6 +114,8 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; @Autowired private BenDetailRepo benDetailRepo; + @Autowired + private RedisStorage redisStorage; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ) throws Exception { @@ -124,6 +127,20 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { ArrayList cBACDetailsIds = new ArrayList<>(); ArrayList houseHoldDetailsIds = new ArrayList<>(); + // Read camp vanID/parkingPlaceID from Redis (set by MMU-API on van login) + Integer campVanID = null; + Integer campParkingPlaceID = null; + try { + String vanVal = redisStorage.getObject("camp:vanID", false, 0); + String ppVal = redisStorage.getObject("camp:parkingPlaceID", false, 0); + if (vanVal != null && !vanVal.isBlank()) campVanID = Integer.parseInt(vanVal); + if (ppVal != null && !ppVal.isBlank()) campParkingPlaceID = Integer.parseInt(ppVal); + } catch (Exception ignored) { + // no camp configured — vanID stamping skipped + } + final Integer vanID = campVanID; + final Integer parkingPlaceID = campParkingPlaceID; + try { if (requestOBJ != null && !requestOBJ.isEmpty()) { JsonObject jsnOBJ = new JsonObject(); @@ -199,22 +216,24 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } obj.setRelatedBeneficiaryIdsDB(sb.toString()); } - if(!rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).isEmpty()){ - RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = - rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).get(0); - if (rmnchmBeneficiarydetail != null) { - rmnchmBeneficiarydetail.setFirstName(obj.getFirstName()); - rmnchmBeneficiarydetail.setLastName(obj.getLastName()); - rmnchmBeneficiarydetail.setFatherName(obj.getFatherName()); - rmnchmBeneficiarydetail.setMotherName(obj.getMotherName()); - rmnchmBeneficiarydetail.setDob(obj.getDob()); - rmnchmBeneficiarydetail.setSpousename(obj.getSpousename()); - rmnchmBeneficiarydetail.setGender(obj.getGender()); - rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); - rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); - rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); - benDetailsList.add(rmnchmBeneficiarydetail); - } + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } + RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = + rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()); + if (rmnchmBeneficiarydetail != null) { + rmnchmBeneficiarydetail.setFirstName(obj.getFirstName()); + rmnchmBeneficiarydetail.setLastName(obj.getLastName()); + rmnchmBeneficiarydetail.setFatherName(obj.getFatherName()); + rmnchmBeneficiarydetail.setMotherName(obj.getMotherName()); + rmnchmBeneficiarydetail.setDob(obj.getDob()); + rmnchmBeneficiarydetail.setSpousename(obj.getSpousename()); + rmnchmBeneficiarydetail.setGender(obj.getGender()); + rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); + rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); + rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); + benDetailsList.add(rmnchmBeneficiarydetail); } } @@ -253,6 +272,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { RMNCHBornBirthDetails temp = rMNCHBornBirthDetailsRepo.getByRegID(benRegID); if (temp != null) obj.setBornBirthDeatilsId(temp.getBornBirthDeatilsId()); + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } bornBirthList = (ArrayList) rMNCHBornBirthDetailsRepo .saveAll(bornBirthList); @@ -276,6 +299,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { RMNCHCBACdetails temp = rMNCHCBACDetailsRepo.getByRegID(benRegID); if (temp != null) obj.setCBACDetailsid(temp.getCBACDetailsid()); + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } cbacList = (ArrayList) rMNCHCBACDetailsRepo.saveAll(cbacList); @@ -305,16 +332,14 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } for (RMNCHHouseHoldDetails obj : houseHoldList) { - if(!rMNCHHouseHoldDetailsRepo - .getByHouseHoldID(obj.getHouseoldId()).isEmpty()){ - RMNCHHouseHoldDetails temp = rMNCHHouseHoldDetailsRepo - .getByHouseHoldID(obj.getHouseoldId()).get(0); - if (temp != null) - obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); - if (hhTimestampMap.containsKey(obj.getHouseoldId())) - obj.setGpsTimestamp(new Timestamp(hhTimestampMap.get(obj.getHouseoldId()))); + RMNCHHouseHoldDetails temp = rMNCHHouseHoldDetailsRepo + .getByHouseHoldID(obj.getHouseoldId()); + if (temp != null) + obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); } - } houseHoldList = (ArrayList) rMNCHHouseHoldDetailsRepo .saveAll(houseHoldList); From 75836205efb26d926ec6be75ec6ea7d1dfc1cf1e Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 5 Jun 2026 18:23:47 +0530 Subject: [PATCH 22/62] fix: preserve @Transient anthropometry fields lost after JPA saveAll merge Hibernate merge() returns new managed instances that do not carry @Transient field values (height/weight/bmi/temperature). Keep a reference to the original list before saveAll so the anthropometry loop reads from objects that still have the mobile payload values. Co-Authored-By: Claude Sonnet 4.6 --- .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index a35ad27d..fcaf4329 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -238,6 +238,9 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } + // Keep original list before saveAll — @Transient fields (height/weight/bmi/temperature) + // are lost in the JPA-managed instances returned by merge() + List benDetailsOriginalList = new ArrayList<>(benDetailsExtraList); benDetailsExtraList = (ArrayList) rMNCHBeneficiaryDetailsRmnchRepo .saveAll(benDetailsExtraList); @@ -247,7 +250,7 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { // Write anthropometry (height/weight/bmi/temperature) to i_beneficiarydetails.otherFields. // i_beneficiarydetails_rmnch has no these columns; FLW-API getBeneficiaryData reads from otherFields. - for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsExtraList) { + for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsOriginalList) { if (obj.getBenRegId() != null && hasAnthropometryData(obj)) { try { MBeneficiarydetail benDetail = benDetailRepo.findByBenRegId(obj.getBenRegId()); From 0ad7988224338495d1538cfa712b11e1f5503f5e Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 12 Jun 2026 13:56:10 +0530 Subject: [PATCH 23/62] fix: prevent camp:vanID deletion on every syncDataToAmrit call getObject("camp:vanID", false, 0) was calling EXPIRE key 0 which immediately deletes the key in Redis after every sync. Replaced with getRaw() which reads the value without modifying TTL, so camp:vanID persists across multiple syncs until MMU logout. Co-Authored-By: Claude Sonnet 4.6 --- .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 4 ++-- .../com/iemr/common/identity/utils/redis/RedisStorage.java | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index fcaf4329..7383add3 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -131,8 +131,8 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { Integer campVanID = null; Integer campParkingPlaceID = null; try { - String vanVal = redisStorage.getObject("camp:vanID", false, 0); - String ppVal = redisStorage.getObject("camp:parkingPlaceID", false, 0); + String vanVal = redisStorage.getRaw("camp:vanID"); + String ppVal = redisStorage.getRaw("camp:parkingPlaceID"); if (vanVal != null && !vanVal.isBlank()) campVanID = Integer.parseInt(vanVal); if (ppVal != null && !ppVal.isBlank()) campParkingPlaceID = Integer.parseInt(ppVal); } catch (Exception ignored) { diff --git a/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java b/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java index 04a3f1d8..98ced6fa 100644 --- a/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java @@ -67,6 +67,13 @@ public String getObject(String key, Boolean extendExpirationTime, int expiration return userRespFromRedis; } + public String getRaw(String key) { + RedisConnection redCon = connection.getConnection(); + byte[] data = redCon.get(key.getBytes()); + redCon.close(); + return data != null ? new String(data) : null; + } + public Long deleteObject(String key) throws RedisSessionException { RedisConnection redCon = connection.getConnection(); Long userRespFromRedis = Long.valueOf(0L); From 11ffc75412fb0d3af7c251d64b3ff3193aa061ee Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 16 Jun 2026 16:32:08 +0530 Subject: [PATCH 24/62] feat(stoptb): add stoptb.enforce.vanid flag to syncDataToAmrit The Stop TB mobile app calls this RMNCH sync endpoint directly to save household details. Same issue as elsewhere: vanID stamping is skipped silently when Redis has no camp:vanID, so household records end up with vanID=NULL. When stoptb.enforce.vanid=true, sync now fails with a clear error instead. --- src/main/environment/common_ci.properties | 3 +++ src/main/environment/common_docker.properties | 3 +++ src/main/environment/common_example.properties | 5 +++++ .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 9 +++++++++ 4 files changed, 20 insertions(+) diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index e32dc366..1ce43664 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -22,6 +22,9 @@ fhir-url=@env.FHIR_API@ # Redis Config spring.redis.host=@env.REDIS_HOST@ +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=@env.STOPTB_ENFORCE_VANID@ + cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ # Elasticsearch Configuration diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 07bd53cc..36735700 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -22,6 +22,9 @@ fhir-url=${FHIR_API} # Redis Config spring.redis.host=${REDIS_HOST} +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=${STOPTB_ENFORCE_VANID} + cors.allowed-origins=${CORS_ALLOWED_ORIGINS} # Elasticsearch Configuration diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index b78483f2..dd235c95 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -17,6 +17,11 @@ fhir-url=http://localhost:8093/ # Redis Config spring.redis.host=localhost + +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not +# configured instead of silently skipping vanID stamping +stoptb.enforce.vanid=false + cors.allowed-origins=http://localhost:* # Elasticsearch Configuration diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 7383add3..01feb1b9 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -116,6 +116,11 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private BenDetailRepo benDetailRepo; @Autowired private RedisStorage redisStorage; + + // When true, sync fails loudly if camp is not configured instead of silently + // skipping vanID stamping + @Value("${stoptb.enforce.vanid:false}") + private boolean enforceVanID; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ) throws Exception { @@ -138,6 +143,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } catch (Exception ignored) { // no camp configured — vanID stamping skipped } + if (campVanID == null && enforceVanID) { + throw new Exception( + "Camp not configured: vanID missing. Please select van/service point in MMU before syncing data."); + } final Integer vanID = campVanID; final Integer parkingPlaceID = campParkingPlaceID; From 0dc4c3884abff7fbb3244bf8e7992fe3d53c33be Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 16 Jun 2026 16:33:49 +0530 Subject: [PATCH 25/62] chore: bump version to 3.8.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3b57f1c..ae4c0f8f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.6.2 + 3.8.2 war From bdbf7c92de37b45318d82565146d9fc33e160d3a Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 17 Jun 2026 15:46:22 +0530 Subject: [PATCH 26/62] duplicate ben id issue --- .../identity/service/IdentityService.java | 3 -- .../rmnch/RmnchDataSyncServiceImpl.java | 47 +++++++++---------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 212e8b5c..94cbbe04 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -902,9 +902,6 @@ private MBeneficiarymapping getBeneficiariesDTONew(Object[] benMapArr) { } } - - - } return mapping; } diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 01feb1b9..2a352a31 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -225,24 +225,22 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } obj.setRelatedBeneficiaryIdsDB(sb.toString()); } - if (obj.getVanID() == null && vanID != null) { - obj.setVanID(vanID); - obj.setParkingPlaceID(parkingPlaceID); - } - RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = - rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()); - if (rmnchmBeneficiarydetail != null) { - rmnchmBeneficiarydetail.setFirstName(obj.getFirstName()); - rmnchmBeneficiarydetail.setLastName(obj.getLastName()); - rmnchmBeneficiarydetail.setFatherName(obj.getFatherName()); - rmnchmBeneficiarydetail.setMotherName(obj.getMotherName()); - rmnchmBeneficiarydetail.setDob(obj.getDob()); - rmnchmBeneficiarydetail.setSpousename(obj.getSpousename()); - rmnchmBeneficiarydetail.setGender(obj.getGender()); - rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); - rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); - rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); - benDetailsList.add(rmnchmBeneficiarydetail); + if(!rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).isEmpty()){ + RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = + rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).get(0); + if (rmnchmBeneficiarydetail != null) { + rmnchmBeneficiarydetail.setFirstName(obj.getFirstName()); + rmnchmBeneficiarydetail.setLastName(obj.getLastName()); + rmnchmBeneficiarydetail.setFatherName(obj.getFatherName()); + rmnchmBeneficiarydetail.setMotherName(obj.getMotherName()); + rmnchmBeneficiarydetail.setDob(obj.getDob()); + rmnchmBeneficiarydetail.setSpousename(obj.getSpousename()); + rmnchmBeneficiarydetail.setGender(obj.getGender()); + rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); + rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); + rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); + benDetailsList.add(rmnchmBeneficiarydetail); + } } } @@ -344,13 +342,12 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } for (RMNCHHouseHoldDetails obj : houseHoldList) { - RMNCHHouseHoldDetails temp = rMNCHHouseHoldDetailsRepo - .getByHouseHoldID(obj.getHouseoldId()); - if (temp != null) - obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); - if (obj.getVanID() == null && vanID != null) { - obj.setVanID(vanID); - obj.setParkingPlaceID(parkingPlaceID); + if(!rMNCHHouseHoldDetailsRepo + .getByHouseHoldID(obj.getHouseoldId()).isEmpty()){ + RMNCHHouseHoldDetails temp = rMNCHHouseHoldDetailsRepo + .getByHouseHoldID(obj.getHouseoldId()).get(0); + if (temp != null) + obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); } } houseHoldList = (ArrayList) rMNCHHouseHoldDetailsRepo From 69cfd1941e32afa4c64eee602e5ed196af7b7e0a Mon Sep 17 00:00:00 2001 From: KOPPIREDDY DURGA PRASAD <144464542+DurgaPrasad-54@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:33:38 +0530 Subject: [PATCH 27/62] Add server urls and update swagger workflow (#135) * docs(readme): add documentation badge * chore(swagger): automate swagger sync to amrit-docs * chore(swagger): automate swagger sync to amrit-docs * chore(swagger): update github workflow for swagger json * fix(swagger): fix the stop api issue in swagger workflow * fix(swagger): removed the unnecessary initial sleep * chore(swagger): add env-driven Dev/UAT/Demo servers and update swagger workflow --- .../common/identity/repo/BenDetailRepo.java | 44 +++++++++---------- .../utils/JwtUserIdValidationFilter.java | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java index 413298ba..07fc0638 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java @@ -160,7 +160,7 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "m.BenRegId, " + // 0 "brm.beneficiaryID, " + // 1 "d.FirstName, " + // 2 - "d.MiddleName, " + // 3 + "d.MiddleName, " + // 3 "d.LastName, " + // 4 "d.GenderID, " + // 5 "g.GenderName, " + // 6 @@ -168,7 +168,7 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "TIMESTAMPDIFF(YEAR, d.DOB, CURDATE()) as Age, " + // 8 "d.FatherName, " + // 9 "d.SpouseName, " + // 10 - "d.MaritalStatusID, " + // 11 + "d.MaritalStatusID, " + // 11 "ms.Status as MaritalStatusName, " + // 12 "d.IsHIVPositive, " + // 13 "m.CreatedBy, " + // 14 @@ -186,19 +186,19 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "addr.CurrServicePoint, " + // 26 "addr.ParkingPlaceID, " + // 27 "contact.PreferredPhoneNum, " + // 28 - "addr.CurrVillageId, " + // 29 - "addr.CurrVillage " + // 30 + "addr.CurrVillageId, " + // 29 + "addr.CurrVillage " + // 30 "FROM i_beneficiarymapping m " + "LEFT JOIN i_beneficiarydetails d ON m.BenDetailsId = d.BeneficiaryDetailsID " + "LEFT JOIN m_beneficiaryregidmapping brm ON brm.BenRegId = m.BenRegId " + "LEFT JOIN db_iemr.m_gender g ON d.GenderID = g.GenderID " + - "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + + "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + "LEFT JOIN i_beneficiaryaddress addr ON m.BenAddressId = addr.BenAddressID " + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + - "WHERE m.BenRegId IN (:ids) AND m.Deleted = false", + "WHERE m.BenRegId IN (:ids) AND m.Deleted = false", nativeQuery = true) List findCompleteDataByIds(@Param("ids") List ids); - + /** * Direct search in database (fallback) */ @@ -206,7 +206,7 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "m.BenRegId, " + "brm.beneficiaryID, " + "d.FirstName, " + - "d.MiddleName, " + + "d.MiddleName, " + "d.LastName, " + "d.GenderID, " + "g.GenderName, " + @@ -214,8 +214,8 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "TIMESTAMPDIFF(YEAR, d.DOB, CURDATE()) as Age, " + "d.FatherName, " + "d.SpouseName, " + - "d.MaritalStatusID, " + - "ms.Status as MaritalStatusName, " + + "d.MaritalStatusID, " + + "ms.Status as MaritalStatusName, " + "d.IsHIVPositive, " + "m.CreatedBy, " + "m.CreatedDate, " + @@ -232,17 +232,17 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "addr.CurrServicePoint, " + "addr.ParkingPlaceID, " + "contact.PreferredPhoneNum, " + - "addr.CurrVillageId, " + - "addr.CurrVillage " + + "addr.CurrVillageId, " + + "addr.CurrVillage " + "FROM i_beneficiarymapping m " + "LEFT JOIN i_beneficiarydetails d ON m.BenDetailsId = d.BeneficiaryDetailsID " + "LEFT JOIN db_iemr.m_gender g ON d.GenderID = g.GenderID " + "LEFT JOIN m_beneficiaryregidmapping brm ON brm.BenRegId = m.BenRegId " + - "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + + "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + "LEFT JOIN i_beneficiaryaddress addr ON m.BenAddressId = addr.BenAddressID " + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + "WHERE (d.FirstName LIKE CONCAT('%', :query, '%') " + - " OR d.MiddleName LIKE CONCAT('%', :query, '%') " + + " OR d.MiddleName LIKE CONCAT('%', :query, '%') " + " OR d.LastName LIKE CONCAT('%', :query, '%') " + " OR d.FatherName LIKE CONCAT('%', :query, '%') " + " OR d.BeneficiaryRegID = :query " + @@ -253,10 +253,10 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi " OR contact.PhoneNum4 = :query " + " OR contact.PhoneNum5 = :query) " + "AND m.Deleted = false " + - "LIMIT 20", + "LIMIT 20", nativeQuery = true) List searchBeneficiaries(@Param("query") String query); - + /** * Get all phone numbers for a beneficiary */ @@ -292,10 +292,10 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "FROM i_beneficiarymapping m " + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + "WHERE m.BenRegId = :beneficiaryId AND contact.PhoneNum5 IS NOT NULL " + - "ORDER BY priority", + "ORDER BY priority", nativeQuery = true) List findPhoneNumbersByBeneficiaryId(@Param("beneficiaryId") Long beneficiaryId); - + // Advance Search ES @Query(value = "SELECT DISTINCT " + @@ -328,14 +328,14 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "addr.CurrServicePoint, " + // 26 "addr.ParkingPlaceID, " + // 27 "contact.PreferredPhoneNum, " + // 28 - "addr.CurrVillageId, " + - "addr.CurrVillage " + + "addr.CurrVillageId, " + + "addr.CurrVillage " + "FROM i_beneficiarymapping m " + "LEFT JOIN i_beneficiarydetails d " + " ON m.BenDetailsId = d.BeneficiaryDetailsID " + "LEFT JOIN db_iemr.m_gender g " + " ON d.GenderID = g.GenderID " + - "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + + "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + "LEFT JOIN i_beneficiaryaddress addr " + " ON m.BenAddressId = addr.BenAddressID " + "LEFT JOIN i_beneficiarycontacts contact " + @@ -344,7 +344,7 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi "WHERE m.Deleted = false " + "AND (:firstName IS NULL OR d.FirstName LIKE CONCAT('%', :firstName, '%')) " + - "AND (:middleName IS NULL OR d.MiddleName LIKE CONCAT('%', :middleName, '%')) " + + "AND (:middleName IS NULL OR d.MiddleName LIKE CONCAT('%', :middleName, '%')) " + "AND (:lastName IS NULL OR d.LastName LIKE CONCAT('%', :lastName, '%')) " + "AND (:genderId IS NULL OR d.GenderID = :genderId) " + "AND (:dob IS NULL OR DATE(d.DOB) = DATE(:dob)) " + diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java index cf959aa6..cf0fd63c 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -48,7 +48,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo logger.info("JwtUserIdValidationFilter invoked for requestURI: {}, servletPath: {}", path, servletPath); // Skip JWT validation for public endpoints - if (servletPath.equals("/health") || servletPath.equals("/version") || + if (servletPath.equals("/health") || servletPath.equals("/version") || path.endsWith("/health") || path.endsWith("/version")) { logger.info("Public endpoint accessed: {} - skipping JWT validation", path); filterChain.doFilter(servletRequest, servletResponse); From 6e735bf652a82bbd92235fe82d13386dcf13343e Mon Sep 17 00:00:00 2001 From: Sehjot Singh Pannu Date: Thu, 18 Jun 2026 17:09:32 +0530 Subject: [PATCH 28/62] feat(STOP-148): add GPS location capture support for beneficiary and RMNCH records Introduces GPS-related fields (gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable, and gpsUnavailableReason) across beneficiary address and RMNCH domain models, including MBeneficiaryaddress, Address DTO, RMNCHBeneficiaryDetailsRmnch, and RMNCHHouseHoldDetails. Enhances IdentityMapper and IdentityService to map and persist GPS information between incoming DTOs and beneficiary address entities. Updates RmnchDataSyncServiceImpl to extract and synchronize GPS details from the nested i_bendemographics payload during beneficiary sync, and to parse gpsTimestamp from household details during RMNCH household data processing. --- .../common/identity/service/rmnch/RmnchDataSyncServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 2a352a31..65041bbe 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -348,6 +348,8 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { .getByHouseHoldID(obj.getHouseoldId()).get(0); if (temp != null) obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); + if (hhTimestampMap.containsKey(obj.getHouseoldId())) + obj.setGpsTimestamp(new Timestamp(hhTimestampMap.get(obj.getHouseoldId()))); } } houseHoldList = (ArrayList) rMNCHHouseHoldDetailsRepo From 02ffadfc951e9932e0f52cfc8191683a05c5886a Mon Sep 17 00:00:00 2001 From: Vishwanath Balkur <118195001+vishwab1@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:07:54 +0530 Subject: [PATCH 29/62] feat(STOP-148): add GPS location capture support for beneficiary and RMNCH records (#167) Introduces GPS-related fields (gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable, and gpsUnavailableReason) across beneficiary address and RMNCH domain models, including MBeneficiaryaddress, Address DTO, RMNCHBeneficiaryDetailsRmnch, and RMNCHHouseHoldDetails. Enhances IdentityMapper and IdentityService to map and persist GPS information between incoming DTOs and beneficiary address entities. Updates RmnchDataSyncServiceImpl to extract and synchronize GPS details from the nested i_bendemographics payload during beneficiary sync, and to parse gpsTimestamp from household details during RMNCH household data processing. Co-authored-by: Sehjot Singh Pannu --- .../common/identity/mapper/InputMapper.java | 43 ++++++++++++++++++- .../identity/utils/mapper/InputMapper.java | 35 +++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/mapper/InputMapper.java b/src/main/java/com/iemr/common/identity/mapper/InputMapper.java index 9a46ba4f..704fe671 100644 --- a/src/main/java/com/iemr/common/identity/mapper/InputMapper.java +++ b/src/main/java/com/iemr/common/identity/mapper/InputMapper.java @@ -21,6 +21,13 @@ */ package com.iemr.common.identity.mapper; +import java.io.IOException; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +40,10 @@ import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.LongSerializationPolicy; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; import com.iemr.common.identity.exception.IEMRException; public class InputMapper @@ -42,11 +53,41 @@ public class InputMapper private static GsonBuilder builder; private static InputMapper instance = null; + private static final DateTimeFormatter ISO_WITH_Z = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + private static final DateTimeFormatter CLIENT_DATE_FORMAT = + DateTimeFormatter.ofPattern("MMM dd, yyyy, h:mm:ss a", Locale.ENGLISH); + + private static final TypeAdapter TIMESTAMP_ADAPTER = new TypeAdapter() { + @Override + public void write(JsonWriter out, Timestamp value) throws IOException { + if (value == null) out.nullValue(); + else out.value(value.getTime()); + } + + @Override + public Timestamp read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } + if (in.peek() == JsonToken.NUMBER) return new Timestamp(in.nextLong()); + String s = in.nextString(); + // epoch millis as string + try { return new Timestamp(Long.parseLong(s)); } catch (NumberFormatException ignored) {} + // ISO 8601 with Z e.g. "2021-06-18T00:00:00.000Z" + try { return Timestamp.from(Instant.parse(s)); } catch (Exception ignored) {} + // Mobile client format e.g. "Jun 18, 2021, 5:30:00 AM" + try { return Timestamp.valueOf(LocalDateTime.parse(s, CLIENT_DATE_FORMAT)); } catch (Exception ignored) {} + // Fallback: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" literal Z + try { return Timestamp.valueOf(LocalDateTime.parse(s, ISO_WITH_Z)); } catch (Exception ignored) {} + return null; + } + }; + private InputMapper() { builder = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") // .excludeFieldsWithoutExposeAnnotation() - .serializeNulls().setLongSerializationPolicy(LongSerializationPolicy.STRING); + .serializeNulls().setLongSerializationPolicy(LongSerializationPolicy.STRING) + .registerTypeAdapter(Timestamp.class, TIMESTAMP_ADAPTER); } public static InputMapper getInstance() diff --git a/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java b/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java index 4c8db37d..fb419105 100644 --- a/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java +++ b/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java @@ -21,6 +21,13 @@ */ package com.iemr.common.identity.utils.mapper; +import java.io.IOException; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -28,6 +35,10 @@ import com.google.gson.ExclusionStrategy; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; import com.iemr.common.identity.utils.exception.IEMRException; /** @@ -48,6 +59,30 @@ public InputMapper() { if (builder == null) { builder = new GsonBuilder(); builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + builder.registerTypeAdapter(Timestamp.class, new TypeAdapter() { + private final DateTimeFormatter isoWithZ = DateTimeFormatter.ISO_INSTANT; + private final DateTimeFormatter isoNoTz = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); + + @Override + public void write(JsonWriter out, Timestamp value) throws IOException { + if (value == null) out.nullValue(); + else out.value(value.getTime()); + } + + @Override + public Timestamp read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } + if (in.peek() == JsonToken.NUMBER) return new Timestamp(in.nextLong()); + String s = in.nextString(); + // epoch millis as string + try { return new Timestamp(Long.parseLong(s)); } catch (NumberFormatException ignored) {} + // ISO 8601 with timezone (e.g. "2026-05-28T03:24:35.000Z") + try { return Timestamp.from(Instant.parse(s)); } catch (Exception ignored) {} + // ISO without timezone (e.g. "2026-05-28T03:24:35.000") + try { return Timestamp.from(LocalDateTime.parse(s, isoNoTz).toInstant(ZoneOffset.UTC)); } catch (Exception ignored) {} + return null; + } + }); } } From dc2ad36aad93f0726c8aa065121b14cc52d6df0a Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Mon, 22 Jun 2026 18:05:28 +0530 Subject: [PATCH 30/62] abha id add in entity class --- .../identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java | 4 ++++ .../common/identity/data/rmnch/RMNCHMBeneficiarydetail.java | 4 ++++ .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index e403b3d7..1fb1b66d 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -553,4 +553,8 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose @Transient private String abhaId; + + @Expose + @Transient + private String familyId; } diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java index d9221675..44f15679 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java @@ -218,4 +218,8 @@ public class RMNCHMBeneficiarydetail { @Expose @Transient private String abhaId; + + @Expose + @Column(name = "familyid") + private String familyId; } \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index e11aba38..0b469009 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -192,6 +192,10 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); + if(obj.getFamilyId()!=null && !obj.getFamilyId().isEmpty()){ + rmnchmBeneficiarydetail.setFamilyId(obj.getFamilyId()); + + } benDetailsList.add(rmnchmBeneficiarydetail); if (obj.getAbhaId()!=null && !obj.getAbhaId().isEmpty()) { mapHealthIDToBeneficiary(authorization,obj.getBenRegId().longValue(),obj.getBenficieryid().longValue(),obj.getAbhaId(),obj.getCreatedBy(),obj.getFirstName(),obj.getLastName(),obj.getDob().toString(),obj.getProviderServiceMapID()); From 03ad7fec3b17098acbad6a6dab7b70ddd2b3053a Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 22 Jun 2026 18:13:39 +0530 Subject: [PATCH 31/62] fix: fail open on Redis errors in token denylist check A transient Redis connection blip during isTokenDenylisted() was thrown as an exception and treated as authentication failure, causing valid, unexpired JWTs to be intermittently rejected with 401 during normal API calls (e.g. beneficiary registration). Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/iemr/common/identity/utils/TokenDenylist.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java index 1248f478..293b6679 100644 --- a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java +++ b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java @@ -49,8 +49,9 @@ public boolean isTokenDenylisted(String jti) { String key = getKey(jti); // Use helper method to get the key return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } catch (Exception e) { - logger.error("Failed to check denylist status for jti: " + jti, e); - throw new TokenDenylistException("Unable to verify token denylist status", e); + // Fail open: a Redis blip must not lock out a validly authenticated user. + logger.error("Failed to check denylist status for jti: " + jti + ". Treating as not denylisted.", e); + return false; } } From a41ac601fac8b7771b33f74700227750dff1d81c Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 22 Jun 2026 19:05:28 +0530 Subject: [PATCH 32/62] Revert "fix: fail open on Redis errors in token denylist check" This reverts commit 03ad7fec3b17098acbad6a6dab7b70ddd2b3053a. --- .../java/com/iemr/common/identity/utils/TokenDenylist.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java index 293b6679..1248f478 100644 --- a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java +++ b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java @@ -49,9 +49,8 @@ public boolean isTokenDenylisted(String jti) { String key = getKey(jti); // Use helper method to get the key return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } catch (Exception e) { - // Fail open: a Redis blip must not lock out a validly authenticated user. - logger.error("Failed to check denylist status for jti: " + jti + ". Treating as not denylisted.", e); - return false; + logger.error("Failed to check denylist status for jti: " + jti, e); + throw new TokenDenylistException("Unable to verify token denylist status", e); } } From 4eefea7def9a9154532bd6412a4f057ce77ac757 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 22 Jun 2026 19:10:05 +0530 Subject: [PATCH 33/62] fix(STOP-148): use InputMapper Gson for createIdentity to parse epoch-millis gpsTimestamp createIdentity() parsed the request body with a bare new Gson(), whose default Timestamp adapter only accepts ISO8601 date strings. The new gpsTimestamp field is sent as raw epoch millis, causing JsonSyntaxException during parsing whenever GPS data is present. That exception propagated up through JwtUserIdValidationFilter's catch block and was misreported as a 401 Authorization error. InputMapper already registers a Timestamp adapter that handles epoch millis correctly; switch createIdentity() to use it. Co-Authored-By: Claude Sonnet 4.6 --- .../com/iemr/common/identity/controller/IdentityController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityController.java b/src/main/java/com/iemr/common/identity/controller/IdentityController.java index 16995734..de5e551e 100644 --- a/src/main/java/com/iemr/common/identity/controller/IdentityController.java +++ b/src/main/java/com/iemr/common/identity/controller/IdentityController.java @@ -594,7 +594,7 @@ public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"St + " \"createdDate\": \"Timestamp\"\r\n" + " \"faceEmbedding\": [\"Float\"]\r\n" + "}") @RequestBody String identityData) throws IEMRException { logger.info("IdentityController.createIdentity - start"); - IdentityDTO identity = new Gson().fromJson(identityData, IdentityDTO.class); + IdentityDTO identity = InputMapper.getInstance().gson().fromJson(identityData, IdentityDTO.class); logger.info("identity hit: " + identity); BeneficiaryCreateResp map; map = svc.createIdentity(identity); From f1ec9716626d6e143326918f605f7786bee5959a Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 24 Jun 2026 13:55:16 +0530 Subject: [PATCH 34/62] fixed issue of Query did not return a unique result: 2 results were returned in RMNCH --- .../common/identity/service/rmnch/RmnchDataSyncServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 0b469009..f82abf3d 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -356,7 +356,6 @@ public String mapHealthIDToBeneficiary(String authorization, HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - // Agar token me Bearer nahi aa raha hai headers.set("Jwttoken", authorization); From ad138d7da109609aeeec4be194e514f15214624f Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 24 Jun 2026 14:25:58 +0530 Subject: [PATCH 35/62] fixed issue of Query did not return a unique result: 2 results were returned in RMNCH --- .../repo/rmnch/RMNCHBornBirthDetailsRepo.java | 3 ++- .../repo/rmnch/RMNCHCBACDetailsRepo.java | 2 +- .../rmnch/RmnchDataSyncServiceImpl.java | 27 +++++++++++++------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java index 2af4d5b7..21488700 100644 --- a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java @@ -22,6 +22,7 @@ package com.iemr.common.identity.repo.rmnch; import java.math.BigInteger; +import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; @@ -36,5 +37,5 @@ public interface RMNCHBornBirthDetailsRepo extends CrudRepository getByRegID(@Param("benRegID") BigInteger benRegID); } diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHCBACDetailsRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHCBACDetailsRepo.java index 49fb4697..95e452b4 100644 --- a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHCBACDetailsRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHCBACDetailsRepo.java @@ -37,7 +37,7 @@ public interface RMNCHCBACDetailsRepo extends CrudRepository getByRegID(@Param("benRegID") BigInteger benRegID); @Query(value = "select beneficiary_visit_code,visit_category from db_iemr.i_ben_flow_outreach where beneficiary_reg_id=:benRegID AND beneficiary_visit_code is not null AND visit_category is not null order by created_date desc limit 1", nativeQuery = true) public List getVisitDetailsbyRegID(@Param("benRegID") Long benRegID); diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index f82abf3d..0c89c170 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -223,9 +223,12 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex for (RMNCHBornBirthDetails obj : bornBirthList) { benRegID = rMNCHMBenRegIdMapRepo.getRegID(obj.getBenficieryid()); obj.setBenRegId(benRegID); - RMNCHBornBirthDetails temp = rMNCHBornBirthDetailsRepo.getByRegID(benRegID); - if (temp != null) - obj.setBornBirthDeatilsId(temp.getBornBirthDeatilsId()); + if(!rMNCHBornBirthDetailsRepo.getByRegID(benRegID).isEmpty()){ + RMNCHBornBirthDetails temp = rMNCHBornBirthDetailsRepo.getByRegID(benRegID).get(0); + if (temp != null) + obj.setBornBirthDeatilsId(temp.getBornBirthDeatilsId()); + } + } bornBirthList = (ArrayList) rMNCHBornBirthDetailsRepo .saveAll(bornBirthList); @@ -246,9 +249,12 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex obj.setConfirmed_tb("Not checked"); obj.setConfirmed_ncd_diseases("Not checked"); obj.setDiagnosis_status("pending"); - RMNCHCBACdetails temp = rMNCHCBACDetailsRepo.getByRegID(benRegID); - if (temp != null) - obj.setCBACDetailsid(temp.getCBACDetailsid()); + if(!rMNCHCBACDetailsRepo.getByRegID(benRegID).isEmpty()){ + RMNCHCBACdetails temp = rMNCHCBACDetailsRepo.getByRegID(benRegID).get(0); + if (temp != null) + obj.setCBACDetailsid(temp.getCBACDetailsid()); + } + } cbacList = (ArrayList) rMNCHCBACDetailsRepo.saveAll(cbacList); @@ -666,10 +672,15 @@ private String getMappingsForAddressIDs(List addressLi benDetailsRMNCHOBJ = rMNCHBeneficiaryDetailsRmnchRepo .getByRegID(m.getBenRegId()).get(0); } + if(!rMNCHBornBirthDetailsRepo.getByRegID(m.getBenRegId()).isEmpty()){ + benBotnBirthRMNCHROBJ = rMNCHBornBirthDetailsRepo.getByRegID(m.getBenRegId()).get(0); + + } + if(! rMNCHCBACDetailsRepo.getByRegID(m.getBenRegId()).isEmpty()){ + benCABCRMNCHROBJ = rMNCHCBACDetailsRepo.getByRegID(m.getBenRegId()).get(0); - benBotnBirthRMNCHROBJ = rMNCHBornBirthDetailsRepo.getByRegID(m.getBenRegId()); + } - benCABCRMNCHROBJ = rMNCHCBACDetailsRepo.getByRegID(m.getBenRegId()); // 20-09-2021,start NcdTbHrpData res = getHRP_NCD_TB_SuspectedStatus(m.getBenRegId().longValue(), authorisation, benDetailsOBJ); From 37f9ebdabf47797ae60519c965a8db45e3372370 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 30 Jun 2026 10:40:13 +0530 Subject: [PATCH 36/62] fix(datasync): add vanID column to BenGenID import INSERT query SQL had 6 placeholders but object array had 7 elements including vanID, causing parameter index out of bounds error. Added vanID to INSERT columns. Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/iemr/common/identity/service/IdentityService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 94cbbe04..5d6e352e 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -2142,7 +2142,7 @@ public int importBenIdToLocalServer(List benIdImportDTOList) { List dataList = new ArrayList<>(); Object[] objArr; String query = " INSERT INTO m_beneficiaryregidmapping(BenRegId, BeneficiaryID, " - + " Provisioned, CreatedDate, CreatedBy, Reserved) VALUES (?,?,?,?,?,?) "; + + " Provisioned, CreatedDate, CreatedBy, Reserved, vanID) VALUES (?,?,?,?,?,?,?) "; logger.info("query : " + query); for (MBeneficiaryregidmapping obj : mBeneficiaryregidmappingList) { logger.info("inside for check->", obj); From 83b604673d9b835e2d6e288188efa2cac2d0c2be Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 3 Jul 2026 12:04:20 +0530 Subject: [PATCH 37/62] attach logger in response --- .../rmnch/RMNCHMobileAppController.java | 5 +++++ .../service/rmnch/RmnchDataSyncServiceImpl.java | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java index f8a8957f..deada381 100644 --- a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java +++ b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java @@ -63,8 +63,13 @@ public String syncDataToAmrit(@RequestBody String requestOBJ,@RequestHeader(valu OutputResponse response = new OutputResponse(); try { if (requestOBJ != null) { + String s = rmnchDataSyncService.syncDataToAmrit(requestOBJ,authorization); + logger.info("syncDataToAmrit Response: {}", s); + response.setResponse(s); + + logger.info(" syncDataToAmrit Final API Response: {}", response.toString()); } else response.setError(5000, "Invalid/NULL request obj"); } catch (Exception e) { diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 0c89c170..3bbd6b09 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -306,6 +306,21 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex return new Gson().toJson(resultMap); } + /** + * Splits a list into sub-lists (batches) of the given size. + * Last batch may contain fewer elements. + */ + private List> partitionList(List list, int batchSize) { + List> batches = new ArrayList<>(); + if (list == null || list.isEmpty()) { + return batches; + } + for (int i = 0; i < list.size(); i += batchSize) { + batches.add(new ArrayList<>(list.subList(i, Math.min(i + batchSize, list.size())))); + } + return batches; + } + public String mapHealthIDToBeneficiary(String authorization, Long benRegID, Long beneficiaryID, From 3788c85b5b67d40d547a302df53b898f0e057301 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Mon, 6 Jul 2026 15:08:27 +0530 Subject: [PATCH 38/62] fix duplicate issue --- .../repo/rmnch/RMNCHBornBirthDetailsRepo.java | 3 ++- .../rmnch/RmnchDataSyncServiceImpl.java | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java index 2af4d5b7..21488700 100644 --- a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBornBirthDetailsRepo.java @@ -22,6 +22,7 @@ package com.iemr.common.identity.repo.rmnch; import java.math.BigInteger; +import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; @@ -36,5 +37,5 @@ public interface RMNCHBornBirthDetailsRepo extends CrudRepository getByRegID(@Param("benRegID") BigInteger benRegID); } diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 8d301c1b..d1bf1ce9 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -283,13 +283,16 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { for (RMNCHBornBirthDetails obj : bornBirthList) { benRegID = rMNCHMBenRegIdMapRepo.getRegID(obj.getBenficieryid()); obj.setBenRegId(benRegID); - RMNCHBornBirthDetails temp = rMNCHBornBirthDetailsRepo.getByRegID(benRegID); - if (temp != null) - obj.setBornBirthDeatilsId(temp.getBornBirthDeatilsId()); - if (obj.getVanID() == null && vanID != null) { - obj.setVanID(vanID); - obj.setParkingPlaceID(parkingPlaceID); + if(!rMNCHBornBirthDetailsRepo.getByRegID(benRegID).isEmpty()){ + RMNCHBornBirthDetails temp = rMNCHBornBirthDetailsRepo.getByRegID(benRegID).get(0); + if (temp != null) + obj.setBornBirthDeatilsId(temp.getBornBirthDeatilsId()); + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } + } bornBirthList = (ArrayList) rMNCHBornBirthDetailsRepo .saveAll(bornBirthList); @@ -552,8 +555,10 @@ private String getMappingsForAddressIDs(List addressLi benDetailsRMNCHOBJ = rMNCHBeneficiaryDetailsRmnchRepo .getByRegID(m.getBenRegId()).get(0); } + if(!rMNCHBornBirthDetailsRepo.getByRegID(m.getBenRegId()).isEmpty()){ + benBotnBirthRMNCHROBJ = rMNCHBornBirthDetailsRepo.getByRegID(m.getBenRegId()).get(0); - benBotnBirthRMNCHROBJ = rMNCHBornBirthDetailsRepo.getByRegID(m.getBenRegId()); + } benCABCRMNCHROBJ = rMNCHCBACDetailsRepo.getByRegID(m.getBenRegId()); // 20-09-2021,start From b53a96850b6b0fb1c4b2789c3d895bbfac11be93 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 7 Jul 2026 13:48:00 +0530 Subject: [PATCH 39/62] fix(rmnch): surface pinCode in beneficiary details sync response permPinCode was already stored correctly in i_beneficiaryaddress but was never surfaced in the RMNCH sync response, since the response DTO had no pinCode field and the address mapping never copied it across. Co-Authored-By: Claude Sonnet 5 --- .../identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java | 3 +++ .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index b964b25e..f5ad60b5 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -390,6 +390,9 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose @Transient private String addressLine3; + @Expose + @Transient + private String pinCode; // ---------------------------------------------- diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index d1bf1ce9..0f1e7d0e 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -654,6 +654,8 @@ private String getMappingsForAddressIDs(List addressLi benDetailsRMNCHOBJ.setAddressLine2(benAddressOBJ.getPermAddrLine2()); if (benAddressOBJ.getPermAddrLine3() != null) benDetailsRMNCHOBJ.setAddressLine3(benAddressOBJ.getPermAddrLine3()); + if (benAddressOBJ.getPermPinCode() != null) + benDetailsRMNCHOBJ.setPinCode(benAddressOBJ.getPermPinCode()); // related benids if (benDetailsRMNCHOBJ.getRelatedBeneficiaryIdsDB() != null) { From c0f6dea7854e030b11512ce7ba204e0303718b93 Mon Sep 17 00:00:00 2001 From: sehjotsinghunthinkable Date: Thu, 9 Jul 2026 17:15:10 +0530 Subject: [PATCH 40/62] Tuned DB connection pools and enable response compression (#172) --- src/main/resources/application.properties | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 12be1b44..fccbd57c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,10 @@ +# Response compression — shrinks large JSON responses over the wire without changing the API +# contract; honors the Accept-Encoding: gzip clients already send. +server.compression.enabled=true +server.compression.mime-types=application/json,application/xml,text/html,text/plain,text/css,application/javascript +server.compression.min-response-size=1024 + + spring.main.banner-mode=off spring.data.jpa.repositories.enabled=true spring.jpa.hibernate.ddl-auto=none @@ -16,7 +23,8 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect # Connection Pool Size - CRITICAL for handling multiple concurrent requests # Rule: (max-pool-size) = (number of CPUs * 2) + effectiveSpindles # For 8 CPUs: 8*2 + 4 = 20-30 is optimal -spring.datasource.hikari.maximum-pool-size=40 +spring.datasource.hikari.maximum-pool-size=100 +#spring.datasource.hikari.maximum-pool-size=100 spring.datasource.hikari.minimum-idle=20 # Connection Timeouts - REDUCED for faster failure detection From 2ee45866b0cd7c8e4d372bf9c3e607ab75c7b82b Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Sat, 11 Jul 2026 13:20:16 +0530 Subject: [PATCH 41/62] chore: add trace logging to /id/create for registerNew debugging Prints the raw request body, parsed IdentityDTO, and svc.createIdentity result so a Linux-server trace can show whether the beneficiary is actually being saved. Co-Authored-By: Claude Sonnet 5 --- .../iemr/common/identity/controller/IdentityController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityController.java b/src/main/java/com/iemr/common/identity/controller/IdentityController.java index de5e551e..68e47989 100644 --- a/src/main/java/com/iemr/common/identity/controller/IdentityController.java +++ b/src/main/java/com/iemr/common/identity/controller/IdentityController.java @@ -593,11 +593,14 @@ public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"St + " \"sexualOrientationType\": \"String\",\r\n" + " \"vanID\": \"Integer\",\r\n" + " \"createdDate\": \"Timestamp\"\r\n" + " \"faceEmbedding\": [\"Float\"]\r\n" + "}") @RequestBody String identityData) throws IEMRException { logger.info("IdentityController.createIdentity - start"); - + System.out.println("[TRACE][Identity-API] /id/create raw request body : " + identityData); + IdentityDTO identity = InputMapper.getInstance().gson().fromJson(identityData, IdentityDTO.class); logger.info("identity hit: " + identity); + System.out.println("[TRACE][Identity-API] /id/create parsed IdentityDTO : " + identity); BeneficiaryCreateResp map; map = svc.createIdentity(identity); + System.out.println("[TRACE][Identity-API] /id/create svc.createIdentity result : " + InputMapper.getInstance().gson().toJson(map)); String data = InputMapper.getInstance().gson().toJson(map); String response = getSuccessResponseString(data, 200, "success", "createIdentityByAgent"); From f2c0d9ab47d95e02ea96fce3cd1db429abb0006c Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Sat, 11 Jul 2026 13:20:36 +0530 Subject: [PATCH 42/62] chore: add trace logging to syncDataToAmrit Prints request body and response so the AMRIT sync path can be traced alongside the registerNew flow. Co-Authored-By: Claude Sonnet 5 --- .../identity/controller/rmnch/RMNCHMobileAppController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java index 198fd5fc..df7487b8 100644 --- a/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java +++ b/src/main/java/com/iemr/common/identity/controller/rmnch/RMNCHMobileAppController.java @@ -60,7 +60,9 @@ public String syncDataToAmrit(@RequestBody String requestOBJ) { OutputResponse response = new OutputResponse(); try { if (requestOBJ != null) { + System.out.println("syncDataToAmrit request body : " + requestOBJ); String s = rmnchDataSyncService.syncDataToAmrit(requestOBJ); + System.out.println("syncDataToAmrit response body : " + s); response.setResponse(s); } else response.setError(5000, "Invalid/NULL request obj"); From 5696472ed7a6ce42c5308c8adc6013e6065e055e Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:58:09 +0530 Subject: [PATCH 43/62] Fix the Elasticsearch Health Status (#173) * fix: authenticate elasticsearch health probes against secured cluster The /health Elasticsearch client was built without credentials, so probes against a security-enabled cluster returned 401 and reported ES DOWN (forcing overall status DOWN) even though ES was healthy. Inject elasticsearch.username/ password and attach a BasicCredentialsProvider, matching ElasticsearchConfig. Auth is skipped when username is blank (ES security disabled). Co-Authored-By: Claude Opus 4.8 (1M context) * fix: pom version --------- Co-authored-by: Claude Opus 4.8 (1M context) --- pom.xml | 2 +- .../service/health/HealthService.java | 37 ++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 0b2dea62..ae4c0f8f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.8.0 + 3.8.2 war diff --git a/src/main/java/com/iemr/common/identity/service/health/HealthService.java b/src/main/java/com/iemr/common/identity/service/health/HealthService.java index f233d729..a8385b11 100644 --- a/src/main/java/com/iemr/common/identity/service/health/HealthService.java +++ b/src/main/java/com/iemr/common/identity/service/health/HealthService.java @@ -48,10 +48,14 @@ import javax.management.ObjectName; import jakarta.annotation.PostConstruct; import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.BasicCredentialsProvider; import org.elasticsearch.client.Request; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -116,6 +120,8 @@ public class HealthService { private final boolean elasticsearchEnabled; private final boolean elasticsearchIndexingRequired; private final String elasticsearchTargetIndex; + private final String elasticsearchUsername; + private final String elasticsearchPassword; private static final ObjectMapper objectMapper = new ObjectMapper(); private RestClient elasticsearchRestClient; @@ -139,7 +145,9 @@ public HealthService( @Value("${elasticsearch.port:9200}") int elasticsearchPort, @Value("${elasticsearch.enabled:false}") boolean elasticsearchEnabled, @Value("${elasticsearch.target-index:amrit_data}") String elasticsearchTargetIndex, - @Value("${elasticsearch.indexing-required:false}") boolean elasticsearchIndexingRequired) { + @Value("${elasticsearch.indexing-required:false}") boolean elasticsearchIndexingRequired, + @Value("${elasticsearch.username:}") String elasticsearchUsername, + @Value("${elasticsearch.password:}") String elasticsearchPassword) { this.dataSource = dataSource; this.advancedCheckExecutor = Executors.newSingleThreadExecutor(r -> { @@ -153,6 +161,8 @@ public HealthService( this.elasticsearchEnabled = elasticsearchEnabled; this.elasticsearchIndexingRequired = elasticsearchIndexingRequired; this.elasticsearchTargetIndex = (elasticsearchTargetIndex != null) ? elasticsearchTargetIndex : "amrit_data"; + this.elasticsearchUsername = elasticsearchUsername; + this.elasticsearchPassword = elasticsearchPassword; } @PostConstruct @@ -176,15 +186,30 @@ public void cleanup() { private void initializeElasticsearchClient() { try { - this.elasticsearchRestClient = RestClient.builder( + RestClientBuilder builder = RestClient.builder( new HttpHost(elasticsearchHost, elasticsearchPort, "http")) .setRequestConfigCallback(cb -> cb .setConnectTimeout(ELASTICSEARCH_CONNECT_TIMEOUT_MS) - .setSocketTimeout(ELASTICSEARCH_SOCKET_TIMEOUT_MS)) - .build(); + .setSocketTimeout(ELASTICSEARCH_SOCKET_TIMEOUT_MS)); + + // Attach Basic Auth when credentials are configured, so the health + // probes authenticate against a security-enabled cluster (matches + // ElasticsearchConfig). When username is blank (security disabled), + // the client stays unauthenticated. + if (elasticsearchUsername != null && !elasticsearchUsername.isEmpty()) { + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + AuthScope.ANY, + new UsernamePasswordCredentials(elasticsearchUsername, elasticsearchPassword)); + builder.setHttpClientConfigCallback(httpClientBuilder -> + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); + } + + this.elasticsearchRestClient = builder.build(); this.elasticsearchClientReady = true; - logger.info("Elasticsearch client initialized (connect/socket timeout: {}ms)", - ELASTICSEARCH_CONNECT_TIMEOUT_MS); + logger.info("Elasticsearch client initialized (connect/socket timeout: {}ms, auth: {})", + ELASTICSEARCH_CONNECT_TIMEOUT_MS, + (elasticsearchUsername != null && !elasticsearchUsername.isEmpty()) ? "enabled" : "disabled"); } catch (Exception e) { logger.warn("Failed to initialize Elasticsearch client: {}", e.getMessage()); this.elasticsearchClientReady = false; From ff5c97b747c2e1781f28078bfee8ca82e0ad8e9c Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 17 Jul 2026 09:41:46 +0530 Subject: [PATCH 44/62] chore(dob): add debug trace logging at DTO->entity mapping and around detailRepo.save Co-Authored-By: Claude Sonnet 5 --- .../java/com/iemr/common/identity/service/IdentityService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 5d6e352e..a70597dc 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -1492,8 +1492,10 @@ public BeneficiaryCreateResp createIdentity(IdentityDTO identity) { Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); mDetl.setCreatedDate(ts); } + System.out.println("[TRACE][Identity-API] before detailRepo.save mDetl.getDob()=" + mDetl.getDob()); mDetl = detailRepo.save(mDetl); logger.info("IdentityService.createIdentity - Details saved - id = " + mDetl.getBeneficiaryDetailsId()); + System.out.println("[TRACE][Identity-API] after detailRepo.save mDetl.getDob()=" + mDetl.getDob() + " id=" + mDetl.getBeneficiaryDetailsId()); // Update van serial no for data sync detailRepo.updateVanSerialNo(mDetl.getBeneficiaryDetailsId()); @@ -1687,6 +1689,7 @@ private MBeneficiarydetail convertIdentityDTOToMBeneficiarydetail(IdentityDTO dt } beneficiarydetail.setCommunity(dto.getCommunity()); beneficiarydetail.setCommunityId(dto.getCommunityId()); + System.out.println("[TRACE][Identity-API] convertIdentityDTOToMBeneficiarydetail dto.getDob()=" + dto.getDob()); beneficiarydetail.setDob(dto.getDob()); beneficiarydetail.setEducation(dto.getEducation()); beneficiarydetail.setEducationId(dto.getEducationId()); From 8a7124975efed5bb8d58b9f0846fb0c9fd21b095 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 17 Jul 2026 10:42:24 +0530 Subject: [PATCH 45/62] feat(household): add totalHhMembers and registeredAtCampSite fields Co-Authored-By: Claude Sonnet 5 --- .../identity/data/rmnch/RMNCHHouseHoldDetails.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index b73f9b00..b5137442 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -112,6 +112,18 @@ public class RMNCHHouseHoldDetails { @Column(name = "familyName") private String familyName; + @Expose + @Column(name = "totalHhMembers") + private Integer totalHhMembers; + + @Expose + @Column(name = "registeredAtCampSite") + private String registeredAtCampSite; + + @Expose + @Column(name = "registeredAtCampSiteId") + private Integer registeredAtCampSiteId; + @Expose @Column(name = "fuelUsed") private String fuelUsed; From c130590b72c3ad38126b033f472ad1807b07253d Mon Sep 17 00:00:00 2001 From: sehjotsinghunthinkable Date: Thu, 16 Jul 2026 17:04:52 +0530 Subject: [PATCH 46/62] Adding placeOfCurrentLiving, otherPlaceOfCurrentLiving and institutionName in RMNCHMBeneficiarydetail and RMNCHBeneficiaryDetailsRmnch (#175) --- .../data/rmnch/RMNCHBeneficiaryDetailsRmnch.java | 9 +++++++++ .../identity/data/rmnch/RMNCHMBeneficiarydetail.java | 9 +++++++++ .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 3 +++ 3 files changed, 21 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index f5ad60b5..8829d483 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -530,6 +530,15 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose private String otherPlaceOfDeath; + @Expose + private String placeOfCurrentLiving; + + @Expose + private String otherPlaceOfCurrentLiving; + + @Expose + private String institutionName; + @Expose private Boolean isSpouseAdded; diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java index 397c5a66..a3b71194 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java @@ -214,4 +214,13 @@ public class RMNCHMBeneficiarydetail { @Expose @Transient private Integer ProviderServiceMapID; + + @Expose + private String placeOfCurrentLiving; + + @Expose + private String otherPlaceOfCurrentLiving; + + @Expose + private String institutionName; } \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 0f1e7d0e..22a80eae 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -243,6 +243,9 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); + rmnchmBeneficiarydetail.setPlaceOfCurrentLiving(obj.getPlaceOfCurrentLiving()); + rmnchmBeneficiarydetail.setOtherPlaceOfCurrentLiving(obj.getOtherPlaceOfCurrentLiving()); + rmnchmBeneficiarydetail.setInstitutionName(obj.getInstitutionName()); benDetailsList.add(rmnchmBeneficiarydetail); } } From d7d1e6d0b59d1a235570822151fb422ff21ae12c Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 17 Jul 2026 16:26:37 +0530 Subject: [PATCH 47/62] chore(trace): log pinCode presence in i_bendemographics during syncDataToAmrit Co-Authored-By: Claude Sonnet 5 --- .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 22a80eae..302e1723 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -190,6 +190,10 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { obj.setBenRegId(benRegID); // Extract GPS from i_bendemographics JsonObject demog = benGpsMap.get(obj.getBenficieryid()); + System.out.println("[TRACE][Identity-API] syncDataToAmrit benficieryid=" + obj.getBenficieryid() + + " i_bendemographics.pinCode=" + (demog != null && demog.has("pinCode") && !demog.get("pinCode").isJsonNull() + ? demog.get("pinCode") : "ABSENT") + + " entity.getPinCode()=" + obj.getPinCode()); if (demog != null) { if (demog.has("latitude") && !demog.get("latitude").isJsonNull()) obj.setGpsLatitude(demog.get("latitude").getAsDouble()); From 76a678a6a63ac11e954f62aebcf73d267e531352 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Sat, 18 Jul 2026 17:07:41 +0530 Subject: [PATCH 48/62] Revert "Tuned DB connection pools and enable response compression (#172)" This reverts commit c0f6dea7854e030b11512ce7ba204e0303718b93. --- src/main/resources/application.properties | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fccbd57c..12be1b44 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,10 +1,3 @@ -# Response compression — shrinks large JSON responses over the wire without changing the API -# contract; honors the Accept-Encoding: gzip clients already send. -server.compression.enabled=true -server.compression.mime-types=application/json,application/xml,text/html,text/plain,text/css,application/javascript -server.compression.min-response-size=1024 - - spring.main.banner-mode=off spring.data.jpa.repositories.enabled=true spring.jpa.hibernate.ddl-auto=none @@ -23,8 +16,7 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect # Connection Pool Size - CRITICAL for handling multiple concurrent requests # Rule: (max-pool-size) = (number of CPUs * 2) + effectiveSpindles # For 8 CPUs: 8*2 + 4 = 20-30 is optimal -spring.datasource.hikari.maximum-pool-size=100 -#spring.datasource.hikari.maximum-pool-size=100 +spring.datasource.hikari.maximum-pool-size=40 spring.datasource.hikari.minimum-idle=20 # Connection Timeouts - REDUCED for faster failure detection From 8e586fe9bdaee54d2bbdc60bf41d93d606b8fe16 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Sat, 18 Jul 2026 17:54:10 +0530 Subject: [PATCH 49/62] fix(dob): stop global Timestamp adapter from nulling dob on GPS-enabled endpoints The GPS feature (STOP-148) registered a custom Gson TypeAdapter globally on InputMapper's GsonBuilder to parse the new gpsTimestamp field. Because it was global, it also intercepted dob, silently returning null whenever the incoming date string didn't match one of its four hardcoded formats. - Add GpsTimestampAdapter, attached only via @JsonAdapter on the gpsTimestamp field (Address, RMNCHBeneficiaryDetailsRmnch, RMNCHHouseHoldDetails), so it can't affect any other Timestamp field. - Remove the global registerTypeAdapter(Timestamp.class, ...) from both InputMapper.java copies, restoring Gson's default Timestamp parsing for dob and everything else (matching vb/stoptb). - Revert createIdentity()'s parser back to a bare new Gson(), matching Common-API's RegisterBenificiaryServiceImpl, which also serializes the outgoing identity payload with a bare new Gson(). InputMapper's setDateFormat is incompatible with that wire format (non-zero-padded day), which is why dob was nulling specifically for beneficiaries born on the 1st-9th of a month once createIdentity switched to InputMapper's Gson. Co-Authored-By: Claude Sonnet 5 --- .../controller/IdentityController.java | 7 +- .../rmnch/RMNCHBeneficiaryDetailsRmnch.java | 3 + .../data/rmnch/RMNCHHouseHoldDetails.java | 3 + .../iemr/common/identity/domain/Address.java | 4 + .../identity/mapper/GpsTimestampAdapter.java | 116 ++++++++++++++++++ .../common/identity/mapper/InputMapper.java | 47 +------ .../identity/utils/mapper/InputMapper.java | 40 +----- 7 files changed, 142 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityController.java b/src/main/java/com/iemr/common/identity/controller/IdentityController.java index de5e551e..948d38b6 100644 --- a/src/main/java/com/iemr/common/identity/controller/IdentityController.java +++ b/src/main/java/com/iemr/common/identity/controller/IdentityController.java @@ -594,7 +594,12 @@ public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"St + " \"createdDate\": \"Timestamp\"\r\n" + " \"faceEmbedding\": [\"Float\"]\r\n" + "}") @RequestBody String identityData) throws IEMRException { logger.info("IdentityController.createIdentity - start"); - IdentityDTO identity = InputMapper.getInstance().gson().fromJson(identityData, IdentityDTO.class); + // Bare Gson matches Common-API's RegisterBenificiaryServiceImpl, which also + // serializes the outgoing identity payload with a bare new Gson(). dob relies + // on this symmetric default format; gpsTimestamp is still parsed correctly via + // its field-level @JsonAdapter(GpsTimestampAdapter.class) on Address, which + // works regardless of which Gson instance performs the parse. + IdentityDTO identity = new Gson().fromJson(identityData, IdentityDTO.class); logger.info("identity hit: " + identity); BeneficiaryCreateResp map; map = svc.createIdentity(identity); diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index f5ad60b5..49030bdd 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -34,6 +34,8 @@ import jakarta.persistence.Transient; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.JsonAdapter; +import com.iemr.common.identity.mapper.GpsTimestampAdapter; import lombok.Data; @@ -567,6 +569,7 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose @Column(name = "gpsTimestamp") + @JsonAdapter(GpsTimestampAdapter.class) private Timestamp gpsTimestamp; @Expose diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index b73f9b00..7000f6d0 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -31,7 +31,9 @@ import jakarta.persistence.Table; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; +import com.iemr.common.identity.mapper.GpsTimestampAdapter; import lombok.Data; @@ -376,6 +378,7 @@ public class RMNCHHouseHoldDetails { @Expose @Column(name = "gpsTimestamp") + @JsonAdapter(GpsTimestampAdapter.class) private Timestamp gpsTimestamp; @Expose diff --git a/src/main/java/com/iemr/common/identity/domain/Address.java b/src/main/java/com/iemr/common/identity/domain/Address.java index dfce1672..f355cbc8 100644 --- a/src/main/java/com/iemr/common/identity/domain/Address.java +++ b/src/main/java/com/iemr/common/identity/domain/Address.java @@ -25,6 +25,9 @@ import java.sql.Timestamp; +import com.google.gson.annotations.JsonAdapter; +import com.iemr.common.identity.mapper.GpsTimestampAdapter; + public @Data class Address { private String addrLine1; private String addrLine2; @@ -56,6 +59,7 @@ private Double gpsLatitude; private Double gpsLongitude; private String digipin; + @JsonAdapter(GpsTimestampAdapter.class) private Timestamp gpsTimestamp; private Boolean isGpsUnavailable; private String gpsUnavailableReason; diff --git a/src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java b/src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java new file mode 100644 index 00000000..1f1fe2ff --- /dev/null +++ b/src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java @@ -0,0 +1,116 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.identity.mapper; + +import java.io.IOException; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * Parses the GPS capture timestamp (epoch millis, ISO-8601 with/without a literal + * 'Z', or the mobile client's "MMM dd, yyyy, h:mm:ss a" format). + * + * Attach with {@code @JsonAdapter(GpsTimestampAdapter.class)} directly on a + * gpsTimestamp field only. Do NOT register this globally on a GsonBuilder for + * Timestamp.class — that previously intercepted every Timestamp field in the + * request (including dob) and silently nulled it out whenever the client's + * format didn't match one of the patterns below. + */ +public class GpsTimestampAdapter extends TypeAdapter { + + private static final Logger logger = LoggerFactory.getLogger(GpsTimestampAdapter.class); + + private static final DateTimeFormatter ISO_WITH_Z = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + private static final DateTimeFormatter ISO_NO_TZ = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); + private static final DateTimeFormatter CLIENT_DATE_FORMAT = + DateTimeFormatter.ofPattern("MMM dd, yyyy, h:mm:ss a", Locale.ENGLISH); + + @Override + public void write(JsonWriter out, Timestamp value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(value.getTime()); + } + } + + @Override + public Timestamp read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + if (in.peek() == JsonToken.NUMBER) { + return new Timestamp(in.nextLong()); + } + + String s = in.nextString(); + + // epoch millis as string + try { + return new Timestamp(Long.parseLong(s)); + } catch (NumberFormatException ignored) { + // not epoch millis, try the date formats below + } + // ISO 8601 with Z, e.g. "2021-06-18T00:00:00.000Z" + try { + return Timestamp.from(Instant.parse(s)); + } catch (Exception ignored) { + // not this format + } + // Mobile client format, e.g. "Jun 18, 2021, 5:30:00 AM" + try { + return Timestamp.valueOf(LocalDateTime.parse(s, CLIENT_DATE_FORMAT)); + } catch (Exception ignored) { + // not this format + } + // ISO with literal 'Z' pattern, e.g. "2021-06-18T00:00:00.000Z" parsed as local + try { + return Timestamp.valueOf(LocalDateTime.parse(s, ISO_WITH_Z)); + } catch (Exception ignored) { + // not this format + } + // ISO without timezone, e.g. "2021-06-18T00:00:00.000" (assume UTC) + try { + return Timestamp.from(LocalDateTime.parse(s, ISO_NO_TZ).toInstant(ZoneOffset.UTC)); + } catch (Exception ignored) { + // not this format + } + + logger.warn("GpsTimestampAdapter: unable to parse gpsTimestamp value '{}' with any known format; storing as null", s); + return null; + } +} diff --git a/src/main/java/com/iemr/common/identity/mapper/InputMapper.java b/src/main/java/com/iemr/common/identity/mapper/InputMapper.java index 704fe671..3549efc2 100644 --- a/src/main/java/com/iemr/common/identity/mapper/InputMapper.java +++ b/src/main/java/com/iemr/common/identity/mapper/InputMapper.java @@ -21,13 +21,6 @@ */ package com.iemr.common.identity.mapper; -import java.io.IOException; -import java.sql.Timestamp; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Locale; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,10 +33,6 @@ import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.LongSerializationPolicy; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; import com.iemr.common.identity.exception.IEMRException; public class InputMapper @@ -53,41 +42,15 @@ public class InputMapper private static GsonBuilder builder; private static InputMapper instance = null; - private static final DateTimeFormatter ISO_WITH_Z = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - private static final DateTimeFormatter CLIENT_DATE_FORMAT = - DateTimeFormatter.ofPattern("MMM dd, yyyy, h:mm:ss a", Locale.ENGLISH); - - private static final TypeAdapter TIMESTAMP_ADAPTER = new TypeAdapter() { - @Override - public void write(JsonWriter out, Timestamp value) throws IOException { - if (value == null) out.nullValue(); - else out.value(value.getTime()); - } - - @Override - public Timestamp read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } - if (in.peek() == JsonToken.NUMBER) return new Timestamp(in.nextLong()); - String s = in.nextString(); - // epoch millis as string - try { return new Timestamp(Long.parseLong(s)); } catch (NumberFormatException ignored) {} - // ISO 8601 with Z e.g. "2021-06-18T00:00:00.000Z" - try { return Timestamp.from(Instant.parse(s)); } catch (Exception ignored) {} - // Mobile client format e.g. "Jun 18, 2021, 5:30:00 AM" - try { return Timestamp.valueOf(LocalDateTime.parse(s, CLIENT_DATE_FORMAT)); } catch (Exception ignored) {} - // Fallback: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" literal Z - try { return Timestamp.valueOf(LocalDateTime.parse(s, ISO_WITH_Z)); } catch (Exception ignored) {} - return null; - } - }; - private InputMapper() { + // Timestamp fields (including dob) use Gson's default parsing here, same as + // on vb/stoptb. The gpsTimestamp field on Address/RMNCH entities is parsed by + // GpsTimestampAdapter via a field-level @JsonAdapter annotation instead of a + // global registration, so it can't affect any other Timestamp field. builder = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") // .excludeFieldsWithoutExposeAnnotation() - .serializeNulls().setLongSerializationPolicy(LongSerializationPolicy.STRING) - .registerTypeAdapter(Timestamp.class, TIMESTAMP_ADAPTER); + .serializeNulls().setLongSerializationPolicy(LongSerializationPolicy.STRING); } public static InputMapper getInstance() diff --git a/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java b/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java index fb419105..6dbc0301 100644 --- a/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java +++ b/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java @@ -21,13 +21,6 @@ */ package com.iemr.common.identity.utils.mapper; -import java.io.IOException; -import java.sql.Timestamp; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -35,10 +28,6 @@ import com.google.gson.ExclusionStrategy; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; import com.iemr.common.identity.utils.exception.IEMRException; /** @@ -59,30 +48,11 @@ public InputMapper() { if (builder == null) { builder = new GsonBuilder(); builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - builder.registerTypeAdapter(Timestamp.class, new TypeAdapter() { - private final DateTimeFormatter isoWithZ = DateTimeFormatter.ISO_INSTANT; - private final DateTimeFormatter isoNoTz = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); - - @Override - public void write(JsonWriter out, Timestamp value) throws IOException { - if (value == null) out.nullValue(); - else out.value(value.getTime()); - } - - @Override - public Timestamp read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } - if (in.peek() == JsonToken.NUMBER) return new Timestamp(in.nextLong()); - String s = in.nextString(); - // epoch millis as string - try { return new Timestamp(Long.parseLong(s)); } catch (NumberFormatException ignored) {} - // ISO 8601 with timezone (e.g. "2026-05-28T03:24:35.000Z") - try { return Timestamp.from(Instant.parse(s)); } catch (Exception ignored) {} - // ISO without timezone (e.g. "2026-05-28T03:24:35.000") - try { return Timestamp.from(LocalDateTime.parse(s, isoNoTz).toInstant(ZoneOffset.UTC)); } catch (Exception ignored) {} - return null; - } - }); + // Timestamp fields (including dob) use Gson's default parsing here, same as + // on vb/stoptb. The gpsTimestamp field on RMNCH entities is parsed by + // com.iemr.common.identity.mapper.GpsTimestampAdapter via a field-level + // @JsonAdapter annotation instead of a global registration, so it can't + // affect any other Timestamp field. } } From c3aabc9a32fd4c0a9228688e1e55cdd49a15f3d7 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 20 Jul 2026 17:05:06 +0530 Subject: [PATCH 50/62] fix(household): accept Pincode key variant during RMNCH sync deserialization FLW-Mobile-App's Household.kt serializes the field as "Pincode" (capital P), but the entity had no @SerializedName so Gson's exact-case field matching against "pincode" silently dropped the value on every sync. Co-Authored-By: Claude Sonnet 5 --- .../iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index b5137442..7105ba2e 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -204,6 +204,7 @@ public class RMNCHHouseHoldDetails { private String other_sourceofDrinkingWater; @Expose + @SerializedName(value = "pincode", alternate = "Pincode") @Column(name = "pincode") private Integer pincode; From fcead7143cf0c4894d88ea332d721d762274db42 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 20 Jul 2026 17:54:23 +0530 Subject: [PATCH 51/62] feat(household): add address column mapping to i_householddetails New address VARCHAR(500) column added to i_householddetails so household address can be captured once at HH Registration instead of being duplicated per-member on i_beneficiaryaddress. Accepts both "address" and "Address" keys during sync, following the same pattern as the earlier pincode key-case bug. Co-Authored-By: Claude Sonnet 5 --- .../common/identity/data/rmnch/RMNCHHouseHoldDetails.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index 7105ba2e..f24f0904 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -112,6 +112,11 @@ public class RMNCHHouseHoldDetails { @Column(name = "familyName") private String familyName; + @Expose + @SerializedName(value = "address", alternate = "Address") + @Column(name = "address") + private String address; + @Expose @Column(name = "totalHhMembers") private Integer totalHhMembers; From 26032ed767270c931f5a428820900700ca8e8299 Mon Sep 17 00:00:00 2001 From: Vishwanath Balkur <118195001+vishwab1@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:32:01 +0530 Subject: [PATCH 52/62] Merge 3.8.2 code (#177) * feat: write anthropometry from rmnch/syncDataToAmrit to i_beneficiarydetails.otherFields Mobile sends height/weight/bmi/temperature in beneficiaryDetails payload. i_beneficiarydetails_rmnch has no these columns so they were lost. FLW-API getBeneficiaryData reads from otherFields (temperatureValue key). - RMNCHBeneficiaryDetailsRmnch: @Transient height/weight/bmi/temperature - BenDetailRepo: updateOtherFieldsByBenRegId query - RmnchDataSyncServiceImpl: merge anthropometry into otherFields after save Co-Authored-By: Claude Sonnet 4.6 * feat: stamp vanID from Redis on all 4 RMNCH entities in syncDataToAmrit Reads camp:vanID and camp:parkingPlaceID from Redis (written by MMU-API on van login) and stamps them onto RMNCHBeneficiaryDetailsRmnch, RMNCHBornBirthDetails, RMNCHCBACdetails, and RMNCHHouseHoldDetails when the mobile payload carries VanID=null or 0. Also writes anthropometry (height/weight/bmi/temperature) from mobile beneficiaryDetails payload into i_beneficiarydetails.otherFields so FLW-API getBeneficiaryData can return them. Gracefully skips if Redis has no camp configured. Co-Authored-By: Claude Sonnet 4.6 * fix: preserve @Transient anthropometry fields lost after JPA saveAll merge Hibernate merge() returns new managed instances that do not carry @Transient field values (height/weight/bmi/temperature). Keep a reference to the original list before saveAll so the anthropometry loop reads from objects that still have the mobile payload values. Co-Authored-By: Claude Sonnet 4.6 * fix: prevent camp:vanID deletion on every syncDataToAmrit call getObject("camp:vanID", false, 0) was calling EXPIRE key 0 which immediately deletes the key in Redis after every sync. Replaced with getRaw() which reads the value without modifying TTL, so camp:vanID persists across multiple syncs until MMU logout. Co-Authored-By: Claude Sonnet 4.6 * feat(stoptb): add stoptb.enforce.vanid flag to syncDataToAmrit The Stop TB mobile app calls this RMNCH sync endpoint directly to save household details. Same issue as elsewhere: vanID stamping is skipped silently when Redis has no camp:vanID, so household records end up with vanID=NULL. When stoptb.enforce.vanid=true, sync now fails with a clear error instead. * feat(STOP-148): add GPS location capture support for beneficiary and RMNCH records Introduces GPS-related fields (gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable, and gpsUnavailableReason) across beneficiary address and RMNCH domain models, including MBeneficiaryaddress, Address DTO, RMNCHBeneficiaryDetailsRmnch, and RMNCHHouseHoldDetails. Enhances IdentityMapper and IdentityService to map and persist GPS information between incoming DTOs and beneficiary address entities. Updates RmnchDataSyncServiceImpl to extract and synchronize GPS details from the nested i_bendemographics payload during beneficiary sync, and to parse gpsTimestamp from household details during RMNCH household data processing. * feat(STOP-148): add GPS location capture support for beneficiary and RMNCH records (#167) Introduces GPS-related fields (gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable, and gpsUnavailableReason) across beneficiary address and RMNCH domain models, including MBeneficiaryaddress, Address DTO, RMNCHBeneficiaryDetailsRmnch, and RMNCHHouseHoldDetails. Enhances IdentityMapper and IdentityService to map and persist GPS information between incoming DTOs and beneficiary address entities. Updates RmnchDataSyncServiceImpl to extract and synchronize GPS details from the nested i_bendemographics payload during beneficiary sync, and to parse gpsTimestamp from household details during RMNCH household data processing. Co-authored-by: Sehjot Singh Pannu * fix(STOP-148): use InputMapper Gson for createIdentity to parse epoch-millis gpsTimestamp createIdentity() parsed the request body with a bare new Gson(), whose default Timestamp adapter only accepts ISO8601 date strings. The new gpsTimestamp field is sent as raw epoch millis, causing JsonSyntaxException during parsing whenever GPS data is present. That exception propagated up through JwtUserIdValidationFilter's catch block and was misreported as a 401 Authorization error. InputMapper already registers a Timestamp adapter that handles epoch millis correctly; switch createIdentity() to use it. Co-Authored-By: Claude Sonnet 4.6 * fix(datasync): add vanID column to BenGenID import INSERT query SQL had 6 placeholders but object array had 7 elements including vanID, causing parameter index out of bounds error. Added vanID to INSERT columns. Co-Authored-By: Claude Sonnet 4.6 * fix(rmnch): surface pinCode in beneficiary details sync response permPinCode was already stored correctly in i_beneficiaryaddress but was never surfaced in the RMNCH sync response, since the response DTO had no pinCode field and the address mapping never copied it across. Co-Authored-By: Claude Sonnet 5 * fix(dob): stop global Timestamp adapter from nulling dob on GPS-enabled endpoints The GPS feature (STOP-148) registered a custom Gson TypeAdapter globally on InputMapper's GsonBuilder to parse the new gpsTimestamp field. Because it was global, it also intercepted dob, silently returning null whenever the incoming date string didn't match one of its four hardcoded formats. - Add GpsTimestampAdapter, attached only via @JsonAdapter on the gpsTimestamp field (Address, RMNCHBeneficiaryDetailsRmnch, RMNCHHouseHoldDetails), so it can't affect any other Timestamp field. - Remove the global registerTypeAdapter(Timestamp.class, ...) from both InputMapper.java copies, restoring Gson's default Timestamp parsing for dob and everything else (matching vb/stoptb). - Revert createIdentity()'s parser back to a bare new Gson(), matching Common-API's RegisterBenificiaryServiceImpl, which also serializes the outgoing identity payload with a bare new Gson(). InputMapper's setDateFormat is incompatible with that wire format (non-zero-padded day), which is why dob was nulling specifically for beneficiaries born on the 1st-9th of a month once createIdentity switched to InputMapper's Gson. Co-Authored-By: Claude Sonnet 5 --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Sehjot Singh Pannu --- src/main/environment/common_ci.properties | 3 + src/main/environment/common_docker.properties | 3 + .../environment/common_example.properties | 5 + .../controller/IdentityController.java | 5 + .../rmnch/RMNCHBeneficiaryDetailsRmnch.java | 46 +++ .../data/rmnch/RMNCHHouseHoldDetails.java | 72 +++-- .../iemr/common/identity/domain/Address.java | 12 + .../identity/domain/MBeneficiaryaddress.java | 60 ++-- .../identity/mapper/GpsTimestampAdapter.java | 116 ++++++++ .../identity/mapper/IdentityMapper.java | 84 +++--- .../common/identity/mapper/InputMapper.java | 4 + .../common/identity/repo/BenDetailRepo.java | 5 + .../identity/service/IdentityService.java | 264 +++++++++--------- .../rmnch/RmnchDataSyncServiceImpl.java | 141 +++++++++- .../identity/utils/mapper/InputMapper.java | 5 + .../identity/utils/redis/RedisStorage.java | 7 + 16 files changed, 618 insertions(+), 214 deletions(-) create mode 100644 src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index e32dc366..1ce43664 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -22,6 +22,9 @@ fhir-url=@env.FHIR_API@ # Redis Config spring.redis.host=@env.REDIS_HOST@ +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=@env.STOPTB_ENFORCE_VANID@ + cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ # Elasticsearch Configuration diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 07bd53cc..36735700 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -22,6 +22,9 @@ fhir-url=${FHIR_API} # Redis Config spring.redis.host=${REDIS_HOST} +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=${STOPTB_ENFORCE_VANID} + cors.allowed-origins=${CORS_ALLOWED_ORIGINS} # Elasticsearch Configuration diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index b78483f2..dd235c95 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -17,6 +17,11 @@ fhir-url=http://localhost:8093/ # Redis Config spring.redis.host=localhost + +# Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not +# configured instead of silently skipping vanID stamping +stoptb.enforce.vanid=false + cors.allowed-origins=http://localhost:* # Elasticsearch Configuration diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityController.java b/src/main/java/com/iemr/common/identity/controller/IdentityController.java index fc894761..8d015bd1 100644 --- a/src/main/java/com/iemr/common/identity/controller/IdentityController.java +++ b/src/main/java/com/iemr/common/identity/controller/IdentityController.java @@ -608,6 +608,11 @@ public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"St + " \"createdDate\": \"Timestamp\"\r\n" + " \"faceEmbedding\": [\"Float\"]\r\n" + "}") @RequestBody String identityData) throws IEMRException { logger.info("IdentityController.createIdentity - start"); + // Bare Gson matches Common-API's RegisterBenificiaryServiceImpl, which also + // serializes the outgoing identity payload with a bare new Gson(). dob relies + // on this symmetric default format; gpsTimestamp is still parsed correctly via + // its field-level @JsonAdapter(GpsTimestampAdapter.class) on Address, which + // works regardless of which Gson instance performs the parse. IdentityDTO identity = new Gson().fromJson(identityData, IdentityDTO.class); logger.info("identity hit: " + identity); BeneficiaryCreateResp map; diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index 1fb1b66d..814d2c73 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -34,6 +34,8 @@ import jakarta.persistence.Transient; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.JsonAdapter; +import com.iemr.common.identity.mapper.GpsTimestampAdapter; import lombok.Data; @@ -390,6 +392,9 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose @Transient private String addressLine3; + @Expose + @Transient + private String pinCode; // ---------------------------------------------- @@ -557,4 +562,45 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose @Transient private String familyId; + + // Anthropometry fields sent by Stop TB mobile app via beneficiaryDetails payload. + // i_beneficiarydetails_rmnch has no these columns — stored in i_beneficiarydetails.otherFields instead. + @Expose + @Transient + private Double height; + @Expose + @Transient + private Double weight; + @Expose + @Transient + private Double bmi; + @Expose + @Transient + private Double temperature; // stored as "temperatureValue" in otherFields to match getBeneficiaryData key + + @Expose + @Column(name = "gpsLatitude") + private Double gpsLatitude; + + @Expose + @Column(name = "gpsLongitude") + private Double gpsLongitude; + + @Expose + @Column(name = "digipin") + private String digipin; + + @Expose + @Column(name = "gpsTimestamp") + @JsonAdapter(GpsTimestampAdapter.class) + 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; + } diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index 8aa8ed47..7000f6d0 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -1,24 +1,24 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ package com.iemr.common.identity.data.rmnch; import java.sql.Timestamp; @@ -31,11 +31,14 @@ import jakarta.persistence.Table; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.iemr.common.identity.mapper.GpsTimestampAdapter; import lombok.Data; /** - * + * * @author de40034072 * */ @@ -359,4 +362,31 @@ public class RMNCHHouseHoldDetails { @Column(name = "mohallaName") private String mohallaName; + @Expose + @SerializedName("latitude") + @Column(name = "gpsLatitude") + private Double gpsLatitude; + + @Expose + @SerializedName("longitude") + @Column(name = "gpsLongitude") + private Double gpsLongitude; + + @Expose + @Column(name = "digipin") + private String digipin; + + @Expose + @Column(name = "gpsTimestamp") + @JsonAdapter(GpsTimestampAdapter.class) + 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; + } diff --git a/src/main/java/com/iemr/common/identity/domain/Address.java b/src/main/java/com/iemr/common/identity/domain/Address.java index c37b4507..f355cbc8 100644 --- a/src/main/java/com/iemr/common/identity/domain/Address.java +++ b/src/main/java/com/iemr/common/identity/domain/Address.java @@ -23,6 +23,11 @@ import lombok.Data; +import java.sql.Timestamp; + +import com.google.gson.annotations.JsonAdapter; +import com.iemr.common.identity.mapper.GpsTimestampAdapter; + public @Data class Address { private String addrLine1; private String addrLine2; @@ -51,4 +56,11 @@ private Integer vanID; private Integer parkingPlaceID; + private Double gpsLatitude; + private Double gpsLongitude; + private String digipin; + @JsonAdapter(GpsTimestampAdapter.class) + private Timestamp gpsTimestamp; + private Boolean isGpsUnavailable; + private String gpsUnavailableReason; } diff --git a/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java b/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java index 0e2ab415..d5eaf7bd 100644 --- a/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java +++ b/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java @@ -1,24 +1,24 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ package com.iemr.common.identity.domain; import java.io.Serializable; @@ -39,7 +39,7 @@ /** * The persistent class for the m_beneficiaryaddress database table. - * + * */ @Entity @Table(name = "i_beneficiaryaddress") @@ -260,6 +260,24 @@ public class MBeneficiaryaddress implements Serializable { // END OF new column added for data sync + @Column(name = "gpsLatitude") + private Double gpsLatitude; + + @Column(name = "gpsLongitude") + private Double gpsLongitude; + + @Column(name = "digipin") + private String digipin; + + @Column(name = "gpsTimestamp") + private Timestamp gpsTimestamp; + + @Column(name = "isGpsUnavailable", nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0") + private Boolean isGpsUnavailable = false; + + @Column(name = "gpsUnavailableReason") + private String gpsUnavailableReason; + public MBeneficiaryaddress setCurrentAddress(Address address) { this.currAddrLine1 = address.getAddrLine1(); this.currAddrLine2 = address.getAddrLine2(); diff --git a/src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java b/src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java new file mode 100644 index 00000000..1f1fe2ff --- /dev/null +++ b/src/main/java/com/iemr/common/identity/mapper/GpsTimestampAdapter.java @@ -0,0 +1,116 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.identity.mapper; + +import java.io.IOException; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * Parses the GPS capture timestamp (epoch millis, ISO-8601 with/without a literal + * 'Z', or the mobile client's "MMM dd, yyyy, h:mm:ss a" format). + * + * Attach with {@code @JsonAdapter(GpsTimestampAdapter.class)} directly on a + * gpsTimestamp field only. Do NOT register this globally on a GsonBuilder for + * Timestamp.class — that previously intercepted every Timestamp field in the + * request (including dob) and silently nulled it out whenever the client's + * format didn't match one of the patterns below. + */ +public class GpsTimestampAdapter extends TypeAdapter { + + private static final Logger logger = LoggerFactory.getLogger(GpsTimestampAdapter.class); + + private static final DateTimeFormatter ISO_WITH_Z = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + private static final DateTimeFormatter ISO_NO_TZ = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); + private static final DateTimeFormatter CLIENT_DATE_FORMAT = + DateTimeFormatter.ofPattern("MMM dd, yyyy, h:mm:ss a", Locale.ENGLISH); + + @Override + public void write(JsonWriter out, Timestamp value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(value.getTime()); + } + } + + @Override + public Timestamp read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + if (in.peek() == JsonToken.NUMBER) { + return new Timestamp(in.nextLong()); + } + + String s = in.nextString(); + + // epoch millis as string + try { + return new Timestamp(Long.parseLong(s)); + } catch (NumberFormatException ignored) { + // not epoch millis, try the date formats below + } + // ISO 8601 with Z, e.g. "2021-06-18T00:00:00.000Z" + try { + return Timestamp.from(Instant.parse(s)); + } catch (Exception ignored) { + // not this format + } + // Mobile client format, e.g. "Jun 18, 2021, 5:30:00 AM" + try { + return Timestamp.valueOf(LocalDateTime.parse(s, CLIENT_DATE_FORMAT)); + } catch (Exception ignored) { + // not this format + } + // ISO with literal 'Z' pattern, e.g. "2021-06-18T00:00:00.000Z" parsed as local + try { + return Timestamp.valueOf(LocalDateTime.parse(s, ISO_WITH_Z)); + } catch (Exception ignored) { + // not this format + } + // ISO without timezone, e.g. "2021-06-18T00:00:00.000" (assume UTC) + try { + return Timestamp.from(LocalDateTime.parse(s, ISO_NO_TZ).toInstant(ZoneOffset.UTC)); + } catch (Exception ignored) { + // not this format + } + + logger.warn("GpsTimestampAdapter: unable to parse gpsTimestamp value '{}' with any known format; storing as null", s); + return null; + } +} diff --git a/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java b/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java index bc3ad686..765acb97 100644 --- a/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java +++ b/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java @@ -1,24 +1,24 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ package com.iemr.common.identity.mapper; import java.sql.Timestamp; @@ -66,7 +66,7 @@ public interface IdentityMapper { MBeneficiarymapping identityDTOToMBeneficiarymapping(IdentityDTO dto); - + @Mapping(source = "defaultNo", target = "shareAnonymousWithGovt") @Mapping(source = "defaultNo", target = "shareAnonymousWithMedicalCommunity") @@ -93,7 +93,7 @@ public interface IdentityMapper { MBeneficiaryconsent identityDTOToDefaultMBeneficiaryconsent(IdentityDTO dto, Boolean defaultYes, Boolean defaultNo); - + // @Mapping(source = "dto.areaId", target = "areaId") // @Mapping(source = "dto.beneficiaryRegId", target = "beneficiaryRegID") @@ -144,7 +144,7 @@ public interface IdentityMapper { // @Mapping(source = "dto.vanID", target = "vanID") // @Mapping(source = "dto.parkingPlaceId", target = "parkingPlaceID") // MBeneficiarydetail identityDTOToMBeneficiarydetail(IdentityDTO dto); - + @Mapping(source = "benFamilyDTO.isEmergencyContact", target = "isEmergencyContact") @Mapping(source = "benFamilyDTO.relationshipToSelf", target = "relationshipToSelf") @@ -152,7 +152,7 @@ public interface IdentityMapper { @Mapping(source = "createdBy", target = "createdBy") @Mapping(source = "createdDate", target = "createdDate") MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO benFamilyDTO, String createdBy, - Timestamp createdDate); + Timestamp createdDate); List identityDTOListToMBeneficiaryfamilymappingList(List list); @@ -210,6 +210,12 @@ MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO be @Mapping(target = "currentAddress.village", source = "map.MBeneficiaryaddress.currVillage") @Mapping(target = "currentAddress.addressValue", source = "map.MBeneficiaryaddress.currAddressValue") @Mapping(target = "currentAddress.pinCode", source = "map.MBeneficiaryaddress.currPinCode") + @Mapping(target = "currentAddress.gpsLatitude", source = "map.MBeneficiaryaddress.gpsLatitude") + @Mapping(target = "currentAddress.gpsLongitude", source = "map.MBeneficiaryaddress.gpsLongitude") + @Mapping(target = "currentAddress.digipin", source = "map.MBeneficiaryaddress.digipin") + @Mapping(target = "currentAddress.gpsTimestamp", source = "map.MBeneficiaryaddress.gpsTimestamp") + @Mapping(target = "currentAddress.isGpsUnavailable", source = "map.MBeneficiaryaddress.isGpsUnavailable") + @Mapping(target = "currentAddress.gpsUnavailableReason", source = "map.MBeneficiaryaddress.gpsUnavailableReason") @Mapping(target = "emergencyAddress.addrLine1", source = "map.MBeneficiaryaddress.emerAddrLine1") @Mapping(target = "emergencyAddress.addrLine2", source = "map.MBeneficiaryaddress.emerAddrLine2") @Mapping(target = "emergencyAddress.addrLine3", source = "map.MBeneficiaryaddress.emerAddrLine3") @@ -296,13 +302,13 @@ MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO be @Mapping(target = "beneficiaryDetails.title", source = "map.MBeneficiarydetail.title") @Mapping(target = "beneficiaryDetails.zoneId", source = "map.MBeneficiarydetail.zoneId") @Mapping(target = "contacts", expression = "java( map != null && map.getMBeneficiarycontact() != null && " - + "map.getMBeneficiarydetail() != null ? " - + "Phone.createContactList(map.getMBeneficiarycontact(), " - + "(benRegId != null ? benRegId.toString() : null), " - + "(map.getMBeneficiarydetail().getFirstName() != null ? map.getMBeneficiarydetail().getFirstName() : \"\") + \" \" + " - + "(map.getMBeneficiarydetail().getMiddleName() != null ? map.getMBeneficiarydetail().getMiddleName() : \"\") + \" \" + " - + "(map.getMBeneficiarydetail().getLastName() != null ? map.getMBeneficiarydetail().getLastName() : \"\") " - + ") : null)") + + "map.getMBeneficiarydetail() != null ? " + + "Phone.createContactList(map.getMBeneficiarycontact(), " + + "(map.getBenRegId() != null ? map.getBenRegId().toString() : null), " + + "(map.getMBeneficiarydetail().getFirstName() != null ? map.getMBeneficiarydetail().getFirstName() : \"\") + \" \" + " + + "(map.getMBeneficiarydetail().getMiddleName() != null ? map.getMBeneficiarydetail().getMiddleName() : \"\") + \" \" + " + + "(map.getMBeneficiarydetail().getLastName() != null ? map.getMBeneficiarydetail().getLastName() : \"\") " + + ") : null)") @Mapping(target = "permanentAddress.zoneID", source = "map.MBeneficiaryaddress.permZoneID") @Mapping(target = "permanentAddress.zoneName", source = "map.MBeneficiaryaddress.permZone") @@ -334,13 +340,13 @@ MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO be @Mapping(target = "accountNo", source = "map.MBeneficiaryAccount.accountNo") @Mapping(target = "benAccountID", source = "map.benAccountID") @Mapping(target = "ageAtMarriage", expression = "java(map != null && map.getMBeneficiarydetail() != null ? " - + "MBeneficiarydetail.getAgeAtMarriageCalc(map.getMBeneficiarydetail().getDob(), " - + "map.getMBeneficiarydetail().getMarriageDate(), " - + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") + + "MBeneficiarydetail.getAgeAtMarriageCalc(map.getMBeneficiarydetail().getDob(), " + + "map.getMBeneficiarydetail().getMarriageDate(), " + + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") @Mapping(target = "marriageDate", expression = "java(map != null && map.getMBeneficiarydetail() != null ? " - + "MBeneficiarydetail.getMarriageDateCalc(map.getMBeneficiarydetail().getDob(), " - + "map.getMBeneficiarydetail().getMarriageDate(), " - + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") + + "MBeneficiarydetail.getMarriageDateCalc(map.getMBeneficiarydetail().getDob(), " + + "map.getMBeneficiarydetail().getMarriageDate(), " + + "map.getMBeneficiarydetail().getAgeAtMarriage()) : null)") @Mapping(target = "literacyStatus", source = "map.MBeneficiarydetail.literacyStatus") @Mapping(target = "motherName", source = "map.MBeneficiarydetail.motherName") @@ -481,7 +487,7 @@ List mapToMBeneficiaryfamilymappingWithBenFamilyDTOList( @Mapping(source = "dto.createdDate", target = "createdDate") @Mapping(source = "dto.vanID", target = "vanID") @Mapping(source = "dto.parkingPlaceId", target = "parkingPlaceID") - // End + // End MBeneficiaryAccount identityDTOToMBeneficiaryAccount(IdentityDTO dto); @InheritInverseConfiguration @@ -490,7 +496,7 @@ List mapToMBeneficiaryfamilymappingWithBenFamilyDTOList( @Mapping(source = "dto.benImage", target = "benImage") @Mapping(source = "dto.agentName", target = "createdBy") @Mapping(source = "dto.createdDate", target = "createdDate") - + @Mapping(source = "dto.vanID", target = "vanID") @Mapping(source = "dto.parkingPlaceId", target = "parkingPlaceID") diff --git a/src/main/java/com/iemr/common/identity/mapper/InputMapper.java b/src/main/java/com/iemr/common/identity/mapper/InputMapper.java index 9a46ba4f..3549efc2 100644 --- a/src/main/java/com/iemr/common/identity/mapper/InputMapper.java +++ b/src/main/java/com/iemr/common/identity/mapper/InputMapper.java @@ -44,6 +44,10 @@ public class InputMapper private InputMapper() { + // Timestamp fields (including dob) use Gson's default parsing here, same as + // on vb/stoptb. The gpsTimestamp field on Address/RMNCH entities is parsed by + // GpsTimestampAdapter via a field-level @JsonAdapter annotation instead of a + // global registration, so it can't affect any other Timestamp field. builder = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") // .excludeFieldsWithoutExposeAnnotation() .serializeNulls().setLongSerializationPolicy(LongSerializationPolicy.STRING); diff --git a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java index 4e8bcc01..413298ba 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java @@ -148,6 +148,11 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi @Query("SELECT b FROM MBeneficiarydetail b WHERE b.familyId =:familyid ") List searchByFamilyId(@Param("familyid") String familyid); + @Transactional + @Modifying + @Query("UPDATE MBeneficiarydetail d SET d.otherFields = :otherFields WHERE d.mBeneficiarymapping.benRegId = :benRegId") + int updateOtherFieldsByBenRegId(@Param("benRegId") BigInteger benRegId, @Param("otherFields") String otherFields); + /** * Find complete beneficiary data by IDs from Elasticsearch */ diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 90e660b9..bb1dd988 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -1,23 +1,23 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution -* -* Copyright (C) "Piramal Swasthya Management and Research Institute" -* -* This file is part of AMRIT. -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. */ package com.iemr.common.identity.service; @@ -229,7 +229,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null || list3.get(i).getBeneficiaryDetails().getFirstName() == null || !list3.get(i).getBeneficiaryDetails().getFirstName() - .equalsIgnoreCase(searchDTO.getFirstName())) { + .equalsIgnoreCase(searchDTO.getFirstName())) { list3.remove(i); i--; @@ -241,7 +241,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null || list3.get(i).getBeneficiaryDetails().getLastName() == null || !list3.get(i).getBeneficiaryDetails().getLastName() - .equalsIgnoreCase(searchDTO.getLastName())) { + .equalsIgnoreCase(searchDTO.getLastName())) { list3.remove(i); i--; @@ -286,7 +286,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null || list3.get(i).getCurrentAddress().getDistrictId() == null || !list3.get(i).getCurrentAddress().getDistrictId() - .equals(searchDTO.getCurrentAddress().getDistrictId())) { + .equals(searchDTO.getCurrentAddress().getDistrictId())) { list3.remove(i); i--; @@ -298,7 +298,7 @@ public List getBeneficiaries(IdentitySearchDTO searchDTO) if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null || list3.get(i).getCurrentAddress().getVillageId() == null || !list3.get(i).getCurrentAddress().getVillageId() - .equals(searchDTO.getCurrentAddress().getVillageId())) { + .equals(searchDTO.getCurrentAddress().getVillageId())) { list3.remove(i); i--; @@ -515,7 +515,7 @@ public List getBeneficiariesByBenId(BigInteger benId) /** * - * @param BenRegId + * @param benRegId * @return */ public List getBeneficiariesByBenRegId(BigInteger benRegId) @@ -591,104 +591,104 @@ public List getBeneficiariesByPhoneNum(String phoneNum) } -/** - * Advanced search using Elasticsearch with fallback to database - */ -public Map advancedSearchBeneficiariesES( - String firstName, String middleName, String lastName, Integer genderId, java.util.Date dob, - Integer stateId, Integer districtId, Integer blockId, Integer villageId, - String fatherName, String spouseName, String maritalStatus, String phoneNumber, - String beneficiaryId, String healthId, String aadharNo, - Integer userId, String auth, Boolean is1097) throws Exception { - - try { - logger.info("IdentityService.advancedSearchBeneficiariesES - start"); - logger.info("ES enabled: {}", esEnabled); - - Map response = new HashMap<>(); - - if (esEnabled) { - logger.info("Using Elasticsearch for advanced search"); - - // Call Elasticsearch service - List> esResults = elasticsearchService.advancedSearch( - firstName, middleName, lastName, genderId, dob, stateId, districtId, - blockId, villageId, fatherName, spouseName, maritalStatus, phoneNumber, - beneficiaryId, healthId, aadharNo, userId - ); - - response.put("data", esResults); - response.put("count", esResults.size()); - response.put("source", "elasticsearch"); - - logger.info("ES returned {} results", esResults.size()); - - } else { - logger.info("ES disabled - using database for advanced search"); - - IdentitySearchDTO searchDTO = new IdentitySearchDTO(); - searchDTO.setFirstName(firstName); - searchDTO.setLastName(lastName); - searchDTO.setGenderId(genderId); - searchDTO.setDob(dob != null ? new Timestamp(dob.getTime()) : null); - searchDTO.setFatherName(fatherName); - searchDTO.setSpouseName(spouseName); - searchDTO.setContactNumber(phoneNumber); - - if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { - try { - searchDTO.setBeneficiaryId(new BigInteger(beneficiaryId)); - } catch (NumberFormatException e) { - logger.warn("Invalid beneficiaryId format: {}", beneficiaryId); + /** + * Advanced search using Elasticsearch with fallback to database + */ + public Map advancedSearchBeneficiariesES( + String firstName, String middleName, String lastName, Integer genderId, Date dob, + Integer stateId, Integer districtId, Integer blockId, Integer villageId, + String fatherName, String spouseName, String maritalStatus, String phoneNumber, + String beneficiaryId, String healthId, String aadharNo, + Integer userId, String auth, Boolean is1097) throws Exception { + + try { + logger.info("IdentityService.advancedSearchBeneficiariesES - start"); + logger.info("ES enabled: {}", esEnabled); + + Map response = new HashMap<>(); + + if (esEnabled) { + logger.info("Using Elasticsearch for advanced search"); + + // Call Elasticsearch service + List> esResults = elasticsearchService.advancedSearch( + firstName, middleName, lastName, genderId, dob, stateId, districtId, + blockId, villageId, fatherName, spouseName, maritalStatus, phoneNumber, + beneficiaryId, healthId, aadharNo, userId + ); + + response.put("data", esResults); + response.put("count", esResults.size()); + response.put("source", "elasticsearch"); + + logger.info("ES returned {} results", esResults.size()); + + } else { + logger.info("ES disabled - using database for advanced search"); + + IdentitySearchDTO searchDTO = new IdentitySearchDTO(); + searchDTO.setFirstName(firstName); + searchDTO.setLastName(lastName); + searchDTO.setGenderId(genderId); + searchDTO.setDob(dob != null ? new Timestamp(dob.getTime()) : null); + searchDTO.setFatherName(fatherName); + searchDTO.setSpouseName(spouseName); + searchDTO.setContactNumber(phoneNumber); + + if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { + try { + searchDTO.setBeneficiaryId(new BigInteger(beneficiaryId)); + } catch (NumberFormatException e) { + logger.warn("Invalid beneficiaryId format: {}", beneficiaryId); + } } + + if (stateId != null || districtId != null || blockId != null || villageId != null) { + Address addressDTO = new Address(); + addressDTO.setStateId(stateId); + addressDTO.setDistrictId(districtId); + addressDTO.setSubDistrictId(blockId); + addressDTO.setVillageId(villageId); + searchDTO.setCurrentAddress(addressDTO); + } + + List dbResults = this.getBeneficiaries(searchDTO); + + List> formattedResults = dbResults.stream() + .map(this::convertBeneficiaryDTOToMap) + .collect(Collectors.toList()); + + response.put("data", formattedResults); + response.put("count", formattedResults.size()); + response.put("source", "database"); + + logger.info("Database returned {} results", formattedResults.size()); } - - if (stateId != null || districtId != null || blockId != null || villageId != null) { - Address addressDTO = new Address(); - addressDTO.setStateId(stateId); - addressDTO.setDistrictId(districtId); - addressDTO.setSubDistrictId(blockId); - addressDTO.setVillageId(villageId); - searchDTO.setCurrentAddress(addressDTO); - } - - List dbResults = this.getBeneficiaries(searchDTO); - - List> formattedResults = dbResults.stream() - .map(this::convertBeneficiaryDTOToMap) - .collect(Collectors.toList()); - - response.put("data", formattedResults); - response.put("count", formattedResults.size()); - response.put("source", "database"); - - logger.info("Database returned {} results", formattedResults.size()); - } - - logger.info("IdentityService.advancedSearchBeneficiariesES - end"); - return response; - - } catch (Exception e) { - logger.error("Advanced search failed: {}", e.getMessage(), e); - throw new Exception("Error in advanced search: " + e.getMessage(), e); - } -}/** - * Convert BeneficiariesDTO to Map format - */ -private Map convertBeneficiaryDTOToMap(BeneficiariesDTO dto) { - try { - ObjectMapper mapper = new ObjectMapper(); - String json = mapper.writeValueAsString(dto); - return mapper.readValue(json, Map.class); - } catch (Exception e) { - logger.error("Error converting DTO to map", e); - return new HashMap<>(); + + logger.info("IdentityService.advancedSearchBeneficiariesES - end"); + return response; + + } catch (Exception e) { + logger.error("Advanced search failed: {}", e.getMessage(), e); + throw new Exception("Error in advanced search: " + e.getMessage(), e); + } + }/** + * Convert BeneficiariesDTO to Map format + */ + private Map convertBeneficiaryDTOToMap(BeneficiariesDTO dto) { + try { + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(dto); + return mapper.readValue(json, Map.class); + } catch (Exception e) { + logger.error("Error converting DTO to map", e); + return new HashMap<>(); + } } -} - /** + /** * * * * Search beneficiary by healthID / ABHA address @@ -763,7 +763,7 @@ public List searhBeneficiaryByFamilyId(String familyId) List benDetailsList = detailRepo.searchByFamilyId(familyId); if (benDetailsList == null || benDetailsList.isEmpty()) { - return beneficiaryList; + return beneficiaryList; }else { // considering as of now family creation is possible through facility modules // only @@ -789,7 +789,7 @@ public List searhBeneficiaryByFamilyId(String familyId) } public List searchBeneficiaryByVillageIdAndLastModifyDate(List villageIDs, - Timestamp lastModifiedDate) { + Timestamp lastModifiedDate) { List beneficiaryList = new ArrayList<>(); try { @@ -843,7 +843,7 @@ public List searhBeneficiaryByGovIdentity(String identity) // find benmap ids if (benIdentityList == null || benIdentityList.isEmpty()) { - return beneficiaryList; + return beneficiaryList; }else { for (MBeneficiaryidentity identityObj : benIdentityList) { benMapObjArr.addAll( @@ -1124,7 +1124,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiaryaddress().getBenAddressID(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benAddressID != null) { - mbAddr.setBenAddressID(benAddressID); + mbAddr.setBenAddressID(benAddressID); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1151,7 +1151,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiarycontact().getBenContactsID(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benContactsID != null) { - benCon.setBenContactsID(benContactsID); + benCon.setBenContactsID(benContactsID); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1269,7 +1269,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiaryAccount().getBenAccountID(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benAccountID != null) { - beneficiaryAccount.setBenAccountID(benAccountID); + beneficiaryAccount.setBenAccountID(benAccountID); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1294,7 +1294,7 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields benMapping.getMBeneficiaryImage().getBenImageId(), benMapping.getVanID()); // next statement is new one, setting correct beneficiaryDetailsId if (benImageId != null) { - beneficiaryImage.setBenImageId(benImageId); + beneficiaryImage.setBenImageId(benImageId); }else { throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); } @@ -1314,11 +1314,11 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields logger.info("Triggering Elasticsearch sync for benRegId: {}", identity.getBeneficiaryRegId()); syncService.syncBeneficiaryAsync(identity.getBeneficiaryRegId()); } - - logger.info("IdentityService.editIdentity - end. id = " + benMapping.getBenMapId()); -} - + logger.info("IdentityService.editIdentity - end. id = " + benMapping.getBenMapId()); + } + + private MBeneficiarydetail convertIdentityEditDTOToMBeneficiarydetail(IdentityEditDTO dto) { MBeneficiarydetail beneficiarydetail = new MBeneficiarydetail(); @@ -1829,6 +1829,12 @@ private MBeneficiaryaddress identityDTOToMBeneficiaryaddress(IdentityDTO dto) { beneficiaryAddress.setCurrServicePointId(dto.getCurrentAddress().getServicePointID()); beneficiaryAddress.setCurrServicePoint(dto.getCurrentAddress().getServicePointName()); beneficiaryAddress.setCurrHabitation(dto.getCurrentAddress().getHabitation()); + beneficiaryAddress.setGpsLatitude(dto.getCurrentAddress().getGpsLatitude()); + beneficiaryAddress.setGpsLongitude(dto.getCurrentAddress().getGpsLongitude()); + beneficiaryAddress.setDigipin(dto.getCurrentAddress().getDigipin()); + beneficiaryAddress.setGpsTimestamp(dto.getCurrentAddress().getGpsTimestamp()); + beneficiaryAddress.setIsGpsUnavailable(dto.getCurrentAddress().getIsGpsUnavailable()); + beneficiaryAddress.setGpsUnavailableReason(dto.getCurrentAddress().getGpsUnavailableReason()); } if (dto.getEmergencyAddress() != null) { beneficiaryAddress.setEmerAddrLine1(dto.getEmergencyAddress().getAddrLine1()); @@ -1939,7 +1945,7 @@ public String unReserveIdentity(ReserveIdentityDTO unReserve) { * Get partial details of beneficiaries (first name middle name and last * name) list on benId's list * - * @param BenRegIds + * @param benRegIds * @return {@link List} Beneficiaries */ public List getBeneficiariesPartialDeatilsByBenRegIdList(List benRegIds) { @@ -2157,7 +2163,7 @@ public int importBenIdToLocalServer(List benIdImportDTOList) { List dataList = new ArrayList<>(); Object[] objArr; String query = " INSERT INTO m_beneficiaryregidmapping(BenRegId, BeneficiaryID, " - + " Provisioned, CreatedDate, CreatedBy, Reserved) VALUES (?,?,?,?,?,?) "; + + " Provisioned, CreatedDate, CreatedBy, Reserved, vanID) VALUES (?,?,?,?,?,?,?) "; logger.info("query : " + query); for (MBeneficiaryregidmapping obj : mBeneficiaryregidmappingList) { logger.info("inside for check->", obj); diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 3bbd6b09..528310d4 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -46,6 +46,7 @@ import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -73,6 +74,9 @@ import com.iemr.common.identity.repo.rmnch.RMNCHCBACDetailsRepo; import com.iemr.common.identity.repo.rmnch.RMNCHHouseHoldDetailsRepo; import com.iemr.common.identity.repo.rmnch.RMNCHMBenMappingRepo; +import com.iemr.common.identity.domain.MBeneficiarydetail; +import com.iemr.common.identity.repo.BenDetailRepo; +import com.iemr.common.identity.utils.redis.RedisStorage; import com.iemr.common.identity.repo.rmnch.RMNCHMBenRegIdMapRepo; import com.iemr.common.identity.utils.config.ConfigProperties; import com.iemr.common.identity.utils.exception.IEMRException; @@ -116,6 +120,16 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { @Value("${fhir-url}") private String fhirUrl; + + @Autowired + private BenDetailRepo benDetailRepo; + @Autowired + private RedisStorage redisStorage; + + // When true, sync fails loudly if camp is not configured instead of silently + // skipping vanID stamping + @Value("${stoptb.enforce.vanid:false}") + private boolean enforceVanID; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override public String syncDataToAmrit(String requestOBJ, String authorization) throws Exception { @@ -128,6 +142,24 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex ArrayList cBACDetailsIds = new ArrayList<>(); ArrayList houseHoldDetailsIds = new ArrayList<>(); + // Read camp vanID/parkingPlaceID from Redis (set by MMU-API on van login) + Integer campVanID = null; + Integer campParkingPlaceID = null; + try { + String vanVal = redisStorage.getRaw("camp:vanID"); + String ppVal = redisStorage.getRaw("camp:parkingPlaceID"); + if (vanVal != null && !vanVal.isBlank()) campVanID = Integer.parseInt(vanVal); + if (ppVal != null && !ppVal.isBlank()) campParkingPlaceID = Integer.parseInt(ppVal); + } catch (Exception ignored) { + // no camp configured — vanID stamping skipped + } + if (campVanID == null && enforceVanID) { + throw new Exception( + "Camp not configured: vanID missing. Please select van/service point in MMU before syncing data."); + } + final Integer vanID = campVanID; + final Integer parkingPlaceID = campParkingPlaceID; + try { if (requestOBJ != null && !requestOBJ.isEmpty()) { JsonObject jsnOBJ = new JsonObject(); @@ -151,10 +183,37 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex // benRegID = rMNCHMBenRegIdMapRepo.getRegID(benDetailsExtraList.get(0).getBenficieryid()); // // if (benRegID != null) { - + + // Build GPS lookup map from i_bendemographics in raw JSON + Map benGpsMap = new HashMap<>(); + JsonArray benJsonArr = jsnOBJ.getAsJsonArray("beneficiaryDetails"); + for (JsonElement el : benJsonArr) { + JsonObject benJson = el.getAsJsonObject(); + if (benJson.has("benficieryid") && !benJson.get("benficieryid").isJsonNull() + && benJson.has("i_bendemographics") + && !benJson.get("i_bendemographics").isJsonNull()) { + benGpsMap.put(benJson.get("benficieryid").getAsBigInteger(), + benJson.getAsJsonObject("i_bendemographics")); + } + } + for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsExtraList) { benRegID = rMNCHMBenRegIdMapRepo.getRegID(obj.getBenficieryid()); obj.setBenRegId(benRegID); + // Extract GPS from i_bendemographics + JsonObject demog = benGpsMap.get(obj.getBenficieryid()); + if (demog != null) { + if (demog.has("latitude") && !demog.get("latitude").isJsonNull()) + obj.setGpsLatitude(demog.get("latitude").getAsDouble()); + if (demog.has("longitude") && !demog.get("longitude").isJsonNull()) + obj.setGpsLongitude(demog.get("longitude").getAsDouble()); + if (demog.has("digipin") && !demog.get("digipin").isJsonNull()) + obj.setDigipin(demog.get("digipin").getAsString()); + if (demog.has("gpsTimestamp") && !demog.get("gpsTimestamp").isJsonNull()) + obj.setGpsTimestamp(new Timestamp(demog.get("gpsTimestamp").getAsLong())); + if (demog.has("isGpsUnavailable") && !demog.get("isGpsUnavailable").isJsonNull()) + obj.setIsGpsUnavailable(demog.get("isGpsUnavailable").getAsBoolean()); + } if(!rMNCHBeneficiaryDetailsRmnchRepo .getByRegID(benRegID).isEmpty()){ RMNCHBeneficiaryDetailsRmnch temp = rMNCHBeneficiaryDetailsRmnchRepo @@ -178,6 +237,10 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex } obj.setRelatedBeneficiaryIdsDB(sb.toString()); } + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } if(!rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).isEmpty()){ RMNCHMBeneficiarydetail rmnchmBeneficiarydetail = rMNCHBenDetailsRepo.getByBenRegID(obj.getBenRegId()).get(0); @@ -208,6 +271,9 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex } + // Keep original list before saveAll — @Transient fields (height/weight/bmi/temperature) + // are lost in the JPA-managed instances returned by merge() + List benDetailsOriginalList = new ArrayList<>(benDetailsExtraList); benDetailsExtraList = (ArrayList) rMNCHBeneficiaryDetailsRmnchRepo .saveAll(benDetailsExtraList); @@ -215,6 +281,22 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex // update beneficiary data in i_beneficiarydetails table rMNCHBenDetailsRepo.saveAll(benDetailsList); + // Write anthropometry (height/weight/bmi/temperature) to i_beneficiarydetails.otherFields. + // i_beneficiarydetails_rmnch has no these columns; FLW-API getBeneficiaryData reads from otherFields. + for (RMNCHBeneficiaryDetailsRmnch obj : benDetailsOriginalList) { + if (obj.getBenRegId() != null && hasAnthropometryData(obj)) { + try { + MBeneficiarydetail benDetail = benDetailRepo.findByBenRegId(obj.getBenRegId()); + if (benDetail != null) { + String merged = mergeAnthropometry(benDetail.getOtherFields(), obj); + benDetailRepo.updateOtherFieldsByBenRegId(obj.getBenRegId(), merged); + } + } catch (Exception ex) { + logger.warn("Failed to update otherFields for benRegId: " + obj.getBenRegId() + " - " + ex.getMessage()); + } + } + } + // born birth details if (jsnOBJ != null && jsnOBJ.has("bornBirthDeatils")) { RMNCHBornBirthDetails[] objArr1 = InputMapper.gson() @@ -228,7 +310,10 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex if (temp != null) obj.setBornBirthDeatilsId(temp.getBornBirthDeatilsId()); } - + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } bornBirthList = (ArrayList) rMNCHBornBirthDetailsRepo .saveAll(bornBirthList); @@ -254,7 +339,10 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex if (temp != null) obj.setCBACDetailsid(temp.getCBACDetailsid()); } - + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } } cbacList = (ArrayList) rMNCHCBACDetailsRepo.saveAll(cbacList); @@ -267,6 +355,22 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex .fromJson(jsnOBJ.get("houseHoldDetails"), RMNCHHouseHoldDetails[].class); List houseHoldList = Arrays.asList(objArr3); + // Build gpsTimestamp map (sent as string, needs manual parse) + Map hhTimestampMap = new HashMap<>(); + JsonArray hhJsonArr = jsnOBJ.getAsJsonArray("houseHoldDetails"); + for (JsonElement el : hhJsonArr) { + JsonObject hhJson = el.getAsJsonObject(); + try { + if (hhJson.has("houseoldId") && !hhJson.get("houseoldId").isJsonNull() + && hhJson.has("gpsTimestamp") + && !hhJson.get("gpsTimestamp").isJsonNull()) { + hhTimestampMap.put( + Long.parseLong(hhJson.get("houseoldId").getAsString()), + hhJson.get("gpsTimestamp").getAsLong()); + } + } catch (NumberFormatException ignored) {} + } + for (RMNCHHouseHoldDetails obj : houseHoldList) { if(!rMNCHHouseHoldDetailsRepo .getByHouseHoldID(obj.getHouseoldId()).isEmpty()){ @@ -274,8 +378,13 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex .getByHouseHoldID(obj.getHouseoldId()).get(0); if (temp != null) obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); + if (hhTimestampMap.containsKey(obj.getHouseoldId())) + obj.setGpsTimestamp(new Timestamp(hhTimestampMap.get(obj.getHouseoldId()))); + } + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); } - } houseHoldList = (ArrayList) rMNCHHouseHoldDetailsRepo .saveAll(houseHoldList); @@ -543,6 +652,28 @@ private Integer getInt(JsonObject obj, String key, Integer defaultVal) { ? obj.get(key).getAsInt() : defaultVal; } + + private boolean hasAnthropometryData(RMNCHBeneficiaryDetailsRmnch obj) { + return obj.getHeight() != null || obj.getWeight() != null + || obj.getBmi() != null || obj.getTemperature() != null; + } + + private String mergeAnthropometry(String existingOtherFields, RMNCHBeneficiaryDetailsRmnch obj) { + JsonObject json = new JsonObject(); + if (existingOtherFields != null && !existingOtherFields.isBlank()) { + try { + json = new JsonParser().parse(existingOtherFields).getAsJsonObject(); + } catch (Exception ignored) { + } + } + if (obj.getHeight() != null) json.addProperty("height", obj.getHeight()); + if (obj.getWeight() != null) json.addProperty("weight", obj.getWeight()); + if (obj.getBmi() != null) json.addProperty("bmi", obj.getBmi()); + // mobile sends "temperature"; FLW-API getBeneficiaryData reads "temperatureValue" + if (obj.getTemperature() != null) json.addProperty("temperatureValue", obj.getTemperature()); + return new Gson().toJson(json); + } + @Override public String getBenData(String requestOBJ, String authorisation) throws Exception { String outputResponse = null; @@ -789,6 +920,8 @@ private String getMappingsForAddressIDs(List addressLi benDetailsRMNCHOBJ.setAddressLine2(benAddressOBJ.getPermAddrLine2()); if (benAddressOBJ.getPermAddrLine3() != null) benDetailsRMNCHOBJ.setAddressLine3(benAddressOBJ.getPermAddrLine3()); + if (benAddressOBJ.getPermPinCode() != null) + benDetailsRMNCHOBJ.setPinCode(benAddressOBJ.getPermPinCode()); // related benids if (benDetailsRMNCHOBJ.getRelatedBeneficiaryIdsDB() != null) { diff --git a/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java b/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java index 4c8db37d..6dbc0301 100644 --- a/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java +++ b/src/main/java/com/iemr/common/identity/utils/mapper/InputMapper.java @@ -48,6 +48,11 @@ public InputMapper() { if (builder == null) { builder = new GsonBuilder(); builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + // Timestamp fields (including dob) use Gson's default parsing here, same as + // on vb/stoptb. The gpsTimestamp field on RMNCH entities is parsed by + // com.iemr.common.identity.mapper.GpsTimestampAdapter via a field-level + // @JsonAdapter annotation instead of a global registration, so it can't + // affect any other Timestamp field. } } diff --git a/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java b/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java index 04a3f1d8..98ced6fa 100644 --- a/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/common/identity/utils/redis/RedisStorage.java @@ -67,6 +67,13 @@ public String getObject(String key, Boolean extendExpirationTime, int expiration return userRespFromRedis; } + public String getRaw(String key) { + RedisConnection redCon = connection.getConnection(); + byte[] data = redCon.get(key.getBytes()); + redCon.close(); + return data != null ? new String(data) : null; + } + public Long deleteObject(String key) throws RedisSessionException { RedisConnection redCon = connection.getConnection(); Long userRespFromRedis = Long.valueOf(0L); From cd5de787ba9068d4de93648477830b43401c8c05 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 17 Jul 2026 10:42:24 +0530 Subject: [PATCH 53/62] feat(household): add totalHhMembers and registeredAtCampSite fields Co-Authored-By: Claude Sonnet 5 --- .../identity/data/rmnch/RMNCHHouseHoldDetails.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index 7000f6d0..3b842822 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -114,6 +114,18 @@ public class RMNCHHouseHoldDetails { @Column(name = "familyName") private String familyName; + @Expose + @Column(name = "totalHhMembers") + private Integer totalHhMembers; + + @Expose + @Column(name = "registeredAtCampSite") + private String registeredAtCampSite; + + @Expose + @Column(name = "registeredAtCampSiteId") + private Integer registeredAtCampSiteId; + @Expose @Column(name = "fuelUsed") private String fuelUsed; From 439a64f475e557d4193036328e4652f5ac6bd72a Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Wed, 29 Jul 2026 15:31:17 +0530 Subject: [PATCH 54/62] feat(rmnch): add placeOfCurrentLiving, otherPlaceOfCurrentLiving, institutionName fields --- .../data/rmnch/RMNCHBeneficiaryDetailsRmnch.java | 9 +++++++++ .../identity/data/rmnch/RMNCHMBeneficiarydetail.java | 9 +++++++++ .../identity/service/rmnch/RmnchDataSyncServiceImpl.java | 3 +++ 3 files changed, 21 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index 814d2c73..a6604f69 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -532,6 +532,15 @@ public class RMNCHBeneficiaryDetailsRmnch { @Expose private String otherPlaceOfDeath; + @Expose + private String placeOfCurrentLiving; + + @Expose + private String otherPlaceOfCurrentLiving; + + @Expose + private String institutionName; + @Expose private Boolean isSpouseAdded; diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java index 44f15679..0e86127e 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHMBeneficiarydetail.java @@ -222,4 +222,13 @@ public class RMNCHMBeneficiarydetail { @Expose @Column(name = "familyid") private String familyId; + + @Expose + private String placeOfCurrentLiving; + + @Expose + private String otherPlaceOfCurrentLiving; + + @Expose + private String institutionName; } \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 528310d4..afa8155b 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -255,6 +255,9 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex rmnchmBeneficiarydetail.setGenderId(obj.getGenderId()); rmnchmBeneficiarydetail.setMaritalstatus(obj.getMaritalstatus()); rmnchmBeneficiarydetail.setMaritalstatusId(obj.getMaritalstatusId()); + rmnchmBeneficiarydetail.setPlaceOfCurrentLiving(obj.getPlaceOfCurrentLiving()); + rmnchmBeneficiarydetail.setOtherPlaceOfCurrentLiving(obj.getOtherPlaceOfCurrentLiving()); + rmnchmBeneficiarydetail.setInstitutionName(obj.getInstitutionName()); if(obj.getFamilyId()!=null && !obj.getFamilyId().isEmpty()){ rmnchmBeneficiarydetail.setFamilyId(obj.getFamilyId()); From 5d72275a8ba12269cf0575cc6e97f322f07c9402 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 20 Jul 2026 17:05:06 +0530 Subject: [PATCH 55/62] fix(household): accept Pincode key variant during RMNCH sync deserialization FLW-Mobile-App's Household.kt serializes the field as "Pincode" (capital P), but the entity had no @SerializedName so Gson's exact-case field matching against "pincode" silently dropped the value on every sync. Co-Authored-By: Claude Sonnet 5 --- .../iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index 3b842822..bbfb7725 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -206,6 +206,7 @@ public class RMNCHHouseHoldDetails { private String other_sourceofDrinkingWater; @Expose + @SerializedName(value = "pincode", alternate = "Pincode") @Column(name = "pincode") private Integer pincode; From f6cc2453942cbc9cfa3d8f2e774119855ef03a75 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 20 Jul 2026 17:54:23 +0530 Subject: [PATCH 56/62] feat(household): add address column mapping to i_householddetails New address VARCHAR(500) column added to i_householddetails so household address can be captured once at HH Registration instead of being duplicated per-member on i_beneficiaryaddress. Accepts both "address" and "Address" keys during sync, following the same pattern as the earlier pincode key-case bug. Co-Authored-By: Claude Sonnet 5 --- .../common/identity/data/rmnch/RMNCHHouseHoldDetails.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java index bbfb7725..471bdaa6 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHHouseHoldDetails.java @@ -114,6 +114,11 @@ public class RMNCHHouseHoldDetails { @Column(name = "familyName") private String familyName; + @Expose + @SerializedName(value = "address", alternate = "Address") + @Column(name = "address") + private String address; + @Expose @Column(name = "totalHhMembers") private Integer totalHhMembers; From 2783f7d5e525d118ee7c92311f0393b0f72d205f Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Wed, 29 Jul 2026 19:07:29 +0530 Subject: [PATCH 57/62] chore(release): bump version to 3.8.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ae4c0f8f..04fad9b0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.8.2 + 3.8.3 war From 298a1fa1043a5f688d0d7e2cea012081f69a078e Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Wed, 5 Aug 2026 14:21:27 +0530 Subject: [PATCH 58/62] fix(rmnch): stop garbage device timestamps from corrupting CreatedDate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i_beneficiarydetails_rmnch.CreatedDate was trusting whatever the syncing device sent, with no validation. Confirmed on UAT: 48/463 rows future-dated (up to 2026-08-29, ~3 weeks ahead), 6/463 stuck at the 1970 epoch default — both classic broken-device-clock symptoms (never set, or set ahead). Add isPlausibleDeviceTimestamp() (rejects null, epoch-default, and future-dated values) and apply it in syncDataToAmrit()'s RMNCH beneficiary details block: - Existing record already has a plausible CreatedDate -> keep it; a later re-sync must never overwrite a good value with a possibly-bad one. - Existing record's value is bad but this sync's incoming value is plausible -> self-heal using the incoming value. - Neither is plausible (broken clock both times) -> fall back to server sync time as the least-wrong value instead of storing garbage. - New record with a plausible device value -> trust it as-is, since offline captures legitimately sync well after the actual event. Build verified: mvn -o clean compile, BUILD SUCCESS. Co-Authored-By: Claude Sonnet 5 --- .../rmnch/RmnchDataSyncServiceImpl.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index b0a78bff..aeb3f912 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -219,8 +219,26 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex .getByRegID(benRegID).get(0); if (temp != null) { obj.setBeneficiaryDetails_RmnchId(temp.getBeneficiaryDetails_RmnchId()); + if (isPlausibleDeviceTimestamp(temp.getCreatedDate())) { + // Already has a good CreatedDate from the first sync — a later + // re-sync must never overwrite it. + obj.setCreatedDate(temp.getCreatedDate()); + } else if (isPlausibleDeviceTimestamp(obj.getCreatedDate())) { + // Stored value was garbage but this re-sync brought a plausible + // one from the device — self-heal using it (obj already has it). + } else { + obj.setCreatedDate(new Timestamp(System.currentTimeMillis())); + } } + } else if (!isPlausibleDeviceTimestamp(obj.getCreatedDate())) { + // Device clock is broken (unset -> 1970 epoch, or set ahead -> future + // date). We can't recover the true capture time, so fall back to the + // sync time as the least-wrong value instead of storing garbage. + obj.setCreatedDate(new Timestamp(System.currentTimeMillis())); } + // else: trust the device-supplied CreatedDate as-is — offline captures + // legitimately sync well after the actual event, so a later server time + // would be less accurate than the device's own timestamp. @@ -655,6 +673,22 @@ private Integer getInt(JsonObject obj, String key, Integer defaultVal) { : defaultVal; } + /** + * A device-supplied CreatedDate is trustworthy only if it is non-null, + * not the classic unset-clock epoch default, and not after the current + * server time (an offline capture can only have happened before the sync + * request that reports it, so a future date means the device's clock is + * wrong, not that the record is legitimately from the future). + */ + private boolean isPlausibleDeviceTimestamp(Timestamp createdDate) { + if (createdDate == null) { + return false; + } + long epochDayZero = 24L * 60 * 60 * 1000; // guard band around 1970-01-01 for epoch defaults + long now = System.currentTimeMillis(); + return createdDate.getTime() > epochDayZero && createdDate.getTime() <= now; + } + private boolean hasAnthropometryData(RMNCHBeneficiaryDetailsRmnch obj) { return obj.getHeight() != null || obj.getWeight() != null || obj.getBmi() != null || obj.getTemperature() != null; From 001d509cab69c515e2c3ca1b5a1d228939642ca9 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Thu, 6 Aug 2026 15:20:46 +0530 Subject: [PATCH 59/62] fix(stoptb): enforce vanID in createIdentity(), require explicit enforce.vanid config - IdentityService.createIdentity(): previously had zero enforce-vanid check at all (only RmnchDataSyncServiceImpl had it) - now throws if vanID is missing and enforceVanID=true, matching the convention everywhere else - Remove inline :false default from stoptb.enforce.vanid @Value in both IdentityService and RmnchDataSyncServiceImpl - every properties file must set this explicitly now, so a forgotten config fails loudly at startup instead of running fail-open - Add stoptb.enforce.vanid=false to the 1097_* profile properties (separate Maven build sharing these same classes, not a van deployment, so stays false, but must be explicit) Co-Authored-By: Claude Sonnet 5 --- src/main/environment/1097_ci.properties | 5 +++++ src/main/environment/1097_docker.properties | 5 +++++ src/main/environment/1097_example.properties | 5 +++++ .../identity/service/IdentityService.java | 17 +++++++++++++++-- .../service/rmnch/RmnchDataSyncServiceImpl.java | 16 ++++++++++------ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/main/environment/1097_ci.properties b/src/main/environment/1097_ci.properties index 514f675d..8a1835aa 100644 --- a/src/main/environment/1097_ci.properties +++ b/src/main/environment/1097_ci.properties @@ -34,3 +34,8 @@ elasticsearch.index.beneficiary=@env.ELASTICSEARCH_INDEX_BENEFICIARY@ # Enable/Disable ES (for gradual rollout) elasticsearch.enabled=@env.ELASTICSEARCH_ENABLED@ +# Van/local-laptop deployments only. The 1097 service is not a van deployment, so this +# stays false — but must still be set explicitly, since IdentityService.java (shared with +# the common_* build) now requires this property with no inline default. +stoptb.enforce.vanid=false + diff --git a/src/main/environment/1097_docker.properties b/src/main/environment/1097_docker.properties index 88df0354..afe4177c 100644 --- a/src/main/environment/1097_docker.properties +++ b/src/main/environment/1097_docker.properties @@ -34,3 +34,8 @@ elasticsearch.index.beneficiary=${ELASTICSEARCH_INDEX_BENEFICIARY} # Enable/Disable ES (for gradual rollout) elasticsearch.enabled=${ELASTICSEARCH_ENABLED} +# Van/local-laptop deployments only. The 1097 service is not a van deployment, so this +# stays false — but must still be set explicitly, since IdentityService.java (shared with +# the common_* build) now requires this property with no inline default. +stoptb.enforce.vanid=false + diff --git a/src/main/environment/1097_example.properties b/src/main/environment/1097_example.properties index ba2f3211..3e9f808a 100644 --- a/src/main/environment/1097_example.properties +++ b/src/main/environment/1097_example.properties @@ -31,3 +31,8 @@ elasticsearch.index.beneficiary=beneficiary_index # Enable/Disable ES (for gradual rollout) elasticsearch.enabled=true +# Van/local-laptop deployments only. The 1097 service is not a van deployment, so this +# stays false — but must still be set explicitly, since IdentityService.java (shared with +# the common_* build) now requires this property with no inline default. +stoptb.enforce.vanid=false + diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index a70597dc..86d49a4f 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -169,6 +169,14 @@ private JdbcTemplate getJdbcTemplate() { @Value("${elasticsearch.enabled}") private boolean esEnabled; + // Van/local-laptop deployments only — see RmnchDataSyncServiceImpl and FLW-API's + // CampConfigService for the same flag. createIdentity() previously had no enforcement + // check at all, so a missing vanID here would silently save VanID=NULL instead of failing. + // No inline default: every properties file must set this explicitly, so a forgotten + // config fails loudly at startup instead of silently running fail-open. + @Value("${stoptb.enforce.vanid}") + private boolean enforceVanID; + public void getBenAdress() { logger.debug("Address count: " + addressRepo.count()); logger.debug( @@ -1375,6 +1383,11 @@ private MBeneficiarydetail convertIdentityEditDTOToMBeneficiarydetail(IdentityEd public BeneficiaryCreateResp createIdentity(IdentityDTO identity) { logger.info("IdentityService.createIdentity - start"); + if (identity.getVanID() == null && enforceVanID) { + throw new IllegalStateException( + "Camp not configured: vanID missing. Please select van/service point in MMU before registering beneficiary."); + } + // Atomically claim the next available ID using SELECT … FOR UPDATE SKIP LOCKED. // This is safe across multiple app servers sharing the same database — each server // locks and reserves a distinct row, so duplicate BenRegId inserts cannot occur. @@ -1757,8 +1770,8 @@ private MBeneficiaryImage identityDTOToMBeneficiaryImage(IdentityDTO identity) { beneficiaryImage.setCreatedDate(identity.getCreatedDate()); if (identity.getVanID() != null) { beneficiaryImage.setVanID(identity.getVanID()); - } - if (identity.getBenFamilyDTOs() != null) { + } else if (identity.getBenFamilyDTOs() != null && !identity.getBenFamilyDTOs().isEmpty() + && identity.getBenFamilyDTOs().get(0).getVanID() != null) { beneficiaryImage.setVanID(identity.getBenFamilyDTOs().get(0).getVanID()); } diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 302e1723..2c8ece8a 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -118,8 +118,9 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RedisStorage redisStorage; // When true, sync fails loudly if camp is not configured instead of silently - // skipping vanID stamping - @Value("${stoptb.enforce.vanid:false}") + // skipping vanID stamping. No inline default — every properties file must set this + // explicitly, so a forgotten config fails loudly at startup instead of running fail-open. + @Value("${stoptb.enforce.vanid}") private boolean enforceVanID; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) @Override @@ -356,16 +357,19 @@ public String syncDataToAmrit(String requestOBJ) throws Exception { } for (RMNCHHouseHoldDetails obj : houseHoldList) { + // Set VanID/ParkingPlaceID for both NEW and existing households — previously + // this only ran inside the "household already exists" branch below, so a + // brand-new household never got VanID stamped, breaking van-scoped sync. + if (obj.getVanID() == null && vanID != null) { + obj.setVanID(vanID); + obj.setParkingPlaceID(parkingPlaceID); + } if(!rMNCHHouseHoldDetailsRepo .getByHouseHoldID(obj.getHouseoldId()).isEmpty()){ RMNCHHouseHoldDetails temp = rMNCHHouseHoldDetailsRepo .getByHouseHoldID(obj.getHouseoldId()).get(0); if (temp != null) obj.setHouseHoldDetailsId(temp.getHouseHoldDetailsId()); - if (obj.getVanID() == null && vanID != null) { - obj.setVanID(vanID); - obj.setParkingPlaceID(parkingPlaceID); - } if (hhTimestampMap.containsKey(obj.getHouseoldId())) obj.setGpsTimestamp(new Timestamp(hhTimestampMap.get(obj.getHouseoldId()))); } From c8a8560d426f30a9c0453295cdf97d6a5610bf3a Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Thu, 6 Aug 2026 20:46:57 +0530 Subject: [PATCH 60/62] fix(stoptb): fix RMNCH VanID placeholder-zero and id/VanSerialNo collision - RMNCHBeneficiaryDetailsRmnch: mobile sends VanID=0 as a placeholder (not null) for a fresh record - the existing '== null' check never caught it, leaving the real vanID from Redis unapplied. Now checks for both null and 0. - Both RMNCHBeneficiaryDetailsRmnch and RMNCHHouseHoldDetails map their VanSerialNo column to a Java field literally named 'id' with no @SerializedName - any incoming JSON payload that happens to carry its own 'id' key collides with it during Gson deserialization, silently overwriting the intended VanSerialNo with whatever unrelated value the client sent (observed live: every row stuck at VanSerialNo=1). Added updateVanSerialNo() to both repos and call it after each saveAll(), same pattern already used for i_beneficiaryimage/i_beneficiaryaddress in IdentityService.createIdentity(). - Left RMNCHCBACdetails/RMNCHBornBirthDetails alone despite sharing the same id-collision pattern - not part of the Stop TB flow, zero rows in practice, out of scope for this fix. Co-Authored-By: Claude Sonnet 5 --- .../repo/rmnch/RMNCHBeneficiaryDetailsRmnchRepo.java | 11 +++++++++++ .../repo/rmnch/RMNCHHouseHoldDetailsRepo.java | 11 +++++++++++ .../service/rmnch/RmnchDataSyncServiceImpl.java | 12 +++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBeneficiaryDetailsRmnchRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBeneficiaryDetailsRmnchRepo.java index 52916f31..cc36e5b4 100644 --- a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBeneficiaryDetailsRmnchRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHBeneficiaryDetailsRmnchRepo.java @@ -24,10 +24,12 @@ import java.math.BigInteger; import java.util.List; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import com.iemr.common.identity.data.rmnch.RMNCHBeneficiaryDetailsRmnch; @@ -39,4 +41,13 @@ public RMNCHBeneficiaryDetailsRmnch getByIdAndVanID(@Param("vanSerialNo") BigInt @Query(" SELECT t FROM RMNCHBeneficiaryDetailsRmnch t WHERE t.BenRegId =:benRegID ") public List getByRegID(@Param("benRegID") BigInteger benRegId); + + // The Java field bound to the VanSerialNo column is literally named `id` with no + // @SerializedName - any incoming JSON that happens to carry its own "id" key (e.g. a + // client-side list-item id) collides with it during Gson deserialization and silently + // overwrites the intended VanSerialNo value. Force it back to the row's own PK after save. + @Transactional + @Modifying + @Query("UPDATE RMNCHBeneficiaryDetailsRmnch t SET t.id = :id WHERE t.beneficiaryDetails_RmnchId = :id") + void updateVanSerialNo(@Param("id") BigInteger id); } diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java index 76490cbd..b8e0fc4b 100644 --- a/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/RMNCHHouseHoldDetailsRepo.java @@ -21,10 +21,12 @@ */ package com.iemr.common.identity.repo.rmnch; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import com.iemr.common.identity.data.rmnch.RMNCHHouseHoldDetails; @@ -37,4 +39,13 @@ public interface RMNCHHouseHoldDetailsRepo extends CrudRepository getByHouseHoldID(@Param("houseoldId") long houseoldId); + + // The Java field bound to the VanSerialNo column is literally named `id` with no + // @SerializedName - any incoming JSON that happens to carry its own "id" key collides + // with it during Gson deserialization and silently overwrites the intended VanSerialNo + // value. Force it back to the row's own PK after save. + @Transactional + @Modifying + @Query("UPDATE RMNCHHouseHoldDetails t SET t.id = :id WHERE t.houseHoldDetailsId = :id") + void updateVanSerialNo(@Param("id") Long id); } diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 27bc3cbf..57e76a8d 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -255,7 +255,9 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex } obj.setRelatedBeneficiaryIdsDB(sb.toString()); } - if (obj.getVanID() == null && vanID != null) { + // Mobile sends VanID=0 as a placeholder (not null) for a fresh record — + // `== null` alone never catches it, leaving the placeholder in place. + if ((obj.getVanID() == null || obj.getVanID() == 0) && vanID != null) { obj.setVanID(vanID); obj.setParkingPlaceID(parkingPlaceID); } @@ -297,6 +299,10 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex List benDetailsOriginalList = new ArrayList<>(benDetailsExtraList); benDetailsExtraList = (ArrayList) rMNCHBeneficiaryDetailsRmnchRepo .saveAll(benDetailsExtraList); + // The `id`/VanSerialNo field is Gson-collision-prone (see repo javadoc) — + // force it back to each row's own PK after save. + benDetailsExtraList.forEach((n) -> rMNCHBeneficiaryDetailsRmnchRepo + .updateVanSerialNo(n.getBeneficiaryDetails_RmnchId())); benDetailsExtraList.forEach((n) -> beneficiaryDetailsIds.add(n.getId())); // update beneficiary data in i_beneficiarydetails table @@ -413,6 +419,10 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex } houseHoldList = (ArrayList) rMNCHHouseHoldDetailsRepo .saveAll(houseHoldList); + // The `id`/VanSerialNo field is Gson-collision-prone (see repo javadoc) — + // force it back to each row's own PK after save. + houseHoldList.forEach((n) -> rMNCHHouseHoldDetailsRepo + .updateVanSerialNo(n.getHouseHoldDetailsId())); // success response houseHoldList.forEach((n) -> houseHoldDetailsIds.add(n.getId())); } From 20f1c9faf1428186b2cc242cb5e44421f3ec7c52 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Sun, 9 Aug 2026 11:23:11 +0530 Subject: [PATCH 61/62] fix(stoptb): read vanID from stoptb.van.id property instead of Redis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redis (camp:vanID) was written once at MMU login and deleted globally, unscoped, on ANY user's logout — a Redis outage or an unrelated user's logout would silently break RMNCH sync on this camp. Each camp/van already runs its own dedicated backend instance, so which van this is never actually changes at runtime. syncDataToAmrit() now reads vanID from the new stoptb.van.id property (no inline default, every properties file must set it explicitly, same convention as stoptb.enforce.vanid) instead of an inline Redis lookup via RedisStorage. Removed the now-unused RedisStorage dependency from this class. Added stoptb.van.id=0 to the 1097 (Helpline) profile's properties files too, since RmnchDataSyncServiceImpl is shared with that build and the property now has no inline default. Scope: vanID only, parkingPlaceID is not part of this change. Co-Authored-By: Claude Sonnet 5 --- src/main/environment/1097_ci.properties | 3 ++ src/main/environment/1097_docker.properties | 3 ++ src/main/environment/1097_example.properties | 3 ++ src/main/environment/common_ci.properties | 2 ++ src/main/environment/common_docker.properties | 2 ++ .../environment/common_example.properties | 2 ++ .../rmnch/RmnchDataSyncServiceImpl.java | 32 +++++++++---------- 7 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/main/environment/1097_ci.properties b/src/main/environment/1097_ci.properties index 8a1835aa..371df704 100644 --- a/src/main/environment/1097_ci.properties +++ b/src/main/environment/1097_ci.properties @@ -38,4 +38,7 @@ elasticsearch.enabled=@env.ELASTICSEARCH_ENABLED@ # stays false — but must still be set explicitly, since IdentityService.java (shared with # the common_* build) now requires this property with no inline default. stoptb.enforce.vanid=false +# Same reasoning — not a van deployment, but RmnchDataSyncServiceImpl (shared with the +# common_* build) now requires this property explicitly, no inline default. +stoptb.van.id=0 diff --git a/src/main/environment/1097_docker.properties b/src/main/environment/1097_docker.properties index afe4177c..d3eefc0d 100644 --- a/src/main/environment/1097_docker.properties +++ b/src/main/environment/1097_docker.properties @@ -38,4 +38,7 @@ elasticsearch.enabled=${ELASTICSEARCH_ENABLED} # stays false — but must still be set explicitly, since IdentityService.java (shared with # the common_* build) now requires this property with no inline default. stoptb.enforce.vanid=false +# Same reasoning — not a van deployment, but RmnchDataSyncServiceImpl (shared with the +# common_* build) now requires this property explicitly, no inline default. +stoptb.van.id=0 diff --git a/src/main/environment/1097_example.properties b/src/main/environment/1097_example.properties index 3e9f808a..ab928d52 100644 --- a/src/main/environment/1097_example.properties +++ b/src/main/environment/1097_example.properties @@ -35,4 +35,7 @@ elasticsearch.enabled=true # stays false — but must still be set explicitly, since IdentityService.java (shared with # the common_* build) now requires this property with no inline default. stoptb.enforce.vanid=false +# Same reasoning — not a van deployment, but RmnchDataSyncServiceImpl (shared with the +# common_* build) now requires this property explicitly, no inline default. +stoptb.van.id=0 diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 1ce43664..ae899db2 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -24,6 +24,8 @@ spring.redis.host=@env.REDIS_HOST@ # Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured stoptb.enforce.vanid=@env.STOPTB_ENFORCE_VANID@ +# Stop TB: this deployment's van/camp ID, replacing the old Redis camp:vanID lookup +stoptb.van.id=@env.STOPTB_VAN_ID@ cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 36735700..3eed5222 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -24,6 +24,8 @@ spring.redis.host=${REDIS_HOST} # Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not configured stoptb.enforce.vanid=${STOPTB_ENFORCE_VANID} +# Stop TB: this deployment's van/camp ID, replacing the old Redis camp:vanID lookup +stoptb.van.id=${STOPTB_VAN_ID} cors.allowed-origins=${CORS_ALLOWED_ORIGINS} diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index dd235c95..b9d36842 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -21,6 +21,8 @@ spring.redis.host=localhost # Stop TB: when true, RMNCH data sync fails with an error if camp (vanID) is not # configured instead of silently skipping vanID stamping stoptb.enforce.vanid=false +# Stop TB: this deployment's van/camp ID, replacing the old Redis camp:vanID lookup +stoptb.van.id=0 cors.allowed-origins=http://localhost:* diff --git a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java index 57e76a8d..aec3c44a 100644 --- a/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/rmnch/RmnchDataSyncServiceImpl.java @@ -76,7 +76,6 @@ import com.iemr.common.identity.repo.rmnch.RMNCHMBenMappingRepo; import com.iemr.common.identity.domain.MBeneficiarydetail; import com.iemr.common.identity.repo.BenDetailRepo; -import com.iemr.common.identity.utils.redis.RedisStorage; import com.iemr.common.identity.repo.rmnch.RMNCHMBenRegIdMapRepo; import com.iemr.common.identity.utils.config.ConfigProperties; import com.iemr.common.identity.utils.exception.IEMRException; @@ -119,12 +118,20 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; @Autowired private BenDetailRepo benDetailRepo; - @Autowired - private RedisStorage redisStorage; @Value("${fhir-url}") private String fhirUrl; + // This deployment's van/camp ID. Previously looked up from Redis ("camp:vanID"), + // written at MMU login and deleted (globally, unscoped) on ANY user's logout — a Redis + // outage or an unrelated user's logout would silently break sync on this camp. Each + // camp/van already runs its own dedicated backend instance, so which van this is never + // actually changes at runtime; reading it from properties removes the Redis dependency + // entirely. No inline default — every properties file must set this explicitly. + // Scope: vanID only, parkingPlaceID is not part of this change. + @Value("${stoptb.van.id}") + private int configuredVanID; + // When true, sync fails loudly if camp is not configured instead of silently // skipping vanID stamping. No inline default — every properties file must set this // explicitly, so a forgotten config fails loudly at startup instead of running fail-open. @@ -142,23 +149,16 @@ public String syncDataToAmrit(String requestOBJ, String authorization) throws Ex ArrayList cBACDetailsIds = new ArrayList<>(); ArrayList houseHoldDetailsIds = new ArrayList<>(); - // Read camp vanID/parkingPlaceID from Redis (set by MMU-API on van login) - Integer campVanID = null; - Integer campParkingPlaceID = null; - try { - String vanVal = redisStorage.getRaw("camp:vanID"); - String ppVal = redisStorage.getRaw("camp:parkingPlaceID"); - if (vanVal != null && !vanVal.isBlank()) campVanID = Integer.parseInt(vanVal); - if (ppVal != null && !ppVal.isBlank()) campParkingPlaceID = Integer.parseInt(ppVal); - } catch (Exception ignored) { - // no camp configured — vanID stamping skipped - } + // Configured van ID for this deployment (see configuredVanID field javadoc above). + // parkingPlaceID is out of scope for this change — kept null, same as before whenever + // Redis had no value for it. + Integer campVanID = configuredVanID > 0 ? configuredVanID : null; if (campVanID == null && enforceVanID) { throw new Exception( - "Camp not configured: vanID missing. Please select van/service point in MMU before syncing data."); + "Camp not configured: stoptb.van.id is 0. Set stoptb.van.id in this deployment's properties file."); } final Integer vanID = campVanID; - final Integer parkingPlaceID = campParkingPlaceID; + final Integer parkingPlaceID = null; try { if (requestOBJ != null && !requestOBJ.isEmpty()) { From 149b503567581cfe756d3e64f04736524d05c5ce Mon Sep 17 00:00:00 2001 From: sehjotsinghunthinkable Date: Thu, 13 Aug 2026 14:03:48 +0530 Subject: [PATCH 62/62] Removing duplicate fields in i_beneficiaryaddress table (#186) Co-authored-by: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> --- .../identity/domain/MBeneficiaryaddress.java | 18 ------------------ .../common/identity/mapper/IdentityMapper.java | 6 ------ .../identity/service/IdentityService.java | 6 ------ 3 files changed, 30 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java b/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java index d5eaf7bd..3c98e412 100644 --- a/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java +++ b/src/main/java/com/iemr/common/identity/domain/MBeneficiaryaddress.java @@ -260,24 +260,6 @@ public class MBeneficiaryaddress implements Serializable { // END OF new column added for data sync - @Column(name = "gpsLatitude") - private Double gpsLatitude; - - @Column(name = "gpsLongitude") - private Double gpsLongitude; - - @Column(name = "digipin") - private String digipin; - - @Column(name = "gpsTimestamp") - private Timestamp gpsTimestamp; - - @Column(name = "isGpsUnavailable", nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0") - private Boolean isGpsUnavailable = false; - - @Column(name = "gpsUnavailableReason") - private String gpsUnavailableReason; - public MBeneficiaryaddress setCurrentAddress(Address address) { this.currAddrLine1 = address.getAddrLine1(); this.currAddrLine2 = address.getAddrLine2(); diff --git a/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java b/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java index 765acb97..1e2b1920 100644 --- a/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java +++ b/src/main/java/com/iemr/common/identity/mapper/IdentityMapper.java @@ -210,12 +210,6 @@ MBeneficiaryfamilymapping identityDTOToMBeneficiaryfamilymapping(BenFamilyDTO be @Mapping(target = "currentAddress.village", source = "map.MBeneficiaryaddress.currVillage") @Mapping(target = "currentAddress.addressValue", source = "map.MBeneficiaryaddress.currAddressValue") @Mapping(target = "currentAddress.pinCode", source = "map.MBeneficiaryaddress.currPinCode") - @Mapping(target = "currentAddress.gpsLatitude", source = "map.MBeneficiaryaddress.gpsLatitude") - @Mapping(target = "currentAddress.gpsLongitude", source = "map.MBeneficiaryaddress.gpsLongitude") - @Mapping(target = "currentAddress.digipin", source = "map.MBeneficiaryaddress.digipin") - @Mapping(target = "currentAddress.gpsTimestamp", source = "map.MBeneficiaryaddress.gpsTimestamp") - @Mapping(target = "currentAddress.isGpsUnavailable", source = "map.MBeneficiaryaddress.isGpsUnavailable") - @Mapping(target = "currentAddress.gpsUnavailableReason", source = "map.MBeneficiaryaddress.gpsUnavailableReason") @Mapping(target = "emergencyAddress.addrLine1", source = "map.MBeneficiaryaddress.emerAddrLine1") @Mapping(target = "emergencyAddress.addrLine2", source = "map.MBeneficiaryaddress.emerAddrLine2") @Mapping(target = "emergencyAddress.addrLine3", source = "map.MBeneficiaryaddress.emerAddrLine3") diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 88cb26bb..ff0f5e38 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -1839,12 +1839,6 @@ private MBeneficiaryaddress identityDTOToMBeneficiaryaddress(IdentityDTO dto) { beneficiaryAddress.setCurrServicePointId(dto.getCurrentAddress().getServicePointID()); beneficiaryAddress.setCurrServicePoint(dto.getCurrentAddress().getServicePointName()); beneficiaryAddress.setCurrHabitation(dto.getCurrentAddress().getHabitation()); - beneficiaryAddress.setGpsLatitude(dto.getCurrentAddress().getGpsLatitude()); - beneficiaryAddress.setGpsLongitude(dto.getCurrentAddress().getGpsLongitude()); - beneficiaryAddress.setDigipin(dto.getCurrentAddress().getDigipin()); - beneficiaryAddress.setGpsTimestamp(dto.getCurrentAddress().getGpsTimestamp()); - beneficiaryAddress.setIsGpsUnavailable(dto.getCurrentAddress().getIsGpsUnavailable()); - beneficiaryAddress.setGpsUnavailableReason(dto.getCurrentAddress().getGpsUnavailableReason()); } if (dto.getEmergencyAddress() != null) { beneficiaryAddress.setEmerAddrLine1(dto.getEmergencyAddress().getAddrLine1());