From ef9d827fa337c179e79dd0bd55a7cd6c5b455657 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Mon, 28 Sep 2026 09:49:36 +0530 Subject: [PATCH 1/4] perf(nikshay): return only id/name/parent from TU, facility and village lookups A single district can return thousands of rows (Pune MC: 5,327 facilities, Ganganagar: 3,058 villages), and each full entity carried audit fields and codes the Admin UI never reads. Map each row to its ID, name and parent ID before serializing. Field names are unchanged, so older Admin UI builds keep working. Pune MC facilities response drops from ~1.4 MB to ~490 kB. Co-Authored-By: Claude Opus 5.5 --- .../nikshay/NikshayLocationController.java | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) 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 aa8d0a8..3ee62ff 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; @@ -121,7 +122,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 +137,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); @@ -151,7 +152,7 @@ public String getVillages(@RequestParam("facilityIDs") String facilityIDs) { try { List ids = parseIntCsv(facilityIDs); List villages = nikshayVillageRepo.findByFacilityIDs(ids); - response.setResponse(villages.toString()); + response.setResponse(new Gson().toJson(villages.stream().map(this::toLite).collect(Collectors.toList()))); } catch (Exception e) { logger.error("Error fetching villages for facilityIDs " + facilityIDs + ": " + e.getMessage(), e); response.setError(e); @@ -183,6 +184,35 @@ 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; + } + + private Map toLite(NikshayVillage v) { + Map m = new LinkedHashMap<>(); + m.put("nikshayVillageID", v.getNikshayVillageID()); + m.put("villageName", v.getVillageName()); + m.put("nikshayFacilityID", v.getNikshayFacilityID()); + return m; + } + private List parseIntCsv(String csv) { return Arrays.stream(csv.split(",")) .map(String::trim) From d81e65e2285d2defe4248bd3cc97fb5b70e338e0 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 9 Oct 2026 22:32:26 +0530 Subject: [PATCH 2/4] perf(nikshay): read only id/name/facility for the villages lookup UAT districts hold 1 lakh+ village rows (the same village repeated under each facility). Loading full NikshayVillage entities for that was most of the response time. Select the three columns the Admin UI uses instead. Alluri Sitharama Raju at UAT scale (171,003 villages, local test copy): 1.5-2.0 s -> 0.8-1.1 s, same rows and fields. Order is now name, then village ID, so villages with the same name always come back in one order. Co-Authored-By: Claude Opus 5.5 --- .../nikshay/NikshayLocationController.java | 15 ++++++++------- .../admin/repo/nikshay/NikshayVillageRepo.java | 8 ++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) 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 3ee62ff..81729a1 100644 --- a/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java +++ b/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java @@ -41,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; @@ -151,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(new Gson().toJson(villages.stream().map(this::toLite).collect(Collectors.toList()))); + 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); @@ -205,11 +204,13 @@ private Map toLite(NikshayFacility f) { return m; } - private Map toLite(NikshayVillage v) { + // row = [nikshayVillageID, villageName, nikshayFacilityID], see + // NikshayVillageRepo.findLiteByFacilityIDs + private Map toLiteVillage(Object[] row) { Map m = new LinkedHashMap<>(); - m.put("nikshayVillageID", v.getNikshayVillageID()); - m.put("villageName", v.getVillageName()); - m.put("nikshayFacilityID", v.getNikshayFacilityID()); + m.put("nikshayVillageID", row[0]); + m.put("villageName", row[1]); + m.put("nikshayFacilityID", row[2]); return m; } 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 6f0741b..510f229 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); } From 2970b560f97fda20a66356d32f99a7d360c877fb Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Wed, 14 Oct 2026 22:31:39 +0530 Subject: [PATCH 3/4] feat(stoptb): team (MMU van) mapping API for Work Location Mapping (AMM-2496) In AMRIT a team is a van. Stop TB teams are the MMU vans of the user's state (the camp laptops already use them). A team has many users and a user is in one team per Stop TB service line. - GET /stoptb/team/vans?stateID&serviceProviderID teams of a state - GET /stoptb/team/user?userID&providerServiceMapID user's team - GET /stoptb/team/members?vanID&providerServiceMapID team members - POST /stoptb/team/assign {userID, providerServiceMapID, vanID|null, modifiedBy} moves the user into the team (or out of it) in one transaction Membership is stored the way AMRIT already links users to vans (m_userparkingplacemap -> m_uservanmapping), so van-based logins see the team. No schema change. Rejects non-Stop TB service lines and vans of another state. Co-Authored-By: Claude Opus 5.5 --- .../stoptbteam/StopTBTeamController.java | 106 +++++++++++ .../model/stoptbteam/TeamAssignRequest.java | 65 +++++++ .../stoptbteam/StopTBTeamRepository.java | 169 ++++++++++++++++++ .../service/stoptbteam/StopTBTeamService.java | 153 ++++++++++++++++ 4 files changed, 493 insertions(+) create mode 100644 src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java create mode 100644 src/main/java/com/iemr/admin/model/stoptbteam/TeamAssignRequest.java create mode 100644 src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java create mode 100644 src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java 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 0000000..6a969d9 --- /dev/null +++ b/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java @@ -0,0 +1,106 @@ +/* +* 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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +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.model.stoptbteam.TeamAssignRequest; +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(); + } + + @Operation(summary = "Move a Stop TB user into a team, or out of their team when vanID is null") + @PostMapping(value = "/assign", produces = MediaType.APPLICATION_JSON, headers = "Authorization") + public String assignTeam(@RequestBody TeamAssignRequest request) { + OutputResponse response = new OutputResponse(); + try { + response.setResponse(OutputMapper.gsonWithoutExpose().toJson(teamService.assignTeam(request))); + } catch (Exception e) { + logger.error("Stop TB team assignment failed", e); + response.setError(e); + } + return response.toString(); + } +} diff --git a/src/main/java/com/iemr/admin/model/stoptbteam/TeamAssignRequest.java b/src/main/java/com/iemr/admin/model/stoptbteam/TeamAssignRequest.java new file mode 100644 index 0000000..09a78f6 --- /dev/null +++ b/src/main/java/com/iemr/admin/model/stoptbteam/TeamAssignRequest.java @@ -0,0 +1,65 @@ +/* +* 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.stoptbteam; + +/** + * Puts a Stop TB user in a team (an MMU van), or takes them out of their team + * when vanID is null. A user is in at most one team per Stop TB service line. + */ +public class TeamAssignRequest { + private Integer userID; + private Integer providerServiceMapID; + private Integer vanID; + private String modifiedBy; + + public Integer getUserID() { + return userID; + } + + public void setUserID(Integer userID) { + this.userID = userID; + } + + public Integer getProviderServiceMapID() { + return providerServiceMapID; + } + + public void setProviderServiceMapID(Integer providerServiceMapID) { + this.providerServiceMapID = providerServiceMapID; + } + + public Integer getVanID() { + return vanID; + } + + public void setVanID(Integer vanID) { + this.vanID = vanID; + } + + public String getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } +} 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 0000000..d973dc2 --- /dev/null +++ b/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java @@ -0,0 +1,169 @@ +/* +* 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 are MMU vans. A user's team is stored the way AMRIT already + * links users to vans (m_userparkingplacemap -> m_uservanmapping), under the + * user's Stop TB ProviderServiceMapID, so van-based logins see the same team. + */ +@Repository +public class StopTBTeamRepository { + private static final String NOT_DELETED = "(%s IS NULL OR %s = b'0')"; + + private static String notDeleted(String column) { + return String.format(NOT_DELETED, column, column); + } + + 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 = 'MMU' 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, v.ParkingPlaceID, 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 = 'MMU'" + + " AND " + notDeleted("p.Deleted") + " AND " + notDeleted("v.Deleted"); + + private static final String SELECT_STOPTB_SERVICE = + "SELECT p.StateID, p.ServiceProviderID FROM m_providerservicemapping p" + + " JOIN m_servicemaster s ON s.ServiceID = p.ServiceID" + + " WHERE p.ProviderServiceMapID = :providerServiceMapID AND s.ServiceName = 'Stop TB'"; + + private static final String SELECT_USER_TEAM = + "SELECT uv.VanID, v.VanName FROM m_userparkingplacemap up" + + " JOIN m_uservanmapping uv ON uv.UserParkingPlaceMapID = up.UserParkingPlaceMapID" + + " JOIN m_van v ON v.VanID = uv.VanID" + + " WHERE up.UserID = :userID AND up.ProviderServiceMapID = :providerServiceMapID" + + " AND " + notDeleted("up.Deleted") + " AND " + notDeleted("uv.Deleted") + + " ORDER BY uv.UserVanMapID DESC"; + + private static final String SELECT_TEAM_MEMBERS = + "SELECT DISTINCT u.UserID, u.UserName, u.FirstName, u.LastName FROM m_userparkingplacemap up" + + " JOIN m_uservanmapping uv ON uv.UserParkingPlaceMapID = up.UserParkingPlaceMapID" + + " JOIN m_user u ON u.UserID = up.UserID" + + " WHERE uv.VanID = :vanID AND up.ProviderServiceMapID = :providerServiceMapID" + + " AND " + notDeleted("up.Deleted") + " AND " + notDeleted("uv.Deleted") + + " AND " + notDeleted("u.Deleted") + + " ORDER BY u.UserName"; + + private static final String REMOVE_USER_VANS = + "UPDATE m_uservanmapping uv" + + " JOIN m_userparkingplacemap up ON up.UserParkingPlaceMapID = uv.UserParkingPlaceMapID" + + " SET uv.Deleted = b'1', uv.ModifiedBy = :modifiedBy" + + " WHERE up.UserID = :userID AND up.ProviderServiceMapID = :providerServiceMapID" + + " AND " + notDeleted("uv.Deleted"); + + private static final String REMOVE_USER_PARKING_PLACES = + "UPDATE m_userparkingplacemap SET Deleted = b'1', ModifiedBy = :modifiedBy" + + " WHERE UserID = :userID AND ProviderServiceMapID = :providerServiceMapID" + + " AND " + notDeleted("Deleted"); + + private static final String INSERT_USER_PARKING_PLACE = + "INSERT INTO m_userparkingplacemap (UserID, ParkingPlaceID, StateID, ProviderServiceMapID, CreatedBy)" + + " VALUES (:userID, :parkingPlaceID, :stateID, :providerServiceMapID, :createdBy)"; + + private static final String INSERT_USER_VAN = + "INSERT INTO m_uservanmapping (UserParkingPlaceMapID, VanID, ProviderServiceMapID, CreatedBy)" + + " VALUES (:userParkingPlaceMapID, :vanID, :providerServiceMapID, :createdBy)"; + + @PersistenceContext + private EntityManager entityManager; + + /** {VanID, VanName, VehicalNo, ParkingPlaceID, ParkingPlaceName} of the state's MMU vans. */ + @SuppressWarnings("unchecked") + public List stateVans(Integer stateID, Integer serviceProviderID) { + return entityManager.createNativeQuery(SELECT_STATE_VANS).setParameter("stateID", stateID) + .setParameter("serviceProviderID", serviceProviderID).getResultList(); + } + + /** {VanID, ParkingPlaceID, StateID, ServiceProviderID} of an active MMU van, or null. */ + public Object[] van(Integer vanID) { + return first(entityManager.createNativeQuery(SELECT_VAN).setParameter("vanID", vanID)); + } + + /** {StateID, ServiceProviderID} when the ID is a Stop TB service line mapping, else null. */ + public Object[] stopTBService(Integer providerServiceMapID) { + return first(entityManager.createNativeQuery(SELECT_STOPTB_SERVICE) + .setParameter("providerServiceMapID", providerServiceMapID)); + } + + /** {VanID, VanName} of the user's current 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(); + } + + /** Soft-deletes every team link the user has under this Stop TB service line. */ + public void removeUserFromTeams(Integer userID, Integer providerServiceMapID, String modifiedBy) { + entityManager.createNativeQuery(REMOVE_USER_VANS).setParameter("userID", userID) + .setParameter("providerServiceMapID", providerServiceMapID).setParameter("modifiedBy", modifiedBy) + .executeUpdate(); + entityManager.createNativeQuery(REMOVE_USER_PARKING_PLACES).setParameter("userID", userID) + .setParameter("providerServiceMapID", providerServiceMapID).setParameter("modifiedBy", modifiedBy) + .executeUpdate(); + } + + /** Links the user to the van through the van's parking place. */ + public void addUserToTeam(Integer userID, Integer vanID, Integer parkingPlaceID, Integer stateID, + Integer providerServiceMapID, String createdBy) { + entityManager.createNativeQuery(INSERT_USER_PARKING_PLACE).setParameter("userID", userID) + .setParameter("parkingPlaceID", parkingPlaceID).setParameter("stateID", stateID) + .setParameter("providerServiceMapID", providerServiceMapID).setParameter("createdBy", createdBy) + .executeUpdate(); + Number userParkingPlaceMapID = (Number) entityManager.createNativeQuery("SELECT LAST_INSERT_ID()") + .getSingleResult(); + entityManager.createNativeQuery(INSERT_USER_VAN) + .setParameter("userParkingPlaceMapID", userParkingPlaceMapID.intValue()).setParameter("vanID", vanID) + .setParameter("providerServiceMapID", providerServiceMapID).setParameter("createdBy", createdBy) + .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/service/stoptbteam/StopTBTeamService.java b/src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java new file mode 100644 index 0000000..7aeed60 --- /dev/null +++ b/src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java @@ -0,0 +1,153 @@ +/* +* 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 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.stoptbteam.TeamAssignRequest; +import com.iemr.admin.repository.stoptbteam.StopTBTeamRepository; + +/** + * Stop TB team (MMU van) membership for Work Location Mapping. A team has many + * users; a user is in at most one team per Stop TB service line, so assigning a + * new team moves the user. + */ +@Service +public class StopTBTeamService { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired + private StopTBTeamRepository teamRepository; + + /** The MMU vans of the state, 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(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 current 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; + } + + /** + * Moves the user into the requested team, or out of any team when vanID is + * null. Old and new links change in one transaction. + */ + @Transactional(rollbackFor = Exception.class) + public Map assignTeam(TeamAssignRequest request) { + if (request == null || request.getUserID() == null || request.getProviderServiceMapID() == null) { + throw new IllegalArgumentException("User and service line are required"); + } + String modifiedBy = request.getModifiedBy() == null ? "" : request.getModifiedBy().trim(); + if (modifiedBy.isEmpty()) { + throw new IllegalArgumentException("Modified by is required"); + } + Object[] stopTB = teamRepository.stopTBService(request.getProviderServiceMapID()); + if (stopTB == null) { + throw new IllegalArgumentException("Teams can only be assigned on a Stop TB service line"); + } + Integer stateID = toInt(stopTB[0]); + Integer serviceProviderID = toInt(stopTB[1]); + + Object[] current = teamRepository.userTeam(request.getUserID(), request.getProviderServiceMapID()); + Integer currentVanID = current == null ? null : toInt(current[0]); + if (java.util.Objects.equals(currentVanID, request.getVanID())) { + return userTeam(request.getUserID(), request.getProviderServiceMapID()); + } + + Object[] van = null; + if (request.getVanID() != null) { + van = teamRepository.van(request.getVanID()); + if (van == null) { + throw new IllegalArgumentException("Team " + request.getVanID() + " is not an active MMU van"); + } + if (!java.util.Objects.equals(toInt(van[2]), stateID) + || !java.util.Objects.equals(toInt(van[3]), serviceProviderID)) { + throw new IllegalArgumentException("Team " + request.getVanID() + " is not in this user's state"); + } + if (van[1] == null) { + throw new IllegalArgumentException("Team " + request.getVanID() + " has no parking place"); + } + } + + teamRepository.removeUserFromTeams(request.getUserID(), request.getProviderServiceMapID(), modifiedBy); + if (van != null) { + teamRepository.addUserToTeam(request.getUserID(), request.getVanID(), toInt(van[1]), stateID, + request.getProviderServiceMapID(), modifiedBy); + } + logger.info("Stop TB team for user {} on service line {}: {} -> {}", request.getUserID(), + request.getProviderServiceMapID(), currentVanID, request.getVanID()); + return userTeam(request.getUserID(), request.getProviderServiceMapID()); + } + + private static Integer toInt(Object value) { + return value == null ? null : ((Number) value).intValue(); + } +} From eed7768742762e5db7a9af3d6cd1967fb0576e49 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Thu, 15 Oct 2026 00:34:41 +0530 Subject: [PATCH 4/4] feat(stoptb): store the team as VanID on the work location mapping (AMM-2496) The team now lives on m_userservicerolemapping.VanID (AMRIT-DB V127) instead of m_userparkingplacemap/m_uservanmapping, which belong to MMU's AMRIT parking-place setup. The future Stop TB service (Nikshay-based) reads location and van from the same mapping row. - Create/Update save vanID with the mapping. All of a user's active Stop TB rows always get the same VanID; a new role without a team takes the user's current team. Deactivating a role leaves the team as is. - A team is only accepted on a Stop TB mapping, and must be a van of that state (checked before saving). - Team vans come from stoptb.team.van-service (default MMU). - /stoptb/team/user and /members read the column; /assign is removed. - nikshay/location/userMapping also returns vanID. Requires the VanID column (AMRIT-DB V127) before deploying. Co-Authored-By: Claude Opus 5.5 --- .../EmployeeMasterController.java | 2 + .../nikshay/NikshayLocationController.java | 1 + .../stoptbteam/StopTBTeamController.java | 16 --- .../M_UserServiceRoleMapping2.java | 6 + .../model/stoptbteam/TeamAssignRequest.java | 65 ---------- .../stoptbteam/StopTBTeamRepository.java | 115 +++++++----------- .../EmployeeMasterServiceImpl.java | 29 +++++ .../service/stoptbteam/StopTBTeamService.java | 87 ++++++------- .../to/employeemaster/Previleges1097_3.java | 10 ++ 9 files changed, 126 insertions(+), 205 deletions(-) delete mode 100644 src/main/java/com/iemr/admin/model/stoptbteam/TeamAssignRequest.java 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 cfe1979..55bf70c 100644 --- a/src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java +++ b/src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java @@ -1820,6 +1820,7 @@ public String UserRoleMappings(@RequestBody String userRoleMapping, HttpServletR resDataMap1.setFacilityID(previl.getFacilityID()); resDataMap1.setNikshayTUID(previl.getNikshayTUID()); resDataMap1.setNikshayFacilityID(previl.getNikshayFacilityID()); + resDataMap1.setVanID(previl.getVanID()); resList1.add(resDataMap1); } @@ -1895,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/nikshay/NikshayLocationController.java b/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java index 81729a1..5678969 100644 --- a/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java +++ b/src/main/java/com/iemr/admin/controller/nikshay/NikshayLocationController.java @@ -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) { diff --git a/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java b/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java index 6a969d9..ff24607 100644 --- a/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java +++ b/src/main/java/com/iemr/admin/controller/stoptbteam/StopTBTeamController.java @@ -27,13 +27,10 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; 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.model.stoptbteam.TeamAssignRequest; import com.iemr.admin.service.stoptbteam.StopTBTeamService; import com.iemr.admin.utils.mapper.OutputMapper; import com.iemr.admin.utils.response.OutputResponse; @@ -90,17 +87,4 @@ public String getTeamMembers(@RequestParam("vanID") Integer vanID, } return response.toString(); } - - @Operation(summary = "Move a Stop TB user into a team, or out of their team when vanID is null") - @PostMapping(value = "/assign", produces = MediaType.APPLICATION_JSON, headers = "Authorization") - public String assignTeam(@RequestBody TeamAssignRequest request) { - OutputResponse response = new OutputResponse(); - try { - response.setResponse(OutputMapper.gsonWithoutExpose().toJson(teamService.assignTeam(request))); - } catch (Exception e) { - logger.error("Stop TB team assignment failed", e); - response.setError(e); - } - 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 e33da77..1d54fc9 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/model/stoptbteam/TeamAssignRequest.java b/src/main/java/com/iemr/admin/model/stoptbteam/TeamAssignRequest.java deleted file mode 100644 index 09a78f6..0000000 --- a/src/main/java/com/iemr/admin/model/stoptbteam/TeamAssignRequest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* -* 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.stoptbteam; - -/** - * Puts a Stop TB user in a team (an MMU van), or takes them out of their team - * when vanID is null. A user is in at most one team per Stop TB service line. - */ -public class TeamAssignRequest { - private Integer userID; - private Integer providerServiceMapID; - private Integer vanID; - private String modifiedBy; - - public Integer getUserID() { - return userID; - } - - public void setUserID(Integer userID) { - this.userID = userID; - } - - public Integer getProviderServiceMapID() { - return providerServiceMapID; - } - - public void setProviderServiceMapID(Integer providerServiceMapID) { - this.providerServiceMapID = providerServiceMapID; - } - - public Integer getVanID() { - return vanID; - } - - public void setVanID(Integer vanID) { - this.vanID = vanID; - } - - public String getModifiedBy() { - return modifiedBy; - } - - public void setModifiedBy(String modifiedBy) { - this.modifiedBy = modifiedBy; - } -} diff --git a/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java b/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java index d973dc2..f2b1371 100644 --- a/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java +++ b/src/main/java/com/iemr/admin/repository/stoptbteam/StopTBTeamRepository.java @@ -30,101 +30,88 @@ import jakarta.persistence.Query; /** - * Stop TB teams are MMU vans. A user's team is stored the way AMRIT already - * links users to vans (m_userparkingplacemap -> m_uservanmapping), under the - * user's Stop TB ProviderServiceMapID, so van-based logins see the same team. + * 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 final String NOT_DELETED = "(%s IS NULL OR %s = b'0')"; - private static String notDeleted(String column) { - return String.format(NOT_DELETED, column, 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 = 'MMU' AND p.StateID = :stateID" + + " 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, v.ParkingPlaceID, p.StateID, p.ServiceProviderID" - + " FROM m_van v" + "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 = 'MMU'" + + " 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" - + " JOIN m_servicemaster s ON s.ServiceID = p.ServiceID" - + " WHERE p.ProviderServiceMapID = :providerServiceMapID AND s.ServiceName = 'Stop TB'"; + + " WHERE p.ProviderServiceMapID = :providerServiceMapID" + + " AND p.ProviderServiceMapID IN (" + STOPTB_SERVICE_LINES + ")"; private static final String SELECT_USER_TEAM = - "SELECT uv.VanID, v.VanName FROM m_userparkingplacemap up" - + " JOIN m_uservanmapping uv ON uv.UserParkingPlaceMapID = up.UserParkingPlaceMapID" - + " JOIN m_van v ON v.VanID = uv.VanID" - + " WHERE up.UserID = :userID AND up.ProviderServiceMapID = :providerServiceMapID" - + " AND " + notDeleted("up.Deleted") + " AND " + notDeleted("uv.Deleted") - + " ORDER BY uv.UserVanMapID DESC"; + "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_userparkingplacemap up" - + " JOIN m_uservanmapping uv ON uv.UserParkingPlaceMapID = up.UserParkingPlaceMapID" - + " JOIN m_user u ON u.UserID = up.UserID" - + " WHERE uv.VanID = :vanID AND up.ProviderServiceMapID = :providerServiceMapID" - + " AND " + notDeleted("up.Deleted") + " AND " + notDeleted("uv.Deleted") - + " AND " + notDeleted("u.Deleted") + "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"; - private static final String REMOVE_USER_VANS = - "UPDATE m_uservanmapping uv" - + " JOIN m_userparkingplacemap up ON up.UserParkingPlaceMapID = uv.UserParkingPlaceMapID" - + " SET uv.Deleted = b'1', uv.ModifiedBy = :modifiedBy" - + " WHERE up.UserID = :userID AND up.ProviderServiceMapID = :providerServiceMapID" - + " AND " + notDeleted("uv.Deleted"); - - private static final String REMOVE_USER_PARKING_PLACES = - "UPDATE m_userparkingplacemap SET Deleted = b'1', ModifiedBy = :modifiedBy" + // 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"); - - private static final String INSERT_USER_PARKING_PLACE = - "INSERT INTO m_userparkingplacemap (UserID, ParkingPlaceID, StateID, ProviderServiceMapID, CreatedBy)" - + " VALUES (:userID, :parkingPlaceID, :stateID, :providerServiceMapID, :createdBy)"; - - private static final String INSERT_USER_VAN = - "INSERT INTO m_uservanmapping (UserParkingPlaceMapID, VanID, ProviderServiceMapID, CreatedBy)" - + " VALUES (:userParkingPlaceMapID, :vanID, :providerServiceMapID, :createdBy)"; + + " AND " + notDeleted("Deleted") + + " AND ProviderServiceMapID IN (" + STOPTB_SERVICE_LINES + ")"; @PersistenceContext private EntityManager entityManager; - /** {VanID, VanName, VehicalNo, ParkingPlaceID, ParkingPlaceName} of the state's MMU vans. */ + /** {VanID, VanName, VehicalNo, ParkingPlaceID, ParkingPlaceName} of the state's team vans. */ @SuppressWarnings("unchecked") - public List stateVans(Integer stateID, Integer serviceProviderID) { - return entityManager.createNativeQuery(SELECT_STATE_VANS).setParameter("stateID", stateID) - .setParameter("serviceProviderID", serviceProviderID).getResultList(); + 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, ParkingPlaceID, StateID, ServiceProviderID} of an active MMU van, or null. */ - public Object[] van(Integer vanID) { - return first(entityManager.createNativeQuery(SELECT_VAN).setParameter("vanID", vanID)); + /** {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 mapping, else null. */ + /** {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 current team, or null. */ + /** {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)); @@ -137,28 +124,10 @@ public List teamMembers(Integer vanID, Integer providerServiceMapID) { .setParameter("providerServiceMapID", providerServiceMapID).getResultList(); } - /** Soft-deletes every team link the user has under this Stop TB service line. */ - public void removeUserFromTeams(Integer userID, Integer providerServiceMapID, String modifiedBy) { - entityManager.createNativeQuery(REMOVE_USER_VANS).setParameter("userID", userID) - .setParameter("providerServiceMapID", providerServiceMapID).setParameter("modifiedBy", modifiedBy) - .executeUpdate(); - entityManager.createNativeQuery(REMOVE_USER_PARKING_PLACES).setParameter("userID", userID) - .setParameter("providerServiceMapID", providerServiceMapID).setParameter("modifiedBy", modifiedBy) - .executeUpdate(); - } - - /** Links the user to the van through the van's parking place. */ - public void addUserToTeam(Integer userID, Integer vanID, Integer parkingPlaceID, Integer stateID, - Integer providerServiceMapID, String createdBy) { - entityManager.createNativeQuery(INSERT_USER_PARKING_PLACE).setParameter("userID", userID) - .setParameter("parkingPlaceID", parkingPlaceID).setParameter("stateID", stateID) - .setParameter("providerServiceMapID", providerServiceMapID).setParameter("createdBy", createdBy) - .executeUpdate(); - Number userParkingPlaceMapID = (Number) entityManager.createNativeQuery("SELECT LAST_INSERT_ID()") - .getSingleResult(); - entityManager.createNativeQuery(INSERT_USER_VAN) - .setParameter("userParkingPlaceMapID", userParkingPlaceMapID.intValue()).setParameter("vanID", vanID) - .setParameter("providerServiceMapID", providerServiceMapID).setParameter("createdBy", createdBy) + /** 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(); } 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 16229ca..365b315 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; } @@ -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 index 7aeed60..07c4a99 100644 --- a/src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java +++ b/src/main/java/com/iemr/admin/service/stoptbteam/StopTBTeamService.java @@ -25,35 +25,37 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; 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.model.stoptbteam.TeamAssignRequest; import com.iemr.admin.repository.stoptbteam.StopTBTeamRepository; /** - * Stop TB team (MMU van) membership for Work Location Mapping. A team has many - * users; a user is in at most one team per Stop TB service line, so assigning a - * new team moves the user. + * 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 { - private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); - @Autowired private StopTBTeamRepository teamRepository; - /** The MMU vans of the state, for the Team dropdown. */ + // 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(stateID, serviceProviderID)) { + for (Object[] row : teamRepository.stateVans(vanService, stateID, serviceProviderID)) { Map team = new LinkedHashMap<>(); team.put("vanID", toInt(row[0])); team.put("vanName", row[1]); @@ -65,7 +67,7 @@ public List> stateTeams(Integer stateID, Integer serviceProv return teams; } - /** The user's current team ({vanID, vanName}), or an empty map. */ + /** 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"); @@ -97,54 +99,37 @@ public List> teamMembers(Integer vanID, Integer providerServ } /** - * Moves the user into the requested team, or out of any team when vanID is - * null. Old and new links change in one transaction. + * 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. */ - @Transactional(rollbackFor = Exception.class) - public Map assignTeam(TeamAssignRequest request) { - if (request == null || request.getUserID() == null || request.getProviderServiceMapID() == null) { - throw new IllegalArgumentException("User and service line are required"); - } - String modifiedBy = request.getModifiedBy() == null ? "" : request.getModifiedBy().trim(); - if (modifiedBy.isEmpty()) { - throw new IllegalArgumentException("Modified by is required"); + public void validateTeam(Integer providerServiceMapID, Integer vanID) { + if (vanID == null) { + return; } - Object[] stopTB = teamRepository.stopTBService(request.getProviderServiceMapID()); + Object[] stopTB = providerServiceMapID == null ? null : teamRepository.stopTBService(providerServiceMapID); if (stopTB == null) { - throw new IllegalArgumentException("Teams can only be assigned on a Stop TB service line"); + throw new IllegalArgumentException("A team can only be set on a Stop TB mapping"); } - Integer stateID = toInt(stopTB[0]); - Integer serviceProviderID = toInt(stopTB[1]); - - Object[] current = teamRepository.userTeam(request.getUserID(), request.getProviderServiceMapID()); - Integer currentVanID = current == null ? null : toInt(current[0]); - if (java.util.Objects.equals(currentVanID, request.getVanID())) { - return userTeam(request.getUserID(), request.getProviderServiceMapID()); + Object[] van = teamRepository.van(vanService, vanID); + if (van == null) { + throw new IllegalArgumentException("Team " + vanID + " is not an active " + vanService + " van"); } - - Object[] van = null; - if (request.getVanID() != null) { - van = teamRepository.van(request.getVanID()); - if (van == null) { - throw new IllegalArgumentException("Team " + request.getVanID() + " is not an active MMU van"); - } - if (!java.util.Objects.equals(toInt(van[2]), stateID) - || !java.util.Objects.equals(toInt(van[3]), serviceProviderID)) { - throw new IllegalArgumentException("Team " + request.getVanID() + " is not in this user's state"); - } - if (van[1] == null) { - throw new IllegalArgumentException("Team " + request.getVanID() + " has no parking place"); - } + 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"); } + } - teamRepository.removeUserFromTeams(request.getUserID(), request.getProviderServiceMapID(), modifiedBy); - if (van != null) { - teamRepository.addUserToTeam(request.getUserID(), request.getVanID(), toInt(van[1]), stateID, - request.getProviderServiceMapID(), modifiedBy); + /** + * 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; } - logger.info("Stop TB team for user {} on service line {}: {} -> {}", request.getUserID(), - request.getProviderServiceMapID(), currentVanID, request.getVanID()); - return userTeam(request.getUserID(), request.getProviderServiceMapID()); + teamRepository.setUserTeam(userID, providerServiceMapID, vanID); } private static Integer toInt(Object value) { 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 4c6a757..095cbcc 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;