diff --git a/health-services/project-factory/src/server/api/campaignApis.ts b/health-services/project-factory/src/server/api/campaignApis.ts index 0e02024452..841ffa7b51 100644 --- a/health-services/project-factory/src/server/api/campaignApis.ts +++ b/health-services/project-factory/src/server/api/campaignApis.ts @@ -1848,21 +1848,9 @@ async function projectCreate(projectCreateBody: any, request: any) { logger.info( `for boundary type ${projectCreateResponse?.Project[0]?.address?.boundaryType} and code ${projectCreateResponse?.Project[0]?.address?.boundary}` ); - // if ( - // !request.body.newlyCreatedBoundaryProjectMap[ - // projectCreateBody?.Projects?.[0]?.address?.boundary - // ] - // ) { - // request.body.newlyCreatedBoundaryProjectMap[ - // projectCreateBody?.Projects?.[0]?.address?.boundary - // ] = {}; - // } request.body.boundaryProjectMapping[ projectCreateBody?.Projects?.[0]?.address?.boundary ].projectId = projectCreateResponse?.Project[0]?.id; - // request.body.newlyCreatedBoundaryProjectMap[ - // projectCreateBody?.Projects?.[0]?.address?.boundary - // ].projectId = projectCreateResponse?.Project[0]?.id; } else { throwError( "PROJECT", diff --git a/health-services/project-factory/src/server/api/genericApis.ts b/health-services/project-factory/src/server/api/genericApis.ts index 2ae5f0f749..37eab0ba05 100644 --- a/health-services/project-factory/src/server/api/genericApis.ts +++ b/health-services/project-factory/src/server/api/genericApis.ts @@ -4,13 +4,12 @@ import FormData from "form-data"; // Import FormData for handling multipart/form import { defaultheader, httpRequest } from "../utils/request"; // Import httpRequest function for making HTTP requests import { getFormattedStringForDebug, logger } from "../utils/logger"; // Import logger for logging import { correctParentValues, findMapValue, generateActivityMessage, getBoundaryRelationshipData, getDataSheetReady, getLocalizedHeaders, sortCampaignDetails, throwError } from "../utils/genericUtils"; // Import utility functions -import { extractCodesFromBoundaryRelationshipResponse, generateFilteredBoundaryData, getConfigurableColumnHeadersBasedOnCampaignType, getFiltersFromCampaignSearchResponse, getLocalizedName } from '../utils/campaignUtils'; // Import utility functions +import { extractCodesFromBoundaryRelationshipResponse, generateFilteredBoundaryData, getConfigurableColumnHeadersBasedOnCampaignType, getFiltersFromCampaignSearchResponse, getLocalizedName, processDataForTargetCalculation } from '../utils/campaignUtils'; // Import utility functions import { getCampaignSearchResponse, getHierarchy } from './campaignApis'; const _ = require('lodash'); // Import lodash library import { getExcelWorkbookFromFileURL } from "../utils/excelUtils"; import { processMapping } from "../utils/campaignMappingUtils"; - //Function to get Workbook with different tabs (for type target) const getTargetWorkbook = async (fileUrl: string, localizationMap?: any) => { // Define headers for HTTP request @@ -209,6 +208,7 @@ const getTargetSheetDataAfterCode = async ( for (const sheetName of localizedSheetNames) { const worksheet = workbook.getWorksheet(sheetName); const sheetData = getSheetDataFromWorksheet(worksheet); + const jsonData = getJsonData(sheetData,true,true, sheetName); // Find the target column index where the first row value matches codeColumnName const firstRow = sheetData[0]; @@ -224,44 +224,7 @@ const getTargetSheetDataAfterCode = async ( console.warn(`Column "${codeColumnName}" not found in sheet "${sheetName}".`); continue; } - - // Process data from sheet - const processedData = sheetData.map((row: any, rowIndex: any) => { - if (rowIndex <= 0) return null; // Skip header row - - let rowData: any = { [codeColumnName]: row[boundaryCodeColumnIndex] }; - - // Add integer values in the target column for the current row - let sumOfCurrentTargets = 0; - let sumOfParentTargets = 0; - const remainingColumns = row.length - boundaryCodeColumnIndex - 1; - const halfPoint = Math.floor(remainingColumns / 2); - let startColIndex = boundaryCodeColumnIndex + 1; - - if (request?.body?.parentCampaign) { - for (let colIndex = startColIndex; colIndex < startColIndex + halfPoint; colIndex++) { - const value = row[colIndex]; - if (typeof value === 'number' && Number.isInteger(value)) { - sumOfParentTargets += value; - } - } - // Add the sum to the row data - rowData['Parent Target at the Selected Boundary level'] = sumOfParentTargets; - - // Calculate middle point of remaining columns - startColIndex = boundaryCodeColumnIndex + 1 + halfPoint; - } - for (let colIndex = startColIndex; colIndex < row.length; colIndex++) { - const value = row[colIndex]; - if (typeof value === 'number' && Number.isInteger(value)) { - sumOfCurrentTargets += value; - } - } - - // Add the sum to the row data - rowData['Target at the Selected Boundary level'] = sumOfCurrentTargets; - return rowData; - }).filter(Boolean); // Remove null entries + const processedData = await processDataForTargetCalculation(request, jsonData, codeColumnName, localizationMap); workbookData[sheetName] = processedData; } diff --git a/health-services/project-factory/src/server/utils/campaignUtils.ts b/health-services/project-factory/src/server/utils/campaignUtils.ts index 6a24266c28..821c75ae2f 100644 --- a/health-services/project-factory/src/server/utils/campaignUtils.ts +++ b/health-services/project-factory/src/server/utils/campaignUtils.ts @@ -25,6 +25,7 @@ import { createAndUploadJsonFile, createBoundaryRelationship, getSheetData, + searchMDMS } from "../api/genericApis"; import { getFormattedStringForDebug, logger } from "./logger"; import createAndSearch from "../config/createAndSearch"; @@ -1695,21 +1696,37 @@ function mapBoundariesParent(boundaryResponse: any, request: any, parent: any) { function mapTargets(boundaryResponses: any, codesTargetMapping: any) { if (!boundaryResponses || !codesTargetMapping) return; - for (const boundaryResponse of boundaryResponses) { - const mapBoundary = (boundary: any) => { - if (!boundary.children || boundary.children.length === 0) { - const targetValue = codesTargetMapping[boundary.code]; - return targetValue ? targetValue : 0; - } + // Helper function to map individual boundaries + const mapBoundary = (boundary: any) => { + if (!boundary.children || boundary.children.length === 0) { + // If no children, simply return the target value object or default to empty object + const targetValue = codesTargetMapping[boundary.code]; + return targetValue || {}; + } - let totalTargetValue = 0; - for (const child of boundary.children) { - const childTargetValue = mapBoundary(child); - totalTargetValue += childTargetValue; + // Initialize a new object to accumulate total target values from children + let totalTargetValue: any = {}; + + // Iterate through each child and accumulate their target values + for (const child of boundary.children) { + const childTargetValue = mapBoundary(child); + + // Accumulate the child target values into the total target value + for (const key in childTargetValue) { + if (childTargetValue.hasOwnProperty(key)) { + // Initialize key in totalTargetValue if it doesn't exist + totalTargetValue[key] = (totalTargetValue[key] || 0) + childTargetValue[key]; + } } - codesTargetMapping[boundary.code] = totalTargetValue; - return totalTargetValue; - }; + } + + // Store the accumulated total target value for the current boundary + codesTargetMapping[boundary.code] = totalTargetValue; + return totalTargetValue; + }; + + // Map each boundary response + for (const boundaryResponse of boundaryResponses) { mapBoundary(boundaryResponse); } } @@ -2169,13 +2186,14 @@ async function getCodesTarget(request: any, localizationMap?: any) { fileResponse?.fileStoreIds?.[0]?.url, true, true, - codeColumnName + codeColumnName, + localizationMap ); const boundaryTargetMapping: any = {}; // Iterate through each key in targetData for (const key in targetData) { // Iterate through each entry in the array under the current key - targetData[key].forEach((entry) => { + targetData[key].forEach((entry : any) => { // Check if the entry has both "Boundary Code" and "Target at the Selected Boundary level" if ( entry[codeColumnName] !== undefined && @@ -2185,7 +2203,7 @@ async function getCodesTarget(request: any, localizationMap?: any) { boundaryTargetMapping[entry[codeColumnName]] = entry["Target at the Selected Boundary level"]; if ( - entry["Parent Target at the Selected Boundary level"] !== 0 && + Object.keys(entry["Parent Target at the Selected Boundary level"]).length > 0 && entry["Parent Target at the Selected Boundary level"] !== entry["Target at the Selected Boundary level"] ) { @@ -2236,6 +2254,7 @@ async function createProject( Projects, }; boundaries = await reorderBoundaries(request, localizationMap); + const codesTargetMapping = request?.body?.CampaignDetails?.codesTargetMapping; let boundariesAlreadyWithProjects: any; if (request?.body?.parentCampaign) { // make search to project with parent campaign root project id @@ -2267,38 +2286,7 @@ async function createProject( ); const projectToUpdate = projectSearchResponse?.Project?.[0]; if (projectToUpdate) { - const filteredTargets = projectToUpdate.targets.filter( - (e: any) => - e.beneficiaryType == - request?.body?.CampaignDetails?.additionalDetails - ?.beneficiaryType - ); - if (filteredTargets.length == 0) { - projectToUpdate.targets = [ - { - beneficiaryType: - request?.body?.CampaignDetails?.additionalDetails - ?.beneficiaryType, - totalNo: - request?.body?.CampaignDetails?.codesTargetMapping[ - boundary - ], - targetNo: - request?.body?.CampaignDetails?.codesTargetMapping[ - boundary - ], - }, - ]; - } else { - const targetobj = filteredTargets[0]; - (targetobj.totalNo = - request?.body?.CampaignDetails?.codesTargetMapping[boundary]), - (targetobj.targetNo = - request?.body?.CampaignDetails?.codesTargetMapping[ - boundary - ]); - projectToUpdate.targets = [targetobj]; - } + enrichTargetForProject(projectToUpdate, codesTargetMapping, boundary); const projectUpdateBody = { RequestInfo: request?.body?.RequestInfo, Projects: [projectToUpdate], @@ -2344,21 +2332,7 @@ async function createProject( // Set the reference ID and project targets Projects[0].referenceID = request?.body?.CampaignDetails?.campaignNumber; - (Projects[0].targets = [ - { - beneficiaryType: - request?.body?.CampaignDetails?.additionalDetails - ?.beneficiaryType, - totalNo: - request?.body?.CampaignDetails?.codesTargetMapping[ - boundaryCode - ], - targetNo: - request?.body?.CampaignDetails?.codesTargetMapping[ - boundaryCode - ], - }, - ]); + enrichTargetForProject(Projects[0], codesTargetMapping, boundaryCode); await projectCreate(projectCreateBody, request); } } @@ -2385,6 +2359,26 @@ async function createProject( ); } +const enrichTargetForProject = (project: any, codesTargetMapping: any, boundaryCode: any) => { + if (codesTargetMapping?.[boundaryCode] && Object.keys(codesTargetMapping[boundaryCode]).length > 0) { + let targets = []; + for (const key in codesTargetMapping?.[boundaryCode]) { + let targetNo = parseInt(codesTargetMapping?.[boundaryCode][key]); + let beneficiaryType = key; + targets.push({ + beneficiaryType: beneficiaryType, + targetNo: targetNo, + }); + } + if(targets.length > 0){ + project.targets = targets; + } + } + else{ + logger.info(`No targets found for boundary code ${boundaryCode}`); + } +} + async function processAfterPersist(request: any, actionInUrl: any) { try { const localizationMap = await getLocalizedMessagesHandler( @@ -3030,6 +3024,84 @@ async function updateAndPersistResourceDetails( ); } +export async function processDataForTargetCalculation(request: any, jsonData: any, codeColumnName: string, localizationMap?: any) { + // Retrieve targetConfigs from MDMS + const targetConfigs = await searchMDMS([request?.body?.CampaignDetails?.projectType], "HCM-ADMIN-CONSOLE.targetConfigs", request?.body?.RequestInfo); + + // Process each row of the sheet data + const resultantData = jsonData.map((row: any) => { + + // Initialize an object to hold row-specific data + let rowData: any = { [codeColumnName]: row[codeColumnName] }; + + // Add placeholder fields for Parent Target and Current Target data + rowData['Parent Target at the Selected Boundary level'] = {}; + rowData['Target at the Selected Boundary level'] = {}; + const beneficiaries = targetConfigs?.mdms?.[0]?.data?.beneficiaries; + // Calculate the parent target values + calculateTargetsAtParentLevel(request, row, rowData, beneficiaries, localizationMap); + // Calculate the current target values + calculateTargetsAtCurrentLevel(row, rowData, beneficiaries, localizationMap); + + // Return the processed row data + return rowData; + }).filter(Boolean); // Remove any null entries from the map (i.e., skip the header row) + + return resultantData; +} + +export function calculateTargetsAtParentLevel(request: any, row: any, rowData: any, beneficiaries: any, localizationMap?: any) { + // Check if a parent campaign exists in the request body + if (request?.body?.parentCampaign) { + // Loop through the beneficiaries for the specified campaign type + if (Array.isArray(beneficiaries) && beneficiaries?.length > 0) { + for (const beneficiary of beneficiaries) { + const beneficiaryType = beneficiary?.beneficiaryType; + const columns = beneficiary?.columns; + let totalParentValue = 0; + + // Loop through each column to calculate the total parent value + for (const col of columns) { + // Get the parent value from the column and add it if it's an integer + const parentValue = row[`${getLocalizedName(col, localizationMap)}(OLD)`]; + if (typeof parentValue === 'number' && Number.isInteger(parentValue)) { + totalParentValue += parentValue; + } + } + // Assign the total parent value to the corresponding beneficiary type + rowData['Parent Target at the Selected Boundary level'][beneficiaryType] = totalParentValue; + } + } + else { + logger.warn("No beneficiaries config found for the specified campaign type"); + } + } +} + +export function calculateTargetsAtCurrentLevel(row: any, rowData: any, beneficiaries: any, localizationMap?: any) { + // Loop through the beneficiaries again to calculate the current target values + if (Array.isArray(beneficiaries) && beneficiaries?.length > 0) { + for (const beneficiary of beneficiaries) { + const beneficiaryType = beneficiary?.beneficiaryType; + const columns = beneficiary?.columns; + let totalCurrentValue = 0; + + // Loop through each column to calculate the total current value + for (const col of columns) { + const currentValue = row[getLocalizedName(col, localizationMap)]; + if (typeof currentValue === 'number' && Number.isInteger(currentValue)) { + totalCurrentValue += currentValue; + } + } + // Assign the total current value to the corresponding beneficiary type + rowData['Target at the Selected Boundary level'][beneficiaryType] = totalCurrentValue; + } + } + else { + logger.warn("No beneficiaries config found for the specified campaign type"); + } +} + async function getResourceDetails(request: any) { const { tenantId, type, hierarchyType } = request?.body?.ResourceDetails || request?.query; diff --git a/health-services/project-factory/src/server/validators/campaignValidators.ts b/health-services/project-factory/src/server/validators/campaignValidators.ts index 50271de76c..f27a777fd8 100644 --- a/health-services/project-factory/src/server/validators/campaignValidators.ts +++ b/health-services/project-factory/src/server/validators/campaignValidators.ts @@ -22,7 +22,7 @@ import { getBoundaryColumnName, getBoundaryTabName } from "../utils/boundaryUtil import addAjvErrors from "ajv-errors"; import { generateTargetColumnsBasedOnDeliveryConditions, isDynamicTargetTemplateForProjectType, modifyDeliveryConditions } from "../utils/targetUtils"; import { getBoundariesFromCampaignSearchResponse, validateBoundariesIfParentPresent } from "../utils/onGoingCampaignUpdateUtils"; -import { validateExtraBoundariesForMicroplan, validateFacilityBoundaryForLowestLevel, validateLatLongForMicroplanCampaigns, validatePhoneNumberSheetWise, validateTargetsForMicroplanCampaigns, validateUniqueSheetWise, validateUserForMicroplan } from "./microplanValidators"; +import { validateExtraBoundariesForMicroplan, validateFacilityBoundaryForLowestLevel, validateLatLongForMicroplanCampaigns, validatePhoneNumberSheetWise, validateRequiredTargetsForMicroplanCampaigns, validateUniqueSheetWise, validateUserForMicroplan } from "./microplanValidators"; import { produceModifiedMessages } from "../kafka/Producer"; import { planConfigSearch, planFacilitySearch } from "../utils/microplanUtils"; import { getPvarIds } from "../utils/campaignMappingUtils"; @@ -88,7 +88,10 @@ async function fetchBoundariesFromCampaignDetails(request: any) { return responseBoundaries; } -function validateTargetForNormalCampaigns(data: any, errors: any, localizedTargetColumnNames: any, localizationMap?: { [key: string]: string }) { +function validateTargetForNormalCampaigns(data: any, errors: any, schema: any, localizationMap?: { [key: string]: string }) { + const allColumnsForValidation = schema?.columnsNotToBeFreezed; + const setOfRequiredColumns = new Set(getLocalizedHeaders(schema?.required || [], localizationMap)); + const localizedTargetColumnNames = getLocalizedHeaders(allColumnsForValidation, localizationMap); for (const key in data) { if (key !== getLocalizedName(getBoundaryTabName(), localizationMap) && key !== getLocalizedName(config.values?.readMeTab, localizationMap)) { if (Array.isArray(data[key])) { @@ -96,35 +99,38 @@ function validateTargetForNormalCampaigns(data: any, errors: any, localizedTarge boundaryData.forEach((obj: any, index: number) => { for (const targetColumn of localizedTargetColumnNames) { const target = obj[targetColumn]; - if (!target) { + if (!target && setOfRequiredColumns.has(targetColumn)) { errors.push({ status: "INVALID", rowNumber: obj["!row#number!"], - errorDetails: `Target value is missing at row ${obj['!row#number!']} in sheet ${key}.(Targets values can only be positive numbers less than 1 Million)`, - sheetName: key - }); - } else if (typeof target !== 'number') { - errors.push({ - status: "INVALID", - rowNumber: obj["!row#number!"], - errorDetails: `Target value at row ${obj['!row#number!']} in sheet ${key} is not a number.(Targets values can only be positive numbers less than 1 Million)`, - sheetName: key - }); - } else if (target <= 0 || target > 1000000) { - errors.push({ - status: "INVALID", - rowNumber: obj["!row#number!"], - errorDetails: `Target value at row ${obj['!row#number!']} in sheet ${key} is out of range.(Targets values can only be positive numbers less than 1 Million)`, - sheetName: key - }); - } else if (!Number.isInteger(target)) { - errors.push({ - status: "INVALID", - rowNumber: obj["!row#number!"], - errorDetails: `Target value at row ${obj['!row#number!']} in sheet ${key} is not an integer.(Targets values can only be positive numbers less than 1 Million)`, + errorDetails: `Data in column '${targetColumn}' cannot be empty or zero.(It can only be positive numbers less than or equal to 1 Million)`, sheetName: key }); } + else if (target) { + if (typeof target !== 'number') { + errors.push({ + status: "INVALID", + rowNumber: obj["!row#number!"], + errorDetails: `Data in column '${targetColumn}' is not a number.(It can only be positive numbers less than or equal to 1 Million)`, + sheetName: key + }); + } else if (target <= 0 || target > 1000000) { + errors.push({ + status: "INVALID", + rowNumber: obj["!row#number!"], + errorDetails: `Data in column '${targetColumn}' is out of range.(It can only be positive numbers less than or equal to 1 Million)`, + sheetName: key + }); + } else if (!Number.isInteger(target)) { + errors.push({ + status: "INVALID", + rowNumber: obj["!row#number!"], + errorDetails: `Data in column '${targetColumn}' is not an integer.(It can only be positive numbers less than or equal to 1 Million)`, + sheetName: key + }); + } + } } }); } @@ -135,27 +141,27 @@ function validateTargetForNormalCampaigns(data: any, errors: any, localizedTarge async function validateTargets(request: any, data: any[], errors: any[], localizationMap?: any) { let columnsToValidate: any; + let schema: any; const responseFromCampaignSearch = await getCampaignSearchResponse(request); const campaignObject = responseFromCampaignSearch?.CampaignDetails?.[0]; if (isDynamicTargetTemplateForProjectType(campaignObject?.projectType) && campaignObject.deliveryRules && campaignObject.deliveryRules.length > 0) { - const modifiedUniqueDeliveryConditions = modifyDeliveryConditions(campaignObject.deliveryRules); columnsToValidate = generateTargetColumnsBasedOnDeliveryConditions(modifiedUniqueDeliveryConditions, localizationMap); } else { - const mdmsResponse = await getMdmsDataBasedOnCampaignType(request); - const columnsNotToBeFreezed = mdmsResponse?.columnsNotToBeFreezed; - const requiredColumns = mdmsResponse?.required; + schema = await getMdmsDataBasedOnCampaignType(request); + const columnsNotToBeFreezed = schema?.columnsNotToBeFreezed; + const requiredColumns = schema?.required; columnsToValidate = columnsNotToBeFreezed.filter((element: any) => requiredColumns.includes(element)); } const localizedTargetColumnNames = getLocalizedHeaders(columnsToValidate, localizationMap); if (request?.body?.ResourceDetails?.additionalDetails?.source === "microplan") { - validateTargetsForMicroplanCampaigns(data, errors, localizedTargetColumnNames, localizationMap); + validateRequiredTargetsForMicroplanCampaigns(data, errors, localizedTargetColumnNames, localizationMap); validateLatLongForMicroplanCampaigns(data, errors, localizationMap); } else { - validateTargetForNormalCampaigns(data, errors, localizedTargetColumnNames, localizationMap); + validateTargetForNormalCampaigns(data, errors, schema, localizationMap); } } diff --git a/health-services/project-factory/src/server/validators/microplanValidators.ts b/health-services/project-factory/src/server/validators/microplanValidators.ts index 4bc9f35eba..cbac9e81e6 100644 --- a/health-services/project-factory/src/server/validators/microplanValidators.ts +++ b/health-services/project-factory/src/server/validators/microplanValidators.ts @@ -128,7 +128,7 @@ export function validateUniqueSheetWise(schema: any, data: any[], request: any, } } -export function validateTargetsForMicroplanCampaigns(data: any, errors: any, localizedTargetColumnNames: any, localizationMap?: { [key: string]: string }) { +export function validateRequiredTargetsForMicroplanCampaigns(data: any, errors: any, localizedTargetColumnNames: any, localizationMap?: { [key: string]: string }) { for (const key in data) { if (key !== getLocalizedName(getBoundaryTabName(), localizationMap) && key !== getLocalizedName(config?.values?.readMeTab, localizationMap)) { if (Array.isArray(data[key])) {