diff --git a/pom.xml b/pom.xml index 062df585..1b6299ab 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.admin admin-api - 3.8.2 + 3.12.0 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 33d980ca..55bf70c1 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,23 @@ 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()); + resDataMap1.setVanID(previl.getVanID()); + 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 +1870,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()); @@ -1885,6 +1896,7 @@ public String updateUserRoleMapping(@RequestBody String updateUserRoleMapping, H usrRole.setFacilityID(pre.getFacilityID()); usrRole.setNikshayTUID(pre.getNikshayTUID()); usrRole.setNikshayFacilityID(pre.getNikshayFacilityID()); + usrRole.setVanID(pre.getVanID()); if (pre.getTeleConsultation() != null) { usrRole.setTeleConsultation(pre.getTeleConsultation()); diff --git a/src/main/java/com/iemr/admin/controller/item/ItemController.java b/src/main/java/com/iemr/admin/controller/item/ItemController.java index 78c64009..954e7a84 100644 --- a/src/main/java/com/iemr/admin/controller/item/ItemController.java +++ b/src/main/java/com/iemr/admin/controller/item/ItemController.java @@ -234,6 +234,11 @@ public String editItemMaster(@RequestBody ItemMaster item) { saveData.setItemDesc(item.getItemDesc()); saveData.setSctCode(item.getSctCode()); saveData.setSctTerm(item.getSctTerm()); + // Only when sent, so screens that don't show these fields can't wipe them (0 clears the threshold) + if (item.getMinStock() != null) + saveData.setMinStock(item.getMinStock()); + if (item.getMaxStock() != null) + saveData.setMaxStock(item.getMaxStock()); saveData.setModifiedBy(item.getModifiedBy()); saveData = itemService.createItemMaster(saveData); response.setResponse(saveData.toString()); diff --git a/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java b/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java index aa8d0a8d..5678969f 100644 --- a/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java +++ b/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -40,7 +41,6 @@ import com.iemr.admin.data.nikshay.NikshayFacility; import com.iemr.admin.data.nikshay.NikshayState; import com.iemr.admin.data.nikshay.NikshayTU; -import com.iemr.admin.data.nikshay.NikshayVillage; import com.iemr.admin.repo.employeemaster.EmployeeMasterRepo; import com.iemr.admin.repo.nikshay.NikshayDistrictRepo; import com.iemr.admin.repo.nikshay.NikshayFacilityRepo; @@ -121,7 +121,7 @@ public String getTUs(@RequestParam("districtID") Integer districtID) { OutputResponse response = new OutputResponse(); try { List tus = nikshayTURepo.findByDistrictID(districtID); - response.setResponse(tus.toString()); + response.setResponse(new Gson().toJson(tus.stream().map(this::toLite).collect(Collectors.toList()))); } catch (Exception e) { logger.error("Error fetching Nikshay TUs for districtID " + districtID + ": " + e.getMessage(), e); response.setError(e); @@ -136,7 +136,7 @@ public String getFacilities(@RequestParam("tuIDs") String tuIDs) { try { List ids = parseIntCsv(tuIDs); List facilities = nikshayFacilityRepo.findByTUIDs(ids); - response.setResponse(facilities.toString()); + response.setResponse(new Gson().toJson(facilities.stream().map(this::toLite).collect(Collectors.toList()))); } catch (Exception e) { logger.error("Error fetching Nikshay facilities for tuIDs " + tuIDs + ": " + e.getMessage(), e); response.setError(e); @@ -150,8 +150,8 @@ public String getVillages(@RequestParam("facilityIDs") String facilityIDs) { OutputResponse response = new OutputResponse(); try { List ids = parseIntCsv(facilityIDs); - List villages = nikshayVillageRepo.findByFacilityIDs(ids); - response.setResponse(villages.toString()); + List villages = nikshayVillageRepo.findLiteByFacilityIDs(ids); + response.setResponse(new Gson().toJson(villages.stream().map(this::toLiteVillage).collect(Collectors.toList()))); } catch (Exception e) { logger.error("Error fetching villages for facilityIDs " + facilityIDs + ": " + e.getMessage(), e); response.setError(e); @@ -174,6 +174,7 @@ public String getUserMappingNikshayData(@RequestParam("usrMappingID") Integer us result.put("districtID", row.getDistrictID()); result.put("nikshayTUID", row.getNikshayTUID()); result.put("nikshayFacilityID", row.getNikshayFacilityID()); + result.put("vanID", row.getVanID()); } response.setResponse(new Gson().toJson(result)); } catch (Exception e) { @@ -183,6 +184,37 @@ public String getUserMappingNikshayData(@RequestParam("usrMappingID") Integer us return response.toString(); } + // TU/Facility/Village lists can run into thousands of rows for a single + // district (e.g. Pune MC: 5,327 facilities), and the full entities carry + // audit fields (createdBy/createdDate/modifiedBy/lastModDate/codes) the + // Admin UI never reads. Send only the ID, name and parent ID each dropdown + // needs — same field names as before, so older UI builds keep working. + private Map toLite(NikshayTU t) { + Map m = new LinkedHashMap<>(); + m.put("nikshayTUID", t.getNikshayTUID()); + m.put("tUName", t.getTUName()); + m.put("nikshayDistrictID", t.getNikshayDistrictID()); + return m; + } + + private Map toLite(NikshayFacility f) { + Map m = new LinkedHashMap<>(); + m.put("nikshayFacilityID", f.getNikshayFacilityID()); + m.put("facilityName", f.getFacilityName()); + m.put("nikshayTUID", f.getNikshayTUID()); + return m; + } + + // row = [nikshayVillageID, villageName, nikshayFacilityID], see + // NikshayVillageRepo.findLiteByFacilityIDs + private Map toLiteVillage(Object[] row) { + Map m = new LinkedHashMap<>(); + m.put("nikshayVillageID", row[0]); + m.put("villageName", row[1]); + m.put("nikshayFacilityID", row[2]); + return m; + } + private List parseIntCsv(String csv) { return Arrays.stream(csv.split(",")) .map(String::trim) diff --git a/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java b/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java new file mode 100644 index 00000000..ff24607d --- /dev/null +++ b/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java @@ -0,0 +1,90 @@ +/* +* 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.stoptbteam; + +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.iemr.admin.service.stoptbteam.StopTBTeamService; +import com.iemr.admin.utils.mapper.OutputMapper; +import com.iemr.admin.utils.response.OutputResponse; + +import io.swagger.v3.oas.annotations.Operation; + +/** Stop TB teams (MMU vans) for Work Location Mapping (AMM-2496). */ +@RestController +@RequestMapping(value = "/stoptb/team") +public class StopTBTeamController { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired + private StopTBTeamService teamService; + + @Operation(summary = "Stop TB teams (MMU vans) of a state") + @GetMapping(value = "/vans", produces = MediaType.APPLICATION_JSON) + public String getStateTeams(@RequestParam("stateID") Integer stateID, + @RequestParam("serviceProviderID") Integer serviceProviderID) { + OutputResponse response = new OutputResponse(); + try { + response.setResponse(OutputMapper.gsonWithoutExpose().toJson(teamService.stateTeams(stateID, serviceProviderID))); + } catch (Exception e) { + logger.error("Stop TB teams for state {} failed", stateID, e); + response.setError(e); + } + return response.toString(); + } + + @Operation(summary = "A Stop TB user's current team") + @GetMapping(value = "/user", produces = MediaType.APPLICATION_JSON) + public String getUserTeam(@RequestParam("userID") Integer userID, + @RequestParam("providerServiceMapID") Integer providerServiceMapID) { + OutputResponse response = new OutputResponse(); + try { + response.setResponse(OutputMapper.gsonWithoutExpose().toJson(teamService.userTeam(userID, providerServiceMapID))); + } catch (Exception e) { + logger.error("Stop TB team of user {} failed", userID, e); + response.setError(e); + } + return response.toString(); + } + + @Operation(summary = "Members of a Stop TB team") + @GetMapping(value = "/members", produces = MediaType.APPLICATION_JSON) + public String getTeamMembers(@RequestParam("vanID") Integer vanID, + @RequestParam("providerServiceMapID") Integer providerServiceMapID) { + OutputResponse response = new OutputResponse(); + try { + response.setResponse(OutputMapper.gsonWithoutExpose().toJson(teamService.teamMembers(vanID, providerServiceMapID))); + } catch (Exception e) { + logger.error("Stop TB team {} members failed", vanID, e); + response.setError(e); + } + return response.toString(); + } +} 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 00000000..ccfac3f8 --- /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/data/employeemaster/M_UserServiceRoleMapping2.java b/src/main/java/com/iemr/admin/data/employeemaster/M_UserServiceRoleMapping2.java index e33da770..1d54fc95 100644 --- a/src/main/java/com/iemr/admin/data/employeemaster/M_UserServiceRoleMapping2.java +++ b/src/main/java/com/iemr/admin/data/employeemaster/M_UserServiceRoleMapping2.java @@ -435,6 +435,12 @@ public class M_UserServiceRoleMapping2 { @Column(name = "NikshayFacilityID") private String nikshayFacilityID; + // Stop TB team (AMM-2496): the van (camp laptop) the user works with. Same + // value on all of the user's active Stop TB rows; null when not in a team. + @Expose + @Column(name = "VanID") + private Integer vanID; + public M_UserServiceRoleMapping2() { } diff --git a/src/main/java/com/iemr/admin/data/items/ItemMaster.java b/src/main/java/com/iemr/admin/data/items/ItemMaster.java index 610e2c39..f5e2d599 100644 --- a/src/main/java/com/iemr/admin/data/items/ItemMaster.java +++ b/src/main/java/com/iemr/admin/data/items/ItemMaster.java @@ -113,7 +113,16 @@ public class ItemMaster { @Expose @Column(name="Strength") private String strength; - + + // Stock threshold: at or below this quantity a store's Low Stock report lists the item + @Expose + @Column(name="Min_Stock") + private Integer minStock; + + @Expose + @Column(name="Max_Stock") + private Integer maxStock; + @Expose @Column(name="UOMID") private Integer uomID; 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 00000000..7b6e1175 --- /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 00000000..7326f478 --- /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 e2bf74f0..33929fe6 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/repo/employeemaster/EmployeeMasterRepoo.java b/src/main/java/com/iemr/admin/repo/employeemaster/EmployeeMasterRepoo.java index 74fe406c..ba80879d 100644 --- a/src/main/java/com/iemr/admin/repo/employeemaster/EmployeeMasterRepoo.java +++ b/src/main/java/com/iemr/admin/repo/employeemaster/EmployeeMasterRepoo.java @@ -48,6 +48,9 @@ public interface EmployeeMasterRepoo extends CrudRepository M_User1 findEmployeeByName(@Param("userName") String userName); + @Query("SELECT COUNT(u) FROM M_User1 u WHERE LOWER(TRIM(u.employeeID)) = LOWER(TRIM(:employeeID)) AND deleted=false ") + Long countEmployeeByEmployeeID(@Param("employeeID") String employeeID); + @Query("SELECT u FROM M_User1 u WHERE u.contactNo=:contactNo AND deleted=false ") M_User1 findEmployeeByContact(@Param("contactNo") String userName); diff --git a/src/main/java/com/iemr/admin/repo/nikshay/NikshayVillageRepo.java b/src/main/java/com/iemr/admin/repo/nikshay/NikshayVillageRepo.java index 6f0741b2..510f2292 100644 --- a/src/main/java/com/iemr/admin/repo/nikshay/NikshayVillageRepo.java +++ b/src/main/java/com/iemr/admin/repo/nikshay/NikshayVillageRepo.java @@ -35,4 +35,12 @@ public interface NikshayVillageRepo extends CrudRepository findByFacilityIDs(@Param("facilityIDs") List facilityIDs); + + // Only the three columns the village dropdown uses, as [nikshayVillageID, + // villageName, nikshayFacilityID]. A district can have 1 lakh+ village rows + // (the same village repeated under each facility); loading full entities + // for that was most of the response time. + @Query("select v.nikshayVillageID, v.villageName, v.nikshayFacilityID from NikshayVillage v " + + "where v.nikshayFacilityID in (:facilityIDs) and v.deleted = false order by v.villageName, v.nikshayVillageID") + List findLiteByFacilityIDs(@Param("facilityIDs") List facilityIDs); } diff --git a/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java b/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java new file mode 100644 index 00000000..f2b13711 --- /dev/null +++ b/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java @@ -0,0 +1,138 @@ +/* +* 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.stoptbteam; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; + +/** + * Stop TB teams (AMM-2496). A team is a van; the user's team is the VanID + * on their m_userservicerolemapping rows. Team vans come from one service + * line's vans (MMU today, see stoptb.team.van-service). + */ +@Repository +public class StopTBTeamRepository { + private static String notDeleted(String column) { + return "(" + column + " IS NULL OR " + column + " = b'0')"; + } + + private static final String STOPTB_SERVICE_LINES = + "SELECT p.ProviderServiceMapID FROM m_providerservicemapping p" + + " JOIN m_servicemaster s ON s.ServiceID = p.ServiceID WHERE s.ServiceName = 'Stop TB'"; + + private static final String SELECT_STATE_VANS = + "SELECT v.VanID, v.VanName, v.VehicalNo, v.ParkingPlaceID, pp.ParkingPlaceName" + + " FROM m_van v" + + " JOIN m_providerservicemapping p ON p.ProviderServiceMapID = v.ProviderServiceMapID" + + " JOIN m_servicemaster s ON s.ServiceID = p.ServiceID" + + " LEFT JOIN m_parkingplace pp ON pp.ParkingPlaceID = v.ParkingPlaceID" + + " WHERE s.ServiceName = :vanService AND p.StateID = :stateID" + + " AND p.ServiceProviderID = :serviceProviderID" + + " AND " + notDeleted("p.Deleted") + " AND " + notDeleted("v.Deleted") + + " ORDER BY v.VanName, v.VanID"; + + private static final String SELECT_VAN = + "SELECT v.VanID, p.StateID, p.ServiceProviderID FROM m_van v" + + " JOIN m_providerservicemapping p ON p.ProviderServiceMapID = v.ProviderServiceMapID" + + " JOIN m_servicemaster s ON s.ServiceID = p.ServiceID" + + " WHERE v.VanID = :vanID AND s.ServiceName = :vanService" + + " AND " + notDeleted("p.Deleted") + " AND " + notDeleted("v.Deleted"); + + private static final String SELECT_STOPTB_SERVICE = + "SELECT p.StateID, p.ServiceProviderID FROM m_providerservicemapping p" + + " WHERE p.ProviderServiceMapID = :providerServiceMapID" + + " AND p.ProviderServiceMapID IN (" + STOPTB_SERVICE_LINES + ")"; + + private static final String SELECT_USER_TEAM = + "SELECT m.VanID, v.VanName FROM m_userservicerolemapping m" + + " JOIN m_van v ON v.VanID = m.VanID" + + " WHERE m.UserID = :userID AND m.ProviderServiceMapID = :providerServiceMapID" + + " AND " + notDeleted("m.Deleted") + + " ORDER BY m.LastModDate DESC, m.USRMappingID DESC"; + + private static final String SELECT_TEAM_MEMBERS = + "SELECT DISTINCT u.UserID, u.UserName, u.FirstName, u.LastName FROM m_userservicerolemapping m" + + " JOIN m_user u ON u.UserID = m.UserID" + + " WHERE m.VanID = :vanID AND m.ProviderServiceMapID = :providerServiceMapID" + + " AND " + notDeleted("m.Deleted") + " AND " + notDeleted("u.Deleted") + + " ORDER BY u.UserName"; + + // One team per user: every active Stop TB row of the user gets the same VanID. + private static final String SET_USER_TEAM = + "UPDATE m_userservicerolemapping SET VanID = :vanID" + + " WHERE UserID = :userID AND ProviderServiceMapID = :providerServiceMapID" + + " AND " + notDeleted("Deleted") + + " AND ProviderServiceMapID IN (" + STOPTB_SERVICE_LINES + ")"; + + @PersistenceContext + private EntityManager entityManager; + + /** {VanID, VanName, VehicalNo, ParkingPlaceID, ParkingPlaceName} of the state's team vans. */ + @SuppressWarnings("unchecked") + public List stateVans(String vanService, Integer stateID, Integer serviceProviderID) { + return entityManager.createNativeQuery(SELECT_STATE_VANS).setParameter("vanService", vanService) + .setParameter("stateID", stateID).setParameter("serviceProviderID", serviceProviderID) + .getResultList(); + } + + /** {VanID, StateID, ServiceProviderID} of an active team van, or null. */ + public Object[] van(String vanService, Integer vanID) { + return first(entityManager.createNativeQuery(SELECT_VAN).setParameter("vanService", vanService) + .setParameter("vanID", vanID)); + } + + /** {StateID, ServiceProviderID} when the ID is a Stop TB service line, else null. */ + public Object[] stopTBService(Integer providerServiceMapID) { + return first(entityManager.createNativeQuery(SELECT_STOPTB_SERVICE) + .setParameter("providerServiceMapID", providerServiceMapID)); + } + + /** {VanID, VanName} of the user's team, or null. */ + public Object[] userTeam(Integer userID, Integer providerServiceMapID) { + return first(entityManager.createNativeQuery(SELECT_USER_TEAM).setParameter("userID", userID) + .setParameter("providerServiceMapID", providerServiceMapID)); + } + + /** {UserID, UserName, FirstName, LastName} of the team's members. */ + @SuppressWarnings("unchecked") + public List teamMembers(Integer vanID, Integer providerServiceMapID) { + return entityManager.createNativeQuery(SELECT_TEAM_MEMBERS).setParameter("vanID", vanID) + .setParameter("providerServiceMapID", providerServiceMapID).getResultList(); + } + + /** Puts the user in the team (or none) on all their active Stop TB rows. */ + public int setUserTeam(Integer userID, Integer providerServiceMapID, Integer vanID) { + return entityManager.createNativeQuery(SET_USER_TEAM).setParameter("vanID", vanID) + .setParameter("userID", userID).setParameter("providerServiceMapID", providerServiceMapID) + .executeUpdate(); + } + + private Object[] first(Query query) { + List rows = query.setMaxResults(1).getResultList(); + return rows.isEmpty() ? null : (Object[]) rows.get(0); + } +} 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 00000000..273e8545 --- /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/bulkRegistration/BulkRegistrationServiceImpl.java b/src/main/java/com/iemr/admin/service/bulkRegistration/BulkRegistrationServiceImpl.java index 46873628..64656d95 100644 --- a/src/main/java/com/iemr/admin/service/bulkRegistration/BulkRegistrationServiceImpl.java +++ b/src/main/java/com/iemr/admin/service/bulkRegistration/BulkRegistrationServiceImpl.java @@ -8,6 +8,7 @@ import com.iemr.admin.data.nikshay.NikshayDistrict; import com.iemr.admin.data.nikshay.NikshayState; import com.iemr.admin.data.rolemaster.StateMasterForRole; +import com.iemr.admin.repo.employeemaster.EmployeeMasterRepoo; import com.iemr.admin.repo.employeemaster.M_DesignationRepo; import com.iemr.admin.repo.employeemaster.M_UserDemographicsRepo; import com.iemr.admin.repo.employeemaster.V_ShowuserRepo; @@ -65,6 +66,9 @@ public class BulkRegistrationServiceImpl implements BulkRegistrationService { @Autowired private M_DesignationRepo m_DesignationRepo; + @Autowired + private EmployeeMasterRepoo employeeMasterRepoo; + @Autowired private Role_MasterInter roleMasterInter; @@ -125,10 +129,48 @@ public void registerBulkUser( } totalEmployeeListSize = employeeList.getEmployees().size(); + // usernames are unique across the server, so every row whose username appears more than once + // in the file is rejected (none of them is saved) and the uploader has to fix the file + // the same applies to employee ids + Set uploadedUserNames = new HashSet<>(); + Set duplicateUserNames = new HashSet<>(); + Set uploadedEmployeeIds = new HashSet<>(); + Set duplicateEmployeeIds = new HashSet<>(); + for (Employee employee : employeeList.getEmployees()) { + // normalise the username and employee id once so the duplicate checks, the DB lookups and the saved values all agree + if (employee.getUserName() != null) { + employee.setUserName(employee.getUserName().trim()); + } + if (employee.getEmployeeId() != null) { + employee.setEmployeeId(employee.getEmployeeId().trim()); + } + if (employee.getUserName() != null && !employee.getUserName().isBlank() + && !uploadedUserNames.add(employee.getUserName().trim().toLowerCase())) { + duplicateUserNames.add(employee.getUserName().trim().toLowerCase()); + } + if (employee.getEmployeeId() != null && !employee.getEmployeeId().isBlank() + && !uploadedEmployeeIds.add(employee.getEmployeeId().trim().toLowerCase())) { + duplicateEmployeeIds.add(employee.getEmployeeId().trim().toLowerCase()); + } + } for (int i = 0; i < totalEmployeeListSize; i++) { Employee employee = employeeList.getEmployees().get(i); + List duplicateErrors = new ArrayList<>(); + if (employee.getUserName() != null + && duplicateUserNames.contains(employee.getUserName().trim().toLowerCase())) { + duplicateErrors.add("Duplicate UserName in uploaded file : " + employee.getUserName()); + } + if (employee.getEmployeeId() != null + && duplicateEmployeeIds.contains(employee.getEmployeeId().trim().toLowerCase())) { + duplicateErrors.add("Duplicate Employee Id in uploaded file : " + employee.getEmployeeId()); + } + if (!duplicateErrors.isEmpty()) { + logAndCollectError(i, employee, duplicateErrors, new BulkRegistrationError()); + continue; + } + try { processUserUser( employee, @@ -151,7 +193,7 @@ public static String escapeXmlSpecialChars(String xml) { */ private void processUserUser(Employee employee, Integer row, String authorization, String createdBy, Integer serviceProviderID) throws Exception { - if (employee.getUserName() == null || employee.getUserName().isEmpty()) { + if (employee.getUserName() == null || employee.getUserName().isBlank()) { List validationErrors = new ArrayList<>(); BulkRegistrationError bulkRegistrationErrors_ = new BulkRegistrationError(); validationErrors.add("Please Enter UserName"); @@ -166,8 +208,10 @@ private void processUserUser(Employee employee, Integer row, String authorizatio // user not found → create flow saveUserUser(employee, row, authorization, createdBy, serviceProviderID); } else { - // user already exists → update flow - updateUserUser(employee, row, authorization, createdBy, serviceProviderID); +// bulk upload only creates new users; usernames are unique across the server + List validationErrors = new ArrayList<>(); + validationErrors.add("User already exists with username : " + employee.getUserName()); + logAndCollectError(row, employee, validationErrors, new BulkRegistrationError()); } } @@ -180,12 +224,17 @@ private void saveUserUser(Employee employee, Integer row, String authorization, M_UserServiceRoleMapping2 m_userServiceRoleMapping = new M_UserServiceRoleMapping2(); logger.info("employee_list after for loop" + employee.toString()); - if (!employee.getUserName().isEmpty()) { + if (!employee.getUserName().isBlank()) { String checkUserIsExist = employeeMasterInter.FindEmployeeName(employee.getUserName()); String checkContactIsExist = employeeMasterInter.FindEmployeeContact(employee.getContactNo()); logger.info("checkAAdharIsExist" + checkUserIsExist); if (checkUserIsExist.equalsIgnoreCase("usernotexist")) { if (checkContactIsExist.equalsIgnoreCase("contactnotexist")) { + if (employee.getEmployeeId() == null || employee.getEmployeeId().isBlank()) { + validationErrors.add("Employee Id is missing."); + } else if (employeeMasterRepoo.countEmployeeByEmployeeID(employee.getEmployeeId().trim()) > 0) { + validationErrors.add("User already exists with Employee Id : " + employee.getEmployeeId()); + } if (employee.getTitle() == null || employee.getTitle().isEmpty()) { validationErrors.add("Title is missing."); } @@ -251,6 +300,8 @@ private void saveUserUser(Employee employee, Integer row, String authorization, if (employee.getDesignation().isEmpty()) { validationErrors.add("Designation is missing"); + } else if (getDesignationByDesignationName(employee.getDesignation()) == 0) { + validationErrors.add("Designation is invalid."); } if (employee.getEmergencyContactNo().isEmpty()) { validationErrors.add("Emergency contact number is missing"); @@ -604,6 +655,9 @@ private void updateUserUser(Employee employee, Integer row, String authorization if (checkContactIsExist.equalsIgnoreCase("contactnotexist")) { + if (employee.getEmployeeId() == null || employee.getEmployeeId().isBlank()) { + validationErrors.add("Employee Id is missing."); + } if (employee.getTitle() == null || employee.getTitle().isEmpty()) { validationErrors.add("Title is missing."); } @@ -656,6 +710,8 @@ private void updateUserUser(Employee employee, Integer row, String authorization if (employee.getDesignation().isEmpty()) { validationErrors.add("Designation is missing"); + } else if (getDesignationByDesignationName(employee.getDesignation()) == 0) { + validationErrors.add("Designation is invalid."); } if (employee.getEmergencyContactNo().isEmpty()) { @@ -705,10 +761,18 @@ private void updateUserUser(Employee employee, Integer row, String authorization validationErrors.add("Current State is invalid."); } + // district ids are looked up once here and reused for the save, so a district + // that is not found in the master is reported and the row is not saved. + // getDistrictId() searches the districts of the state last passed to getStateId(), + // so each district is looked up right after its own state + int districtId = employee.getDistrict().isEmpty() ? 0 + : isStopTB ? getNikshayDistrictId(employee.getState(), employee.getDistrict()) + : getDistrictId(employee.getDistrict()); + if (employee.getDistrict().isEmpty()) { validationErrors.add("Current District is missing."); } - if (!employee.getDistrict().isEmpty() && getDistrictId(employee.getDistrict()) == 0) { + if (!employee.getDistrict().isEmpty() && districtId == 0) { validationErrors.add("Current District is invalid."); } @@ -719,10 +783,14 @@ private void updateUserUser(Employee employee, Integer row, String authorization validationErrors.add("Permanent State is invalid."); } + int permDistrictId = employee.getPermanentDistrict().isEmpty() ? 0 + : isStopTB ? getNikshayDistrictId(employee.getPermanentState(), employee.getPermanentDistrict()) + : getDistrictId(employee.getPermanentDistrict()); + if (employee.getPermanentDistrict().isEmpty()) { validationErrors.add("Permanent District is missing."); } - if (!employee.getPermanentDistrict().isEmpty() && getDistrictId(employee.getPermanentDistrict()) == 0) { + if (!employee.getPermanentDistrict().isEmpty() && permDistrictId == 0) { validationErrors.add("Permanent District is invalid."); } @@ -747,7 +815,7 @@ private void updateUserUser(Employee employee, Integer row, String authorization && isValidDate(convertStringIntoDate(employee.getDob()).toString()) && isValidDate(convertStringIntoDate(employee.getDateOfJoining()).toString()); - if (coreFieldsValid) { + if (coreFieldsValid && districtId != 0 && permDistrictId != 0) { try { M_User1 mUser = existingUser; mUser.setUserID(mUser.getUserID()); @@ -796,6 +864,7 @@ && isValidDate(convertStringIntoDate(employee.getDob()).toString()) mUserDemographics = new M_UserDemographics(); mUserDemographics.setUserID(updatedUser.getUserID()); mUserDemographics.setCountryID(91); + mUserDemographics.setCreatedBy(modifiedBy); } if (!employee.getCommunity().isEmpty()) { @@ -813,7 +882,7 @@ && isValidDate(convertStringIntoDate(employee.getDob()).toString()) mUserDemographics.setPermStateID(getStateId(employee.getPermanentState())); } if (!employee.getPermanentDistrict().isEmpty()) { - mUserDemographics.setPermDistrictID(getDistrictId(employee.getPermanentDistrict())); + mUserDemographics.setPermDistrictID(permDistrictId); } if (!employee.getPermanentPincode().isEmpty()) { mUserDemographics.setPermPinCode(Integer.valueOf(employee.getPermanentPincode())); @@ -831,12 +900,7 @@ && isValidDate(convertStringIntoDate(employee.getDob()).toString()) mUserDemographics.setStateID(getStateId(employee.getState())); } if (!employee.getDistrict().isEmpty()) { - int districtId = isStopTB ? getNikshayDistrictId(employee.getState(), employee.getDistrict()) - : getDistrictId(employee.getDistrict()); - if (districtId == 0) { - validationErrors.add("Current District is invalid."); - - } + mUserDemographics.setDistrictID(districtId); } if (!employee.getPincode().isEmpty()) { mUserDemographics.setPinCode(employee.getPincode().toString()); @@ -987,6 +1051,13 @@ private int getTitleId(String titleString) { return titleId; } + public int getDesignationByDesignationName(String designationString) { + if (designationString == null || designationString.isBlank()) { + return 0; + } + List designations = m_DesignationRepo.findByDesignationName(designationString.trim()); + return designations.isEmpty() ? 0 : designations.get(0).getDesignationID(); + } public int getDesignationId(String designationString) { if(!m_DesignationRepo.findByDesignationName(designationString).isEmpty()){ 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 859f087d..9b51b815 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 f09d0bd6..365b3158 100644 --- a/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImpl.java +++ b/src/main/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImpl.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.springframework.web.context.request.RequestContextHolder; @@ -60,6 +61,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonParser; +import com.iemr.admin.service.stoptbteam.StopTBTeamService; import com.iemr.admin.data.blocking.M_Providerservicemapping_Blocking; import com.iemr.admin.data.employeemaster.EmployeeSignature; import com.iemr.admin.data.employeemaster.M_Community; @@ -130,6 +132,9 @@ public class EmployeeMasterServiceImpl implements EmployeeMasterInter { @Autowired private M_CommunityRepo m_CommunityRepo; + @Autowired + private StopTBTeamService stopTBTeamService; + @Autowired private M_ReligionRepo m_ReligionRepo; @@ -348,6 +353,7 @@ public ArrayList mapLanguage(List resList) public ArrayList mapRole(List resList1, String authToken) throws JsonMappingException, JsonProcessingException { // Fix 4 + Fix 16 + Fix 17: validate each mapping before any save for (M_UserServiceRoleMapping2 mapping : resList1) { + stopTBTeamService.validateTeam(mapping.getProviderServiceMapID(), mapping.getVanID()); M_Role role = null; if (mapping.getRoleID() != null) { role = roleRepo.findByRoleID(mapping.getRoleID()); @@ -431,6 +437,21 @@ public ArrayList mapRole(List teamRows = new LinkedHashMap<>(); + for (M_UserServiceRoleMapping2 mapping : resList1) { + teamRows.putIfAbsent(mapping.getUserID() + "-" + mapping.getProviderServiceMapID(), mapping); + } + for (M_UserServiceRoleMapping2 mapping : teamRows.values()) { + Integer vanID = mapping.getVanID(); + if (vanID == null) { + Object current = stopTBTeamService.userTeam(mapping.getUserID(), mapping.getProviderServiceMapID()) + .get("vanID"); + vanID = (Integer) current; + } + stopTBTeamService.applyTeam(mapping.getUserID(), mapping.getProviderServiceMapID(), vanID); + } return reslist; } @@ -1025,8 +1046,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 @@ -1125,6 +1146,10 @@ public M_UserServiceRoleMapping2 saveRoleMappingeditedData(M_UserServiceRoleMapp } } + boolean activeRow = !Boolean.TRUE.equals(usrRole.getDeleted()); + if (activeRow) { + stopTBTeamService.validateTeam(usrRole.getProviderServiceMapID(), usrRole.getVanID()); + } M_UserServiceRoleMapping2 data = employeeMasterRepo.save(usrRole); if (ENABLE_CTI_USER_CREATION) { List list = new ArrayList(); @@ -1156,6 +1181,10 @@ public M_UserServiceRoleMapping2 saveRoleMappingeditedData(M_UserServiceRoleMapp M_UserServiceRoleMapping2 savedData = employeeMasterRepo.save(usrRole); } + // Stop TB team (AMM-2496): the same team on all of the user's role rows. + if (activeRow) { + stopTBTeamService.applyTeam(usrRole.getUserID(), usrRole.getProviderServiceMapID(), usrRole.getVanID()); + } return data; } diff --git a/src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java b/src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java new file mode 100644 index 00000000..07c4a99b --- /dev/null +++ b/src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java @@ -0,0 +1,138 @@ +/* +* 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.stoptbteam; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.iemr.admin.repository.stoptbteam.StopTBTeamRepository; + +/** + * Stop TB teams for Work Location Mapping (AMM-2496). A team is a van (the + * camp laptop's STOPTB_VAN_ID). A team has many users; a user is in one team, + * stored as VanID on all of their active Stop TB m_userservicerolemapping rows. + */ +@Service +public class StopTBTeamService { + @Autowired + private StopTBTeamRepository teamRepository; + + // Service line whose vans are the teams: MMU today, the Stop TB service + // once it has its own vans. + @Value("${stoptb.team.van-service:MMU}") + private String vanService; + + /** The state's team vans, for the Team dropdown. */ + public List> stateTeams(Integer stateID, Integer serviceProviderID) { + if (stateID == null || serviceProviderID == null) { + throw new IllegalArgumentException("State and service provider are required"); + } + List> teams = new ArrayList<>(); + for (Object[] row : teamRepository.stateVans(vanService, stateID, serviceProviderID)) { + Map team = new LinkedHashMap<>(); + team.put("vanID", toInt(row[0])); + team.put("vanName", row[1]); + team.put("vehicalNo", row[2]); + team.put("parkingPlaceID", toInt(row[3])); + team.put("parkingPlaceName", row[4]); + teams.add(team); + } + return teams; + } + + /** The user's team ({vanID, vanName}), or an empty map. */ + public Map userTeam(Integer userID, Integer providerServiceMapID) { + if (userID == null || providerServiceMapID == null) { + throw new IllegalArgumentException("User and service line are required"); + } + Map team = new LinkedHashMap<>(); + Object[] row = teamRepository.userTeam(userID, providerServiceMapID); + if (row != null) { + team.put("vanID", toInt(row[0])); + team.put("vanName", row[1]); + } + return team; + } + + /** The members of a team under this Stop TB service line. */ + public List> teamMembers(Integer vanID, Integer providerServiceMapID) { + if (vanID == null || providerServiceMapID == null) { + throw new IllegalArgumentException("Team and service line are required"); + } + List> members = new ArrayList<>(); + for (Object[] row : teamRepository.teamMembers(vanID, providerServiceMapID)) { + Map member = new LinkedHashMap<>(); + member.put("userID", toInt(row[0])); + member.put("userName", row[1]); + member.put("firstName", row[2]); + member.put("lastName", row[3]); + members.add(member); + } + return members; + } + + /** + * Checks a mapping's team before it is saved: a team is only allowed on a + * Stop TB service line, and must be a van of that service line's state. + */ + public void validateTeam(Integer providerServiceMapID, Integer vanID) { + if (vanID == null) { + return; + } + Object[] stopTB = providerServiceMapID == null ? null : teamRepository.stopTBService(providerServiceMapID); + if (stopTB == null) { + throw new IllegalArgumentException("A team can only be set on a Stop TB mapping"); + } + Object[] van = teamRepository.van(vanService, vanID); + if (van == null) { + throw new IllegalArgumentException("Team " + vanID + " is not an active " + vanService + " van"); + } + if (!Objects.equals(toInt(van[1]), toInt(stopTB[0])) || !Objects.equals(toInt(van[2]), toInt(stopTB[1]))) { + throw new IllegalArgumentException("Team " + vanID + " is not in this user's state"); + } + } + + /** + * After a Stop TB mapping is saved, gives all of the user's active Stop TB + * rows the same team, so one user is never in two teams. No-op for other + * service lines. + */ + @Transactional(rollbackFor = Exception.class) + public void applyTeam(Integer userID, Integer providerServiceMapID, Integer vanID) { + if (userID == null || providerServiceMapID == null) { + return; + } + teamRepository.setUserTeam(userID, providerServiceMapID, vanID); + } + + private static Integer toInt(Object value) { + return value == null ? null : ((Number) value).intValue(); + } +} 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 00000000..6bdde031 --- /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 00000000..e90d5ccb --- /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; + } +} diff --git a/src/main/java/com/iemr/admin/to/employeemaster/Previleges1097_3.java b/src/main/java/com/iemr/admin/to/employeemaster/Previleges1097_3.java index 4c6a7578..095cbcca 100644 --- a/src/main/java/com/iemr/admin/to/employeemaster/Previleges1097_3.java +++ b/src/main/java/com/iemr/admin/to/employeemaster/Previleges1097_3.java @@ -38,6 +38,16 @@ public class Previleges1097_3 { // M_UserServiceRoleMapping2's NikshayTUID/NikshayFacilityID columns. private String nikshayTUID; private String nikshayFacilityID; + // Stop TB team (van), AMM-2496 + private Integer vanID; + + public Integer getVanID() { + return vanID; + } + + public void setVanID(Integer vanID) { + this.vanID = vanID; + } public Integer getProviderServiceMapID() { return providerServiceMapID;