/** * @module scenarioParser * @description * This module exports a function that parses an event and triggers notification scenarios based on the event data. */ const eventParser = require("./eventParser"); const { client: gqlClient } = require("../graphql-client/graphql-client"); const queries = require("../graphql-client/queries"); const { isEmpty, isFunction } = require("lodash"); const { getMatchingScenarios } = require("./scenarioMapperr"); const consoleDir = require("../utils/consoleDir"); const { dispatchEmailsToQueue } = require("./queues/emailQueue"); const { dispatchAppsToQueue } = require("./queues/appQueue"); /** * Parses an event and determines matching scenarios for notifications. * Queries job watchers and notification settings before triggering scenario builders. */ const scenarioParser = async (req, jobIdField) => { const { event, trigger, table } = req.body; const { logger } = req; if (!event?.data || !trigger || !table) { throw new Error("Missing required request fields: event data, trigger, or table."); } // Step 1: Parse event data to extract necessary details. const eventData = await eventParser({ newData: event.data.new, oldData: event.data.old, trigger, table, jobIdField }); // Step 2: Query job watchers for the given job ID. const watcherData = await gqlClient.request(queries.GET_JOB_WATCHERS, { jobid: eventData.jobId }); const jobWatchers = watcherData?.job_watchers_aggregate?.nodes?.map((watcher) => ({ email: watcher.user_email, firstName: watcher?.user?.employee?.first_name, lastName: watcher?.user?.employee?.last_name, employeeId: watcher?.user?.employee?.id })); if (isEmpty(jobWatchers)) { return; } // Step 3: Retrieve body shop information from the job. const bodyShopId = watcherData?.job?.bodyshop?.id; const bodyShopName = watcherData?.job?.bodyshop?.shopname; const jobRoNumber = watcherData?.job?.ro_number; const jobClaimNumber = watcherData?.job?.clm_no; if (!bodyShopId || !bodyShopName) { throw new Error("No bodyshop data found for this job."); } // Step 4: Determine matching scenarios based on event data. const matchingScenarios = getMatchingScenarios({ ...eventData, jobWatchers, bodyShopId, bodyShopName }); if (isEmpty(matchingScenarios)) { return; } const finalScenarioData = { ...eventData, jobWatchers, bodyShopId, bodyShopName, matchingScenarios }; // Step 5: Query notification settings for job watchers. const associationsData = await gqlClient.request(queries.GET_NOTIFICATION_ASSOCIATIONS, { emails: jobWatchers.map((x) => x.email), shopid: bodyShopId }); if (isEmpty(associationsData?.associations)) { return; } // Step 6: Filter scenario watchers based on enabled notification methods. finalScenarioData.matchingScenarios = finalScenarioData.matchingScenarios.map((scenario) => ({ ...scenario, scenarioWatchers: associationsData.associations .filter((assoc) => { const settings = assoc.notification_settings && assoc.notification_settings[scenario.key]; return settings && (settings.app || settings.email || settings.fcm); }) .map((assoc) => { const settings = assoc.notification_settings[scenario.key]; const watcherEmail = assoc.user || assoc.useremail; const matchingWatcher = jobWatchers.find((watcher) => watcher.email === watcherEmail); return { user: watcherEmail, email: settings.email, app: settings.app, fcm: settings.fcm, firstName: matchingWatcher ? matchingWatcher.firstName : undefined, lastName: matchingWatcher ? matchingWatcher.lastName : undefined, employeeId: matchingWatcher ? matchingWatcher.employeeId : undefined }; }) })); if (isEmpty(finalScenarioData?.matchingScenarios)) { return; } // Step 7: Trigger scenario builders for matching scenarios with eligible watchers. const scenariosToDispatch = []; for (const scenario of finalScenarioData.matchingScenarios) { if (isEmpty(scenario.scenarioWatchers) || !isFunction(scenario.builder)) { continue; } let eligibleWatchers = scenario.scenarioWatchers; // Ensure watchers are only notified if they are assigned to the changed field. if (scenario.matchToUserFields && scenario.matchToUserFields.length > 0) { eligibleWatchers = scenario.scenarioWatchers.filter((watcher) => scenario.matchToUserFields.some( (field) => eventData.changedFieldNames.includes(field) && eventData.data[field]?.includes(watcher.employeeId) ) ); } if (isEmpty(eligibleWatchers)) { continue; } // Step 8: Filter scenario fields to only include changed fields. const filteredScenarioFields = scenario.fields?.filter((field) => eventData.changedFieldNames.includes(field)) || []; scenariosToDispatch.push( scenario.builder({ trigger: finalScenarioData.trigger.name, bodyShopId: finalScenarioData.bodyShopId, bodyShopName: finalScenarioData.bodyShopName, scenarioKey: scenario.key, scenarioTable: scenario.table, scenarioFields: filteredScenarioFields, scenarioBuilder: scenario.builder, scenarioWatchers: eligibleWatchers, jobId: finalScenarioData.jobId, jobRoNumber: jobRoNumber, jobClaimNumber: jobClaimNumber, isNew: finalScenarioData.isNew, changedFieldNames: finalScenarioData.changedFieldNames, changedFields: finalScenarioData.changedFields, data: finalScenarioData.data }) ); } if (isEmpty(scenariosToDispatch)) { return; } // Step 9: Dispatch Email Notifications to the Email Notification Queue const emailsToDispatch = scenariosToDispatch.map((scenario) => scenario?.email); if (!isEmpty(emailsToDispatch)) { dispatchEmailsToQueue({ emailsToDispatch, logger }).catch((e) => logger.log("Something went wrong dispatching emails to the Email Notification Queue", "error", "queue", null, { message: e?.message }) ); } // Step 10: Dispatch App Notifications to the App Notification Queue const appsToDispatch = scenariosToDispatch.map((scenario) => scenario?.app); if (!isEmpty(appsToDispatch)) { dispatchAppsToQueue({ appsToDispatch, logger }).catch((e) => logger.log("Something went wrong dispatching apps to the App Notification Queue", "error", "queue", null, { message: e?.message }) ); } }; module.exports = scenarioParser;