From 601c62f7a8a79db9d3bd15297ed682d06a500f04 Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 13 Sep 2024 11:49:55 +0300 Subject: [PATCH] LocationHierarchy Inventory Filter fixes #81 --- .../fhir/gateway/plugins/Constants.java | 3 + .../LocationHierarchyEndpointHelper.java | 84 +++++++++++++++---- .../PractitionerDetailsEndpointHelper.java | 3 +- .../LocationHierarchyEndpointHelperTest.java | 64 +++++++++++++- 4 files changed, 135 insertions(+), 19 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java index 6d07594..7e1e074 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java @@ -6,6 +6,7 @@ public class Constants { "https://smartregister.org/care-team-tag-id"; public static final String CODE_URL_VALUE_SEPARATOR = "|"; public static final String EMPTY_STRING = ""; + public static final String FORWARD_SLASH = "/"; public static final String LOCATION_TAG_URL_ENV = "LOCATION_TAG_URL"; public static final String DEFAULT_LOCATION_TAG_URL = "https://smartregister.org/location-tag-id"; @@ -23,6 +24,7 @@ public class Constants { public static final String IDENTIFIER = "_id"; public static final String MIN_ADMIN_LEVEL = "administrativeLevelMin"; public static final String MAX_ADMIN_LEVEL = "administrativeLevelMax"; + public static final String FILTER_INVENTORY = "filterInventory"; public static final int DEFAULT_MAX_ADMIN_LEVEL = 10; public static final int DEFAULT_MIN_ADMIN_LEVEL = 0; public static final String PAGINATION_PAGE_SIZE = "_count"; @@ -38,6 +40,7 @@ public class Constants { public static final String ROLE_WEB_CLIENT = "WEB_CLIENT"; public static final String MODE = "mode"; public static final String LIST = "list"; + public static final String SUBJECT = "subject"; public static final String CORS_ALLOW_HEADERS_KEY = "Access-Control-Allow-Headers"; public static final String CORS_ALLOW_HEADERS_VALUE = "authorization, cache-control"; public static final String CORS_ALLOW_METHODS_KEY = "Access-Control-Allow-Methods"; diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index a97787f..df8d394 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -19,6 +19,7 @@ import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.ListResource; import org.hl7.fhir.r4.model.Location; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; @@ -57,12 +58,14 @@ private IGenericClient getFhirClientForR4() { public LocationHierarchy getLocationHierarchy( String locationId, List preFetchAdminLevels, - List postFetchAdminLevels) { + List postFetchAdminLevels, + Boolean filterInventory) { LocationHierarchy locationHierarchy; if (CacheHelper.INSTANCE.skipCache()) { locationHierarchy = - getLocationHierarchyCore(locationId, preFetchAdminLevels, postFetchAdminLevels); + getLocationHierarchyCore( + locationId, preFetchAdminLevels, postFetchAdminLevels, filterInventory); } else { locationHierarchy = (LocationHierarchy) @@ -72,7 +75,8 @@ public LocationHierarchy getLocationHierarchy( getLocationHierarchyCore( locationId, preFetchAdminLevels, - postFetchAdminLevels)); + postFetchAdminLevels, + filterInventory)); } return locationHierarchy; } @@ -80,19 +84,24 @@ public LocationHierarchy getLocationHierarchy( public List getLocationHierarchies( List locationIds, List preFetchAdminLevels, - List postFetchAdminLevels) { + List postFetchAdminLevels, + Boolean filterInventory) { return locationIds.parallelStream() .map( locationId -> getLocationHierarchy( - locationId, preFetchAdminLevels, postFetchAdminLevels)) + locationId, + preFetchAdminLevels, + postFetchAdminLevels, + filterInventory)) .collect(Collectors.toList()); } public LocationHierarchy getLocationHierarchyCore( String locationId, List preFetchAdminLevels, - List postFetchAdminLevels) { + List postFetchAdminLevels, + Boolean filterInventory) { Location location = getLocationById(locationId); LocationHierarchyTree locationHierarchyTree = new LocationHierarchyTree(); @@ -100,10 +109,12 @@ public LocationHierarchy getLocationHierarchyCore( if (location != null) { logger.info("Building Location Hierarchy of Location Id : {}", locationId); + List descendants = getDescendants(locationId, location, preFetchAdminLevels); + if (filterInventory) { + descendants = filterLocationsByInventory(descendants); + } locationHierarchyTree.buildTreeFromList( - filterLocationsByAdminLevels( - getDescendants(locationId, location, preFetchAdminLevels), - postFetchAdminLevels)); + filterLocationsByAdminLevels(descendants, postFetchAdminLevels)); StringType locationIdString = new StringType().setId(locationId).getIdElement(); locationHierarchy.setLocationId(locationIdString); locationHierarchy.setId(LOCATION_RESOURCE + locationId); @@ -120,7 +131,8 @@ public List getLocationHierarchyLocations( String locationId, Location parentLocation, List preFetchAdminLevels, - List postFetchAdminLevels) { + List postFetchAdminLevels, + Boolean filterInventory) { List descendants; if (CacheHelper.INSTANCE.skipCache()) { @@ -131,6 +143,9 @@ public List getLocationHierarchyLocations( locationId, key -> getDescendants(locationId, parentLocation, preFetchAdminLevels)); } + if (filterInventory) { + descendants = filterLocationsByInventory(descendants); + } return filterLocationsByAdminLevels(descendants, postFetchAdminLevels); } @@ -199,18 +214,20 @@ public List getDescendants( public Bundle handleIdentifierRequest(HttpServletRequest request, String identifier) { String administrativeLevelMin = request.getParameter(Constants.MIN_ADMIN_LEVEL); String administrativeLevelMax = request.getParameter(Constants.MAX_ADMIN_LEVEL); + String mode = request.getParameter(Constants.MODE); + Boolean filterInventory = Boolean.valueOf(request.getParameter(Constants.FILTER_INVENTORY)); List preFetchAdminLevels = generateAdminLevels( String.valueOf(Constants.DEFAULT_MIN_ADMIN_LEVEL), administrativeLevelMax); List postFetchAdminLevels = generateAdminLevels(administrativeLevelMin, administrativeLevelMax); - String mode = request.getParameter(Constants.MODE); if (Constants.LIST.equals(mode)) { List locationIds = Collections.singletonList(identifier); return getPaginatedLocations(request, locationIds); } else { LocationHierarchy locationHierarchy = - getLocationHierarchy(identifier, preFetchAdminLevels, postFetchAdminLevels); + getLocationHierarchy( + identifier, preFetchAdminLevels, postFetchAdminLevels, filterInventory); return Utils.createBundle(Collections.singletonList(locationHierarchy)); } } @@ -223,6 +240,7 @@ public Bundle handleNonIdentifierRequest( String syncLocationsParam = request.getParameter(Constants.SYNC_LOCATIONS_SEARCH_PARAM); String administrativeLevelMin = request.getParameter(Constants.MIN_ADMIN_LEVEL); String administrativeLevelMax = request.getParameter(Constants.MAX_ADMIN_LEVEL); + Boolean filterInventory = Boolean.valueOf(request.getParameter(Constants.FILTER_INVENTORY)); List preFetchAdminLevels = generateAdminLevels( String.valueOf(Constants.DEFAULT_MIN_ADMIN_LEVEL), administrativeLevelMax); @@ -259,7 +277,10 @@ public Bundle handleNonIdentifierRequest( && !selectedSyncLocations.isEmpty()) { List locationHierarchies = getLocationHierarchies( - selectedSyncLocations, preFetchAdminLevels, postFetchAdminLevels); + selectedSyncLocations, + preFetchAdminLevels, + postFetchAdminLevels, + filterInventory); List resourceList = locationHierarchies.stream() .map(locationHierarchy -> (Resource) locationHierarchy) @@ -330,6 +351,7 @@ public Bundle getPaginatedLocations(HttpServletRequest request, List loc String pageNumber = request.getParameter(Constants.PAGINATION_PAGE_NUMBER); String administrativeLevelMin = request.getParameter(Constants.MIN_ADMIN_LEVEL); String administrativeLevelMax = request.getParameter(Constants.MAX_ADMIN_LEVEL); + Boolean filterInventory = Boolean.valueOf(request.getParameter(Constants.FILTER_INVENTORY)); List preFetchAdminLevels = generateAdminLevels( String.valueOf(Constants.DEFAULT_MIN_ADMIN_LEVEL), administrativeLevelMax); @@ -353,7 +375,11 @@ public Bundle getPaginatedLocations(HttpServletRequest request, List loc Location parentLocation = getLocationById(identifier); List locations = getLocationHierarchyLocations( - identifier, parentLocation, preFetchAdminLevels, postFetchAdminLevels); + identifier, + parentLocation, + preFetchAdminLevels, + postFetchAdminLevels, + filterInventory); resourceLocations.addAll(locations); } int totalEntries = resourceLocations.size(); @@ -435,4 +461,34 @@ public List filterLocationsByAdminLevels( } return allLocations; } + + public List filterLocationsByInventory(List locations) { + List filteredLocations = Collections.synchronizedList(new ArrayList<>()); + + locations.parallelStream() + .forEach( + location -> { + String locationId = location.getIdElement().getIdPart(); + String locationReference = + Constants.SyncStrategy.LOCATION + + Constants.FORWARD_SLASH + + locationId; + + Bundle listBundle = + getFhirClientForR4() + .search() + .forResource(ListResource.class) + .where( + new ReferenceClientParam(Constants.SUBJECT) + .hasId(locationReference)) + .usingStyle(SearchStyleEnum.POST) + .returnBundle(Bundle.class) + .execute(); + + if (listBundle != null && !listBundle.getEntry().isEmpty()) { + filteredLocations.add(location); + } + }); + return filteredLocations; + } } diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailsEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailsEndpointHelper.java index 1ef9e96..4b79816 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailsEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailsEndpointHelper.java @@ -573,7 +573,8 @@ public static List getLocationsHierarchy(List locatio .map( locationsIdentifier -> new LocationHierarchyEndpointHelper(r4FHIRClient) - .getLocationHierarchy(locationsIdentifier, null, null)) + .getLocationHierarchy( + locationsIdentifier, null, null, false)) .filter( locationHierarchy -> !org.smartregister.utils.Constants.LOCATION_RESOURCE_NOT_FOUND diff --git a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java index 92d9e44..25c3186 100644 --- a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java +++ b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java @@ -19,6 +19,7 @@ import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.ListResource; import org.hl7.fhir.r4.model.Location; import org.junit.Assert; import org.junit.Before; @@ -54,7 +55,8 @@ public void testGetLocationHierarchyNotFound() { .when(client) .fetchResourceFromUrl(any(), any()); LocationHierarchy locationHierarchy = - locationHierarchyEndpointHelper.getLocationHierarchy("non-existent", null, null); + locationHierarchyEndpointHelper.getLocationHierarchy( + "non-existent", null, null, false); assertEquals( org.smartregister.utils.Constants.LOCATION_RESOURCE_NOT_FOUND, locationHierarchy.getId()); @@ -69,7 +71,7 @@ public void testGetLocationHierarchyFound() { .when(client) .fetchResourceFromUrl(Location.class, "Location/12345"); LocationHierarchy locationHierarchy = - locationHierarchyEndpointHelper.getLocationHierarchy("12345", null, null); + locationHierarchyEndpointHelper.getLocationHierarchy("12345", null, null, false); assertEquals("Location Resource : 12345", locationHierarchy.getId()); } @@ -105,7 +107,7 @@ public void testGetPaginatedLocationsPaginatesLocations() { .filterLocationsByAdminLevels(locations, adminLevels); Mockito.doReturn(locations) .when(mockLocationHierarchyEndpointHelper) - .getLocationHierarchyLocations("12345", null, adminLevels, adminLevels); + .getLocationHierarchyLocations("12345", null, adminLevels, adminLevels, false); Bundle resultBundle = mockLocationHierarchyEndpointHelper.getPaginatedLocations(request, locationIds); @@ -181,7 +183,11 @@ public void testHandleNonIdentifierRequestListModePaginatesLocations() { Mockito.doReturn(locations) .when(mockLocationHierarchyEndpointHelper) .getLocationHierarchyLocations( - Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.anyString(), + Mockito.any(), + Mockito.any(), + Mockito.any(), + Mockito.any()); Mockito.doReturn(Constants.SyncStrategy.RELATED_ENTITY_LOCATION) .when(mockLocationHierarchyEndpointHelper) .getSyncStrategyByAppId(Mockito.any()); @@ -316,6 +322,56 @@ public void testFilterLocationsByAdminLevelsWithNullAdminLevelsDoesNotFilter() { Assert.assertEquals("4", filteredLocations.get(4).getId()); } + @Test + public void testFilterLocationsByInventoryWithInventory() { + IUntypedQuery untypedQueryMock = mock(IUntypedQuery.class); + IQuery queryMock = mock(IQuery.class); + + Bundle bundleWithInventory = new Bundle(); + List entriesWithInventory = new ArrayList<>(); + + ListResource resource1 = new ListResource(); + resource1.setId("1"); + entriesWithInventory.add(new Bundle.BundleEntryComponent().setResource(resource1)); + bundleWithInventory.setEntry(entriesWithInventory); + + Mockito.doReturn(untypedQueryMock).when(client).search(); + Mockito.doReturn(queryMock).when(untypedQueryMock).forResource(ListResource.class); + Mockito.doReturn(queryMock).when(queryMock).where(any(ICriterion.class)); + Mockito.doReturn(queryMock).when(queryMock).usingStyle(SearchStyleEnum.POST); + Mockito.doReturn(queryMock).when(queryMock).returnBundle(Bundle.class); + Mockito.doReturn(bundleWithInventory).when(queryMock).execute(); + + List locations = createLocationList(5, true); + List filteredLocations = + locationHierarchyEndpointHelper.filterLocationsByInventory(locations); + + Assert.assertNotNull(filteredLocations); + Assert.assertEquals(5, filteredLocations.size()); + } + + @Test + public void testFilterLocationsByInventoryNoInventory() { + IUntypedQuery untypedQueryMock = mock(IUntypedQuery.class); + IQuery queryMock = mock(IQuery.class); + + Bundle bundleWithInventory = new Bundle(); + + Mockito.doReturn(untypedQueryMock).when(client).search(); + Mockito.doReturn(queryMock).when(untypedQueryMock).forResource(ListResource.class); + Mockito.doReturn(queryMock).when(queryMock).where(any(ICriterion.class)); + Mockito.doReturn(queryMock).when(queryMock).usingStyle(SearchStyleEnum.POST); + Mockito.doReturn(queryMock).when(queryMock).returnBundle(Bundle.class); + Mockito.doReturn(bundleWithInventory).when(queryMock).execute(); + + List locations = createLocationList(5, true); + List filteredLocations = + locationHierarchyEndpointHelper.filterLocationsByInventory(locations); + + Assert.assertNotNull(filteredLocations); + Assert.assertEquals(0, filteredLocations.size()); + } + private Bundle getLocationBundle() { Bundle bundleLocation = new Bundle(); bundleLocation.setId("Location/1234");