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;
+ }
+}