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 scenarioParser = async (req, jobIdField) => { // Destructure required fields from the request body 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 the changes from the event const eventData = await eventParser({ newData: event.data.new, oldData: event.data.old, trigger, table, jobIdField }); // Step 2: Query jobWatchers for this job 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: Infer bodyshop information from the job and validate const bodyShopId = watcherData?.job?.bodyshop?.id; const bodyShopName = watcherData?.job?.bodyshop?.shopname; if (!bodyShopId || !bodyShopName) { throw new Error("No bodyshop data found for this job."); } // Step 4: Get matching scenarios based on eventData and jobWatchers const matchingScenarios = getMatchingScenarios({ ...eventData, jobWatchers, bodyShopId, bodyShopName }); if (isEmpty(matchingScenarios)) { return; } // Prepare the final scenario data const finalScenarioData = { ...eventData, jobWatchers, bodyShopId, bodyShopName, matchingScenarios }; // Step 5: Query associations (notification_settings) for each watcher // Filter by both useremail and shopid const associationsData = await gqlClient.request(queries.GET_NOTIFICATION_ASSOCIATIONS, { emails: jobWatchers.map((x) => x.email), shopid: bodyShopId }); if (isEmpty(associationsData?.associations)) { return; } // Step 6: For each matching scenario, add a scenarioWatchers property // that includes only the jobWatchers with at least one notification method enabled. // Each watcher object is formatted as: { user, email, app, fcm } finalScenarioData.matchingScenarios = finalScenarioData.matchingScenarios.map((scenario) => ({ ...scenario, scenarioWatchers: associationsData.associations .filter((assoc) => { // Retrieve the settings object for this scenario (it now contains app, email, and fcm) const settings = assoc.notification_settings && assoc.notification_settings[scenario.key]; // Only include this association if at least one notification channel is enabled return settings && (settings.app || settings.email || settings.fcm); }) .map((assoc) => { const settings = assoc.notification_settings[scenario.key]; // Determine the email from the association—either from assoc.user or assoc.useremail const watcherEmail = assoc.user || assoc.useremail; // Find the matching watcher object from jobWatchers using the email address const matchingWatcher = jobWatchers.find((watcher) => watcher.email === watcherEmail); return { // This is the common identifier (email in this case) user: watcherEmail, // Notification settings for this scenario email: settings.email, app: settings.app, fcm: settings.fcm, // Additional fields from the watcher lookup firstName: matchingWatcher ? matchingWatcher.firstName : undefined, lastName: matchingWatcher ? matchingWatcher.lastName : undefined, employeeId: matchingWatcher ? matchingWatcher.employeeId : undefined }; }) })); if (isEmpty(finalScenarioData?.matchingScenarios)) { return; } // Step 7: Call builder functions for each matching scenario (fire-and-forget) // Only invoke a builder if its scenario has at least one watcher for (const scenario of finalScenarioData.matchingScenarios) { if (isEmpty(scenario.scenarioWatchers) || !isFunction(scenario.builder)) { continue; } scenario .builder({ trigger: finalScenarioData.trigger.name, bodyShopId: finalScenarioData.bodyShopId, bodyShopName: finalScenarioData.bodyShopName, scenarioKey: scenario.key, scenarioTable: scenario.table, scenarioFields: scenario.fields, scenarioBuilder: scenario.builder, scenarioWatchers: scenario.scenarioWatchers, jobId: finalScenarioData.jobId, isNew: finalScenarioData.isNew, changedFieldNames: finalScenarioData.changedFieldNames, changedFields: finalScenarioData.changedFields, data: finalScenarioData.data }) .catch((error) => console.error(`Error in builder for scenario '${scenario.key}':`, error)); } }; module.exports = scenarioParser;