diff --git a/health-services/plan-service/CHANGELOG.md b/health-services/plan-service/CHANGELOG.md index a73439d7658..bacefd74a24 100644 --- a/health-services/plan-service/CHANGELOG.md +++ b/health-services/plan-service/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this module will be documented in this file. + +## 1.0.1 - 2025-01-30 +1. Enabled support for Mixed Distribution Strategy for Registration and Service Delivery +2. Adding additional search filters for Estimation Dashboard + ## 1.0.0 - 2024-12-03 ### Plan Service diff --git a/health-services/plan-service/pom.xml b/health-services/plan-service/pom.xml index c41f44a9f05..d0dba48a760 100644 --- a/health-services/plan-service/pom.xml +++ b/health-services/plan-service/pom.xml @@ -4,7 +4,7 @@ plan-service jar plan-service - 1.0.0 + 1.0.1 17 ${java.version} 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 a2b9696b482..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 @@ -360,10 +360,20 @@ public class ServiceConstants { public static final String FACILITY_NAME_SEARCH_PARAMETER_KEY = "facilityName"; + public static final String FACILITY_ID_SEARCH_PARAMETER_KEY = "facilityId"; + public static final String FACILITY_STATUS_SEARCH_PARAMETER_KEY = "facilityStatus"; public static final String FACILITY_TYPE_SEARCH_PARAMETER_KEY = "facilityType"; + public static final String TERRAIN_CONDITION_SEARCH_PARAMETER_KEY = "accessibilityDetails|terrain|code"; + + public static final String ROAD_CONDITION_SEARCH_PARAMETER_KEY = "accessibilityDetails|roadCondition|code"; + + public static final String SECURITY_Q1_SEARCH_PARAMETER_KEY = "securityDetails|1|code"; + + public static final String SECURITY_Q2_SEARCH_PARAMETER_KEY = "securityDetails|2|code"; + public static final String COMMA_DELIMITER = ","; public static final String SERVING_POPULATION_CODE = "servingPopulation"; @@ -374,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/impl/PlanRepositoryImpl.java b/health-services/plan-service/src/main/java/digit/repository/impl/PlanRepositoryImpl.java index 99e4b9487de..90cdb199bf8 100644 --- a/health-services/plan-service/src/main/java/digit/repository/impl/PlanRepositoryImpl.java +++ b/health-services/plan-service/src/main/java/digit/repository/impl/PlanRepositoryImpl.java @@ -189,6 +189,7 @@ private PlanRequestDTO convertToPlanReqDTO(PlanRequest planRequest) { .assignee(assignee) .additionalDetails(plan.getAdditionalDetails()) .jurisdictionMapping(plan.getJurisdictionMapping()) + .additionalFields(plan.getAdditionalFields()) .activities(plan.getActivities()) .resources(plan.getResources()) .targets(plan.getTargets()) 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 5c8bc91097b..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 { @@ -30,12 +30,14 @@ public PlanQueryBuilder(Configuration config, QueryUtil queryUtil) { "\t plan_activity.id as plan_activity_id, plan_activity.code as plan_activity_code, plan_activity.description as plan_activity_description, plan_activity.planned_start_date as plan_activity_planned_start_date, plan_activity.planned_end_date as plan_activity_planned_end_date, plan_activity.dependencies as plan_activity_dependencies, plan_activity.plan_id as plan_activity_plan_id, plan_activity.created_by as plan_activity_created_by, plan_activity.created_time as plan_activity_created_time, plan_activity.last_modified_by as plan_activity_last_modified_by, plan_activity.last_modified_time as plan_activity_last_modified_time,\n" + "\t plan_activity_condition.id as plan_activity_condition_id, plan_activity_condition.entity as plan_activity_condition_entity, plan_activity_condition.entity_property as plan_activity_condition_entity_property, plan_activity_condition.expression as plan_activity_condition_expression, plan_activity_condition.activity_id as plan_activity_condition_activity_id, plan_activity_condition.is_active as plan_activity_condition_is_active, plan_activity_condition.created_by as plan_activity_condition_created_by, plan_activity_condition.created_time as plan_activity_condition_created_time, plan_activity_condition.last_modified_by as plan_activity_condition_last_modified_by, plan_activity_condition.last_modified_time as plan_activity_condition_last_modified_time,\n" + "\t plan_resource.id as plan_resource_id, plan_resource.resource_type as plan_resource_resource_type, plan_resource.estimated_number as plan_resource_estimated_number, plan_resource.plan_id as plan_resource_plan_id, plan_resource.activity_code as plan_resource_activity_code, plan_resource.created_by as plan_resource_created_by, plan_resource.created_time as plan_resource_created_time, plan_resource.last_modified_by as plan_resource_last_modified_by, plan_resource.last_modified_time as plan_resource_last_modified_time,\n" + - "\t plan_target.id as plan_target_id, plan_target.metric as plan_target_metric, plan_target.metric_value as plan_target_metric_value, plan_target.metric_comparator as plan_target_metric_comparator, plan_target.metric_unit as plan_target_metric_unit, plan_target.plan_id as plan_target_plan_id, plan_target.activity_code as plan_target_activity_code, plan_target.created_by as plan_target_created_by, plan_target.created_time as plan_target_created_time, plan_target.last_modified_by as plan_target_last_modified_by, plan_target.last_modified_time as plan_target_last_modified_time\n" + + "\t plan_target.id as plan_target_id, plan_target.metric as plan_target_metric, plan_target.metric_value as plan_target_metric_value, plan_target.metric_comparator as plan_target_metric_comparator, plan_target.metric_unit as plan_target_metric_unit, plan_target.plan_id as plan_target_plan_id, plan_target.activity_code as plan_target_activity_code, plan_target.created_by as plan_target_created_by, plan_target.created_time as plan_target_created_time, plan_target.last_modified_by as plan_target_last_modified_by, plan_target.last_modified_time as plan_target_last_modified_time, \n" + + "\t paf.id as plan_additional_field_id, paf.plan_id as plan_additional_field_plan_id, paf.key as plan_additional_field_key, paf.value as plan_additional_field_value, paf.show_on_ui as plan_additional_field_show_on_ui, paf.editable as plan_additional_field_editable, paf.order as plan_additional_field_order \n" + "\t FROM plan \n" + "\t LEFT JOIN plan_activity ON plan.id = plan_activity.plan_id\n" + "\t LEFT JOIN plan_activity_condition ON plan_activity.id = plan_activity_condition.activity_id\n" + "\t LEFT JOIN plan_resource ON plan.id = plan_resource.plan_id\n" + - "\t LEFT JOIN plan_target ON plan.id = plan_target.plan_id"; + "\t LEFT JOIN plan_target ON plan.id = plan_target.plan_id\n" + + "\t LEFT JOIN plan_additional_field paf ON plan.id = paf.plan_id"; private static final String BULK_PLAN_UPDATE_QUERY = "UPDATE plan SET status = ?, assignee = ?, last_modified_by = ?, last_modified_time = ? WHERE id = ?"; @@ -170,6 +172,22 @@ private String buildPlanSearchQuery(PlanSearchCriteria planSearchCriteria, List< queryUtil.addToPreparedStatement(preparedStmtList, planSearchCriteria.getJurisdiction()); } + 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(); if (isCount) { diff --git a/health-services/plan-service/src/main/java/digit/repository/rowmapper/PlanRowMapper.java b/health-services/plan-service/src/main/java/digit/repository/rowmapper/PlanRowMapper.java index aa00207a741..b37561fbc3d 100644 --- a/health-services/plan-service/src/main/java/digit/repository/rowmapper/PlanRowMapper.java +++ b/health-services/plan-service/src/main/java/digit/repository/rowmapper/PlanRowMapper.java @@ -30,6 +30,7 @@ public List extractData(ResultSet rs) throws SQLException, DataAccessExcep Set conditionSet = new HashSet<>(); Set resourceSet = new HashSet<>(); Set targetSet = new HashSet<>(); + Set additionalFieldSet = new HashSet<>(); // Traverse through result set and create plan objects while (rs.next()) { @@ -43,6 +44,7 @@ public List extractData(ResultSet rs) throws SQLException, DataAccessExcep conditionSet.clear(); resourceSet.clear(); targetSet.clear(); + additionalFieldSet.clear(); // Prepare audit details AuditDetails auditDetails = AuditDetails.builder() @@ -72,6 +74,7 @@ public List extractData(ResultSet rs) throws SQLException, DataAccessExcep addActivities(rs, planEntry, activityMap, conditionSet); addResources(rs, planEntry, resourceSet); addTargets(rs, planEntry, targetSet); + addAdditionalField(rs, planEntry, additionalFieldSet); planMap.put(planId, planEntry); } @@ -234,4 +237,39 @@ private void addTargets(ResultSet rs, Plan planEntry, Set targetSet) thr targetSet.add(target.getId()); } + + /** + * Adds a AdditionalField object to the plan entry based on the result set. + * + * @param rs The ResultSet containing the data. + * @param additionalFieldSet A set to keep track of added AdditionalField objects. + * @param planEntry The Plan entry to which the AdditionalField object will be added. + * @throws SQLException If an SQL error occurs. + */ + private void addAdditionalField(ResultSet rs, Plan planEntry, Set additionalFieldSet) throws SQLException { + String additionalFieldId = rs.getString("plan_additional_field_id"); + + if (ObjectUtils.isEmpty(additionalFieldId) || additionalFieldSet.contains(additionalFieldId)) { + return; + } + + AdditionalField additionalField = new AdditionalField(); + additionalField.setId(rs.getString("plan_additional_field_id")); + additionalField.setKey(rs.getString("plan_additional_field_key")); + additionalField.setValue(rs.getBigDecimal("plan_additional_field_value")); + additionalField.setShowOnUi(rs.getBoolean("plan_additional_field_show_on_ui")); + additionalField.setEditable(rs.getBoolean("plan_additional_field_editable")); + additionalField.setOrder(rs.getInt("plan_additional_field_order")); + + if (CollectionUtils.isEmpty(planEntry.getAdditionalFields())) { + List additionalFields = new ArrayList<>(); + additionalFields.add(additionalField); + planEntry.setAdditionalFields(additionalFields); + } else { + planEntry.getAdditionalFields().add(additionalField); + } + + additionalFieldSet.add(additionalFieldId); + } + } diff --git a/health-services/plan-service/src/main/java/digit/service/enrichment/PlanEnricher.java b/health-services/plan-service/src/main/java/digit/service/PlanEnricher.java similarity index 78% rename from health-services/plan-service/src/main/java/digit/service/enrichment/PlanEnricher.java rename to health-services/plan-service/src/main/java/digit/service/PlanEnricher.java index 17364bfdb6d..7ab443a6f31 100644 --- a/health-services/plan-service/src/main/java/digit/service/enrichment/PlanEnricher.java +++ b/health-services/plan-service/src/main/java/digit/service/PlanEnricher.java @@ -1,7 +1,6 @@ -package digit.service.enrichment; +package digit.service; -import digit.web.models.Plan; -import digit.web.models.PlanRequest; +import digit.web.models.*; import digit.web.models.boundary.BoundaryTypeHierarchy; import digit.web.models.boundary.BoundaryTypeHierarchyDefinition; import digit.web.models.boundary.EnrichedBoundary; @@ -14,6 +13,8 @@ import java.util.*; +import static digit.config.ServiceConstants.*; + @Component public class PlanEnricher { @@ -53,6 +54,10 @@ public void enrichPlanCreate(PlanRequest body) { // Generate id for targets body.getPlan().getTargets().forEach(target -> UUIDEnrichmentUtil.enrichRandomUuid(target, "id")); + // Generate id for additional fields + if(!CollectionUtils.isEmpty(body.getPlan().getAdditionalFields())) + body.getPlan().getAdditionalFields().forEach(additionalField -> UUIDEnrichmentUtil.enrichRandomUuid(additionalField, "id")); + // Enrich audit details body.getPlan().setAuditDetails(AuditDetailsEnrichmentUtil .prepareAuditDetails(body.getPlan().getAuditDetails(), body.getRequestInfo(), Boolean.TRUE)); @@ -107,6 +112,15 @@ public void enrichPlanUpdate(PlanRequest body) { } }); + // Generate uuid for new additionalFields + if(!CollectionUtils.isEmpty(body.getPlan().getAdditionalFields())) { + body.getPlan().getAdditionalFields().forEach(additionalFields -> { + if(ObjectUtils.isEmpty(additionalFields.getId())) { + UUIDEnrichmentUtil.enrichRandomUuid(additionalFields, "id"); + } + }); + } + // Enriching last modified time for update body.getPlan().getAuditDetails().setLastModifiedTime(System.currentTimeMillis()); } @@ -228,4 +242,45 @@ private List getBoundaryCodeFromAncestralPath(String boundaryAncestralPa } return Arrays.asList(boundaryAncestralPath.split("\\|")); } + + /** + * Enriches the PlanSearchRequest by populating the filters map from the fields in search criteria. + * This filterMap is populated to search the fields in plan additional detail object. + * + * @param planSearchRequest the planSearchRequest object whose search criteria need enrichment. + */ + public void enrichSearchRequest(PlanSearchRequest planSearchRequest) { + PlanSearchCriteria planSearchCriteria = planSearchRequest.getPlanSearchCriteria(); + + // Filter map for filtering plan metadata present in additional details + Map> filtersMap = new LinkedHashMap<>(); + + // Add facility id as a filter if present in search criteria + 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, 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, 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, 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, Collections.singleton(planSearchCriteria.getSecurityQ2())); + } + + if(!CollectionUtils.isEmpty(filtersMap)) + planSearchCriteria.setFiltersMap(filtersMap); + } } diff --git a/health-services/plan-service/src/main/java/digit/service/PlanService.java b/health-services/plan-service/src/main/java/digit/service/PlanService.java index dad7e8b7d83..d4a4416ed47 100644 --- a/health-services/plan-service/src/main/java/digit/service/PlanService.java +++ b/health-services/plan-service/src/main/java/digit/service/PlanService.java @@ -1,8 +1,6 @@ package digit.service; import digit.repository.PlanRepository; -import digit.service.enrichment.PlanEnricher; -import digit.service.validator.PlanValidator; import digit.service.workflow.WorkflowService; import digit.web.models.*; import org.egov.common.utils.ResponseInfoUtil; @@ -61,6 +59,9 @@ public PlanResponse createPlan(PlanRequest body) { * @return */ public PlanResponse searchPlan(PlanSearchRequest body) { + // Enrich search request + planEnricher.enrichSearchRequest(body); + // Delegate search request to repository List planList = planRepository.search(body.getPlanSearchCriteria()); diff --git a/health-services/plan-service/src/main/java/digit/service/validator/PlanValidator.java b/health-services/plan-service/src/main/java/digit/service/PlanValidator.java similarity index 99% rename from health-services/plan-service/src/main/java/digit/service/validator/PlanValidator.java rename to health-services/plan-service/src/main/java/digit/service/PlanValidator.java index 1fd98c823b7..aa1656c00b3 100644 --- a/health-services/plan-service/src/main/java/digit/service/validator/PlanValidator.java +++ b/health-services/plan-service/src/main/java/digit/service/PlanValidator.java @@ -1,11 +1,9 @@ -package digit.service.validator; +package digit.service; import com.jayway.jsonpath.JsonPath; import digit.config.Configuration; import digit.repository.PlanConfigurationRepository; import digit.repository.PlanRepository; -import digit.service.PlanEmployeeService; -import digit.service.enrichment.PlanEnricher; import digit.util.BoundaryUtil; import digit.util.CampaignUtil; import digit.util.CommonUtil; diff --git a/health-services/plan-service/src/main/java/digit/service/enrichment/PlanFacilityEnricher.java b/health-services/plan-service/src/main/java/digit/service/enrichment/PlanFacilityEnricher.java index a07530dbfd2..fccb2de96df 100644 --- a/health-services/plan-service/src/main/java/digit/service/enrichment/PlanFacilityEnricher.java +++ b/health-services/plan-service/src/main/java/digit/service/enrichment/PlanFacilityEnricher.java @@ -3,10 +3,7 @@ import digit.util.BoundaryUtil; import digit.util.CensusUtil; import digit.util.CommonUtil; -import digit.web.models.PlanFacility; -import digit.web.models.PlanFacilityRequest; -import digit.web.models.PlanFacilitySearchCriteria; -import digit.web.models.PlanFacilitySearchRequest; +import digit.web.models.*; import digit.web.models.boundary.BoundarySearchResponse; import digit.web.models.boundary.EnrichedBoundary; import digit.web.models.census.*; diff --git a/health-services/plan-service/src/main/java/digit/service/validator/PlanEmployeeAssignmentValidator.java b/health-services/plan-service/src/main/java/digit/service/validator/PlanEmployeeAssignmentValidator.java index 772ec6dd133..774ff753132 100644 --- a/health-services/plan-service/src/main/java/digit/service/validator/PlanEmployeeAssignmentValidator.java +++ b/health-services/plan-service/src/main/java/digit/service/validator/PlanEmployeeAssignmentValidator.java @@ -317,8 +317,9 @@ public void validateUpdate(PlanEmployeeAssignmentRequest request) { // Validate if Plan employee assignment exists validatePlanEmployeeAssignmentExistance(planEmployeeAssignment); - // Validate campaign id and employee jurisdiction - validateCampaignDetails(planConfigurations.get(0).getCampaignId(), rootTenantId, request); + // Validate campaign id and employee jurisdiction for active records + if(planEmployeeAssignment.getActive()) + validateCampaignDetails(planConfigurations.get(0).getCampaignId(), rootTenantId, request); } diff --git a/health-services/plan-service/src/main/java/digit/service/validator/PlanFacilityValidator.java b/health-services/plan-service/src/main/java/digit/service/validator/PlanFacilityValidator.java index dd7b251eaf1..d8114403d30 100644 --- a/health-services/plan-service/src/main/java/digit/service/validator/PlanFacilityValidator.java +++ b/health-services/plan-service/src/main/java/digit/service/validator/PlanFacilityValidator.java @@ -126,27 +126,45 @@ private void validateCampaignDetails(String campaignId, String rootTenantId, Pla String lowestHierarchy = hierarchyMap.get(LOWEST_HIERARCHY_FIELD_FOR_MICROPLAN); // Collect all boundary code for the campaign - Set boundaryCodes = fetchBoundaryCodes(campaignResponse.getCampaignDetails().get(0), lowestHierarchy); + Set lowestHierarchyBCodes = fetchBoundaryCodes(campaignResponse.getCampaignDetails().get(0), lowestHierarchy); + Set allBoundaryCodes = fetchAllBoundaryCodes(campaignResponse.getCampaignDetails().get(0)); - // Validate residing boundaries - validateResidingBoundaries(boundaryCodes, planFacility); + // Validate residing boundaries against boundary codes across all hierarchy levels, + // as a facility's residing boundary may correspond to any jurisdiction level. + validateResidingBoundaries(allBoundaryCodes, planFacility); - // Validate service boundaries - validateServiceBoundaries(boundaryCodes, planFacility); + // Validate service boundaries against the lowest hierarchy boundary codes, + // as a facility can only be mapped to boundaries at the lowest hierarchy level. + validateServiceBoundaries(lowestHierarchyBCodes, planFacility); //Enrich jurisdiction mapping and boundary ancestral path enrichment.enrichJurisdictionMapping(planFacilityRequest, campaignResponse.getCampaignDetails().get(0).getHierarchyType()); } /** - * This method returns boundary code for the campaign + * This method returns a set of all boundary codes for the given campaign. * - * @param campaignDetail - * @param lowestHierarchy + * @param campaignDetail the campaign details whose BCodes are required. + * @return returns a set of boundaries for the given campaign. */ - private Set fetchBoundaryCodes(CampaignDetail campaignDetail, String lowestHierarchy) { + private Set fetchAllBoundaryCodes(CampaignDetail campaignDetail) { Set boundaryCodes = campaignDetail.getBoundaries().stream() - .filter(boundary -> lowestHierarchy.equals(boundary.getType().toLowerCase())) + .map(Boundary::getCode) + .collect(Collectors.toSet()); + + return boundaryCodes; + } + + /** + * This method filters the boundaries based on given hierarchy type for the campaign and returns a set of those boundaries. + * + * @param campaignDetail the campaign details whose BCodes are required. + * @param hierarchyType hierarchy type of the required boundaries. + * @return returns a set of boundaries of the given hierarchy type. + */ + private Set fetchBoundaryCodes(CampaignDetail campaignDetail, String hierarchyType) { + Set boundaryCodes = campaignDetail.getBoundaries().stream() + .filter(boundary -> hierarchyType.equals(boundary.getType().toLowerCase())) .map(Boundary::getCode) .collect(Collectors.toSet()); 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/census/AdditionalField.java b/health-services/plan-service/src/main/java/digit/web/models/AdditionalField.java similarity index 96% rename from health-services/plan-service/src/main/java/digit/web/models/census/AdditionalField.java rename to health-services/plan-service/src/main/java/digit/web/models/AdditionalField.java index 65ae4222831..10351283cdb 100644 --- a/health-services/plan-service/src/main/java/digit/web/models/census/AdditionalField.java +++ b/health-services/plan-service/src/main/java/digit/web/models/AdditionalField.java @@ -1,4 +1,4 @@ -package digit.web.models.census; +package digit.web.models; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.Valid; diff --git a/health-services/plan-service/src/main/java/digit/web/models/Plan.java b/health-services/plan-service/src/main/java/digit/web/models/Plan.java index 3ff0e8ce87d..f39dcfa6767 100644 --- a/health-services/plan-service/src/main/java/digit/web/models/Plan.java +++ b/health-services/plan-service/src/main/java/digit/web/models/Plan.java @@ -74,6 +74,10 @@ public class Plan { @JsonProperty("jurisdictionMapping") private Map jurisdictionMapping; + @JsonProperty("additionalFields") + @Valid + private List additionalFields = null; + @JsonIgnore private String boundaryAncestralPath = null; diff --git a/health-services/plan-service/src/main/java/digit/web/models/PlanDTO.java b/health-services/plan-service/src/main/java/digit/web/models/PlanDTO.java index f56569c1349..d7adb85ece0 100644 --- a/health-services/plan-service/src/main/java/digit/web/models/PlanDTO.java +++ b/health-services/plan-service/src/main/java/digit/web/models/PlanDTO.java @@ -59,6 +59,10 @@ public class PlanDTO { @JsonProperty("jurisdictionMapping") private Map jurisdictionMapping; + @JsonProperty("additionalFields") + @Valid + private List additionalFields = null; + @JsonProperty("activities") @Valid private List activities; 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 357b306fa8f..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 @@ -1,5 +1,6 @@ package digit.web.models; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -10,6 +11,7 @@ import org.springframework.validation.annotation.Validated; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -38,6 +40,21 @@ public class PlanSearchCriteria { @JsonProperty("planConfigurationId") private String planConfigurationId = null; + @JsonProperty("facilityIds") + private Set facilityIds = null; + + @JsonProperty("onRoadCondition") + private String onRoadCondition = null; + + @JsonProperty("terrain") + private String terrain = null; + + @JsonProperty("securityQ1") + private String securityQ1 = null; + + @JsonProperty("securityQ2") + private String securityQ2 = null; + @JsonProperty("status") private String status = null; @@ -54,4 +71,7 @@ public class PlanSearchCriteria { @JsonProperty("limit") private Integer limit = null; + @JsonIgnore + private Map> filtersMap = null; + } diff --git a/health-services/plan-service/src/main/java/digit/web/models/Resource.java b/health-services/plan-service/src/main/java/digit/web/models/Resource.java index d0d8ce2500b..2a614db0ba6 100644 --- a/health-services/plan-service/src/main/java/digit/web/models/Resource.java +++ b/health-services/plan-service/src/main/java/digit/web/models/Resource.java @@ -29,7 +29,6 @@ public class Resource { private String resourceType = null; @JsonProperty("estimatedNumber") - @NotNull private BigDecimal estimatedNumber = null; @JsonProperty("activityCode") diff --git a/health-services/plan-service/src/main/java/digit/web/models/census/Census.java b/health-services/plan-service/src/main/java/digit/web/models/census/Census.java index e8f384d1e98..ad052ca93ce 100644 --- a/health-services/plan-service/src/main/java/digit/web/models/census/Census.java +++ b/health-services/plan-service/src/main/java/digit/web/models/census/Census.java @@ -8,6 +8,7 @@ import java.util.List; +import digit.web.models.AdditionalField; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; @@ -48,8 +49,7 @@ public class Census { private String boundaryCode = null; @JsonProperty("assignee") - @Size(max = 64) - private String assignee = null; + private List assignee = null; @JsonProperty("status") @Size(max = 64) diff --git a/health-services/plan-service/src/main/resources/db/migration/main/V20242112151500__plan_additional_field_create_ddl.sql b/health-services/plan-service/src/main/resources/db/migration/main/V20242112151500__plan_additional_field_create_ddl.sql new file mode 100644 index 00000000000..764ee79bb2b --- /dev/null +++ b/health-services/plan-service/src/main/resources/db/migration/main/V20242112151500__plan_additional_field_create_ddl.sql @@ -0,0 +1,12 @@ +-- Table: plan_additional_field +CREATE TABLE plan_additional_field ( + id character varying(64) NOT NULL, + plan_id character varying(64) NOT NULL, + "key" character varying(64) NOT NULL, + "value" numeric(12,2) NOT NULL, + show_on_ui boolean DEFAULT true NOT NULL, + editable boolean DEFAULT true NOT NULL, + "order" bigint NOT NULL, + CONSTRAINT uk_plan_additional_field_id PRIMARY KEY (id), + FOREIGN KEY (plan_id) REFERENCES plan(id) +); diff --git a/health-services/plan-service/src/main/resources/db/migration/main/V20242201141000__alter_plan_facility_linkage_facility_name_ddl.sql b/health-services/plan-service/src/main/resources/db/migration/main/V20242201141000__alter_plan_facility_linkage_facility_name_ddl.sql new file mode 100644 index 00000000000..649a80d10f3 --- /dev/null +++ b/health-services/plan-service/src/main/resources/db/migration/main/V20242201141000__alter_plan_facility_linkage_facility_name_ddl.sql @@ -0,0 +1 @@ +ALTER TABLE plan_facility_linkage ALTER COLUMN facility_name TYPE character varying(2000); diff --git a/health-services/resource-generator/CHANGELOG.md b/health-services/resource-generator/CHANGELOG.md index f56811de913..fcc5d765bde 100644 --- a/health-services/resource-generator/CHANGELOG.md +++ b/health-services/resource-generator/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.0.1 - 2025-01-30 +1. Enhancements for Microplan Estimation Downloadable Excel Report +2. Enabled support for Mixed Distribution Strategy for Registration and Service Delivery +3. Changes for supporting filters in Estimation Dashboard + ## 1.0.0 - 2024-12-03 #### Resource Generator Service The Resource Generator Service introduces comprehensive functionalities for microplanning resource estimation and campaign integration: diff --git a/health-services/resource-generator/pom.xml b/health-services/resource-generator/pom.xml index 33ac3c4c7d6..f194f1e0e32 100644 --- a/health-services/resource-generator/pom.xml +++ b/health-services/resource-generator/pom.xml @@ -4,7 +4,7 @@ resource-generator jar file-processor-utility - 1.0.0 + 1.0.1 17 32-SNAPSHOT diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/config/Configuration.java b/health-services/resource-generator/src/main/java/org/egov/processor/config/Configuration.java index c652337ba28..c0e08a02aa2 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/config/Configuration.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/config/Configuration.java @@ -41,6 +41,9 @@ public class Configuration { @Value("${egov.plan.search.endpoint}") private String planSearchEndPoint; + @Value("${egov.plan.facility.search.endpoint}") + private String planFacilitySearchEndPoint; + // Filestore @Value("${egov.filestore.service.host}") @@ -109,11 +112,11 @@ public class Configuration { private String resourceCensusCreateTopic; //Default - @Value("${resource.default.offset}") - private Integer defaultOffset; + @Value("${default.offset.for.mdms.data}") + private Integer defaultOffsetForMdms; - @Value("${resource.default.limit}") - private Integer defaultLimit; + @Value("${default.limit.for.mdms.data}") + private Integer defaultLimitForMdms; //census additonal field configs @Value("${census.additional.field.override.keys}") diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/config/ServiceConstants.java b/health-services/resource-generator/src/main/java/org/egov/processor/config/ServiceConstants.java index 7200f29cecc..ae2e04160d6 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/config/ServiceConstants.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/config/ServiceConstants.java @@ -28,17 +28,27 @@ public class ServiceConstants { public static final String MICROPLANNING_MODULE = "microplan"; + //Custom Exceptions public static final String NO_MDMS_DATA_FOUND_FOR_GIVEN_TENANT_CODE = "NO_MDMS_DATA_FOUND_FOR_GIVEN_TENANT"; public static final String NO_MDMS_DATA_FOUND_FOR_GIVEN_TENANT_MESSAGE = "Invalid or incorrect TenantId. No mdms data found for provided Tenant."; public static final String ERROR_WHILE_FETCHING_FROM_PLAN_SERVICE = "Exception occurred while fetching plan configuration from plan service "; + public static final String ERROR_WHILE_UPDATING_ADDITIONAL_DETAILS_CODE = "ERROR_WHILE_UPDATING_ADDITIONAL_DETAILS"; + public static final String ERROR_WHILE_UPDATING_ADDITIONAL_DETAILS_MESSAGE = "Exception occurred while updating additional details : "; + public static final String NOT_ABLE_TO_CONVERT_MULTIPARTFILE_TO_BYTESTREAM_CODE = "NOT_ABLE_TO_CONVERT_MULTIPARTFILE_TO_BYTESTREAM"; public static final String NOT_ABLE_TO_CONVERT_MULTIPARTFILE_TO_BYTESTREAM_MESSAGE = "Not able to fetch byte stream from a multipart file"; public static final String FILE_NOT_FOUND_CODE = "FILE_NOT_FOUND"; public static final String FILE_NOT_FOUND_MESSAGE = "No file with the specified templateIdentifier found - "; + public static final String PROVIDED_KEY_IS_NOT_PRESENT_IN_JSON_OBJECT_CODE = "PROVIDED_KEY_IS_NOT_PRESENT_IN_JSON_OBJECT"; + public static final String PROVIDED_KEY_IS_NOT_PRESENT_IN_JSON_OBJECT_MESSAGE = "Key is not present in json object - "; + + public static final String EMPTY_HEADER_ROW_CODE = "EMPTY_HEADER_ROW"; + public static final String EMPTY_HEADER_ROW_MESSAGE = "The header row is empty for the given sheet"; + public static final String UNABLE_TO_CREATE_ADDITIONAL_DETAILS_CODE = "UNABLE_TO_CREATE_ADDITIONAL_DETAILS"; public static final String UNABLE_TO_CREATE_ADDITIONAL_DETAILS_MESSAGE = "Unable to create additional details for facility creation."; @@ -48,32 +58,58 @@ public class ServiceConstants { public static final String NO_PLAN_FOUND_FOR_GIVEN_DETAILS_CODE = "NO_PLAN_FOUND_FOR_GIVEN_DETAILS"; public static final String NO_PLAN_FOUND_FOR_GIVEN_DETAILS_MESSAGE = "Plan records do not exists for the given details: "; + public static final String NO_PLAN_FACILITY_FOUND_FOR_GIVEN_DETAILS_CODE = "NO_PLAN_FACILITY_FOUND_FOR_GIVEN_DETAILS"; + public static final String NO_PLAN_FACILITY_FOUND_FOR_GIVEN_DETAILS_MESSAGE = "Plan facilities do not exists for the given details. "; + + public static final String README_SHEET_NAME_LOCALISATION_NOT_FOUND_CODE = "README_SHEET_NAME_LOCALISATION_NOT_FOUND"; + public static final String README_SHEET_NAME_LOCALISATION_NOT_FOUND_MESSAGE = "Constant defined for error message when the README sheet name localization is not found or plan facilities do not exist for the provided details."; + + public static final String NO_MDMS_DATA_FOUND_FOR_MIXED_STRATEGY_MASTER_CODE = "NO_MDMS_DATA_FOUND_FOR_MIXED_STRATEGY_MASTER"; + public static final String NO_MDMS_DATA_FOUND_FOR_MIXED_STRATEGY_MASTER_CODE_MESSAGE = "Master data not found for Mixed Strategy master"; + public static final String BOUNDARY_CODE = "HCM_ADMIN_CONSOLE_BOUNDARY_CODE"; public static final String TOTAL_POPULATION = "HCM_ADMIN_CONSOLE_TOTAL_POPULATION"; + public static final String ERROR_PROCESSING_DATA_FROM_MDMS = "Exception occurred while processing data from mdms "; public static final String ERROR_WHILE_FETCHING_FROM_PLAN_SERVICE_FOR_LOCALITY = "Exception occurred while fetching plan configuration from plan service for Locality "; public static final String ERROR_WHILE_PUSHING_TO_PLAN_SERVICE_FOR_LOCALITY = "Exception occurred while fetching plan configuration from plan service for Locality "; public static final String ERROR_WHILE_SEARCHING_CAMPAIGN = "Exception occurred while searching/updating campaign."; public static final String ERROR_WHILE_DATA_CREATE_CALL = "Exception occurred while creating data for campaign - "; public static final String ERROR_WHILE_CALLING_MICROPLAN_API = "Unexpected error while calling fetch from Microplan API for plan config Id: "; + public static final String INVALID_HEX = "Invalid hex color specified: "; + + public static final String DISTRIBUTION_PROCESS = "DistributionProcess"; + public static final String REGISTRATION_PROCESS = "RegistrationProcess"; + //File constants public static final String FILE_NAME = "output.xls"; public static final String FILE_TYPE = "boundaryWithTarget"; public static final String FILE_TEMPLATE_IDENTIFIER_POPULATION = "Population"; + public static final String FILE_TEMPLATE_IDENTIFIER_BOUNDARY = "boundaryWithTarget"; public static final String FILE_TEMPLATE_IDENTIFIER_FACILITY = "Facilities"; public static final String INPUT_IS_NOT_VALID = "File does not contain valid input for row "; - + + //Mdms constants and masters public static final String MDMS_SCHEMA_TYPE = "type"; public static final String MDMS_SCHEMA_SECTION = "section"; + public static final String MDMS_SCHEMA_TITLE = "title"; public static final String MDMS_PLAN_MODULE_NAME = "hcm-microplanning"; public static final String MDMS_MASTER_SCHEMAS = "Schemas"; + public static final String MDMS_MASTER_ADMIN_SCHEMA = "adminSchema"; public static final String MDMS_CAMPAIGN_TYPE = "campaignType"; public static final String MDMS_SCHEMA_ADMIN_SCHEMA = "adminSchema"; + public static final String MDMS_MASTER_MIXED_STRATEGY = "MixedStrategyOperationLogic"; public static final String MDMS_ADMIN_CONSOLE_MODULE_NAME = "HCM-ADMIN-CONSOLE"; public static final String BOUNDARY = "boundary"; public static final String DOT_SEPARATOR = "."; public static final String MICROPLAN_PREFIX = "MP-"; + public static final Double BRIGHTEN_FACTOR = 1.1; + public static final String ACCESSIBILITY_DETAILS = "accessibilityDetails"; + public static final String SECURITY_DETAILS = "securityDetails"; + public static final String EMPTY_STRING = ""; + public static final String NOT_APPLICABLE = "N/A"; + public static final String FIXED_POST_YES = "yes"; //MDMS field Constants public static final String DATA = "data"; @@ -82,8 +118,11 @@ public class ServiceConstants { public static final String STRING_PROPERTIES = "stringProperties"; public static final String NAME = "name"; + //Error messages public static final String ERROR_WHILE_UPDATING_PLAN_CONFIG = "Exception occurred while updating plan configuration."; - public static final String ERROR_WHILE_SEARCHING_PLAN = "Exception occurred while search plans."; + public static final String ERROR_WHILE_SEARCHING_PLAN = "Exception occurred while searching plans."; + public static final String ERROR_WHILE_SEARCHING_PLAN_FACILITY = "Exception occurred while searching plan facility : "; + public static final String ERROR_WHILE_CREATING_CELL_STYLE = "Failed to create cell style : "; public static final String VALIDATE_STRING_REGX = "^(?!\\d+$).+$"; public static final String VALIDATE_NUMBER_REGX = "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$"; @@ -112,6 +151,10 @@ public class ServiceConstants { //Facility Create constants public static final String TYPE_FACILITY = "facility"; + public static final String FACILITY_NAME = "facilityName"; + public static final String FACILITY_ID = "facilityId"; + public static final String HCM_MICROPLAN_SERVING_FACILITY = "HCM_MICROPLAN_SERVING_FACILITY"; + public static final String FIXED_POST = "fixedPost"; public static final String ACTION_CREATE = "create"; public static final String SOURCE_KEY = "source"; public static final String MICROPLAN_SOURCE_KEY = "microplan"; @@ -120,4 +163,11 @@ public class ServiceConstants { //Census additional field constants public static final String UPLOADED_KEY = "UPLOADED_"; public static final String CONFIRMED_KEY = "CONFIRMED_"; + public static final String CODE = "code"; + + //Excel header row styling constants + public static final String HEX_BACKGROUND_COLOR = "93C47D"; // Background color in HEX format (RRGGBB) for Excel header rows + public static final boolean FREEZE_CELL = true; // Controls whether cells should be locked for editing + public static final int COLUMN_WIDTH = 40; //Default column width in characters (1-255) + public static final int COLUMN_PADDING = 512; } diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/service/ExcelParser.java b/health-services/resource-generator/src/main/java/org/egov/processor/service/ExcelParser.java index 6f2326d628a..56a4e6de9ce 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/service/ExcelParser.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/service/ExcelParser.java @@ -13,15 +13,21 @@ import org.egov.processor.config.ServiceConstants; import org.egov.processor.util.*; import org.egov.processor.web.models.*; -import org.egov.processor.web.models.Locale; import org.egov.processor.web.models.boundary.BoundarySearchResponse; import org.egov.processor.web.models.boundary.EnrichedBoundary; import org.egov.processor.web.models.campaignManager.Boundary; import org.egov.processor.web.models.campaignManager.CampaignResources; import org.egov.processor.web.models.campaignManager.CampaignResponse; +import org.egov.processor.web.models.mdmsV2.MixedStrategyOperationLogic; +import org.egov.processor.web.models.planFacility.PlanFacility; +import org.egov.processor.web.models.planFacility.PlanFacilityResponse; +import org.egov.processor.web.models.planFacility.PlanFacilitySearchCriteria; +import org.egov.processor.web.models.planFacility.PlanFacilitySearchRequest; import org.egov.tracer.model.CustomException; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import java.io.File; import java.io.FileNotFoundException; @@ -31,8 +37,7 @@ import java.util.*; import java.util.stream.Collectors; -import static org.egov.processor.config.ServiceConstants.HCM_ADMIN_CONSOLE_BOUNDARY_DATA; -import static org.egov.processor.config.ServiceConstants.READ_ME_SHEET_NAME; +import static org.egov.processor.config.ServiceConstants.*; @Slf4j @Service @@ -64,9 +69,15 @@ public class ExcelParser implements FileParser { private PlanConfigurationUtil planConfigurationUtil; + private OutputEstimationGenerationUtil outputEstimationGenerationUtil; + + private MixedStrategyUtil mixedStrategyUtil; + + private PlanFacilityUtil planFacilityUtil; + public ExcelParser(ObjectMapper objectMapper, ParsingUtil parsingUtil, FilestoreUtil filestoreUtil, - CalculationUtil calculationUtil, PlanUtil planUtil, CampaignIntegrationUtil campaignIntegrationUtil, - Configuration config, MdmsUtil mdmsUtil, BoundaryUtil boundaryUtil, LocaleUtil localeUtil, CensusUtil censusUtil, EnrichmentUtil enrichmentUtil, PlanConfigurationUtil planConfigurationUtil) { + CalculationUtil calculationUtil, PlanUtil planUtil, CampaignIntegrationUtil campaignIntegrationUtil, + Configuration config, MdmsUtil mdmsUtil, BoundaryUtil boundaryUtil, LocaleUtil localeUtil, CensusUtil censusUtil, EnrichmentUtil enrichmentUtil, PlanConfigurationUtil planConfigurationUtil, OutputEstimationGenerationUtil outputEstimationGenerationUtil, MixedStrategyUtil mixedStrategyUtil, PlanFacilityUtil planFacilityUtil) { this.objectMapper = objectMapper; this.parsingUtil = parsingUtil; this.filestoreUtil = filestoreUtil; @@ -80,6 +91,9 @@ public ExcelParser(ObjectMapper objectMapper, ParsingUtil parsingUtil, Filestore this.censusUtil = censusUtil; this.enrichmentUtil = enrichmentUtil; this.planConfigurationUtil = planConfigurationUtil; + this.outputEstimationGenerationUtil = outputEstimationGenerationUtil; + this.mixedStrategyUtil = mixedStrategyUtil; + this.planFacilityUtil = planFacilityUtil; } /** @@ -123,12 +137,10 @@ private void processExcelFile(PlanConfigurationRequest planConfigurationRequest, Object campaignResponse) { try (Workbook workbook = new XSSFWorkbook(file)) { List campaignBoundaryList = new ArrayList<>(); - List campaignResourcesList = new ArrayList<>(); DataFormatter dataFormatter = new DataFormatter(); processSheets(planConfigurationRequest, fileStoreId, campaignResponse, workbook, campaignBoundaryList, dataFormatter); - uploadFileAndIntegrateCampaign(planConfigurationRequest, campaignResponse, - workbook, campaignBoundaryList, campaignResourcesList); + uploadFileAndIntegrateCampaign(planConfigurationRequest, workbook, fileStoreId); } catch (FileNotFoundException e) { log.error("File not found: {}", e.getMessage()); throw new CustomException("FileNotFound", "The specified file was not found."); @@ -144,16 +156,12 @@ private void processExcelFile(PlanConfigurationRequest planConfigurationRequest, /** * Uploads a converted file and integrates campaign details if configured to do so. - * + * * @param planConfigurationRequest The request containing configuration details including tenant ID. - * @param campaignResponse The response object containing campaign details. - * @param workbook The workbook containing data to be uploaded and integrated. - * @param campaignBoundaryList List of boundary objects related to the campaign. - * @param campaignResourcesList List of campaign resources to be integrated. + * @param workbook The workbook containing data to be uploaded and integrated. + * @param filestoreId The ID of the file in the file store. */ - private void uploadFileAndIntegrateCampaign(PlanConfigurationRequest planConfigurationRequest, - Object campaignResponse, Workbook workbook, - List campaignBoundaryList, List campaignResourcesList) { + private void uploadFileAndIntegrateCampaign(PlanConfigurationRequest planConfigurationRequest, Workbook workbook, String filestoreId) { File fileToUpload = null; try { PlanConfiguration planConfig = planConfigurationRequest.getPlanConfiguration(); @@ -164,9 +172,19 @@ private void uploadFileAndIntegrateCampaign(PlanConfigurationRequest planConfigu planUtil.update(planConfigurationRequest); } if (planConfig.getStatus().equals(config.getPlanConfigUpdatePlanEstimatesIntoOutputFileStatus()) && config.isIntegrateWithAdminConsole()) { + //Upload the processed output file into project factory String uploadedFileStoreId = uploadConvertedFile(fileToUpload, planConfig.getTenantId()); campaignIntegrationUtil.updateResourcesInProjectFactory(planConfigurationRequest, uploadedFileStoreId); - } + + //process output file for localized header columns and addition of new columns + outputEstimationGenerationUtil.processOutputFile(workbook, planConfigurationRequest, filestoreId); + + //upload the processed output file and update the same into plan configuration file object + fileToUpload = convertWorkbookToXls(workbook); + uploadedFileStoreId = uploadConvertedFile(fileToUpload, planConfig.getTenantId()); + planUtil.setFileStoreIdForPopulationTemplate(planConfigurationRequest, uploadedFileStoreId); + planUtil.update(planConfigurationRequest); + } } finally { try { if (fileToUpload != null && !fileToUpload.delete()) { @@ -189,7 +207,6 @@ private void uploadFileAndIntegrateCampaign(PlanConfigurationRequest planConfigu * @param campaignBoundaryList List of boundary objects related to the campaign. * @param dataFormatter The data formatter for formatting cell values. */ - private void processSheets(PlanConfigurationRequest request, String fileStoreId, Object campaignResponse, Workbook excelWorkbook, List campaignBoundaryList, @@ -212,12 +229,20 @@ private void processSheets(PlanConfigurationRequest request, String fileStoreId, (existing, replacement) -> existing, LinkedHashMap::new )); + + // Fetch mdms data for common constants + Map mdmsDataForCommonConstants = mdmsUtil.fetchMdmsDataForCommonConstants( + request.getRequestInfo(), + request.getPlanConfiguration().getTenantId()); + excelWorkbook.forEach(excelWorkbookSheet -> { - if (isSheetAllowedToProcess(request, excelWorkbookSheet.getSheetName(), localeResponse)) { + if (outputEstimationGenerationUtil.isSheetAllowedToProcess(excelWorkbookSheet.getSheetName(), localeResponse, mdmsDataForCommonConstants)) { if (request.getPlanConfiguration().getStatus().equals(config.getPlanConfigTriggerPlanEstimatesStatus())) { - enrichmentUtil.enrichsheetWithApprovedCensusRecords(excelWorkbookSheet, request, fileStoreId, mappedValues); + Map boundaryCodeToCensusAdditionalDetails = new HashMap<>(); + + enrichmentUtil.enrichsheetWithApprovedCensusRecords(excelWorkbookSheet, request, fileStoreId, mappedValues, boundaryCodeToCensusAdditionalDetails); processRows(request, excelWorkbookSheet, dataFormatter, fileStoreId, - campaignBoundaryList, attributeNameVsDataTypeMap, boundaryCodeList); + campaignBoundaryList, attributeNameVsDataTypeMap, boundaryCodeList, boundaryCodeToCensusAdditionalDetails); } else if (request.getPlanConfiguration().getStatus().equals(config.getPlanConfigTriggerCensusRecordsStatus())) { processRowsForCensusRecords(request, excelWorkbookSheet, fileStoreId, attributeNameVsDataTypeMap, boundaryCodeList, campaign.getCampaign().get(0).getHierarchyType()); @@ -228,6 +253,54 @@ private void processSheets(PlanConfigurationRequest request, String fileStoreId, }); } + /** + * This method makes plan facility search call and creates a map of boundary code to it's fixed post facility details. + * + * @param request the plan configuration request. + * @param sheet the Excel sheet to be processed. + * @param fileStoreId the fileStore id of the file. + * @return returns a map of boundary code to it's fixed post facility details. + */ + private Map fetchFixedPostDetails(PlanConfigurationRequest request, Sheet sheet, String fileStoreId) { + PlanConfiguration planConfiguration = request.getPlanConfiguration(); + + //Create plan facility search request + PlanFacilitySearchRequest searchRequest = PlanFacilitySearchRequest.builder() + .requestInfo(request.getRequestInfo()) + .planFacilitySearchCriteria(PlanFacilitySearchCriteria.builder() + .tenantId(planConfiguration.getTenantId()) + .planConfigurationId(planConfiguration.getId()) + .build()) + .build(); + + PlanFacilityResponse planFacilityResponse = planFacilityUtil.search(searchRequest); + + if (ObjectUtils.isEmpty(planFacilityResponse) || CollectionUtils.isEmpty(planFacilityResponse.getPlanFacility())) { + throw new CustomException(NO_PLAN_FACILITY_FOUND_FOR_GIVEN_DETAILS_CODE, NO_PLAN_FACILITY_FOUND_FOR_GIVEN_DETAILS_MESSAGE); + } + + // Create a Boundary Code to Facility's fixed post detail map. + Map boundaryCodeToFixedPostMap = new HashMap<>(); + + for (PlanFacility planFacility : planFacilityResponse.getPlanFacility()) { + // Ensure serviceBoundaries are not empty + if (!CollectionUtils.isEmpty(planFacility.getServiceBoundaries())) { + + // Extract the 'FIXED_POST' field from additional details. + String fixedPostValue = (String) parsingUtil.extractFieldsFromJsonObject(planFacility.getAdditionalDetails(), FIXED_POST); + + // Normalize the value and determine boolean equivalent. + Boolean isFixedPost = !ObjectUtils.isEmpty(fixedPostValue) && fixedPostValue.trim().equalsIgnoreCase(FIXED_POST_YES); + + // Populate the map with boundary code and isFixedPost. + planFacility.getServiceBoundaries().forEach((String boundary) -> + boundaryCodeToFixedPostMap.put(boundary, isFixedPost) + ); + } + } + return boundaryCodeToFixedPostMap; + } + /** * Processes rows of data in an Excel sheet, performs calculations, updates * campaign boundaries, and creates plans. @@ -246,9 +319,13 @@ private void processSheets(PlanConfigurationRequest request, String fileStoreId, * @param boundaryCodeList List of boundary codes. * @throws IOException If an I/O error occurs. */ - private void processRows(PlanConfigurationRequest planConfigurationRequest, Sheet sheet, DataFormatter dataFormatter, String fileStoreId, List campaignBoundaryList, Map attributeNameVsDataTypeMap, List boundaryCodeList) { - PlanConfiguration planConfig = planConfigurationRequest.getPlanConfiguration(); - performRowLevelCalculations(planConfigurationRequest, sheet, dataFormatter, fileStoreId, campaignBoundaryList, planConfig, attributeNameVsDataTypeMap, boundaryCodeList); + private void processRows(PlanConfigurationRequest planConfigurationRequest, Sheet sheet, DataFormatter dataFormatter, + String fileStoreId, List campaignBoundaryList, Map attributeNameVsDataTypeMap, + List boundaryCodeList, Map boundaryCodeToCensusAdditionalDetails) { + + // Create a Map of Boundary Code to Facility's fixed post detail. + Map boundaryCodeToFixedPostMap = fetchFixedPostDetails(planConfigurationRequest, sheet, fileStoreId); + performRowLevelCalculations(planConfigurationRequest, sheet, dataFormatter, fileStoreId, campaignBoundaryList, attributeNameVsDataTypeMap, boundaryCodeList, boundaryCodeToFixedPostMap, boundaryCodeToCensusAdditionalDetails); } private void processRowsForCensusRecords(PlanConfigurationRequest planConfigurationRequest, Sheet sheet, String fileStoreId, Map attributeNameVsDataTypeMap, List boundaryCodeList, String hierarchyType) { @@ -312,14 +389,11 @@ private List getBoundaryCodeList(PlanConfigurationRequest planConfigurat * @return A map of attribute names to their corresponding indices or data types. */ - - //TODO: fetch from adminSchema master private Map prepareAttributeVsIndexMap(PlanConfigurationRequest planConfigurationRequest, String fileStoreId, CampaignResponse campaign, PlanConfiguration planConfig, Object mdmsData) { org.egov.processor.web.models.File file = planConfig.getFiles().stream() .filter(f -> f.getFilestoreId().equalsIgnoreCase(fileStoreId)).findFirst().get(); - return mdmsUtil.filterMasterData(mdmsData.toString(), file.getInputFileType(), - file.getTemplateIdentifier(), campaign.getCampaign().get(0).getProjectType()); + return mdmsUtil.filterMasterData(mdmsData.toString(), campaign.getCampaign().get(0).getProjectType()); } @@ -333,14 +407,15 @@ private Map prepareAttributeVsIndexMap(PlanConfigurationRequest * @param dataFormatter The data formatter for formatting cell values. * @param fileStoreId The ID of the uploaded file in the file store. * @param campaignBoundaryList List of boundary objects related to the campaign. - * @param planConfig The configuration details specific to the plan. * @param attributeNameVsDataTypeMap Mapping of attribute names to their data types. * @param boundaryCodeList List of boundary codes. */ private void performRowLevelCalculations(PlanConfigurationRequest planConfigurationRequest, Sheet sheet, DataFormatter dataFormatter, String fileStoreId, List campaignBoundaryList, - PlanConfiguration planConfig, Map attributeNameVsDataTypeMap, List boundaryCodeList) { + Map attributeNameVsDataTypeMap, List boundaryCodeList, + Map boundaryCodeToFixedPostMap, Map boundaryCodeToCensusAdditionalDetails) { Row firstRow = null; + PlanConfiguration planConfig = planConfigurationRequest.getPlanConfiguration(); Map mappedValues = planConfig.getResourceMapping().stream() .filter(f -> f.getFilestoreId().equals(fileStoreId)) .collect(Collectors.toMap(ResourceMapping::getMappedTo, ResourceMapping::getMappedFrom)); @@ -351,6 +426,9 @@ private void performRowLevelCalculations(PlanConfigurationRequest planConfigurat Integer indexOfBoundaryCode = parsingUtil.getIndexOfBoundaryCode(0, parsingUtil.sortColumnByIndex(mapOfColumnNameAndIndex), mappedValues); + List mixedStrategyOperationLogicList = mixedStrategyUtil + .fetchMixedStrategyOperationLogicFromMDMS(planConfigurationRequest); + for (Row row : sheet) { if(parsingUtil.isRowEmpty(row)) continue; @@ -366,10 +444,11 @@ private void performRowLevelCalculations(PlanConfigurationRequest planConfigurat JsonNode feature = createFeatureNodeFromRow(row, mapOfColumnNameAndIndex); performCalculationsOnOperations(sheet, planConfig, row, resultMap, mappedValues, assumptionValueMap, feature); - if (config.isIntegrateWithAdminConsole()) - campaignIntegrationUtil.updateCampaignBoundary(planConfig, feature, assumptionValueMap, mappedValues, - mapOfColumnNameAndIndex, campaignBoundaryList, resultMap); - planUtil.create(planConfigurationRequest, feature, resultMap, mappedValues); + + // Get Boundary Code for the current row. + String boundaryCode = row.getCell(indexOfBoundaryCode).getStringCellValue(); + mixedStrategyUtil.processResultMap(resultMap, planConfig.getOperations(), mixedStrategyUtil.getCategoriesNotAllowed(boundaryCodeToFixedPostMap.get(boundaryCode), planConfig, mixedStrategyOperationLogicList)); + planUtil.create(planConfigurationRequest, feature, resultMap, mappedValues, boundaryCodeToCensusAdditionalDetails); } } @@ -400,10 +479,12 @@ private void performCalculationsOnOperations(Sheet sheet, PlanConfiguration plan Cell cell = row.createCell(columnIndex++); cell.setCellValue(result.doubleValue()); + cell.getCellStyle().setLocked(false); // Ensure the new cell is editable if (row.getRowNum() == 1) { Cell headerCell = sheet.getRow(0).createCell(row.getLastCellNum() - 1); headerCell.setCellValue(output); + headerCell.getCellStyle().setLocked(true); } } @@ -717,29 +798,6 @@ public List getAllBoundaryPresentforHierarchyType(List } return boundaryList; } - - /** - * Checks if a sheet is allowed to be processed based on MDMS constants and locale-specific configuration. - * - * @param planConfigurationRequest The request containing configuration details including request info and tenant ID. - * @param sheetName The name of the sheet to be processed. - * @return true if the sheet is allowed to be processed, false otherwise. - * @throws JsonMappingException If there's an issue mapping JSON response to Java objects. - * @throws JsonProcessingException If there's an issue processing JSON during conversion. - */ - private boolean isSheetAllowedToProcess(PlanConfigurationRequest planConfigurationRequest, String sheetName, LocaleResponse localeResponse) { - Map mdmsDataConstants = mdmsUtil.fetchMdmsDataForCommonConstants( - planConfigurationRequest.getRequestInfo(), - planConfigurationRequest.getPlanConfiguration().getTenantId()); - - for (Locale locale : localeResponse.getMessages()) { - if ((locale.getCode().equalsIgnoreCase((String) mdmsDataConstants.get(READ_ME_SHEET_NAME))) - || locale.getCode().equalsIgnoreCase(HCM_ADMIN_CONSOLE_BOUNDARY_DATA)) { - if (sheetName.equals(locale.getMessage())) - return false; - } - } - return true; - } + } \ No newline at end of file diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/CalculationUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/CalculationUtil.java index 551206d9b94..d53a749cbff 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/util/CalculationUtil.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/CalculationUtil.java @@ -13,6 +13,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -78,7 +79,7 @@ public void calculateResources(JsonNode jsonNode, PlanConfigurationRequest planC resultMap.put(output, result); ((ObjectNode) feature.get("properties")).put(output, result); } - planUtil.create(planConfigurationRequest,feature,resultMap,mappedValues); + planUtil.create(planConfigurationRequest, feature, resultMap, mappedValues, new HashMap<>()); } } diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/CampaignIntegrationUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/CampaignIntegrationUtil.java index 9bfbe7cb62c..aa394c5a338 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/util/CampaignIntegrationUtil.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/CampaignIntegrationUtil.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; import org.egov.processor.config.Configuration; import org.egov.processor.config.ServiceConstants; import org.egov.processor.repository.ServiceRequestRepository; @@ -14,10 +13,7 @@ import org.egov.tracer.model.CustomException; import org.springframework.stereotype.Component; -import java.io.IOException; -import java.math.BigDecimal; import java.util.*; -import java.util.Map.Entry; import static org.egov.processor.config.ServiceConstants.*; @@ -28,15 +24,11 @@ public class CampaignIntegrationUtil { private ServiceRequestRepository serviceRequestRepository; private Configuration config; private ObjectMapper mapper; - private ParsingUtil parsingUtil; - - public CampaignIntegrationUtil(ServiceRequestRepository serviceRequestRepository, Configuration config, - ObjectMapper mapper, FilestoreUtil filestoreUtil, ParsingUtil parsingUtil) { + public CampaignIntegrationUtil(ServiceRequestRepository serviceRequestRepository, Configuration config, ObjectMapper mapper) { this.serviceRequestRepository = serviceRequestRepository; this.config = config; this.mapper = mapper; - this.parsingUtil= parsingUtil; } /** @@ -82,34 +74,6 @@ private MicroplanDetailsRequest buildMicroplanDetailsForUpdate(PlanConfiguration } - /** - * Updates campaign details based on the provided plan configuration request and response data. - * This method integrates the campaign details obtained from the response into the provided plan configuration request. - * It also updates the campaign boundaries and resources accordingly. - * - * @param planConfigurationRequest The plan configuration request containing the execution plan details. - * @param response The response object containing campaign details. - * @param campaignBoundaryList The list of campaign boundaries. - * @param campaignResourcesList The list of campaign resources. - */ - public void updateCampaignDetails(PlanConfigurationRequest planConfigurationRequest,Object response,List campaignBoundaryList,List campaignResourcesList) { - CampaignResponse campaignResponse = null; - try { - campaignResponse = mapper.convertValue(response, CampaignResponse.class); - campaignResponse.getCampaign().get(0).setResources(campaignResourcesList); - Boundary[] array = campaignBoundaryList.toArray(new Boundary[0]); - campaignResponse.getCampaign().get(0).setBoundaries(campaignBoundaryList.toArray(new Boundary[0])); - serviceRequestRepository.fetchResult( - new StringBuilder(config.getProjectFactoryHostEndPoint() + config.getCampaignIntegrationUpdateEndPoint()), - buildCampaignRequestForUpdate(planConfigurationRequest, campaignResponse)); - log.info("Campaign Integration successful."); - } catch (Exception e) { - log.error(ServiceConstants.ERROR_WHILE_SEARCHING_CAMPAIGN - + planConfigurationRequest.getPlanConfiguration().getCampaignId(), e); - throw new CustomException("Failed to update campaign details in CampaignIntegration class within method updateCampaignDetails.", e.toString()); - } - } - /** * Sends a data creation request to the Project Factory service using the provided * plan and campaign details. @@ -131,41 +95,6 @@ public void createProjectFactoryDataCall(PlanConfigurationRequest planConfigurat } } - /** - * Updates the campaign resources in the given campaign response based on the files specified in the plan configuration request. - * - * @param campaignResponse The campaign response object to be updated with resources. - * @param planConfigurationRequest The plan configuration request containing file information. - * @param fileStoreId The file store ID. - */ - public void updateResources(CampaignResponse campaignResponse, PlanConfigurationRequest planConfigurationRequest, - String fileStoreId) { - List campaignResourcesList = new ArrayList<>(); - List files = planConfigurationRequest.getPlanConfiguration().getFiles(); - for (File file : files) { - CampaignResources campaignResource = new CampaignResources(); - campaignResource.setFilename(ServiceConstants.FILE_NAME); - campaignResource.setFilestoreId(fileStoreId); - campaignResource.setType(ServiceConstants.FILE_TYPE); - campaignResourcesList.add(campaignResource); - } - campaignResponse.getCampaign().get(0).setResources(campaignResourcesList); - } - - /** - * Builds a campaign request object for updating campaign details based on the provided plan configuration request and campaign response. - * - * @param planConfigurationRequest The plan configuration request containing necessary information for updating the campaign. - * @param campaignResponse The campaign response containing the updated campaign details. - * @return The campaign request object built for updating campaign details. - */ - private CampaignRequest buildCampaignRequestForUpdate(PlanConfigurationRequest planConfigurationRequest, - CampaignResponse campaignResponse) { - return CampaignRequest.builder().requestInfo(planConfigurationRequest.getRequestInfo()) - .campaignDetails(campaignResponse.getCampaign().get(0)).build(); - - } - /** * Builds a {@link ResourceDetailsRequest} object for facility creation using the provided * plan configuration and campaign details. @@ -202,121 +131,6 @@ private ResourceDetailsRequest buildResourceDetailsObjectForFacilityCreate(PlanC } - /** - * Updates campaign boundary based on the provided plan configuration, feature, assumption values, mapped values, column index map, boundary list, and result map. - * - * @param planConfig The plan configuration containing relevant details. - * @param feature The JSON node representing the feature. - * @param assumptionValueMap The map containing assumption values. - * @param mappedValues The map containing mapped values. - * @param mapOfColumnNameAndIndex The map containing column names and their indices. - * @param boundaryList The list of campaign boundaries to update. - * @param resultMap The map containing result values. - * @throws IOException If an I/O error occurs. - */ - public void updateCampaignBoundary(PlanConfiguration planConfig, JsonNode feature, - Map assumptionValueMap, Map mappedValues, - Map mapOfColumnNameAndIndex, List boundaryList, - Map resultMap) { - Integer indexOfType = null; - boolean validToAdd = false; - Integer indexValue = 0; - Boundary boundary = new Boundary(); - List> sortedColumnList = parsingUtil.sortColumnByIndex(mapOfColumnNameAndIndex); - indexValue = parsingUtil.getIndexOfBoundaryCode(indexValue, sortedColumnList, mappedValues); - prepareBoundary(indexOfType, indexValue, sortedColumnList, feature, boundary, mappedValues); - if (isValidToAdd(boundaryList, resultMap, validToAdd, boundary)) - boundaryList.add(boundary); - } - - - /** - * Prepares a campaign boundary based on the provided index values, sorted column list, feature, and mapped values. - * - * @param indexOfType The index of the boundary type. - * @param indexValue The index value. - * @param sortedColumnList The sorted list of column names and indices. - * @param feature The JSON node representing the feature. - * @param boundary The boundary object to be prepared. - * @param mappedValues The map containing mapped values. - * @return The index of the boundary type after preparation. - */ - private Integer prepareBoundary(Integer indexOfType, Integer indexValue, - List> sortedColumnList, JsonNode feature, Boundary boundary,Map mappedValues) { - String codeValue = getBoundaryCodeValue(ServiceConstants.BOUNDARY_CODE, feature, mappedValues); - boundary.setCode(codeValue); - for (int j = 0; j < indexValue; j++) { - Map.Entry entry = sortedColumnList.get(j); - String value = String.valueOf(feature.get(PROPERTIES).get(entry.getKey())); - if (StringUtils.isNotBlank(value) && value.length() > 2) { - boundary.setType(entry.getKey()); - indexOfType = entry.getValue(); - } - } - if (indexOfType == 0) { - boundary.setRoot(true); - boundary.setIncludeAllChildren(true); - } - return indexOfType; - } - - /** - * Checks if the provided boundary is valid to add to the boundary list based on the result map. - * - * @param boundaryList The list of existing boundaries. - * @param resultMap The map containing result values. - * @param validToAdd The flag indicating whether the boundary is valid to add. - * @param boundary The boundary to be checked for validity. - * @return True if the boundary is valid to add, false otherwise. - */ - private boolean isValidToAdd(List boundaryList, Map resultMap, boolean validToAdd, - Boundary boundary) { - for (Entry entry : resultMap.entrySet()) { - if (entry.getValue().compareTo(new BigDecimal(0)) > 0) { - validToAdd = true; - } else { - validToAdd = false; - break; - } - } - return validToAdd; - } - - - - /** - * Retrieves the value of the boundary code from the feature JSON node based on the mapped values. - * - * @param input The input key. - * @param feature The JSON node representing the feature. - * @param mappedValues The map containing mapped values. - * @return The value of the boundary code. - * @throws CustomException If the input value is not found in the feature JSON node. - */ - private String getBoundaryCodeValue(String input, JsonNode feature, Map mappedValues) { - if (feature.get(PROPERTIES).get(mappedValues.get(input)) != null) { - String value = String.valueOf(feature.get(PROPERTIES).get(mappedValues.get(input))); - return ((value != null && value.length() > 2) ? value.substring(1, value.length() - 1) : value); - } else { - throw new CustomException("INPUT_VALUE_NOT_FOUND", "Input value not found: " + input); - } - } - - /** - * Updates campaign resources with the provided file store ID. - * - * @param fileStoreId The file store ID. - * @param campaignResourcesList The list of campaign resources to update. - */ - public void updateCampaignResources(String fileStoreId,List campaignResourcesList,String fileName) { - CampaignResources campaignResource = new CampaignResources(); - campaignResource.setFilename(fileName); - campaignResource.setFilestoreId(fileStoreId); - campaignResource.setType(ServiceConstants.FILE_TYPE); - campaignResourcesList.add(campaignResource); - - } - /** * Builds a campaign search request based on the provided plan configuration request. * diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/EnrichmentUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/EnrichmentUtil.java index 89f0a89511b..20414382473 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/util/EnrichmentUtil.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/EnrichmentUtil.java @@ -18,10 +18,12 @@ import org.egov.processor.web.models.mdmsV2.Mdms; import org.egov.tracer.model.CustomException; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -43,12 +45,10 @@ public class EnrichmentUtil { private PlanUtil planUtil; private Configuration config; -// private MultiStateInstanceUtil centralInstanceUtil; public EnrichmentUtil(MdmsV2Util mdmsV2Util, LocaleUtil localeUtil, ParsingUtil parsingUtil, CensusUtil censusUtil, PlanUtil planUtil, Configuration config) { this.mdmsV2Util = mdmsV2Util; this.localeUtil = localeUtil; -// this.centralInstanceUtil = centralInstanceUtil; this.parsingUtil = parsingUtil; this.censusUtil = censusUtil; this.planUtil = planUtil; @@ -65,7 +65,6 @@ public EnrichmentUtil(MdmsV2Util mdmsV2Util, LocaleUtil localeUtil, ParsingUtil */ public void enrichResourceMapping(PlanConfigurationRequest request, LocaleResponse localeResponse, String campaignType, String fileStoreId) { -// String rootTenantId = centralInstanceUtil.getStateLevelTenant(request.getPlanConfiguration().getTenantId()); String rootTenantId = request.getPlanConfiguration().getTenantId().split("\\.")[0]; String uniqueIndentifier = BOUNDARY + DOT_SEPARATOR + MICROPLAN_PREFIX + campaignType; List mdmsV2Data = mdmsV2Util.fetchMdmsV2Data(request.getRequestInfo(), rootTenantId, MDMS_ADMIN_CONSOLE_MODULE_NAME + DOT_SEPARATOR + MDMS_SCHEMA_ADMIN_SCHEMA, uniqueIndentifier); @@ -89,7 +88,7 @@ public void enrichResourceMapping(PlanConfigurationRequest request, LocaleRespon } - public void enrichsheetWithApprovedCensusRecords(Sheet sheet, PlanConfigurationRequest planConfigurationRequest, String fileStoreId, Map mappedValues) { + public void enrichsheetWithApprovedCensusRecords(Sheet sheet, PlanConfigurationRequest planConfigurationRequest, String fileStoreId, Map mappedValues, Map boundaryCodeToCensusAdditionalDetails) { List boundaryCodes = getBoundaryCodesFromTheSheet(sheet, planConfigurationRequest, fileStoreId); Map mapOfColumnNameAndIndex = parsingUtil.getAttributeNameIndexFromExcel(sheet); @@ -143,6 +142,8 @@ public void enrichsheetWithApprovedCensusRecords(Sheet sheet, PlanConfigurationR } } + + boundaryCodeToCensusAdditionalDetails.put(boundaryCode, census.getAdditionalDetails()); } log.info("Successfully update file with approved census data."); @@ -220,10 +221,10 @@ public void enrichsheetWithApprovedPlanEstimates(Sheet sheet, PlanConfigurationR Integer indexOfBoundaryCode = parsingUtil.getIndexOfBoundaryCode(0, parsingUtil.sortColumnByIndex(mapOfColumnNameAndIndex), mappedValues); - //Getting census records for the list of boundaryCodes + //Getting plan records for the list of boundaryCodes List planList = getPlanRecordsForEnrichment(planConfigurationRequest, boundaryCodes); - // Create a map from boundaryCode to Census for quick lookups + // Create a map from boundaryCode to Plan for quick lookups Map planMap = planList.stream() .collect(Collectors.toMap(Plan::getLocality, plan -> plan)); @@ -244,8 +245,13 @@ public void enrichsheetWithApprovedPlanEstimates(Sheet sheet, PlanConfigurationR Plan planEstimate = planMap.get(boundaryCode); if (planEstimate != null) { - Map resourceTypeToEstimatedNumberMap = planEstimate.getResources().stream() - .collect(Collectors.toMap(Resource::getResourceType, Resource::getEstimatedNumber)); + Map resourceTypeToEstimatedNumberMap = new HashMap<>(); + + // If resources are not empty, iterate over each resource and map resourceType with it's estimatedValue. + if(!CollectionUtils.isEmpty(planEstimate.getResources())) + planEstimate.getResources().forEach(resource -> + resourceTypeToEstimatedNumberMap.put(resource.getResourceType(), resource.getEstimatedNumber())); + // Iterate over each output column to update the row cells with resource values for (String resourceType : outputColumnList) { @@ -262,6 +268,17 @@ public void enrichsheetWithApprovedPlanEstimates(Sheet sheet, PlanConfigurationR } cell.setCellValue(estimatedValue.doubleValue()); } + } else { + // If estimatedValue is null, set the cell to empty + Integer columnIndex = mapOfColumnNameAndIndex.get(resourceType); + if (columnIndex != null) { + // Ensure the cell is empty + Cell cell = row.getCell(columnIndex); + if (cell == null) { + cell = row.createCell(columnIndex); + } + cell.setCellValue(NOT_APPLICABLE); // Set as not applicable + } } } } diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/ExcelStylingUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/ExcelStylingUtil.java new file mode 100644 index 00000000000..bdd1b7bea09 --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/ExcelStylingUtil.java @@ -0,0 +1,113 @@ +package org.egov.processor.util; + +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.*; +import org.springframework.stereotype.Component; + +import java.awt.Color; + +import static org.egov.processor.config.ServiceConstants.*; + +@Component +public class ExcelStylingUtil { + + public void styleCell(Cell cell) { + if(cell == null) + return; + + Sheet sheet = cell.getSheet(); + Workbook workbook = sheet.getWorkbook(); + XSSFWorkbook xssfWorkbook = (XSSFWorkbook) workbook; + + // Create a cell style + XSSFCellStyle cellStyle; + try { + cellStyle = (XSSFCellStyle) workbook.createCellStyle(); + } catch (Exception e) { + throw new IllegalStateException(ERROR_WHILE_CREATING_CELL_STYLE, e); + } + + // Set background color + XSSFColor backgroundColor = hexToXSSFColor(HEX_BACKGROUND_COLOR, xssfWorkbook); + cellStyle.setFillForegroundColor(backgroundColor); + cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + // Set font style (bold) + XSSFFont font = (XSSFFont) workbook.createFont(); + font.setBold(true); + cellStyle.setFont(font); + + // Set alignment and wrap text + cellStyle.setAlignment(HorizontalAlignment.LEFT); + cellStyle.setVerticalAlignment(VerticalAlignment.TOP); + cellStyle.setWrapText(true); + + // Lock the cell if FREEZE_CELL is true + if (FREEZE_CELL) { + cellStyle.setLocked(true); + } + + // Apply the style to the cell + cell.setCellStyle(cellStyle); + + } + + /** + * Adjusts the column width to fit the content of the given cell, adding padding for readability. + * + * @param cell the cell whose column width is to be adjusted; does nothing if null. + */ + public void adjustColumnWidthForCell(Cell cell) { + if (cell == null) { + return; + } + + Sheet sheet = cell.getSheet(); + int columnIndex = cell.getColumnIndex(); + int maxWidth = sheet.getColumnWidth(columnIndex); + + // Calculate the width needed for the current cell content + String cellValue = cell.toString(); // Convert cell content to string + int cellWidth = cellValue.length() * 256; // Approximate width (1/256th of character width) + + // Use the maximum width seen so far, including padding for readability + int padding = COLUMN_PADDING; // Adjust padding as needed + int newWidth = Math.max(maxWidth, cellWidth + padding); + + sheet.setColumnWidth(columnIndex, newWidth); + } + + /** + * Converts a HEX color string to XSSFColor using the XSSFWorkbook context. + * + * @param hexColor The HEX color string (e.g., "93C47D"). + * @param xssfWorkbook The XSSFWorkbook context for styles. + * @return XSSFColor The corresponding XSSFColor object. + * @throws IllegalArgumentException if the hex color is null, empty, or in invalid format + */ + public static XSSFColor hexToXSSFColor(String hexColor, XSSFWorkbook xssfWorkbook) { + + if (hexColor == null || hexColor.length() < 6) + throw new IllegalArgumentException(INVALID_HEX + hexColor); + + // Convert HEX to RGB + int red = Integer.valueOf(hexColor.substring(0, 2), 16); + int green = Integer.valueOf(hexColor.substring(2, 4), 16); + int blue = Integer.valueOf(hexColor.substring(4, 6), 16); + + red = (int) (red * BRIGHTEN_FACTOR); // increase red component by 10% + green = (int) (green * BRIGHTEN_FACTOR); // increase green component by 10% + blue = (int) (blue * BRIGHTEN_FACTOR); // increase blue component by 10% + + // Clamp the values to be between 0 and 255 + red = Math.min(255, Math.max(0, red)); + green = Math.min(255, Math.max(0, green)); + blue = Math.min(255, Math.max(0, blue)); + + // Create IndexedColorMap from the workbook's styles source + IndexedColorMap colorMap = xssfWorkbook.getStylesSource().getIndexedColors(); + + // Create XSSFColor using the XSSFWorkbook context and colorMap + return new XSSFColor(new Color(red, green, blue), colorMap); + } +} diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsUtil.java index e3019ded61b..5c168f8d30e 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsUtil.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsUtil.java @@ -1,14 +1,6 @@ package org.egov.processor.util; -import static org.egov.processor.config.ServiceConstants.ERROR_WHILE_FETCHING_FROM_MDMS; -import static org.egov.processor.config.ServiceConstants.NO_MDMS_DATA_FOUND_FOR_GIVEN_TENANT_CODE; -import static org.egov.processor.config.ServiceConstants.NO_MDMS_DATA_FOUND_FOR_GIVEN_TENANT_MESSAGE; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import org.egov.common.contract.request.RequestInfo; import org.egov.mdms.model.MasterDetail; @@ -22,6 +14,7 @@ import org.egov.tracer.model.CustomException; import org.flywaydb.core.internal.util.JsonUtils; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.client.RestTemplate; @@ -31,6 +24,8 @@ import lombok.extern.slf4j.Slf4j; +import static org.egov.processor.config.ServiceConstants.*; + @Slf4j @Component public class MdmsUtil { @@ -41,11 +36,14 @@ public class MdmsUtil { private Configuration configs; - public MdmsUtil(RestTemplate restTemplate, ObjectMapper mapper, Configuration configs) { + private ParsingUtil parsingUtil; + + public MdmsUtil(RestTemplate restTemplate, ObjectMapper mapper, Configuration configs, ParsingUtil parsingUtil) { this.restTemplate = restTemplate; this.mapper = mapper; this.configs = configs; - } + this.parsingUtil = parsingUtil; + } /** * Fetches MDMS (Municipal Data Management System) data using the provided @@ -74,7 +72,7 @@ public Object fetchMdmsData(RequestInfo requestInfo, String tenantId) { if (result == null || ObjectUtils.isEmpty(result)) { log.error(NO_MDMS_DATA_FOUND_FOR_GIVEN_TENANT_MESSAGE + " - " + tenantId); throw new CustomException(NO_MDMS_DATA_FOUND_FOR_GIVEN_TENANT_CODE, - "no data found for the given tenantid "+tenantId + " for master name "+ServiceConstants.MDMS_MASTER_SCHEMAS); + "no data found for the given tenantid "+tenantId + " for master name "+ServiceConstants.MDMS_MASTER_ADMIN_SCHEMA); } return result; } @@ -87,10 +85,9 @@ public Object fetchMdmsData(RequestInfo requestInfo, String tenantId) { * @return The MDMS criteria request object. */ public MdmsCriteriaReq getMdmsRequest(RequestInfo requestInfo, String tenantId) { - - ModuleDetail moduleDetail = getPlanModuleDetail(); + ModuleDetail adminConsoleModuleDetail = getAdminConsoleModuleDetail(); List moduleDetails = new LinkedList<>(); - moduleDetails.add(moduleDetail); + moduleDetails.add(adminConsoleModuleDetail); MdmsCriteria mdmsCriteria = MdmsCriteria.builder().moduleDetails(moduleDetails).tenantId(tenantId).build(); return MdmsCriteriaReq.builder().mdmsCriteria(mdmsCriteria).requestInfo(requestInfo).build(); } @@ -109,34 +106,69 @@ private ModuleDetail getPlanModuleDetail() { .moduleName(ServiceConstants.MDMS_PLAN_MODULE_NAME).build(); } + /** + * Retrieves the module details for the HCM-ADMIN-CONSOLE module. + * + * @return ModuleDetail object containing master details for the HCM-ADMIN-CONSOLE module. + */ + private ModuleDetail getAdminConsoleModuleDetail() { + List adminSchemaMasterDetails = new ArrayList<>(); + MasterDetail schemaDetails = MasterDetail.builder().name(ServiceConstants.MDMS_MASTER_ADMIN_SCHEMA).build(); + adminSchemaMasterDetails.add(schemaDetails); + + return ModuleDetail.builder().masterDetails(adminSchemaMasterDetails) + .moduleName(ServiceConstants.MDMS_ADMIN_CONSOLE_MODULE_NAME).build(); + } + /** * Filters master data based on the provided parameters. * * @param masterDataJson The JSON string representing the master data. - * @param fileType The type of input file. - * @param templateIdentifier The template identifier. * @param campaignType The campaign type. * @return A map containing filtered properties from the master data. * @throws JsonMappingException if there's an issue mapping JSON. * @throws JsonProcessingException if there's an issue processing JSON. */ - public Map filterMasterData(String masterDataJson, File.InputFileTypeEnum fileType, - String templateIdentifier, String campaignType) { + public Map filterMasterData(String masterDataJson, String campaignType) { Map properties = new HashMap<>(); - Map masterData = JsonUtils.parseJson(masterDataJson, Map.class); - Map planModule = (Map) masterData.get(ServiceConstants.MDMS_PLAN_MODULE_NAME); - List> schemas = (List>) planModule - .get(ServiceConstants.MDMS_MASTER_SCHEMAS); - log.info("masterDataJson ==>" + schemas); - for (Map schema : schemas) { - String type = (String) schema.get(ServiceConstants.MDMS_SCHEMA_TYPE); - String campaign = (String) schema.get(ServiceConstants.MDMS_CAMPAIGN_TYPE); - // String fileT = InputFileTypeEnum.valueOf(type); - if (schema.get(ServiceConstants.MDMS_SCHEMA_SECTION).equals(ServiceConstants.FILE_TEMPLATE_IDENTIFIER_POPULATION) - && campaign.equals(campaignType) && type.equals(fileType.toString())) { - Map schemaProperties = (Map) schema.get("schema"); - properties = (Map) schemaProperties.get("Properties"); + try { + Map masterData = JsonUtils.parseJson(masterDataJson, Map.class); + Map adminConsoleModule = (Map) masterData.get(ServiceConstants.MDMS_ADMIN_CONSOLE_MODULE_NAME); + + //Extracting adminSchema master from the HCM-ADMIN-CONSOLE module + List> adminSchema = (List>) adminConsoleModule.get(ServiceConstants.MDMS_MASTER_ADMIN_SCHEMA); + + log.debug("masterDataJson ==> " + adminSchema); + + //Iterating through each schema in adminSchema master + for (Map schema : adminSchema) { + String campaign = (String) schema.get(ServiceConstants.MDMS_CAMPAIGN_TYPE); + + // Skipping schema for which the required fields are missing to avoid null pointer exception + if (ObjectUtils.isEmpty(campaign) || ObjectUtils.isEmpty(schema.get(ServiceConstants.MDMS_SCHEMA_TITLE))) + continue; + + // Check if the schema's title matches the required template identifier + // and the campaign matches the specified campaign type. + if (schema.get(ServiceConstants.MDMS_SCHEMA_TITLE).equals(ServiceConstants.FILE_TEMPLATE_IDENTIFIER_BOUNDARY) + && campaign.equals(MICROPLAN_PREFIX + campaignType)) { + Map> schemaProperties = (Map>) schema.get("properties"); + + // Skipping if schema properties are not present for the given campaign type. + if (CollectionUtils.isEmpty(schemaProperties)) + continue; + + schemaProperties.forEach((propertyType, propertyList) -> + // For each property in the property list, extract its name and add it to the map with the property. + propertyList.forEach(property -> { + String propertyName = (String) parsingUtil.extractFieldsFromJsonObject(property, "name"); + properties.put(propertyName, property); + }) + ); + } } + } catch (Exception e) { + log.error(ERROR_PROCESSING_DATA_FROM_MDMS, e); } return properties; diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsV2Util.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsV2Util.java index 85b87872658..5430e0e55c6 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsV2Util.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/MdmsV2Util.java @@ -67,9 +67,10 @@ private MdmsCriteriaReqV2 getMdmsV2Request(RequestInfo requestInfo, String tenan MdmsCriteriaV2 mdmsCriteriaV2 = MdmsCriteriaV2.builder() .tenantId(tenantId) .schemaCode(schemaCode) - .uniqueIdentifiers(Collections.singletonList(uniqueIdentifier)) - .limit(config.getDefaultLimit()) - .offset(config.getDefaultOffset()).build(); + .uniqueIdentifiers(uniqueIdentifier != null ? Collections.singletonList(uniqueIdentifier) : null) + .limit(config.getDefaultLimitForMdms()) + .offset(config.getDefaultOffsetForMdms()) + .build(); return MdmsCriteriaReqV2.builder() .requestInfo(requestInfo) diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/MixedStrategyUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/MixedStrategyUtil.java new file mode 100644 index 00000000000..f82f258f438 --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/MixedStrategyUtil.java @@ -0,0 +1,119 @@ +package org.egov.processor.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.egov.processor.web.models.Operation; +import org.egov.processor.web.models.PlanConfiguration; +import org.egov.processor.web.models.PlanConfigurationRequest; +import org.egov.processor.web.models.mdmsV2.Mdms; +import org.egov.processor.web.models.mdmsV2.MixedStrategyOperationLogic; +import org.egov.tracer.model.CustomException; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.egov.processor.config.ServiceConstants.*; + +@Component +public class MixedStrategyUtil { + + private MdmsV2Util mdmsV2Util; + + private ParsingUtil parsingUtil; + + private ObjectMapper mapper; + + public MixedStrategyUtil(MdmsV2Util mdmsV2Util, ParsingUtil parsingUtil, ObjectMapper mapper) { + this.mdmsV2Util = mdmsV2Util; + this.parsingUtil = parsingUtil; + this.mapper = mapper; + } + + /** + * Fetches a list of MixedStrategyOperationLogic objects from MDMS based on the provided planConfigurationRequest. + * + * @param request The PlanConfigurationRequest containing the plan configuration and request info. + * @return A list of MixedStrategyOperationLogic objects fetched from MDMS. + */ + public List fetchMixedStrategyOperationLogicFromMDMS(PlanConfigurationRequest request) { + String rootTenantId = request.getPlanConfiguration().getTenantId().split("\\.")[0]; + List mdmsV2Data = mdmsV2Util.fetchMdmsV2Data(request.getRequestInfo(), rootTenantId, MDMS_PLAN_MODULE_NAME + DOT_SEPARATOR + MDMS_MASTER_MIXED_STRATEGY, null); + + if (CollectionUtils.isEmpty(mdmsV2Data)) { + throw new CustomException(NO_MDMS_DATA_FOUND_FOR_MIXED_STRATEGY_MASTER_CODE, NO_MDMS_DATA_FOUND_FOR_MIXED_STRATEGY_MASTER_CODE_MESSAGE); + } + + return mdmsV2Data.stream() + .map(mdms -> mapper.convertValue(mdms.getData(), MixedStrategyOperationLogic.class)) + .collect(Collectors.toList()); + + } + + /** + * Retrieves a list of categories that are restricted based on the provided details. + * Returns an empty list if no match is found. + * + * @param isFixedPost A boolean indicating whether the mapped facility is fixed post. + * @param planConfiguration The plan configuration containing additional details. + * @param logicList A list of MixedStrategyOperationLogic objects to filter against. + * @return A list of categories not allowed to have output value or an empty list if no matching logic is found. + */ + public List getCategoriesNotAllowed(boolean isFixedPost, PlanConfiguration planConfiguration, List logicList) { + + //Extract fields from additional details + String registrationProcess = (String) parsingUtil.extractFieldsFromJsonObject(planConfiguration.getAdditionalDetails(), REGISTRATION_PROCESS); + String distributionProcess = (String) parsingUtil.extractFieldsFromJsonObject(planConfiguration.getAdditionalDetails(), DISTRIBUTION_PROCESS); + + // If any of the process detail value is null, return an empty list + if (ObjectUtils.isEmpty(registrationProcess) || ObjectUtils.isEmpty(distributionProcess)) { + return Collections.emptyList(); + } + + return logicList.stream() + .filter(logic -> logic.isFixedPost() == isFixedPost) + .filter(logic -> logic.getRegistrationProcess().equalsIgnoreCase(registrationProcess)) + .filter(logic -> logic.getDistributionProcess().equalsIgnoreCase(distributionProcess)) + .map(MixedStrategyOperationLogic::getCategoriesNotAllowed) + .findAny() // Returns any matching element since there is only one match + .orElse(List.of()); + + } + + /** + * Nullifies result values in the map for outputs belonging to the categories not allowed. + * Exits early if no restrictions are specified. + * + * @param resultMap A map containing output keys and their corresponding result values. + * @param operations A list of operations. + * @param categoriesNotAllowed A list of categories that are restricted and should not have associated outputs. + */ + public void processResultMap(Map resultMap, List operations, List categoriesNotAllowed) { + + // If all the categories are allowed, don't process further. + if(CollectionUtils.isEmpty(categoriesNotAllowed)) + return; + + // Map categories not allowed to its corresponding list of output keys + Map> categoryNotAllowedToOutputMap = operations.stream() + .filter(op -> op.getActive() && categoriesNotAllowed.contains(op.getCategory())) + .collect(Collectors.groupingBy( + Operation::getCategory, + Collectors.mapping(Operation::getOutput, Collectors.toList()))); + + + // Iterate through categories in the categoriesNotAllowed list and set their result values to null + for (String category : categoriesNotAllowed) { + List outputKeys = categoryNotAllowedToOutputMap.getOrDefault(category, Collections.emptyList()); + for (String outputKey : outputKeys) { + if (resultMap.containsKey(outputKey)) { + resultMap.put(outputKey, null); + } + } + } + } +} diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/OutputEstimationGenerationUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/OutputEstimationGenerationUtil.java new file mode 100644 index 00000000000..b6417c45720 --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/OutputEstimationGenerationUtil.java @@ -0,0 +1,264 @@ +package org.egov.processor.util; + +import org.apache.poi.ss.usermodel.*; +import org.egov.processor.web.models.Locale; +import org.egov.processor.web.models.LocaleResponse; +import org.egov.processor.web.models.PlanConfigurationRequest; +import org.egov.processor.web.models.ResourceMapping; +import org.egov.processor.web.models.census.Census; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.egov.tracer.model.CustomException; +import org.springframework.util.ObjectUtils; + +import static org.egov.processor.config.ServiceConstants.*; + +@Component +public class OutputEstimationGenerationUtil { + + private LocaleUtil localeUtil; + + private ParsingUtil parsingUtil; + + private ExcelStylingUtil excelStylingUtil; + + private EnrichmentUtil enrichmentUtil; + + private MdmsUtil mdmsUtil; + + public OutputEstimationGenerationUtil(LocaleUtil localeUtil, ParsingUtil parsingUtil, EnrichmentUtil enrichmentUtil, ExcelStylingUtil excelStylingUtil, MdmsUtil mdmsUtil) { + this.localeUtil = localeUtil; + this.parsingUtil = parsingUtil; + this.excelStylingUtil = excelStylingUtil; + this.enrichmentUtil = enrichmentUtil; + this.mdmsUtil = mdmsUtil; + } + + /** + * Processes an output Excel workbook by removing unnecessary sheets, localizing header columns, + * and adding facility information for each boundary code. The configuration for processing + * is based on the provided PlanConfigurationRequest. + * + * @param workbook the Excel workbook to process + * @param request the PlanConfigurationRequest containing processing configuration + * @param filestoreId the identifier of the file store for additional processing requirements + */ + public void processOutputFile(Workbook workbook, PlanConfigurationRequest request, String filestoreId) { + LocaleResponse localeResponse = localeUtil.searchLocale(request); + Map mdmsDataForCommonConstants = mdmsUtil.fetchMdmsDataForCommonConstants( + request.getRequestInfo(), + request.getPlanConfiguration().getTenantId()); + + // 1. removing readme sheet + for (int i = workbook.getNumberOfSheets() - 1; i >= 0; i--) { + Sheet sheet = workbook.getSheetAt(i); + if (!isSheetAllowedToProcess(sheet.getSheetName(), localeResponse, mdmsDataForCommonConstants)) { + workbook.removeSheetAt(i); + } + } + + // 2. Stylize and localize output column headers + for(Sheet sheet: workbook) { + processSheetForHeaderLocalization(sheet, localeResponse); + } + + // 3. Adding facility information for each boundary code + for(Sheet sheet: workbook) { + addAssignedFacility(sheet, request, filestoreId); + } + + } + + /** + * Localizes the header row in the given sheet using the provided localization map. + * Applies styling and adjusts column widths for each localized header cell. + * + * @param sheet the Excel sheet whose header row needs localization + * @param localeResponse localization search call response + */ + public void processSheetForHeaderLocalization(Sheet sheet, LocaleResponse localeResponse) { + // create localization code and message map + Map localizationCodeAndMessageMap = localeResponse.getMessages().stream() + .collect(Collectors.toMap( + Locale::getCode, + Locale::getMessage, + (existingValue, newValue) -> existingValue // Keep the existing value in case of duplicates + )); + + // Fetch the header row from sheet + Row row = sheet.getRow(0); + if (parsingUtil.isRowEmpty(row)) + throw new CustomException(EMPTY_HEADER_ROW_CODE, EMPTY_HEADER_ROW_MESSAGE); + + + //Iterate from the end, for every cell localize the header value + for (int i = row.getLastCellNum() - 1; i >= 0; i--) { + Cell headerColumn = row.getCell(i); + + if (headerColumn == null || headerColumn.getCellType() != CellType.STRING) { + continue; + } + String headerColumnValue = headerColumn.getStringCellValue(); + + // Exit the loop if the header column value is not in the localization map + if (!localizationCodeAndMessageMap.containsKey(headerColumnValue)) { + break; + } + + // Update the cell value with the localized message + excelStylingUtil.styleCell(headerColumn); + headerColumn.setCellValue(localizationCodeAndMessageMap.get(headerColumnValue)); + excelStylingUtil.adjustColumnWidthForCell(headerColumn); + } + + } + + /** + * This is the main method responsible for adding an assigned facility name column to each sheet in the workbook. + * It iterates through all the sheets, verifies if they are eligible for processing, retrieves required mappings + * and boundary codes, and populates the new column with facility names based on these mappings. + * + * @param sheet the sheet to be processed. + * @param request the plan configuration request containing the resource mapping and other configurations. + * @param fileStoreId the associated file store ID used to filter resource mappings. + */ + public void addAssignedFacility(Sheet sheet, PlanConfigurationRequest request, String fileStoreId) { + LocaleResponse localeResponse = localeUtil.searchLocale(request); + + // Get the localized column header name for assigned facilities. + String assignedFacilityColHeader = localeUtil.localeSearch(localeResponse.getMessages(), HCM_MICROPLAN_SERVING_FACILITY); + + // Default to a constant value if no localized value is found. + assignedFacilityColHeader = assignedFacilityColHeader != null ? assignedFacilityColHeader : HCM_MICROPLAN_SERVING_FACILITY; + + // Creating a map of MappedTo and MappedFrom values from resource mapping + Map mappedValues = request.getPlanConfiguration().getResourceMapping().stream() + .filter(f -> f.getFilestoreId().equals(fileStoreId)) + .collect(Collectors.toMap( + ResourceMapping::getMappedTo, + ResourceMapping::getMappedFrom, + (existing, replacement) -> existing, + LinkedHashMap::new + )); + + // Create the map of boundary code to the facility assigned to that boundary. + Map boundaryCodeToFacility = getBoundaryCodeToFacilityMap(sheet, request, fileStoreId); + + // Add facility names to the sheet. + addFacilityNameToSheet(sheet, assignedFacilityColHeader, boundaryCodeToFacility, mappedValues); + } + + /** + * Collects boundary codes from all eligible sheets in the workbook, fetches census records for these boundaries, + * and maps each boundary code to its assigned facility name obtained from the census data. + * + * @param sheet the sheet to be processed. + * @param request the plan configuration request with boundary code details. + * @param fileStoreId the associated file store ID for filtering. + * @return a map of boundary codes to their assigned facility names. + */ + public Map getBoundaryCodeToFacilityMap(Sheet sheet, PlanConfigurationRequest request, String fileStoreId) { + List boundaryCodes = new ArrayList<>(); + + // Extract boundary codes from the sheet. + boundaryCodes.addAll(enrichmentUtil.getBoundaryCodesFromTheSheet(sheet, request, fileStoreId)); + + // Fetch census records for the extracted boundary codes. + List censusList = enrichmentUtil.getCensusRecordsForEnrichment(request, boundaryCodes); + return censusList.stream() + .collect(Collectors.toMap( + Census::getBoundaryCode, + census -> (String) parsingUtil.extractFieldsFromJsonObject(census.getAdditionalDetails(), FACILITY_NAME))); + } + + /** + * Processes a given sheet by adding a new column for assigned facilities and populating + * each row with the corresponding facility name based on the boundary code. + * + * @param sheet the sheet being processed. + * @param assignedFacilityColHeader the header for the new assigned facility column. + * @param boundaryCodeToFacility the mapping of boundary codes to assigned facilities. + * @param mappedValues a map of 'MappedTo' to 'MappedFrom' values. + */ + private void addFacilityNameToSheet(Sheet sheet, String assignedFacilityColHeader, Map boundaryCodeToFacility, Map mappedValues) { + int indexOfFacility = createAssignedFacilityColumn(sheet, assignedFacilityColHeader); + + // Get column index mappings from the sheet. + Map columnNameIndexMap = parsingUtil.getAttributeNameIndexFromExcel(sheet); + + // Get the index of the boundary code column based on the provided mappings. + int indexOfBoundaryCode = parsingUtil.getIndexOfBoundaryCode(0, parsingUtil.sortColumnByIndex(columnNameIndexMap), mappedValues); + + // Iterate over each row in the sheet and set the facility name for each row. + for (Row row : sheet) { + if (row.getRowNum() == 0 || parsingUtil.isRowEmpty(row)) { + continue; + } + + String boundaryCode = row.getCell(indexOfBoundaryCode).getStringCellValue(); + + // Get or create the facility cell in the row. + Cell facilityCell = row.getCell(indexOfFacility); + if (facilityCell == null) { + facilityCell = row.createCell(indexOfFacility, CellType.STRING); + } + + // Assign the facility name based on the boundary code. + facilityCell.setCellValue(boundaryCodeToFacility.getOrDefault(boundaryCode, EMPTY_STRING)); + facilityCell.getCellStyle().setLocked(false); // Ensure the new cell is editable + + } + } + + /** + * Adds a new column for the assigned facility name in the provided sheet, styles the header cell, + * and returns the index of the newly created column. + * + * @param sheet the sheet where the column is to be added. + * @param assignedFacilityColHeader the header for the new column. + * @return the index of the newly created column. + */ + private int createAssignedFacilityColumn(Sheet sheet, String assignedFacilityColHeader) { + int indexOfFacility = (int) sheet.getRow(0).getLastCellNum(); + + // Create a new cell for the column header. + Cell facilityColHeader = sheet.getRow(0).createCell(indexOfFacility, CellType.STRING); + + //stylize cell and set cell value as the localized value + excelStylingUtil.styleCell(facilityColHeader); + facilityColHeader.setCellValue(assignedFacilityColHeader); + excelStylingUtil.adjustColumnWidthForCell(facilityColHeader); + return indexOfFacility; + } + + /** + * Checks if a sheet is allowed to be processed based on MDMS constants and locale-specific configuration. + * + * @param sheetName The name of the sheet to be processed. + * @param localeResponse Localisation response for the given tenant id. + * @param mdmsDataConstants Mdms data for common constants. + * @return true if the sheet is allowed to be processed, false otherwise. + */ + public boolean isSheetAllowedToProcess(String sheetName, LocaleResponse localeResponse, Map mdmsDataConstants) { + + String readMeSheetName = (String) mdmsDataConstants.get(READ_ME_SHEET_NAME); + if(ObjectUtils.isEmpty(readMeSheetName)) + throw new CustomException(README_SHEET_NAME_LOCALISATION_NOT_FOUND_CODE, README_SHEET_NAME_LOCALISATION_NOT_FOUND_MESSAGE); + + for (Locale locale : localeResponse.getMessages()) { + if ((locale.getCode().equalsIgnoreCase((String) mdmsDataConstants.get(READ_ME_SHEET_NAME))) + || locale.getCode().equalsIgnoreCase(HCM_ADMIN_CONSOLE_BOUNDARY_DATA)) { + if (sheetName.equals(locale.getMessage())) + return false; + } + } + return true; + + } +} + diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/ParsingUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/ParsingUtil.java index ba10327e4c8..b5f20eaf36f 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/util/ParsingUtil.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/ParsingUtil.java @@ -3,12 +3,13 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.poi.ss.usermodel.*; import org.egov.processor.config.ServiceConstants; -import org.egov.processor.web.models.PlanConfiguration; -import org.egov.processor.web.models.ResourceMapping; +import org.egov.processor.web.models.*; import org.egov.tracer.model.CustomException; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; @@ -17,6 +18,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.*; @@ -24,22 +26,19 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import static org.egov.processor.config.ServiceConstants.PROPERTIES; +import static org.egov.processor.config.ServiceConstants.*; @Slf4j @Component public class ParsingUtil { - private PlanConfigurationUtil planConfigurationUtil; - private FilestoreUtil filestoreUtil; - private CalculationUtil calculationUtil; + private ObjectMapper objectMapper; - public ParsingUtil(PlanConfigurationUtil planConfigurationUtil, FilestoreUtil filestoreUtil, CalculationUtil calculationUtil) { - this.planConfigurationUtil = planConfigurationUtil; + public ParsingUtil(FilestoreUtil filestoreUtil, ObjectMapper objectMapper) { this.filestoreUtil = filestoreUtil; - this.calculationUtil = calculationUtil; + this.objectMapper = objectMapper; } public List fetchAttributeNamesFromJson(JsonNode jsonNode) @@ -314,7 +313,7 @@ public List extractPropertyNamesFromAdminSchema(JsonNode rootNode) { * @param row the Row to check * @return true if the row is empty, false otherwise */ - public static boolean isRowEmpty(Row row) { + public boolean isRowEmpty(Row row) { if (row == null) { return true; } @@ -392,4 +391,65 @@ public void printRow(Sheet sheet, Row row) { System.out.println(); // Move to the next line after printing the row } + /** + * Extracts provided field from the additional details object + * + * @param additionalDetails the additionalDetails object from PlanConfigurationRequest + * @param fieldToExtract the name of the field to be extracted from the additional details + * @return the value of the specified field as a string + * @throws CustomException if the field does not exist + */ + public Object extractFieldsFromJsonObject(Object additionalDetails, String fieldToExtract) { + try { + String jsonString = objectMapper.writeValueAsString(additionalDetails); + JsonNode rootNode = objectMapper.readTree(jsonString); + + JsonNode node = rootNode.get(fieldToExtract); + if (node != null && !node.isNull()) { + + // Check for different types of JSON nodes + if (node.isDouble() || node.isFloat()) { + return BigDecimal.valueOf(node.asDouble()); // Convert Double to BigDecimal + } else if (node.isLong() || node.isInt()) { + return BigDecimal.valueOf(node.asLong()); // Convert Long to BigDecimal + } else if (node.isBoolean()) { + return node.asBoolean(); + } else if (node.isTextual()) { + return node.asText(); + } else if (node.isObject()) { + return objectMapper.convertValue(node, Map.class); // Return the object node as a Map + } + } + log.debug("The field to be extracted - " + fieldToExtract + " is not present in additional details."); + return null; + } catch (Exception e) { + log.error(e.getMessage() + fieldToExtract); + throw new CustomException(PROVIDED_KEY_IS_NOT_PRESENT_IN_JSON_OBJECT_CODE, PROVIDED_KEY_IS_NOT_PRESENT_IN_JSON_OBJECT_MESSAGE + fieldToExtract); + } + } + + /** + * Adds or updates the value of provided field in the additional details object. + * @param additionalDetails + * @param fieldsToBeUpdated + * @return + */ + public Map updateFieldInAdditionalDetails(Object additionalDetails, Map fieldsToBeUpdated) { + try { + + // Get or create the additionalDetails as an ObjectNode + ObjectNode objectNode = (additionalDetails == null || additionalDetails instanceof NullNode) + ? objectMapper.createObjectNode() + : objectMapper.convertValue(additionalDetails, ObjectNode.class); + + // Update or add the field in additional details object + fieldsToBeUpdated.forEach((key, value) -> objectNode.set(key, objectMapper.valueToTree(value))); + + // Convert updated ObjectNode back to a Map + return objectMapper.convertValue(objectNode, Map.class); + + } catch (Exception e) { + throw new CustomException(ERROR_WHILE_UPDATING_ADDITIONAL_DETAILS_CODE, ERROR_WHILE_UPDATING_ADDITIONAL_DETAILS_MESSAGE + e); + } + } } diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/PlanFacilityUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/PlanFacilityUtil.java new file mode 100644 index 00000000000..84673539339 --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/PlanFacilityUtil.java @@ -0,0 +1,53 @@ +package org.egov.processor.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.egov.processor.config.Configuration; +import org.egov.processor.config.ServiceConstants; +import org.egov.processor.repository.ServiceRequestRepository; +import org.springframework.stereotype.Component; +import org.egov.processor.web.models.planFacility.*; + +@Slf4j +@Component +public class PlanFacilityUtil { + private Configuration config; + + private ServiceRequestRepository serviceRequestRepository; + + private ObjectMapper mapper; + + public PlanFacilityUtil(Configuration config, ServiceRequestRepository serviceRequestRepository, ObjectMapper mapper) { + this.config = config; + this.serviceRequestRepository = serviceRequestRepository; + this.mapper = mapper; + } + + /** + * Searches for plan facilities based on the provided search request. + * + * @param planfacilitySearchRequest The search request containing the search criteria. + * @return A response with a list of plan facilities that matches the search criteria. + */ + public PlanFacilityResponse search(PlanFacilitySearchRequest planfacilitySearchRequest) { + + PlanFacilityResponse planFacilityResponse = null; + try { + Object response = serviceRequestRepository.fetchResult(getPlanFacilitySearchUri(), planfacilitySearchRequest); + planFacilityResponse = mapper.convertValue(response, PlanFacilityResponse.class); + + } catch (Exception e) { + log.error(ServiceConstants.ERROR_WHILE_SEARCHING_PLAN_FACILITY, e); + } + + return planFacilityResponse; + } + + /** + * Creates a search uri for plan facility search. + * @return + */ + private StringBuilder getPlanFacilitySearchUri() { + return new StringBuilder().append(config.getPlanConfigHost()).append(config.getPlanFacilitySearchEndPoint()); + } +} diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/util/PlanUtil.java b/health-services/resource-generator/src/main/java/org/egov/processor/util/PlanUtil.java index 97c9810e0c5..1a6ee1e7cda 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/util/PlanUtil.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/util/PlanUtil.java @@ -14,10 +14,10 @@ import org.egov.tracer.model.CustomException; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import static org.egov.processor.config.ServiceConstants.*; @@ -33,11 +33,14 @@ public class PlanUtil { private ObjectMapper mapper; - public PlanUtil(ServiceRequestRepository serviceRequestRepository, Configuration config, Producer producer, ObjectMapper mapper) { + private ParsingUtil parsingUtil; + + public PlanUtil(ServiceRequestRepository serviceRequestRepository, Configuration config, Producer producer, ObjectMapper mapper, ParsingUtil parsingUtil) { this.serviceRequestRepository = serviceRequestRepository; this.config = config; this.producer = producer; this.mapper = mapper; + this.parsingUtil = parsingUtil; } /** @@ -47,10 +50,11 @@ public PlanUtil(ServiceRequestRepository serviceRequestRepository, Configuration * @param feature The feature JSON node. * @param resultMap The result map. * @param mappedValues The mapped values. + * @param boundaryCodeToCensusAdditionalDetails A Map of boundary code to censusAdditionalDetails for that boundary code. */ public void create(PlanConfigurationRequest planConfigurationRequest, JsonNode feature, - Map resultMap, Map mappedValues) { - PlanRequest planRequest = buildPlanRequest(planConfigurationRequest, feature, resultMap, mappedValues); + Map resultMap, Map mappedValues, Map boundaryCodeToCensusAdditionalDetails) { + PlanRequest planRequest = buildPlanRequest(planConfigurationRequest, feature, resultMap, mappedValues, boundaryCodeToCensusAdditionalDetails); try { producer.push(config.getResourceMicroplanCreateTopic(), planRequest); } catch (Exception e) { @@ -61,25 +65,27 @@ public void create(PlanConfigurationRequest planConfigurationRequest, JsonNode f /** * Builds a PlanRequest object using the provided plan configuration request, feature JSON node, * result map, mapped values, and assumption value map. - * + * * @param planConfigurationRequest The plan configuration request. * @param feature The feature JSON node. * @param resultMap The result map. * @param mappedValues The mapped values. + * @param boundaryCodeToCensusAdditionalDetails A Map of boundary code to censusAdditionalDetails for that boundary code. * @return The constructed PlanRequest object. */ private PlanRequest buildPlanRequest(PlanConfigurationRequest planConfigurationRequest, JsonNode feature, - Map resultMap, Map mappedValues) { + Map resultMap, Map mappedValues, Map boundaryCodeToCensusAdditionalDetails) { PlanConfiguration planConfig = planConfigurationRequest.getPlanConfiguration(); + String boundaryCodeValue = getBoundaryCodeValue(ServiceConstants.BOUNDARY_CODE, feature, mappedValues); + return PlanRequest.builder() .requestInfo(planConfigurationRequest.getRequestInfo()) .plan(Plan.builder() .tenantId(planConfig.getTenantId()) .planConfigurationId(planConfig.getId()) .campaignId(planConfig.getCampaignId()) - .locality(getBoundaryCodeValue(ServiceConstants.BOUNDARY_CODE, - feature, mappedValues)) + .locality(boundaryCodeValue) .resources(resultMap.entrySet().stream().map(result -> { Resource res = new Resource(); res.setResourceType(result.getKey()); @@ -90,11 +96,77 @@ private PlanRequest buildPlanRequest(PlanConfigurationRequest planConfigurationR .targets(new ArrayList()) .workflow(Workflow.builder().action(WORKFLOW_ACTION_INITIATE).build()) .isRequestFromResourceEstimationConsumer(true) + .additionalDetails(enrichAdditionalDetails(boundaryCodeToCensusAdditionalDetails, boundaryCodeValue)) .build()) .build(); + } + + /** + * Creates an additional details object. Extracts the required fields from census additional details for the given boundary. + * The extracted fields are then used to update the additional details object. + * + * @param boundaryCodeToCensusAdditionalDetails A map containing boundary codes mapped to their respective census additional details. + * @param boundaryCodeValue The boundary code for which additional details need to be enriched. + * @return An updated object containing extracted and enriched additional details, or null if no details were found or added. + */ + private Object enrichAdditionalDetails(Map boundaryCodeToCensusAdditionalDetails, String boundaryCodeValue) { + if(!CollectionUtils.isEmpty(boundaryCodeToCensusAdditionalDetails)) { + + Object censusAdditionalDetails = boundaryCodeToCensusAdditionalDetails.get(boundaryCodeValue); + + // Return null value if censusAdditionalDetails is null + if(ObjectUtils.isEmpty(censusAdditionalDetails)) + return null; + + // Extract required details from census additional details object. + String facilityId = (String) parsingUtil.extractFieldsFromJsonObject(censusAdditionalDetails, FACILITY_ID); + Object accessibilityDetails = (Object) parsingUtil.extractFieldsFromJsonObject(censusAdditionalDetails, ACCESSIBILITY_DETAILS); + Object securityDetails = (Object) parsingUtil.extractFieldsFromJsonObject(censusAdditionalDetails, SECURITY_DETAILS); + + // Creating a map of fields to be added in plan additional details with their key. + Map fieldsToBeUpdated = new HashMap<>(); + if(!ObjectUtils.isEmpty(facilityId)) + fieldsToBeUpdated.put(FACILITY_ID, facilityId); + + // Add fields from accessibilityDetails to fieldsToBeUpdated map if it's present in censusAdditionalDetails. + if(!ObjectUtils.isEmpty(accessibilityDetails)) { + extractNestedFields((Map) accessibilityDetails, ACCESSIBILITY_DETAILS, fieldsToBeUpdated); + } + + // Add fields from securityDetails to fieldsToBeUpdated map if it's present in censusAdditionalDetails. + if(!ObjectUtils.isEmpty(securityDetails)) { + extractNestedFields((Map) securityDetails, SECURITY_DETAILS, fieldsToBeUpdated); + } + + // If the fieldsToBeUpdated map is not empty, pass a new empty object to serve as the additional details object. + if(!CollectionUtils.isEmpty(fieldsToBeUpdated)) + return parsingUtil.updateFieldInAdditionalDetails(new Object(), fieldsToBeUpdated); + } + return null; + } + /** + * Extracts nested fields from the given additionalDetails map and adds them to fieldsToBeUpdated in a structured format. + * If a nested map contains CODE, its value is stored with the key formatted as "prefix|key|CODE". + * + * @param details The map containing nested key-value pairs to be processed. + * @param prefix The prefix to be used for constructing the final key in fieldsToBeUpdated. + * @param fieldsToBeUpdated The map where extracted values will be stored with formatted keys. + */ + private void extractNestedFields(Map details, String prefix, Map fieldsToBeUpdated) { + for (Map.Entry entry : details.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof Map) { + Map nestedMap = (Map) value; + if (nestedMap.containsKey(CODE)) { + fieldsToBeUpdated.put(prefix + "|" + key + "|" + CODE, nestedMap.get(CODE)); + } + } + } } - + /** * Retrieves the boundary code value from the feature JSON node using the mapped value for the given input. * diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/Resource.java b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/Resource.java index 693ba12ef32..c33fd233ec9 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/Resource.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/Resource.java @@ -29,7 +29,6 @@ public class Resource { private String resourceType = null; @JsonProperty("estimatedNumber") - @NotNull private BigDecimal estimatedNumber = null; @JsonProperty("activityCode") diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/census/Census.java b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/census/Census.java index f895f6c75f1..8db4e8b6ec6 100644 --- a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/census/Census.java +++ b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/census/Census.java @@ -48,8 +48,7 @@ public class Census { private String boundaryCode = null; @JsonProperty("assignee") - @Size(max = 64) - private String assignee = null; + private List assignee = null; @JsonProperty("status") @Size(max = 64) diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/mdmsV2/MixedStrategyOperationLogic.java b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/mdmsV2/MixedStrategyOperationLogic.java new file mode 100644 index 00000000000..19f73f32e4e --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/mdmsV2/MixedStrategyOperationLogic.java @@ -0,0 +1,34 @@ +package org.egov.processor.web.models.mdmsV2; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +/** + * MixedStrategyOperationLogic: + * Represents the configuration logic for mixed strategy operations in microplanning + */ +@Validated +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MixedStrategyOperationLogic { + + @JsonProperty("isFixedPost") + private boolean isFixedPost; + + @JsonProperty("RegistrationProcess") + private String registrationProcess; + + @JsonProperty("DistributionProcess") + private String distributionProcess; + + @JsonProperty("CategoriesNotAllowed") + private List categoriesNotAllowed; +} diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacility.java b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacility.java new file mode 100644 index 00000000000..7111050551b --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacility.java @@ -0,0 +1,89 @@ +package org.egov.processor.web.models.planFacility; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.egov.common.contract.models.AuditDetails; +import org.springframework.validation.annotation.Validated; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Plan Facility + */ +@Validated +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PlanFacility { + + @JsonProperty("id") + private String id = null; + + @JsonProperty("tenantId") + @NotNull + @Size(min = 2, max = 64) + private String tenantId = null; + + @JsonProperty("planConfigurationId") + @NotNull + @Size(max = 64) + private String planConfigurationId = null; + + @JsonProperty("planConfigurationName") + private String planConfigurationName = null; + + @JsonProperty("boundaryAncestralPath") + private String boundaryAncestralPath = null; + + @JsonProperty("facilityId") + @NotNull + @Size(max = 64) + private String facilityId = null; + + @JsonProperty("facilityName") + private String facilityName = null; + + @JsonProperty("residingBoundary") + @NotNull + @Size(min = 1, max = 64) + private String residingBoundary = null; + + @JsonProperty("serviceBoundaries") + @NotNull + @Valid + private List serviceBoundaries; + + @JsonIgnore + private List initiallySetServiceBoundaries; + + @JsonProperty("jurisdictionMapping") + private Map jurisdictionMapping; + + @JsonProperty("additionalDetails") + private Object additionalDetails = null; + + @JsonProperty("active") + @NotNull + private Boolean active = Boolean.TRUE; + + @JsonProperty("auditDetails") + private AuditDetails auditDetails = null; + + public PlanFacility addServiceBoundariesItem(String serviceBoundariesItem) { + if (this.serviceBoundaries == null) { + this.serviceBoundaries = new ArrayList<>(); + } + this.serviceBoundaries.add(serviceBoundariesItem); + return this; + } +} diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilityResponse.java b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilityResponse.java new file mode 100644 index 00000000000..4737171d908 --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilityResponse.java @@ -0,0 +1,33 @@ +package org.egov.processor.web.models.planFacility; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.egov.common.contract.response.ResponseInfo; +import org.springframework.validation.annotation.Validated; +import java.util.List; + +/** + * PlanFacilityResponse + */ +@Validated +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PlanFacilityResponse { + + @JsonProperty("ResponseInfo") + private ResponseInfo responseInfo; + + @JsonProperty("PlanFacility") + @Valid + private List planFacility; + + @JsonProperty("TotalCount") + @Valid + private Integer totalCount; +} diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilitySearchCriteria.java b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilitySearchCriteria.java new file mode 100644 index 00000000000..78b308dc12a --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilitySearchCriteria.java @@ -0,0 +1,71 @@ +package org.egov.processor.web.models.planFacility; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * PlanFacilitySearchCriteria + */ +@Validated +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PlanFacilitySearchCriteria { + + @JsonProperty("ids") + private Set ids = null; + + @JsonProperty("tenantId") + @NotNull + private String tenantId = null; + + @JsonProperty("planConfigurationId") + @NotNull + private String planConfigurationId = null; + + @JsonProperty("planConfigurationName") + private String planConfigurationName = null; + + @JsonProperty("facilityName") + private String facilityName = null; + + @JsonProperty("facilityStatus") + private String facilityStatus = null; + + @JsonProperty("facilityType") + private String facilityType = null; + + @JsonProperty("residingBoundaries") + private List residingBoundaries = null; + + @JsonProperty("jurisdiction") + private List jurisdiction = null; + + @JsonProperty("facilityId") + private String facilityId = null; + + @JsonProperty("offset") + @Min(0) + private Integer offset = null; + + @JsonProperty("limit") + @Min(1) + private Integer limit = null; + + @JsonIgnore + private Map filtersMap = null; + +} diff --git a/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilitySearchRequest.java b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilitySearchRequest.java new file mode 100644 index 00000000000..91fd4fa17c6 --- /dev/null +++ b/health-services/resource-generator/src/main/java/org/egov/processor/web/models/planFacility/PlanFacilitySearchRequest.java @@ -0,0 +1,34 @@ +package org.egov.processor.web.models.planFacility; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.egov.common.contract.request.RequestInfo; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotNull; + +/** + * PlanFacilitySearchRequest + */ +@Validated +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PlanFacilitySearchRequest { + + @JsonProperty("RequestInfo") + @NotNull + @Valid + private RequestInfo requestInfo = null; + + @JsonProperty("PlanFacilitySearchCriteria") + @NotNull + @Valid + private PlanFacilitySearchCriteria planFacilitySearchCriteria = null; + +} diff --git a/health-services/resource-generator/src/main/resources/application.properties b/health-services/resource-generator/src/main/resources/application.properties index 45e36a74e5c..f5f8ba1c4f1 100644 --- a/health-services/resource-generator/src/main/resources/application.properties +++ b/health-services/resource-generator/src/main/resources/application.properties @@ -49,11 +49,6 @@ egov.mdms.host=https://unified-dev.digit.org egov.mdms.search.endpoint=/egov-mdms-service/v1/_search egov.mdms.search.v2.endpoint=/mdms-v2/v2/_search -#plan config -egov.plan.config.host=https://unified-dev.digit.org -#egov.plan.config.host=http://localhost:8080 -egov.plan.config.endpoint=/plan-service/config/_search - #file store #egov.filestore.host=https://unified-dev.digit.org egov.filestore.service.host=http://localhost:8084 @@ -64,10 +59,17 @@ egov.filestore.upload.endpoint=/filestore/v1/files plan.config.consumer.kafka.save.topic=plan-config-create-topic plan.config.consumer.kafka.update.topic=plan-config-update-topic -#Plan Create +#Plan Config +egov.plan.config.host=https://unified-dev.digit.org +egov.plan.config.endpoint=/plan-service/config/_search + +#Plan egov.plan.create.endpoint=/plan-service/plan/_create egov.plan.search.endpoint=/plan-service/plan/_search +#Plan Facility +egov.plan.facility.search.endpoint=/plan-service/plan/facility/_search + #Campaign Manager egov.project.factory.search.endpoint=/project-factory/v1/project-type/search egov.project.factory.update.endpoint=/project-factory/v1/project-type/update @@ -96,8 +98,8 @@ plan.config.trigger.plan.facility.mappings.status=EXECUTION_TO_BE_DONE plan.config.update.plan.estimates.into.output.file.status=RESOURCE_ESTIMATIONS_APPROVED # Pagination config -resource.default.offset=0 -resource.default.limit=10 +default.offset.for.mdms.data=0 +default.limit.for.mdms.data=10 # Census egov.census.host=https://unified-dev.digit.org