diff --git a/pom.xml b/pom.xml index 062df58..9e6a49b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.admin admin-api - 3.8.2 + 3.8.4 war Admin-API Admin Page diff --git a/src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java b/src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java index 33d980c..cfe1979 100644 --- a/src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java +++ b/src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java @@ -1791,6 +1791,12 @@ public String UserRoleMappings(@RequestBody String userRoleMapping, HttpServletR Previleges1097_3[] predata1 = pre.get(x).getPrevileges(); for (Previleges1097_3 previl : predata1) { + // Each role in the ID array must become its own saved row. This used to + // build a single M_UserServiceRoleMapping2 across all iterations of this + // loop and only add it to resList1 once, after the loop — so a batched + // request carrying multiple roles (e.g. Nurse + Counsellor) silently + // discarded every role but the last one. Moving the object creation and + // the add() call inside the loop saves one row per role, as intended. Priveleges1097_2[] predata2 = previl.getID(); for (Priveleges1097_2 previl1 : predata2) { resDataMap1 = new M_UserServiceRoleMapping2(); @@ -1800,22 +1806,22 @@ public String UserRoleMappings(@RequestBody String userRoleMapping, HttpServletR if (previl1.getTeleConsultation() != null) { resDataMap1.setTeleConsultation(previl1.getTeleConsultation()); } + resDataMap1.setUserID(employeeMaster.get(x).getUserID()); + resDataMap1.setProviderServiceMapID(previl.getProviderServiceMapID()); + resDataMap1.setWorkingLocationID(previl.getWorkingLocationID()); + resDataMap1.setStateID(previl.getStateID()); + resDataMap1.setDistrictID(previl.getDistrictID()); + resDataMap1.setCreatedBy(employeeMaster.get(x).getCreatedBy()); + resDataMap1.setServiceProviderID(employeeMaster.get(x).getServiceProviderID()); + resDataMap1.setBlockID(previl.getBlockID()); + resDataMap1.setBlockName(previl.getBlockName()); + resDataMap1.setVillageID(previl.getVillageID()); + resDataMap1.setVillageName(previl.getVillageName()); + resDataMap1.setFacilityID(previl.getFacilityID()); + resDataMap1.setNikshayTUID(previl.getNikshayTUID()); + resDataMap1.setNikshayFacilityID(previl.getNikshayFacilityID()); + resList1.add(resDataMap1); } - resDataMap1.setUserID(employeeMaster.get(x).getUserID()); - resDataMap1.setProviderServiceMapID(previl.getProviderServiceMapID()); - resDataMap1.setWorkingLocationID(previl.getWorkingLocationID()); - resDataMap1.setStateID(previl.getStateID()); - resDataMap1.setDistrictID(previl.getDistrictID()); - resDataMap1.setCreatedBy(employeeMaster.get(x).getCreatedBy()); - resDataMap1.setServiceProviderID(employeeMaster.get(x).getServiceProviderID()); - resDataMap1.setBlockID(previl.getBlockID()); - resDataMap1.setBlockName(previl.getBlockName()); - resDataMap1.setVillageID(previl.getVillageID()); - resDataMap1.setVillageName(previl.getVillageName()); - resDataMap1.setFacilityID(previl.getFacilityID()); - resDataMap1.setNikshayTUID(previl.getNikshayTUID()); - resDataMap1.setNikshayFacilityID(previl.getNikshayFacilityID()); - resList1.add(resDataMap1); } x++; @@ -1863,11 +1869,15 @@ public String updateUserRoleMapping(@RequestBody String updateUserRoleMapping, H } } - // Soft-delete other active mappings for same user+service to prevent duplicates - // (e.g. old roleID 122 rows left over when new roleID 128 mapping was created) - if (pre.getUserID() != null && pre.getProviderServiceMapID() != null && pre.getuSRMappingID() != null) { + // Soft-delete other active mappings for same user+service+role to prevent + // duplicates (e.g. a stale leftover row still claiming this same role). + // Scoped by roleID as well as user+service — otherwise this deletes every + // OTHER role the user holds under this service line too, not just true + // duplicates of the role being saved here. + if (pre.getUserID() != null && pre.getProviderServiceMapID() != null && pre.getRoleID() != null + && pre.getuSRMappingID() != null) { employeeMasterInter.softDeleteOldMappings( - pre.getUserID(), pre.getProviderServiceMapID(), pre.getuSRMappingID()); + pre.getUserID(), pre.getProviderServiceMapID(), pre.getRoleID(), pre.getuSRMappingID()); } usrRole.setUserID(pre.getUserID()); diff --git a/src/main/java/com/iemr/admin/controller/villagemapping/VillageUpdateController.java b/src/main/java/com/iemr/admin/controller/villagemapping/VillageUpdateController.java new file mode 100644 index 0000000..ccfac3f --- /dev/null +++ b/src/main/java/com/iemr/admin/controller/villagemapping/VillageUpdateController.java @@ -0,0 +1,63 @@ +/* +* 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.admin.controller.villagemapping; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.iemr.admin.model.villagemapping.VillageUpdateRequest; +import com.iemr.admin.service.villagemapping.VillageUpdateService; +import com.iemr.admin.utils.mapper.OutputMapper; +import com.iemr.admin.utils.response.OutputResponse; + +import io.swagger.v3.oas.annotations.Operation; + +@RestController +@RequestMapping(value = "/villageMapping") +public class VillageUpdateController { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired + private VillageUpdateService villageUpdateService; + + @Operation(summary = "Replace a village on a user's work location mapping and move their beneficiary addresses") + @RequestMapping(value = "/updateVillage", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON, headers = "Authorization") + public String updateVillage(@RequestBody VillageUpdateRequest updateRequest) { + OutputResponse response = new OutputResponse(); + try { + logger.info("updateVillage received request"); + response.setResponse(OutputMapper.gsonWithoutExpose().toJson(villageUpdateService.updateVillage(updateRequest))); + } catch (Exception e) { + logger.error("updateVillage failed", e); + response.setError(e); + } + logger.info("updateVillage sending response"); + return response.toString(); + } +} diff --git a/src/main/java/com/iemr/admin/model/villagemapping/VillageUpdateRequest.java b/src/main/java/com/iemr/admin/model/villagemapping/VillageUpdateRequest.java new file mode 100644 index 0000000..7b6e117 --- /dev/null +++ b/src/main/java/com/iemr/admin/model/villagemapping/VillageUpdateRequest.java @@ -0,0 +1,108 @@ +/* +* 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.admin.model.villagemapping; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class VillageUpdateRequest { + private Integer userID; + private String userName; + + @JsonProperty("uSRMappingID") + private Integer uSRMappingID; + + private Integer oldVillageID; + private String oldVillageName; + + private Integer newVillageID; + private String newVillageName; + + private String modifiedBy; + + public Integer getUserID() { + return userID; + } + + public void setUserID(Integer userID) { + this.userID = userID; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + @JsonProperty("uSRMappingID") + public Integer getuSRMappingID() { + return uSRMappingID; + } + + @JsonProperty("uSRMappingID") + public void setuSRMappingID(Integer uSRMappingID) { + this.uSRMappingID = uSRMappingID; + } + + public Integer getOldVillageID() { + return oldVillageID; + } + + public void setOldVillageID(Integer oldVillageID) { + this.oldVillageID = oldVillageID; + } + + public String getOldVillageName() { + return oldVillageName; + } + + public void setOldVillageName(String oldVillageName) { + this.oldVillageName = oldVillageName; + } + + public Integer getNewVillageID() { + return newVillageID; + } + + public void setNewVillageID(Integer newVillageID) { + this.newVillageID = newVillageID; + } + + public String getNewVillageName() { + return newVillageName; + } + + public void setNewVillageName(String newVillageName) { + this.newVillageName = newVillageName; + } + + public String getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } +} diff --git a/src/main/java/com/iemr/admin/model/villagemapping/VillageUpdateResponse.java b/src/main/java/com/iemr/admin/model/villagemapping/VillageUpdateResponse.java new file mode 100644 index 0000000..7326f47 --- /dev/null +++ b/src/main/java/com/iemr/admin/model/villagemapping/VillageUpdateResponse.java @@ -0,0 +1,108 @@ +/* +* 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.admin.model.villagemapping; + +public class VillageUpdateResponse { + private Integer uSRMappingID; + private String userName; + + private Integer oldVillageID; + private String oldVillageName; + private Integer newVillageID; + private String newVillageName; + + private long permanentAddressesUpdated; + private long currentAddressesUpdated; + private boolean allAddressesUpdated; + + public Integer getuSRMappingID() { + return uSRMappingID; + } + + public void setuSRMappingID(Integer uSRMappingID) { + this.uSRMappingID = uSRMappingID; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public Integer getOldVillageID() { + return oldVillageID; + } + + public void setOldVillageID(Integer oldVillageID) { + this.oldVillageID = oldVillageID; + } + + public String getOldVillageName() { + return oldVillageName; + } + + public void setOldVillageName(String oldVillageName) { + this.oldVillageName = oldVillageName; + } + + public Integer getNewVillageID() { + return newVillageID; + } + + public void setNewVillageID(Integer newVillageID) { + this.newVillageID = newVillageID; + } + + public String getNewVillageName() { + return newVillageName; + } + + public void setNewVillageName(String newVillageName) { + this.newVillageName = newVillageName; + } + + public long getPermanentAddressesUpdated() { + return permanentAddressesUpdated; + } + + public void setPermanentAddressesUpdated(long permanentAddressesUpdated) { + this.permanentAddressesUpdated = permanentAddressesUpdated; + } + + public long getCurrentAddressesUpdated() { + return currentAddressesUpdated; + } + + public void setCurrentAddressesUpdated(long currentAddressesUpdated) { + this.currentAddressesUpdated = currentAddressesUpdated; + } + + public boolean isAllAddressesUpdated() { + return allAddressesUpdated; + } + + public void setAllAddressesUpdated(boolean allAddressesUpdated) { + this.allAddressesUpdated = allAddressesUpdated; + } +} diff --git a/src/main/java/com/iemr/admin/repo/employeemaster/EmployeeMasterRepo.java b/src/main/java/com/iemr/admin/repo/employeemaster/EmployeeMasterRepo.java index e2bf74f..33929fe 100644 --- a/src/main/java/com/iemr/admin/repo/employeemaster/EmployeeMasterRepo.java +++ b/src/main/java/com/iemr/admin/repo/employeemaster/EmployeeMasterRepo.java @@ -155,10 +155,15 @@ boolean existsByUserIDAndRoleIDAndProviderServiceMapIDAndFacilityIDAndDeletedFal // Fix 2: count active USR rows for supervisor (check if other facilities remain) long countByUserIDAndRoleIDAndDeletedFalse(Integer userID, Integer roleID); - // Soft-delete old duplicate mappings for same user+service, excluding the current row being updated + // Soft-delete old duplicate mappings for the SAME user+service+role, excluding + // the current row being updated. Previously left roleID out of the WHERE + // clause entirely, so updating any one role's mapping soft-deleted every + // OTHER active role that user held under the same service line (e.g. saving + // Registration Officer wiped out an unrelated, already-active Counsellor + // mapping) instead of only cleaning up true duplicates of the same role. @Transactional @Modifying - @Query("UPDATE M_UserServiceRoleMapping2 u SET u.deleted = true WHERE u.userID = :userID AND u.providerServiceMapID = :providerServiceMapID AND u.uSRMappingID != :excludeUSRMappingID AND u.deleted = false") - int softDeleteOldMappings(@Param("userID") Integer userID, @Param("providerServiceMapID") Integer providerServiceMapID, @Param("excludeUSRMappingID") Integer excludeUSRMappingID); + @Query("UPDATE M_UserServiceRoleMapping2 u SET u.deleted = true WHERE u.userID = :userID AND u.providerServiceMapID = :providerServiceMapID AND u.roleID = :roleID AND u.uSRMappingID != :excludeUSRMappingID AND u.deleted = false") + int softDeleteOldMappings(@Param("userID") Integer userID, @Param("providerServiceMapID") Integer providerServiceMapID, @Param("roleID") Integer roleID, @Param("excludeUSRMappingID") Integer excludeUSRMappingID); } diff --git a/src/main/java/com/iemr/admin/repository/villagemapping/VillageUpdateRepository.java b/src/main/java/com/iemr/admin/repository/villagemapping/VillageUpdateRepository.java new file mode 100644 index 0000000..273e854 --- /dev/null +++ b/src/main/java/com/iemr/admin/repository/villagemapping/VillageUpdateRepository.java @@ -0,0 +1,133 @@ +/* +* 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.admin.repository.villagemapping; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; + +@Repository +public class VillageUpdateRepository { + private static final String SELECT_MAPPING_FOR_UPDATE = + "SELECT UserID, Villageid, VillageName, Deleted FROM db_iemr.m_userservicerolemapping" + + " WHERE USRMappingID = :uSRMappingID FOR UPDATE"; + + private static final String SELECT_USER_NAME = + "SELECT UserName FROM db_iemr.m_user WHERE UserID = :userID"; + + private static final String SELECT_VILLAGE_NAME = + "SELECT VillageName FROM db_iemr.m_districtbranchmapping" + + " WHERE DistrictBranchID = :villageID AND (Deleted IS NULL OR Deleted = b'0')"; + + private static final String SET_MAPPING_VILLAGES = + "UPDATE db_iemr.m_userservicerolemapping SET Villageid = :villageIDs, VillageName = :villageNames," + + " ModifiedBy = :modifiedBy WHERE USRMappingID = :uSRMappingID"; + + private static final String SET_PERMANENT_VILLAGE = + "UPDATE db_identity.i_beneficiaryaddress SET PermVillageId = :newVillageID, PermVillage = :newVillageName," + + " ModifiedBy = :modifiedBy WHERE CreatedBy = :userName AND PermVillageId = :oldVillageID"; + + private static final String SET_CURRENT_VILLAGE = + "UPDATE db_identity.i_beneficiaryaddress SET CurrVillageId = :newVillageID, CurrVillage = :newVillageName," + + " ModifiedBy = :modifiedBy WHERE CreatedBy = :userName AND CurrVillageId = :oldVillageID"; + + private static final String SET_ALL_ADDRESS_VILLAGES = + "UPDATE db_identity.i_beneficiaryaddress SET PermVillageId = :newVillageID, PermVillage = :newVillageName," + + " CurrVillageId = :newVillageID, CurrVillage = :newVillageName, ModifiedBy = :modifiedBy" + + " WHERE CreatedBy = :userName"; + + @PersistenceContext + private EntityManager entityManager; + + /** + * Locks the mapping row for the rest of the transaction and returns + * {UserID, Villageid, VillageName, Deleted}, or null when it does not exist. + */ + public Object[] lockMapping(Integer uSRMappingID) { + Query query = entityManager.createNativeQuery(SELECT_MAPPING_FOR_UPDATE); + query.setParameter("uSRMappingID", uSRMappingID); + List rows = query.getResultList(); + return rows.isEmpty() ? null : (Object[]) rows.get(0); + } + + public String userName(Integer userID) { + return single(SELECT_USER_NAME, "userID", userID); + } + + public String villageName(Integer villageID) { + return single(SELECT_VILLAGE_NAME, "villageID", villageID); + } + + private String single(String sql, String parameter, Integer value) { + Query query = entityManager.createNativeQuery(sql); + query.setParameter(parameter, value); + List rows = query.getResultList(); + return rows.isEmpty() || rows.get(0) == null ? null : rows.get(0).toString(); + } + + public long updateMappingVillages(Integer uSRMappingID, String villageIDs, String villageNames, + String modifiedBy) { + Query query = entityManager.createNativeQuery(SET_MAPPING_VILLAGES); + query.setParameter("villageIDs", villageIDs); + query.setParameter("villageNames", villageNames); + query.setParameter("modifiedBy", modifiedBy); + query.setParameter("uSRMappingID", uSRMappingID); + return query.executeUpdate(); + } + + public long updatePermanentVillage(String userName, Integer oldVillageID, Integer newVillageID, + String newVillageName, String modifiedBy) { + return updateAddressVillage(SET_PERMANENT_VILLAGE, userName, oldVillageID, newVillageID, newVillageName, + modifiedBy); + } + + public long updateCurrentVillage(String userName, Integer oldVillageID, Integer newVillageID, + String newVillageName, String modifiedBy) { + return updateAddressVillage(SET_CURRENT_VILLAGE, userName, oldVillageID, newVillageID, newVillageName, + modifiedBy); + } + + public long updateAllAddressVillages(String userName, Integer newVillageID, String newVillageName, + String modifiedBy) { + Query query = entityManager.createNativeQuery(SET_ALL_ADDRESS_VILLAGES); + query.setParameter("newVillageID", newVillageID); + query.setParameter("newVillageName", newVillageName); + query.setParameter("modifiedBy", modifiedBy); + query.setParameter("userName", userName); + return query.executeUpdate(); + } + + private long updateAddressVillage(String sql, String userName, Integer oldVillageID, Integer newVillageID, + String newVillageName, String modifiedBy) { + Query query = entityManager.createNativeQuery(sql); + query.setParameter("newVillageID", newVillageID); + query.setParameter("newVillageName", newVillageName); + query.setParameter("modifiedBy", modifiedBy); + query.setParameter("userName", userName); + query.setParameter("oldVillageID", oldVillageID); + return query.executeUpdate(); + } +} diff --git a/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterInter.java b/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterInter.java index 859f087..9b51b81 100644 --- a/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterInter.java +++ b/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterInter.java @@ -166,8 +166,8 @@ Boolean checkingEmpDetails(String userName, String aadhaarNo, String getpAN, Str // Fix 2: cascade soft-delete asha_supervisor_mapping rows when a user is deactivated void cascadeDeleteAshaMappingsForUser(Integer userID); - // Soft-delete other active mappings for same user+service, excluding the row being updated - int softDeleteOldMappings(Integer userID, Integer providerServiceMapID, Integer excludeUSRMappingID); + // Soft-delete other active mappings for same user+service+role, excluding the row being updated + int softDeleteOldMappings(Integer userID, Integer providerServiceMapID, Integer roleID, Integer excludeUSRMappingID); // Fix 2: smart cascade — for supervisor with multiple facilities, only delete mappings for this facility void cascadeDeleteAshaMappingsForDeactivation(M_UserServiceRoleMapping2 usrRole); diff --git a/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImpl.java b/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImpl.java index f09d0bd..16229ca 100644 --- a/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImpl.java +++ b/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImpl.java @@ -1025,8 +1025,8 @@ public void cascadeDeleteAshaMappingsForUser(Integer userID) { } @Override - public int softDeleteOldMappings(Integer userID, Integer providerServiceMapID, Integer excludeUSRMappingID) { - return employeeMasterRepo.softDeleteOldMappings(userID, providerServiceMapID, excludeUSRMappingID); + public int softDeleteOldMappings(Integer userID, Integer providerServiceMapID, Integer roleID, Integer excludeUSRMappingID) { + return employeeMasterRepo.softDeleteOldMappings(userID, providerServiceMapID, roleID, excludeUSRMappingID); } @Override diff --git a/src/main/java/com/iemr/admin/service/villagemapping/VillageUpdateService.java b/src/main/java/com/iemr/admin/service/villagemapping/VillageUpdateService.java new file mode 100644 index 0000000..6bdde03 --- /dev/null +++ b/src/main/java/com/iemr/admin/service/villagemapping/VillageUpdateService.java @@ -0,0 +1,29 @@ +/* +* 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.admin.service.villagemapping; + +import com.iemr.admin.model.villagemapping.VillageUpdateRequest; +import com.iemr.admin.model.villagemapping.VillageUpdateResponse; + +public interface VillageUpdateService { + VillageUpdateResponse updateVillage(VillageUpdateRequest request) throws Exception; +} diff --git a/src/main/java/com/iemr/admin/service/villagemapping/VillageUpdateServiceImpl.java b/src/main/java/com/iemr/admin/service/villagemapping/VillageUpdateServiceImpl.java new file mode 100644 index 0000000..e90d5cc --- /dev/null +++ b/src/main/java/com/iemr/admin/service/villagemapping/VillageUpdateServiceImpl.java @@ -0,0 +1,194 @@ +/* +* 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.admin.service.villagemapping; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +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.Transactional; + +import com.iemr.admin.model.villagemapping.VillageUpdateRequest; +import com.iemr.admin.model.villagemapping.VillageUpdateResponse; +import com.iemr.admin.repository.villagemapping.VillageUpdateRepository; + +@Service +public class VillageUpdateServiceImpl implements VillageUpdateService { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + private static final int MAX_VILLAGE_IDS_LENGTH = 100; + private static final int MAX_VILLAGE_NAMES_LENGTH = 500; + + @Autowired + private VillageUpdateRepository villageUpdateRepository; + + /** + * Replaces one village on a work location mapping and moves the beneficiary + * addresses that user registered (i_beneficiaryaddress.CreatedBy) to the new + * village, in a single transaction. + */ + @Override + @Transactional(rollbackFor = Exception.class) + public VillageUpdateResponse updateVillage(VillageUpdateRequest request) throws Exception { + validate(request); + + Object[] mapping = villageUpdateRepository.lockMapping(request.getuSRMappingID()); + if (mapping == null) { + throw new IllegalArgumentException("No work location mapping found with ID " + request.getuSRMappingID()); + } + Integer mappedUserID = ((Number) mapping[0]).intValue(); + if (!mappedUserID.equals(request.getUserID())) { + throw new IllegalArgumentException("Mapping " + request.getuSRMappingID() + " does not belong to user " + + request.getUserID() + ". Reload the user and try again."); + } + if (isTrue(mapping[3])) { + throw new IllegalArgumentException("Mapping " + request.getuSRMappingID() + " is deactivated"); + } + + String userName = villageUpdateRepository.userName(request.getUserID()); + if (userName == null) { + throw new IllegalArgumentException("No user found with ID " + request.getUserID()); + } + + String newVillageName = trimToNull(villageUpdateRepository.villageName(request.getNewVillageID())); + if (newVillageName == null) { + throw new IllegalArgumentException("No active village found with ID " + request.getNewVillageID()); + } + + List villageIDs = split((String) mapping[1]); + List villageNames = split((String) mapping[2]); + String oldVillageID = String.valueOf(request.getOldVillageID()); + String newVillageID = String.valueOf(request.getNewVillageID()); + + int index = villageIDs.indexOf(oldVillageID); + if (index < 0) { + throw new IllegalArgumentException("Village " + oldVillageID + " is no longer mapped to mapping " + + request.getuSRMappingID() + ". Reload the user and try again."); + } + if (villageIDs.contains(newVillageID)) { + throw new IllegalArgumentException("Village " + newVillageName + " is already mapped to this user"); + } + + String oldVillageName = index < villageNames.size() ? villageNames.get(index) : request.getOldVillageName(); + villageIDs.set(index, newVillageID); + while (villageNames.size() < villageIDs.size()) { + villageNames.add(""); + } + villageNames.set(index, newVillageName); + + String joinedIDs = String.join(",", villageIDs); + String joinedNames = String.join(",", villageNames); + if (joinedIDs.length() > MAX_VILLAGE_IDS_LENGTH || joinedNames.length() > MAX_VILLAGE_NAMES_LENGTH) { + throw new IllegalArgumentException( + "Updated village list is too long to store on the mapping (Villageid/VillageName limit)"); + } + + String modifiedBy = trimToNull(request.getModifiedBy()); + villageUpdateRepository.updateMappingVillages(request.getuSRMappingID(), joinedIDs, joinedNames, modifiedBy); + + // A single-village user registered every beneficiary under that village, + // so all of their addresses move. With several villages, only addresses on + // the replaced village move, so the other villages are left as they are. + long permanent; + long current; + boolean allAddresses = villageIDs.size() == 1; + if (allAddresses) { + permanent = villageUpdateRepository.updateAllAddressVillages(userName, request.getNewVillageID(), + newVillageName, modifiedBy); + current = permanent; + } else { + permanent = villageUpdateRepository.updatePermanentVillage(userName, request.getOldVillageID(), + request.getNewVillageID(), newVillageName, modifiedBy); + current = villageUpdateRepository.updateCurrentVillage(userName, request.getOldVillageID(), + request.getNewVillageID(), newVillageName, modifiedBy); + } + + logger.info("Village update complete: uSRMappingID {}, village {} -> {}, allAddresses {}, perm {} curr {}", + request.getuSRMappingID(), oldVillageID, newVillageID, allAddresses, permanent, current); + + VillageUpdateResponse response = new VillageUpdateResponse(); + response.setuSRMappingID(request.getuSRMappingID()); + response.setUserName(userName); + response.setOldVillageID(request.getOldVillageID()); + response.setOldVillageName(oldVillageName); + response.setNewVillageID(request.getNewVillageID()); + response.setNewVillageName(newVillageName); + response.setPermanentAddressesUpdated(permanent); + response.setCurrentAddressesUpdated(current); + response.setAllAddressesUpdated(allAddresses); + return response; + } + + private void validate(VillageUpdateRequest request) { + if (request == null) { + throw new IllegalArgumentException("Request body is required"); + } + if (request.getUserID() == null || request.getuSRMappingID() == null) { + throw new IllegalArgumentException("User ID and mapping ID are required"); + } + if (request.getOldVillageID() == null || request.getNewVillageID() == null) { + throw new IllegalArgumentException("Old and new village are required"); + } + if (request.getOldVillageID().equals(request.getNewVillageID())) { + throw new IllegalArgumentException("The new village is the same as the old one"); + } + if (trimToNull(request.getModifiedBy()) == null) { + throw new IllegalArgumentException("Modified by is required"); + } + } + + private List split(String csv) { + List values = new ArrayList<>(); + if (csv == null || csv.trim().isEmpty()) { + return values; + } + for (String value : Arrays.asList(csv.split(","))) { + values.add(value.trim()); + } + return values; + } + + private boolean isTrue(Object value) { + if (value instanceof Boolean bool) { + return bool; + } + if (value instanceof Number number) { + return number.intValue() != 0; + } + if (value instanceof byte[] bytes) { + return bytes.length > 0 && bytes[0] != 0; + } + return false; + } + + private String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } +}