/** * @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 emailQueue = require("./queues/emailQueue"); /** * Parses an event and determines matching scenarios for notifications. * Queries job watchers and notification settings before triggering scenario builders. * *

This function performs the following steps: *

    *
  1. Parse event data to extract necessary details using {@link eventParser}.
  2. *
  3. Query job watchers for the given job ID using a GraphQL client.
  4. *
  5. Retrieve body shop information from the job.
  6. *
  7. Determine matching scenarios based on event data.
  8. *
  9. Query notification settings for job watchers.
  10. *
  11. Filter scenario watchers based on enabled notification methods.
  12. *
  13. Trigger scenario builders for matching scenarios with eligible watchers.
  14. *
* * @async * @function scenarioParser * @param {Object} req - The request object containing event data. * Expected properties: *
 *   {
 *     body: {
 *       event: { data: { new: Object, old: Object } },
 *       trigger: Object,
 *       table: string
 *     }
 *   }
 *   
* @param {string} jobIdField - The field used to identify the job ID. * @returns {Promise} A promise that resolves when the scenarios have been processed. * @throws {Error} Throws an error if required request fields are missing or if body shop data is not found. */ const scenarioParser = async (req, jobIdField) => { const { event, trigger, table } = req.body; 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. // console.log(`1`); 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. // console.log(`2`); 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. // console.log(`3`); 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. // console.log(`4`); 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. // console.log(`5`); 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. // console.log(`6`); 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. // console.log(`7`); 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. // console.log(`8`); 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 // console.log(`8`); const emailsToDispatch = scenariosToDispatch.map((scenario) => scenario.email); // Step 10: Dispatch App Notifications to the App Notification Queue const appsToDispatch = scenariosToDispatch.map((scenario) => scenario.app); // TODO: Test Code for Queues // emailQueue().add("test", { data: "test" }); }; module.exports = scenarioParser;