From 481a41f136f111c4a77eb96974b92eb776df97b7 Mon Sep 17 00:00:00 2001 From: chetansaroya Date: Tue, 6 Oct 2026 18:29:13 +0530 Subject: [PATCH 1/2] feat: stop-542 write data anthropometry and vitals table --- .../data/rmnch/BenAnthropometryDetail.java | 101 ++++++++++++++ .../data/rmnch/BenPhysicalVitalDetail.java | 107 +++++++++++++++ .../repo/rmnch/BenAnthropometryRepo.java | 41 ++++++ .../repo/rmnch/BenPhysicalVitalRepo.java | 41 ++++++ .../rmnch/RmnchDataSyncServiceImpl.java | 125 ++++++++++++++---- 5 files changed, 391 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/iemr/common/identity/data/rmnch/BenAnthropometryDetail.java create mode 100644 src/main/java/com/iemr/common/identity/data/rmnch/BenPhysicalVitalDetail.java create mode 100644 src/main/java/com/iemr/common/identity/repo/rmnch/BenAnthropometryRepo.java create mode 100644 src/main/java/com/iemr/common/identity/repo/rmnch/BenPhysicalVitalRepo.java diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/BenAnthropometryDetail.java b/src/main/java/com/iemr/common/identity/data/rmnch/BenAnthropometryDetail.java new file mode 100644 index 00000000..27a6df6e --- /dev/null +++ b/src/main/java/com/iemr/common/identity/data/rmnch/BenAnthropometryDetail.java @@ -0,0 +1,101 @@ +/* +* 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; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import lombok.Data; + +/** + * Mirrors FLW-API's BenAnthropometryDetail (same db_iemr.t_phy_anthropometry table), so syncDataToAmrit can write + * registration-time values that FLW-API getBeneficiaryData reads back. + */ +@Entity +@Table(name = "t_phy_anthropometry", catalog = "db_iemr") +@Data +public class BenAnthropometryDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private Long id; + + @Column(name = "BeneficiaryRegID") + private Long beneficiaryRegID; + + @Column(name = "BenVisitID") + private Long benVisitID; + + @Column(name = "ProviderServiceMapID") + private Integer providerServiceMapID; + + @Column(name = "VisitCode") + private Long visitCode; + + @Column(name = "Weight_Kg") + private Double weightKg; + + @Column(name = "Height_cm") + private Double heightCm; + + @Column(name = "BMI") + private Double bmi; + + @Column(name = "Deleted", insertable = false, updatable = true) + private Boolean deleted; + + @Column(name = "Processed", insertable = false, updatable = true) + private String processed; + + @Column(name = "CreatedBy") + private String createdBy; + + @Column(name = "CreatedDate", insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(name = "ModifiedBy") + private String modifiedBy; + + @Column(name = "LastModDate", insertable = false, updatable = false) + private Timestamp lastModDate; + + @Column(name = "VanSerialNo") + private Long vanSerialNo; + + @Column(name = "VanID") + private Integer vanID; + + @Column(name = "ParkingPlaceID") + private Integer parkingPlaceID; + + @Column(name = "SyncedBy") + private String syncedBy; + + @Column(name = "SyncedDate") + private Timestamp syncedDate; +} diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/BenPhysicalVitalDetail.java b/src/main/java/com/iemr/common/identity/data/rmnch/BenPhysicalVitalDetail.java new file mode 100644 index 00000000..9e98f289 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/data/rmnch/BenPhysicalVitalDetail.java @@ -0,0 +1,107 @@ +/* +* 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; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import lombok.Data; + +/** + * Mirrors FLW-API's BenPhysicalVitalDetail (same db_iemr.t_phy_vitals table), so syncDataToAmrit can write + * registration-time values that FLW-API getBeneficiaryData reads back. + */ +@Entity +@Table(name = "t_phy_vitals", catalog = "db_iemr") +@Data +public class BenPhysicalVitalDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private Long id; + + @Column(name = "BeneficiaryRegID") + private Long beneficiaryRegID; + + @Column(name = "BenVisitID") + private Long benVisitID; + + @Column(name = "ProviderServiceMapID") + private Integer providerServiceMapID; + + @Column(name = "VisitCode") + private Long visitCode; + + @Column(name = "Temperature") + private Double temperature; + + @Column(name = "PulseRate") + private Short pulseRate; + + @Column(name = "SystolicBP_1stReading") + private Short systolicBP; + + @Column(name = "DiastolicBP_1stReading") + private Short diastolicBP; + + @Column(name = "BloodGlucose_Random") + private Short bloodGlucoseRandom; + + @Column(name = "Deleted", insertable = false, updatable = true) + private Boolean deleted; + + @Column(name = "Processed", insertable = false, updatable = true) + private String processed; + + @Column(name = "CreatedBy") + private String createdBy; + + @Column(name = "CreatedDate", insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(name = "ModifiedBy") + private String modifiedBy; + + @Column(name = "LastModDate", insertable = false, updatable = false) + private Timestamp lastModDate; + + @Column(name = "VanSerialNo") + private Long vanSerialNo; + + @Column(name = "VanID") + private Integer vanID; + + @Column(name = "ParkingPlaceID") + private Integer parkingPlaceID; + + @Column(name = "SyncedBy") + private String syncedBy; + + @Column(name = "SyncedDate") + private Timestamp syncedDate; +} diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/BenAnthropometryRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/BenAnthropometryRepo.java new file mode 100644 index 00000000..95476f67 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/BenAnthropometryRepo.java @@ -0,0 +1,41 @@ +/* +* 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.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.BenAnthropometryDetail; + +@Repository +public interface BenAnthropometryRepo extends CrudRepository { + public BenAnthropometryDetail findFirstByBeneficiaryRegIDOrderByCreatedDateDescIdDesc(Long beneficiaryRegID); + + @Transactional + @Modifying + @Query("UPDATE BenAnthropometryDetail t SET t.vanSerialNo = t.id WHERE t.id = :id") + void updateVanSerialNo(@Param("id") Long id); +} diff --git a/src/main/java/com/iemr/common/identity/repo/rmnch/BenPhysicalVitalRepo.java b/src/main/java/com/iemr/common/identity/repo/rmnch/BenPhysicalVitalRepo.java new file mode 100644 index 00000000..61ddc045 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/repo/rmnch/BenPhysicalVitalRepo.java @@ -0,0 +1,41 @@ +/* +* 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.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.BenPhysicalVitalDetail; + +@Repository +public interface BenPhysicalVitalRepo extends CrudRepository { + public BenPhysicalVitalDetail findFirstByBeneficiaryRegIDAndTemperatureIsNotNullOrderByCreatedDateDescIdDesc(Long beneficiaryRegID); + + @Transactional + @Modifying + @Query("UPDATE BenPhysicalVitalDetail t SET t.vanSerialNo = t.id WHERE t.id = :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 6201d6a3..c8f3b265 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 @@ -51,7 +51,9 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.iemr.common.identity.controller.rmnch.RMNCHMobileAppController; +import com.iemr.common.identity.data.rmnch.BenAnthropometryDetail; import com.iemr.common.identity.data.rmnch.BenHealthIDDetails; +import com.iemr.common.identity.data.rmnch.BenPhysicalVitalDetail; import com.iemr.common.identity.data.rmnch.GetBenRequestHandler; import com.iemr.common.identity.data.rmnch.NcdTbHrpData; import com.iemr.common.identity.data.rmnch.RMNCHBeneficiaryDetailsRmnch; @@ -64,6 +66,8 @@ import com.iemr.common.identity.data.rmnch.RMNCHMBeneficiarycontact; import com.iemr.common.identity.data.rmnch.RMNCHMBeneficiarydetail; import com.iemr.common.identity.data.rmnch.RMNCHMBeneficiarymapping; +import com.iemr.common.identity.repo.rmnch.BenAnthropometryRepo; +import com.iemr.common.identity.repo.rmnch.BenPhysicalVitalRepo; import com.iemr.common.identity.repo.rmnch.RMNCHBenAccountRepo; import com.iemr.common.identity.repo.rmnch.RMNCHBenAddressRepo; import com.iemr.common.identity.repo.rmnch.RMNCHBenContactRepo; @@ -118,6 +122,10 @@ public class RmnchDataSyncServiceImpl implements RmnchDataSyncService { private RMNCHMBenRegIdMapRepo rMNCHMBenRegIdMapRepo; @Autowired private BenDetailRepo benDetailRepo; + @Autowired + private BenAnthropometryRepo benAnthropometryRepo; + @Autowired + private BenPhysicalVitalRepo benPhysicalVitalRepo; @Value("${fhir-url}") private String fhirUrl; @@ -409,19 +417,13 @@ 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. + // Write anthropometry (height/weight/bmi/temperature) to db_iemr.t_phy_anthropometry / + // t_phy_vitals — i_beneficiarydetails_rmnch has no these columns. FLW-API getBeneficiaryData + // and the nurse exam save read them from there. 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()); - } + writeAnthropometry(obj); + writeVitals(obj); } } @@ -881,20 +883,95 @@ private boolean hasAnthropometryData(RMNCHBeneficiaryDetailsRmnch obj) { || 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) { - } + // Inserts a new t_phy_anthropometry row only when height/weight/bmi changed vs the latest row, + // carrying forward any value the app didn't send — from the latest row, or from otherFields for + // beneficiaries whose values were stored there before this table was written. Not tied to a visit — + // BenVisitID/VisitCode are nullable, and FLW-API reads latest by BeneficiaryRegID. + // Not wrapped in try/catch: these tables are now the only store for these values, and a failed save + // marks the sync transaction rollback-only anyway, so let the sync fail and the app retry. + private void writeAnthropometry(RMNCHBeneficiaryDetailsRmnch obj) { + if (obj.getHeight() == null && obj.getWeight() == null && obj.getBmi() == null) + return; + Long benRegID = obj.getBenRegId().longValue(); + if (obj.getCreatedBy() == null) { + logger.warn("Skipping t_phy_anthropometry write, createdBy missing for benRegId: " + benRegID); + return; + } + BenAnthropometryDetail latest = benAnthropometryRepo + .findFirstByBeneficiaryRegIDOrderByCreatedDateDescIdDesc(benRegID); + JsonObject legacy = latest == null ? getLegacyAnthropometry(obj.getBenRegId()) : new JsonObject(); + Double height = obj.getHeight() != null ? obj.getHeight() + : (latest != null ? latest.getHeightCm() : getDouble(legacy, "height")); + Double weight = obj.getWeight() != null ? obj.getWeight() + : (latest != null ? latest.getWeightKg() : getDouble(legacy, "weight")); + Double bmi = obj.getBmi() != null ? obj.getBmi() : (latest != null ? latest.getBmi() : getDouble(legacy, "bmi")); + if (latest != null && sameValue(latest.getHeightCm(), height) && sameValue(latest.getWeightKg(), weight) + && sameValue(latest.getBmi(), bmi)) + return; + + BenAnthropometryDetail a = new BenAnthropometryDetail(); + a.setBeneficiaryRegID(benRegID); + a.setProviderServiceMapID(obj.getProviderServiceMapID()); + a.setCreatedBy(obj.getCreatedBy()); + a.setVanID(obj.getVanID()); + a.setParkingPlaceID(obj.getParkingPlaceID()); + a.setHeightCm(height); + a.setWeightKg(weight); + a.setBmi(bmi); + a = benAnthropometryRepo.save(a); + benAnthropometryRepo.updateVanSerialNo(a.getId()); + } + + // Inserts a new t_phy_vitals row only when temperature changed vs the latest recorded temperature. + // Pulse/BP/glucose aren't part of registration, so they stay null here. + private void writeVitals(RMNCHBeneficiaryDetailsRmnch obj) { + if (obj.getTemperature() == null) + return; + Long benRegID = obj.getBenRegId().longValue(); + if (obj.getCreatedBy() == null) { + logger.warn("Skipping t_phy_vitals write, createdBy missing for benRegId: " + benRegID); + return; + } + BenPhysicalVitalDetail latest = benPhysicalVitalRepo + .findFirstByBeneficiaryRegIDAndTemperatureIsNotNullOrderByCreatedDateDescIdDesc(benRegID); + if (latest != null && sameValue(latest.getTemperature(), obj.getTemperature())) + return; + + BenPhysicalVitalDetail v = new BenPhysicalVitalDetail(); + v.setBeneficiaryRegID(benRegID); + v.setProviderServiceMapID(obj.getProviderServiceMapID()); + v.setCreatedBy(obj.getCreatedBy()); + v.setVanID(obj.getVanID()); + v.setParkingPlaceID(obj.getParkingPlaceID()); + v.setTemperature(obj.getTemperature()); + v = benPhysicalVitalRepo.save(v); + benPhysicalVitalRepo.updateVanSerialNo(v.getId()); + } + + // Columns are DECIMAL(5,2), so compare at that precision. + private boolean sameValue(Double stored, Double incoming) { + if (stored == null || incoming == null) + return stored == incoming; + return Math.round(stored * 100) == Math.round(incoming * 100); + } + + private JsonObject getLegacyAnthropometry(BigInteger benRegID) { + try { + MBeneficiarydetail benDetail = benDetailRepo.findByBenRegId(benRegID); + if (benDetail != null && benDetail.getOtherFields() != null && !benDetail.getOtherFields().isBlank()) + return new JsonParser().parse(benDetail.getOtherFields()).getAsJsonObject(); + } catch (Exception ex) { + logger.warn("Cannot read otherFields for benRegId: " + benRegID + " - " + ex.getMessage()); + } + return new JsonObject(); + } + + private Double getDouble(JsonObject json, String key) { + try { + return json.has(key) && !json.get(key).isJsonNull() ? json.get(key).getAsDouble() : null; + } catch (Exception ex) { + return null; } - 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 From a53ae5f03a7b7bb033064da4d7613bb29df220c6 Mon Sep 17 00:00:00 2001 From: chetansaroya Date: Fri, 9 Oct 2026 09:43:22 +0530 Subject: [PATCH 2/2] feat(STOP-542): update version to 3.12.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9eda8258..667d7cc6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.9.0 + 3.12.0 war