diff --git a/health-services/plan-service/src/main/java/digit/config/ServiceConstants.java b/health-services/plan-service/src/main/java/digit/config/ServiceConstants.java index 69d0986f961..8597c1a2a5d 100644 --- a/health-services/plan-service/src/main/java/digit/config/ServiceConstants.java +++ b/health-services/plan-service/src/main/java/digit/config/ServiceConstants.java @@ -384,4 +384,11 @@ public class ServiceConstants { public static final String CONFIRMED_TARGET_POPULATION = "CONFIRMED_HCM_ADMIN_CONSOLE_TARGET_POPULATION"; + public static final String ADDITIONAL_DETAILS_QUERY = " additional_details @> CAST( ? AS jsonb )"; + + public static final String JSONB_QUERY_FORMAT = "additional_details @> ?::jsonb"; + + public static final String AND_CONDITION = " AND "; + + public static final String OR_CONDITION = " OR "; } diff --git a/health-services/plan-service/src/main/java/digit/repository/querybuilder/PlanQueryBuilder.java b/health-services/plan-service/src/main/java/digit/repository/querybuilder/PlanQueryBuilder.java index 27c53bb9ccb..0b0c7347aa7 100644 --- a/health-services/plan-service/src/main/java/digit/repository/querybuilder/PlanQueryBuilder.java +++ b/health-services/plan-service/src/main/java/digit/repository/querybuilder/PlanQueryBuilder.java @@ -7,10 +7,10 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; +import java.util.*; + +import static digit.config.ServiceConstants.ADDITIONAL_DETAILS_QUERY; +import static digit.config.ServiceConstants.FACILITY_ID_SEARCH_PARAMETER_KEY; @Component public class PlanQueryBuilder { @@ -172,11 +172,21 @@ private String buildPlanSearchQuery(PlanSearchCriteria planSearchCriteria, List< queryUtil.addToPreparedStatement(preparedStmtList, planSearchCriteria.getJurisdiction()); } - if(!CollectionUtils.isEmpty(planSearchCriteria.getFiltersMap())) { - queryUtil.addClauseIfRequired(builder, preparedStmtList); - builder.append(" additional_details @> CAST( ? AS jsonb )"); - String partialQueryJsonString = queryUtil.preparePartialJsonStringFromFilterMap(planSearchCriteria.getFiltersMap()); - preparedStmtList.add(partialQueryJsonString); + if (!CollectionUtils.isEmpty(planSearchCriteria.getFiltersMap())) { + Map> filtersMap = planSearchCriteria.getFiltersMap(); + for (String key : filtersMap.keySet()) { + if (FACILITY_ID_SEARCH_PARAMETER_KEY.equals(key)) { + // its for facility multi select then no need to add to preparedStmtList + String partialQueryJsonString = queryUtil.preparePartialJsonStringFromFilterMap(planSearchCriteria.getFiltersMap(), preparedStmtList, key); + builder.append(partialQueryJsonString); + } else { + // handle all other keys normally. + queryUtil.addClauseIfRequired(builder, preparedStmtList); + builder.append(ADDITIONAL_DETAILS_QUERY); + String partialQueryJsonString = queryUtil.preparePartialJsonStringFromFilterMap(planSearchCriteria.getFiltersMap(), preparedStmtList, key); + preparedStmtList.add(partialQueryJsonString); + } + } } StringBuilder countQuery = new StringBuilder(); diff --git a/health-services/plan-service/src/main/java/digit/service/PlanEnricher.java b/health-services/plan-service/src/main/java/digit/service/PlanEnricher.java index 1cff13076dc..7ab443a6f31 100644 --- a/health-services/plan-service/src/main/java/digit/service/PlanEnricher.java +++ b/health-services/plan-service/src/main/java/digit/service/PlanEnricher.java @@ -253,31 +253,31 @@ public void enrichSearchRequest(PlanSearchRequest planSearchRequest) { PlanSearchCriteria planSearchCriteria = planSearchRequest.getPlanSearchCriteria(); // Filter map for filtering plan metadata present in additional details - Map filtersMap = new LinkedHashMap<>(); + Map> filtersMap = new LinkedHashMap<>(); // Add facility id as a filter if present in search criteria - if (!ObjectUtils.isEmpty(planSearchCriteria.getFacilityId())) { - filtersMap.put(FACILITY_ID_SEARCH_PARAMETER_KEY, planSearchCriteria.getFacilityId()); + if (!ObjectUtils.isEmpty(planSearchCriteria.getFacilityIds())) { + filtersMap.put(FACILITY_ID_SEARCH_PARAMETER_KEY, planSearchCriteria.getFacilityIds()); } // Add terrain as a filter if present in search criteria if (!ObjectUtils.isEmpty(planSearchCriteria.getTerrain())) { - filtersMap.put(TERRAIN_CONDITION_SEARCH_PARAMETER_KEY, planSearchCriteria.getTerrain()); + filtersMap.put(TERRAIN_CONDITION_SEARCH_PARAMETER_KEY, Collections.singleton(planSearchCriteria.getTerrain())); } // Add onRoadCondition as a filter if present in search criteria if (!ObjectUtils.isEmpty(planSearchCriteria.getOnRoadCondition())) { - filtersMap.put(ROAD_CONDITION_SEARCH_PARAMETER_KEY, planSearchCriteria.getOnRoadCondition()); + filtersMap.put(ROAD_CONDITION_SEARCH_PARAMETER_KEY, Collections.singleton(planSearchCriteria.getOnRoadCondition())); } // Add securityQ1 as a filter if present in search criteria if (!ObjectUtils.isEmpty(planSearchCriteria.getSecurityQ1())) { - filtersMap.put(SECURITY_Q1_SEARCH_PARAMETER_KEY, planSearchCriteria.getSecurityQ1()); + filtersMap.put(SECURITY_Q1_SEARCH_PARAMETER_KEY, Collections.singleton(planSearchCriteria.getSecurityQ1())); } // Add securityQ2 as a filter if present in search criteria if (!ObjectUtils.isEmpty(planSearchCriteria.getSecurityQ2())) { - filtersMap.put(SECURITY_Q2_SEARCH_PARAMETER_KEY, planSearchCriteria.getSecurityQ2()); + filtersMap.put(SECURITY_Q2_SEARCH_PARAMETER_KEY, Collections.singleton(planSearchCriteria.getSecurityQ2())); } if(!CollectionUtils.isEmpty(filtersMap)) diff --git a/health-services/plan-service/src/main/java/digit/util/QueryUtil.java b/health-services/plan-service/src/main/java/digit/util/QueryUtil.java index 070f442a897..127dcd951a6 100644 --- a/health-services/plan-service/src/main/java/digit/util/QueryUtil.java +++ b/health-services/plan-service/src/main/java/digit/util/QueryUtil.java @@ -10,14 +10,10 @@ import org.springframework.util.ObjectUtils; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.IntStream; -import static digit.config.ServiceConstants.DOT_REGEX; -import static digit.config.ServiceConstants.DOT_SEPARATOR; +import static digit.config.ServiceConstants.*; @Component public class QueryUtil { @@ -99,6 +95,8 @@ public String addOrderByClause(String query, String orderByClause) { /** * This method prepares partial json string from the filter map to query on jsonb column * + * - For nested keys (keys containing dot separators), a nested JSON structure is constructed. + * - For other keys, a simple key-value JSON structure is returned. * @param filterMap * @return */ @@ -121,6 +119,63 @@ public String preparePartialJsonStringFromFilterMap(Map filterMa return partialJsonQueryString; } + /** + * This is an overloaded function, to handle facility multi select for plan search specifically. + * Prepares a partial JSON query string based on the provided filter map, key, and prepared statement list. + * + * @param filterMap A map containing filter keys and their corresponding set of values. + * @param preparedStmtList A list to which placeholder values for prepared statements are added. + * @param key The key used to construct the partial query. + * @return A partial JSON query string or a query fragment with OR conditions. + * + * This method dynamically constructs a JSON query or query fragment based on the structure of the `key`: + * - For nested keys (keys containing dot separators), a nested JSON structure is constructed. + * - For the "facilityId" key, an OR condition is generated for all values in the set. + * - For other keys, a simple key-value JSON structure is returned. + * + * Notes: + * - When constructing the query for "facilityId", placeholders for each value are added to the `preparedStmtList`. + */ + public String preparePartialJsonStringFromFilterMap(Map> filterMap, List preparedStmtList, String key) { + Map queryMap = new HashMap<>(); + StringBuilder finalJsonQuery = new StringBuilder(); + // Handle nested keys (dot separator) + if (key.contains(DOT_SEPARATOR)) { + String[] keyArray = key.split(DOT_REGEX); + Map nestedQueryMap = new HashMap<>(); + prepareNestedQueryMap(0, keyArray, nestedQueryMap, (String) filterMap.get(key).toArray()[0]); + queryMap.put(keyArray[0], nestedQueryMap.get(keyArray[0])); + return gson.toJson(queryMap); + } else if (FACILITY_ID_SEARCH_PARAMETER_KEY.equals(key)) { + Set values = filterMap.get(key); + if (values != null && !values.isEmpty()) { + StringBuilder orClauseBuilder = new StringBuilder(); + + // For each value, add an OR condition with a placeholder for the value + for (String value : values) { + if (orClauseBuilder.length() > 0) { + orClauseBuilder.append(OR_CONDITION); + } + // Add the condition for the key-value pair + orClauseBuilder.append(JSONB_QUERY_FORMAT); + // Add the actual value in the format { "facilityId": "value" } + preparedStmtList.add("{\"" + key + "\": \"" + value + "\"}"); + } + + // Append the OR clause as part of the AND conditions + finalJsonQuery.append(AND_CONDITION).append("(").append(orClauseBuilder.toString()).append(") "); + return finalJsonQuery.toString(); // Return the query with OR conditions for facilityId + } + } else { + queryMap.put(key, (String) filterMap.get(key).toArray()[0]); + } + + + // Return the dynamically constructed query string + return gson.toJson(queryMap); + } + + /** * Tail recursive method to prepare n-level nested partial json for queries on nested data in * master data. For e.g. , if the key is in the format a.b.c, it will construct a nested json diff --git a/health-services/plan-service/src/main/java/digit/web/models/PlanSearchCriteria.java b/health-services/plan-service/src/main/java/digit/web/models/PlanSearchCriteria.java index f71cca497fc..f0b22e87d65 100644 --- a/health-services/plan-service/src/main/java/digit/web/models/PlanSearchCriteria.java +++ b/health-services/plan-service/src/main/java/digit/web/models/PlanSearchCriteria.java @@ -40,8 +40,8 @@ public class PlanSearchCriteria { @JsonProperty("planConfigurationId") private String planConfigurationId = null; - @JsonProperty("facilityId") - private String facilityId = null; + @JsonProperty("facilityIds") + private Set facilityIds = null; @JsonProperty("onRoadCondition") private String onRoadCondition = null; @@ -72,6 +72,6 @@ public class PlanSearchCriteria { private Integer limit = null; @JsonIgnore - private Map filtersMap = null; + private Map> filtersMap = null; }