Skip to content

Commit c701ce9

Browse files
vishwab1claude
andauthored
fix: facility hierarchy and facility type management (#125)
* fix:changed the pom xml * fix: added facilty type master change * feat: created facility creation * feat:added work location * feat:added work location * fix: rabiit review fix * fix: rabiit review fix * fix: rabiit review fix * fix: ui chnges * fix: pom version * fix: corrections * fix: facilty hierachy * fix: facility heirachy * fix: remove logs folder from repository Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add logs/ to .gitignore Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 68e4130 commit c701ce9

28 files changed

Lines changed: 1323 additions & 228 deletions

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,7 @@ mvnw.cmd
4040
# Properties
4141
src/main/environment/admin_local.properties
4242

43-
node_modules
43+
node_modules
44+
45+
# Logs
46+
logs/

logs/admin-api.log.json

Lines changed: 0 additions & 163 deletions
This file was deleted.
-3.96 KB
Binary file not shown.
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
* AMRIT – Accessible Medical Records via Integrated Technology
3+
* Integrated EHR (Electronic Health Records) Solution
4+
*
5+
* Copyright (C) "Piramal Swasthya Management and Research Institute"
6+
*
7+
* This file is part of AMRIT.
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program. If not, see https://www.gnu.org/licenses/.
21+
*/
22+
package com.iemr.admin.controller.employeemaster;
23+
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.List;
27+
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.web.bind.annotation.RequestBody;
32+
import org.springframework.web.bind.annotation.RequestMapping;
33+
import org.springframework.web.bind.annotation.RequestMethod;
34+
import org.springframework.web.bind.annotation.RestController;
35+
36+
import com.iemr.admin.data.employeemaster.AshaSupervisorMapping;
37+
import com.iemr.admin.data.employeemaster.M_UserServiceRoleMapping2;
38+
import com.iemr.admin.data.store.M_Facility;
39+
import com.iemr.admin.repo.employeemaster.EmployeeMasterRepo;
40+
import com.iemr.admin.repository.store.MainStoreRepo;
41+
import com.iemr.admin.service.employeemaster.AshaSupervisorMappingService;
42+
import com.iemr.admin.utils.mapper.InputMapper;
43+
import com.iemr.admin.utils.response.OutputResponse;
44+
45+
import io.swagger.v3.oas.annotations.Operation;
46+
47+
@RestController
48+
public class AshaSupervisorMappingController {
49+
50+
private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
51+
52+
@Autowired
53+
private AshaSupervisorMappingService ashaSupervisorMappingService;
54+
55+
@Autowired
56+
private EmployeeMasterRepo employeeMasterRepo;
57+
58+
@Autowired
59+
private MainStoreRepo mainStoreRepo;
60+
61+
@Operation(summary = "Get ASHA users by facility IDs")
62+
@RequestMapping(value = "/userFacilityMapping/getAshasByFacility", headers = "Authorization", method = {
63+
RequestMethod.POST }, produces = { "application/json" })
64+
public String getAshasByFacility(@RequestBody String request) {
65+
OutputResponse response = new OutputResponse();
66+
try {
67+
AshaSupervisorMapping reqObj = InputMapper.gson().fromJson(request, AshaSupervisorMapping.class);
68+
List<Integer> facilityIDs = reqObj.getFacilityIDs();
69+
if (facilityIDs == null || facilityIDs.isEmpty()) {
70+
if (reqObj.getFacilityID() != null) {
71+
facilityIDs = Arrays.asList(reqObj.getFacilityID());
72+
}
73+
}
74+
ArrayList<M_UserServiceRoleMapping2> ashaUsers = ashaSupervisorMappingService
75+
.getAshasByFacility(facilityIDs);
76+
response.setResponse(ashaUsers.toString());
77+
} catch (Exception e) {
78+
logger.error("Unexpected error:", e);
79+
response.setError(e);
80+
}
81+
return response.toString();
82+
}
83+
84+
@Operation(summary = "Save ASHA supervisor mappings")
85+
@RequestMapping(value = "/userFacilityMapping/ashaSupervisorMapping/save", headers = "Authorization", method = {
86+
RequestMethod.POST }, produces = { "application/json" })
87+
public String saveAshaSupervisorMapping(@RequestBody String request) {
88+
OutputResponse response = new OutputResponse();
89+
try {
90+
AshaSupervisorMapping[] reqArray = InputMapper.gson().fromJson(request, AshaSupervisorMapping[].class);
91+
List<AshaSupervisorMapping> mappings = Arrays.asList(reqArray);
92+
ArrayList<AshaSupervisorMapping> savedMappings = ashaSupervisorMappingService
93+
.saveAshaSupervisorMappings(mappings);
94+
response.setResponse(savedMappings.toString());
95+
} catch (Exception e) {
96+
logger.error("Unexpected error:", e);
97+
response.setError(e);
98+
}
99+
return response.toString();
100+
}
101+
102+
@Operation(summary = "Get supervisor mappings by facility ID")
103+
@RequestMapping(value = "/userFacilityMapping/ashaSupervisorMapping/getByFacility", headers = "Authorization", method = {
104+
RequestMethod.POST }, produces = { "application/json" })
105+
public String getSupervisorMappingByFacility(@RequestBody String request) {
106+
OutputResponse response = new OutputResponse();
107+
try {
108+
AshaSupervisorMapping reqObj = InputMapper.gson().fromJson(request, AshaSupervisorMapping.class);
109+
ArrayList<AshaSupervisorMapping> mappings = ashaSupervisorMappingService
110+
.getSupervisorMappingByFacility(reqObj.getFacilityID());
111+
response.setResponse(mappings.toString());
112+
} catch (Exception e) {
113+
logger.error("Unexpected error:", e);
114+
response.setError(e);
115+
}
116+
return response.toString();
117+
}
118+
119+
@Operation(summary = "Delete ASHA supervisor mappings by supervisor and facility IDs")
120+
@RequestMapping(value = "/userFacilityMapping/ashaSupervisorMapping/delete", headers = "Authorization", method = {
121+
RequestMethod.POST }, produces = { "application/json" })
122+
public String deleteAshaSupervisorMapping(@RequestBody String request) {
123+
OutputResponse response = new OutputResponse();
124+
try {
125+
AshaSupervisorMapping reqObj = InputMapper.gson().fromJson(request, AshaSupervisorMapping.class);
126+
Integer supervisorUserID = reqObj.getSupervisorUserID();
127+
List<Integer> facilityIDs = reqObj.getFacilityIDs();
128+
if (supervisorUserID != null && facilityIDs != null && !facilityIDs.isEmpty()) {
129+
ashaSupervisorMappingService.deleteBySupervisorAndFacilities(supervisorUserID, facilityIDs, "Admin");
130+
response.setResponse("Deleted successfully");
131+
} else {
132+
response.setError(5000, "supervisorUserID and facilityIDs are required");
133+
}
134+
} catch (Exception e) {
135+
logger.error("Unexpected error:", e);
136+
response.setError(e);
137+
}
138+
return response.toString();
139+
}
140+
141+
@Operation(summary = "Atomically delete old and save new ASHA supervisor mappings (Fix 7)")
142+
@RequestMapping(value = "/userFacilityMapping/ashaSupervisorMapping/updateAtomically", headers = "Authorization", method = {
143+
RequestMethod.POST }, produces = { "application/json" })
144+
public String updateAshaSupervisorMappingAtomically(@RequestBody String request) {
145+
OutputResponse response = new OutputResponse();
146+
try {
147+
com.google.gson.JsonObject reqObj = InputMapper.gson().fromJson(request, com.google.gson.JsonObject.class);
148+
Integer supervisorUserID = reqObj.get("supervisorUserID").getAsInt();
149+
String modifiedBy = reqObj.has("modifiedBy") ? reqObj.get("modifiedBy").getAsString() : "Admin";
150+
List<Integer> facilityIDs = new ArrayList<>();
151+
if (reqObj.has("facilityIDs")) {
152+
for (com.google.gson.JsonElement el : reqObj.getAsJsonArray("facilityIDs")) {
153+
facilityIDs.add(el.getAsInt());
154+
}
155+
}
156+
List<AshaSupervisorMapping> newMappings = new ArrayList<>();
157+
if (reqObj.has("newMappings")) {
158+
AshaSupervisorMapping[] arr = InputMapper.gson().fromJson(
159+
reqObj.getAsJsonArray("newMappings").toString(), AshaSupervisorMapping[].class);
160+
newMappings = Arrays.asList(arr);
161+
}
162+
ArrayList<AshaSupervisorMapping> saved = ashaSupervisorMappingService
163+
.updateAshaMappingsAtomically(supervisorUserID, facilityIDs, newMappings, modifiedBy);
164+
response.setResponse(saved.toString());
165+
} catch (Exception e) {
166+
logger.error("Unexpected error:", e);
167+
response.setError(e);
168+
}
169+
return response.toString();
170+
}
171+
172+
@Operation(summary = "Restore soft-deleted ASHA supervisor mappings by IDs")
173+
@RequestMapping(value = "/userFacilityMapping/ashaSupervisorMapping/restore", headers = "Authorization", method = {
174+
RequestMethod.POST }, produces = { "application/json" })
175+
public String restoreAshaSupervisorMapping(@RequestBody String request) {
176+
OutputResponse response = new OutputResponse();
177+
try {
178+
AshaSupervisorMapping reqObj = InputMapper.gson().fromJson(request, AshaSupervisorMapping.class);
179+
List<Long> ids = reqObj.getIds();
180+
if (ids != null && !ids.isEmpty()) {
181+
ashaSupervisorMappingService.restoreMappings(ids, "Admin");
182+
response.setResponse("Restored successfully");
183+
} else {
184+
response.setError(5000, "ids are required");
185+
}
186+
} catch (Exception e) {
187+
logger.error("Unexpected error:", e);
188+
response.setError(e);
189+
}
190+
return response.toString();
191+
}
192+
193+
@Operation(summary = "Get facility details by USR mapping ID")
194+
@RequestMapping(value = "/userFacilityMapping/getFacilityByMappingID", headers = "Authorization", method = {
195+
RequestMethod.POST }, produces = { "application/json" })
196+
public String getFacilityByMappingID(@RequestBody String request) {
197+
OutputResponse response = new OutputResponse();
198+
try {
199+
M_UserServiceRoleMapping2 reqObj = InputMapper.gson().fromJson(request, M_UserServiceRoleMapping2.class);
200+
Integer mappingID = reqObj.getuSRMappingID();
201+
if (mappingID != null) {
202+
M_UserServiceRoleMapping2 mapping = employeeMasterRepo.findById(mappingID).orElse(null);
203+
// Skip if mapping is soft-deleted or has no facility
204+
if (mapping != null && Boolean.TRUE.equals(mapping.getDeleted())) {
205+
mapping = null;
206+
}
207+
if (mapping != null && mapping.getFacilityID() != null) {
208+
// Use deleted filter to exclude soft-deleted facilities
209+
M_Facility facility = mainStoreRepo.findByFacilityIDAndDeleted(mapping.getFacilityID(), false);
210+
StringBuilder json = new StringBuilder("{");
211+
json.append("\"facilityID\": ").append(mapping.getFacilityID());
212+
if (facility != null) {
213+
json.append(", \"facilityName\": \"").append(facility.getFacilityName() != null ? facility.getFacilityName() : "").append("\"");
214+
json.append(", \"facilityTypeID\": ").append(facility.getFacilityTypeID());
215+
json.append(", \"ruralUrban\": \"").append(facility.getRuralUrban() != null ? facility.getRuralUrban() : "").append("\"");
216+
} else {
217+
// Facility was deleted — return null so frontend knows
218+
json = new StringBuilder("{\"facilityID\": null, \"facilityDeleted\": true");
219+
}
220+
json.append("}");
221+
response.setResponse(json.toString());
222+
} else {
223+
response.setResponse("{\"facilityID\": null}");
224+
}
225+
} else {
226+
response.setResponse("{\"facilityID\": null}");
227+
}
228+
} catch (Exception e) {
229+
logger.error("Unexpected error:", e);
230+
response.setError(e);
231+
}
232+
return response.toString();
233+
}
234+
}

src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,12 +1804,15 @@ public String UserRoleMappings(@RequestBody String userRoleMapping, HttpServletR
18041804
resDataMap1.setUserID(employeeMaster.get(x).getUserID());
18051805
resDataMap1.setProviderServiceMapID(previl.getProviderServiceMapID());
18061806
resDataMap1.setWorkingLocationID(previl.getWorkingLocationID());
1807+
resDataMap1.setStateID(previl.getStateID());
1808+
resDataMap1.setDistrictID(previl.getDistrictID());
18071809
resDataMap1.setCreatedBy(employeeMaster.get(x).getCreatedBy());
18081810
resDataMap1.setServiceProviderID(employeeMaster.get(x).getServiceProviderID());
18091811
resDataMap1.setBlockID(previl.getBlockID());
18101812
resDataMap1.setBlockName(previl.getBlockName());
18111813
resDataMap1.setVillageID(previl.getVillageID());
18121814
resDataMap1.setVillageName(previl.getVillageName());
1815+
resDataMap1.setFacilityID(previl.getFacilityID());
18131816
resList1.add(resDataMap1);
18141817

18151818
}
@@ -1845,16 +1848,32 @@ public String updateUserRoleMapping(@RequestBody String updateUserRoleMapping, H
18451848

18461849
M_UserServiceRoleMapping2 usrRole = employeeMasterInter.getDataUsrId(pre.getuSRMappingID());
18471850

1851+
// Fix 1/3: cascade asha_supervisor_mapping BEFORE modifying entity to avoid JPA L1 cache issue
1852+
if (usrRole != null && usrRole.getUserID() != null) {
1853+
boolean roleChanged = pre.getRoleID() != null && usrRole.getRoleID() != null
1854+
&& !usrRole.getRoleID().equals(pre.getRoleID());
1855+
boolean facilityChanged = usrRole.getFacilityID() != null && pre.getFacilityID() != null
1856+
&& !usrRole.getFacilityID().equals(pre.getFacilityID());
1857+
if (roleChanged || facilityChanged) {
1858+
logger.info("Fix1/3: cascading asha_supervisor_mapping for userID={}, roleChanged={}, facilityChanged={}",
1859+
usrRole.getUserID(), roleChanged, facilityChanged);
1860+
employeeMasterInter.cascadeDeleteAshaMappingsForUser(usrRole.getUserID());
1861+
}
1862+
}
1863+
18481864
usrRole.setUserID(pre.getUserID());
18491865
usrRole.setRoleID(pre.getRoleID());
18501866
usrRole.setAgentPassword(pre.getAgentPassword());
18511867
usrRole.setProviderServiceMapID(pre.getProviderServiceMapID());
18521868
usrRole.setWorkingLocationID(pre.getWorkingLocationID());
1869+
usrRole.setStateID(pre.getStateID());
1870+
usrRole.setDistrictID(pre.getDistrictID());
18531871
usrRole.setModifiedBy(pre.getModifiedBy());
18541872
usrRole.setBlockID(pre.getBlockID());
18551873
usrRole.setBlockName(pre.getBlockName());
18561874
usrRole.setVillageID(pre.getVillageID());
18571875
usrRole.setVillageName(pre.getVillageName());
1876+
usrRole.setFacilityID(pre.getFacilityID());
18581877

18591878
if (pre.getTeleConsultation() != null) {
18601879
usrRole.setTeleConsultation(pre.getTeleConsultation());
@@ -1897,6 +1916,17 @@ public String deleteUserRoleMapping(@RequestBody String deletedUserRoleMapping,
18971916

18981917
M_UserServiceRoleMapping2 usrRole = employeeMasterInter.getDataUsrId(pre.getuSRMappingID());
18991918

1919+
// Fix 2: cascade asha_supervisor_mapping BEFORE setDeleted() to avoid JPA L1 cache issue.
1920+
// After setDeleted(true), findById() in saveRoleMappingeditedData hits the L1 cache
1921+
// returning the already-modified entity, so the "old vs new deleted" check always fails.
1922+
// For ASHA Supervisor with multiple facilities: only delete mappings for this facilityID
1923+
if (Boolean.TRUE.equals(pre.getDeleted()) && !Boolean.TRUE.equals(usrRole.getDeleted())
1924+
&& usrRole.getUserID() != null) {
1925+
logger.info("Fix2: cascading asha_supervisor_mapping soft-delete for userID={}, uSRMappingID={}",
1926+
usrRole.getUserID(), pre.getuSRMappingID());
1927+
employeeMasterInter.cascadeDeleteAshaMappingsForDeactivation(usrRole);
1928+
}
1929+
19001930
usrRole.setDeleted(pre.getDeleted());
19011931

19021932
M_UserServiceRoleMapping2 savedata = employeeMasterInter.saveRoleMappingeditedData(usrRole,

src/main/java/com/iemr/admin/controller/facilitytype/FacilitytypeController.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
3535
import org.springframework.web.bind.annotation.RestController;
3636

3737
import com.iemr.admin.data.facilitytype.M_facilitytype;
38+
import com.iemr.admin.data.store.M_Facility;
3839
import com.iemr.admin.data.store.M_FacilityLevel;
40+
import com.iemr.admin.repository.store.MainStoreRepo;
3941
import com.iemr.admin.service.facilitytype.M_facilitytypeInter;
4042
import com.iemr.admin.utils.mapper.InputMapper;
4143
import com.iemr.admin.utils.response.OutputResponse;
@@ -49,6 +51,9 @@ public class FacilitytypeController {
4951
@Autowired
5052
private M_facilitytypeInter m_facilitytypeInter;
5153

54+
@Autowired
55+
private MainStoreRepo mainStoreRepo;
56+
5257
@Operation(summary = "Get facility")
5358
@RequestMapping(value = "/getFacility", headers = "Authorization", method = { RequestMethod.POST }, produces = {
5459
"application/json" })
@@ -115,7 +120,15 @@ public String editFacility(@RequestBody String editFacility) {
115120
M_facilitytype allFacilityData = m_facilitytypeInter
116121
.editAllFicilityData(facilityDetails.getFacilityTypeID());
117122

118-
allFacilityData.setFacilityTypeDesc(facilityDetails.getFacilityTypeDesc());
123+
if (facilityDetails.getFacilityTypeName() != null) {
124+
allFacilityData.setFacilityTypeName(facilityDetails.getFacilityTypeName());
125+
}
126+
if (facilityDetails.getRuralUrban() != null) {
127+
allFacilityData.setRuralUrban(facilityDetails.getRuralUrban());
128+
}
129+
if (facilityDetails.getFacilityTypeDesc() != null) {
130+
allFacilityData.setFacilityTypeDesc(facilityDetails.getFacilityTypeDesc());
131+
}
119132
allFacilityData.setModifiedBy(facilityDetails.getModifiedBy());
120133

121134
M_facilitytype saveFacilityData = m_facilitytypeInter.updateFacilityData(allFacilityData);
@@ -145,6 +158,16 @@ public String deleteFacility(@RequestBody String deleteFacility) {
145158

146159
M_facilitytype allFacilityData = m_facilitytypeInter
147160
.editAllFicilityData(facilityDetails.getFacilityTypeID());
161+
162+
// Block deactivation if facility type is in use by active facilities
163+
if (Boolean.TRUE.equals(facilityDetails.getDeleted())) {
164+
List<M_Facility> activeFacilities = mainStoreRepo
165+
.findByFacilityTypeIDAndDeletedFalse(facilityDetails.getFacilityTypeID());
166+
if (activeFacilities != null && !activeFacilities.isEmpty()) {
167+
throw new Exception("Cannot deactivate: facility type is in use by " + activeFacilities.size() + " active facilities");
168+
}
169+
}
170+
148171
allFacilityData.setDeleted(facilityDetails.getDeleted());
149172

150173
M_facilitytype saveFacilityData = m_facilitytypeInter.updateFacilityData(allFacilityData);

0 commit comments

Comments
 (0)