From 3f75041ad98bfd359522bf05b2ec26ce9f37e89e Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 12 Feb 2025 11:57:50 -0500 Subject: [PATCH] feature/IO-3096-GlobalNotifications - Checkpoint --- hasura/metadata/tables.yaml | 32 +++--- .../eventHandlers/handleBillsChange.js | 53 +++++++++- .../eventHandlers/handleNotesChange.js | 13 +++ .../eventHandlers/handleTimeTicketsChange.js | 12 ++- .../alternateTransportChangedBuilder.js | 7 ++ .../scenarioBuilders/billPostedHandler.js | 7 ++ .../criticalPartsStatusChangedBuilder.js | 7 ++ ...intakeDeliveryChecklistCompletedBuilder.js | 7 ++ .../jobsAddedToProductionBuilder.js | 7 ++ .../newMediaAddedReassignedBuilder.js | 6 ++ .../scenarioBuilders/newNoteAddedBuilder.js | 7 ++ .../newTimeTicketPostedBuilder.js | 7 ++ .../partMarkedBackOrderedBuilder.js | 7 ++ .../paymentCollectedCompletedBuilder.js | 7 ++ .../scheduleDatesChangedBuilder.js | 7 ++ .../supplementImportedBuilder.js | 7 ++ server/notifications/utils/scenarioMapperr.js | 99 +++++++++++++++---- server/notifications/utils/scenarioParser.js | 15 +++ server/routes/notificationsRoutes.js | 2 + 19 files changed, 275 insertions(+), 34 deletions(-) create mode 100644 server/notifications/eventHandlers/handleNotesChange.js create mode 100644 server/notifications/scenarioBuilders/alternateTransportChangedBuilder.js create mode 100644 server/notifications/scenarioBuilders/billPostedHandler.js create mode 100644 server/notifications/scenarioBuilders/criticalPartsStatusChangedBuilder.js create mode 100644 server/notifications/scenarioBuilders/intakeDeliveryChecklistCompletedBuilder.js create mode 100644 server/notifications/scenarioBuilders/jobsAddedToProductionBuilder.js create mode 100644 server/notifications/scenarioBuilders/newMediaAddedReassignedBuilder.js create mode 100644 server/notifications/scenarioBuilders/newNoteAddedBuilder.js create mode 100644 server/notifications/scenarioBuilders/newTimeTicketPostedBuilder.js create mode 100644 server/notifications/scenarioBuilders/partMarkedBackOrderedBuilder.js create mode 100644 server/notifications/scenarioBuilders/paymentCollectedCompletedBuilder.js create mode 100644 server/notifications/scenarioBuilders/scheduleDatesChangedBuilder.js create mode 100644 server/notifications/scenarioBuilders/supplementImportedBuilder.js diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index a5af9f5dd..974f651f5 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -697,12 +697,6 @@ - name: event-secret value_from_env: EVENT_SECRET request_transform: - body: - action: transform - template: |- - { - "success": true - } method: POST query_params: {} template_engine: Kriti @@ -4825,6 +4819,26 @@ _eq: X-Hasura-User-Id - active: _eq: true + event_triggers: + - name: notifications_notes + definition: + enable_manual: false + insert: + columns: '*' + retry_conf: + interval_sec: 10 + num_retries: 0 + timeout_sec: 60 + webhook_from_env: HASURA_API_URL + headers: + - name: event-secret + value_from_env: EVENT_SECRET + request_transform: + method: POST + query_params: {} + template_engine: Kriti + url: '{{$base_url}}/notifications/events/handleNotesChange' + version: 2 - table: name: notifications schema: public @@ -6311,12 +6325,6 @@ - name: event-secret value_from_env: EVENT_SECRET request_transform: - body: - action: transform - template: |- - { - "success": true - } method: POST query_params: {} template_engine: Kriti diff --git a/server/notifications/eventHandlers/handleBillsChange.js b/server/notifications/eventHandlers/handleBillsChange.js index d444848f2..a2c95eb30 100644 --- a/server/notifications/eventHandlers/handleBillsChange.js +++ b/server/notifications/eventHandlers/handleBillsChange.js @@ -1,5 +1,52 @@ -const handleBillsChange = (req, res) => { - return res.status(200).json({ message: "Bills change handled." }); -}; +const scenarioParser = require("../utils/scenarioParser"); +const handleBillsChange = async (req, res) => { + const { logger } = req; + scenarioParser(req, `req.body.event.new.jobid`).catch((e) => { + logger.log("notifications-error", "error", "notifications", null, { error: e?.message }); + }); + return res.status(200).json({ message: "Bills Changed Notification Event Handled." }); +}; +// module.exports = handleBillsChange; + +//node-app | { +// node-app | created_at: '2025-02-12T16:23:45.397685', +// node-app | delivery_info: { current_retry: 0, max_retries: 0 }, +// node-app | event: { +// node-app | data: { +// node-app | new: { +// node-app | created_at: '2025-02-12T16:23:45.397685+00:00', +// node-app | date: '2025-02-13', +// node-app | due_date: null, +// node-app | exported: false, +// node-app | exported_at: null, +// node-app | federal_tax_rate: 0, +// node-app | id: '873bd1cc-0196-4920-8f2f-4b1bc06f631b', +// node-app | invoice_number: 'sadasdasd', +// node-app | is_credit_memo: false, +// node-app | isinhouse: false, +// node-app | jobid: 'f66534c6-4e1e-462d-bf4f-aca9cf2f03bc', +// node-app | local_tax_rate: 0, +// node-app | state_tax_rate: 7, +// node-app | total: 1, +// node-app | updated_at: '2025-02-12T16:23:45.397685+00:00', +// node-app | vendorid: '4c2ff2c4-af2b-4a5f-970e-3e026f0bbf9f' +// node-app | }, +// node-app | old: null +// node-app | }, +// node-app | op: 'INSERT', +// node-app | session_variables: { +// node-app | 'x-hasura-role': 'user', +// node-app | 'x-hasura-user-id': 'cULlDduYGDgs2oTWSZ1otJIWbfo1' +// node-app | }, +// node-app | trace_context: { +// node-app | sampling_state: '1', +// node-app | span_id: 'b1bfc69e31438823', +// node-app | trace_id: '92d91c363b9a891aa41e5574dbd391d3' +// node-app | } +// node-app | }, +// node-app | id: '2530b665-8421-40b6-bcf1-6d7b39fa020d', +// node-app | table: { name: 'bills', schema: 'public' }, +// node-app | trigger: { name: 'notifications_bills' } +// node-app | } diff --git a/server/notifications/eventHandlers/handleNotesChange.js b/server/notifications/eventHandlers/handleNotesChange.js new file mode 100644 index 000000000..787267b45 --- /dev/null +++ b/server/notifications/eventHandlers/handleNotesChange.js @@ -0,0 +1,13 @@ +const scenarioParser = require("../utils/scenarioParser"); + +const handleNotesChange = async (req, res) => { + const { logger } = req; + + scenarioParser(req, `req.body.event.new.jobid`).catch((e) => { + logger.log("notifications-error", "error", "notifications", null, { error: e?.message }); + }); + + return res.status(200).json({ message: "Notes Changed Notification Event Handled." }); +}; + +module.exports = handleNotesChange; diff --git a/server/notifications/eventHandlers/handleTimeTicketsChange.js b/server/notifications/eventHandlers/handleTimeTicketsChange.js index 403fa4060..e48782412 100644 --- a/server/notifications/eventHandlers/handleTimeTicketsChange.js +++ b/server/notifications/eventHandlers/handleTimeTicketsChange.js @@ -1,5 +1,13 @@ -const handleTimeTicketsChange = (req, res) => { - return res.status(200).json({ message: "Time Tickets change handled." }); +const scenarioParser = require("../utils/scenarioParser"); + +const handleTimeTicketsChange = async (req, res) => { + const { logger } = req; + + scenarioParser(req, `req.body.event.new.jobid`).catch((e) => { + logger.log("notifications-error", "error", "notifications", null, { error: e?.message }); + }); + + return res.status(200).json({ message: "Time Tickets Changed Notification Event Handled." }); }; module.exports = handleTimeTicketsChange; diff --git a/server/notifications/scenarioBuilders/alternateTransportChangedBuilder.js b/server/notifications/scenarioBuilders/alternateTransportChangedBuilder.js new file mode 100644 index 000000000..55e44a56d --- /dev/null +++ b/server/notifications/scenarioBuilders/alternateTransportChangedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const alternateTransportChangedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = alternateTransportChangedBuilder; diff --git a/server/notifications/scenarioBuilders/billPostedHandler.js b/server/notifications/scenarioBuilders/billPostedHandler.js new file mode 100644 index 000000000..2405027a1 --- /dev/null +++ b/server/notifications/scenarioBuilders/billPostedHandler.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const billPostedHandler = (data) => { + consoleDir(data); +}; + +module.exports = billPostedHandler; diff --git a/server/notifications/scenarioBuilders/criticalPartsStatusChangedBuilder.js b/server/notifications/scenarioBuilders/criticalPartsStatusChangedBuilder.js new file mode 100644 index 000000000..9ee4aac39 --- /dev/null +++ b/server/notifications/scenarioBuilders/criticalPartsStatusChangedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const criticalPartsStatusChangedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = criticalPartsStatusChangedBuilder; diff --git a/server/notifications/scenarioBuilders/intakeDeliveryChecklistCompletedBuilder.js b/server/notifications/scenarioBuilders/intakeDeliveryChecklistCompletedBuilder.js new file mode 100644 index 000000000..620fc7751 --- /dev/null +++ b/server/notifications/scenarioBuilders/intakeDeliveryChecklistCompletedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const intakeDeliveryChecklistCompletedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = intakeDeliveryChecklistCompletedBuilder; diff --git a/server/notifications/scenarioBuilders/jobsAddedToProductionBuilder.js b/server/notifications/scenarioBuilders/jobsAddedToProductionBuilder.js new file mode 100644 index 000000000..e6ddfb4e9 --- /dev/null +++ b/server/notifications/scenarioBuilders/jobsAddedToProductionBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const jobsAddedToProductionBuilder = (data) => { + consoleDir(data); +}; + +module.exports = jobsAddedToProductionBuilder; diff --git a/server/notifications/scenarioBuilders/newMediaAddedReassignedBuilder.js b/server/notifications/scenarioBuilders/newMediaAddedReassignedBuilder.js new file mode 100644 index 000000000..72c9ac61e --- /dev/null +++ b/server/notifications/scenarioBuilders/newMediaAddedReassignedBuilder.js @@ -0,0 +1,6 @@ +const consoleDir = require("../../utils/consoleDir"); +const newMediaAddedReassignedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = newMediaAddedReassignedBuilder; diff --git a/server/notifications/scenarioBuilders/newNoteAddedBuilder.js b/server/notifications/scenarioBuilders/newNoteAddedBuilder.js new file mode 100644 index 000000000..86c7ccd98 --- /dev/null +++ b/server/notifications/scenarioBuilders/newNoteAddedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const newNoteAddedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = newNoteAddedBuilder; diff --git a/server/notifications/scenarioBuilders/newTimeTicketPostedBuilder.js b/server/notifications/scenarioBuilders/newTimeTicketPostedBuilder.js new file mode 100644 index 000000000..e31598873 --- /dev/null +++ b/server/notifications/scenarioBuilders/newTimeTicketPostedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const newTimeTicketPostedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = newTimeTicketPostedBuilder; diff --git a/server/notifications/scenarioBuilders/partMarkedBackOrderedBuilder.js b/server/notifications/scenarioBuilders/partMarkedBackOrderedBuilder.js new file mode 100644 index 000000000..d25b462f8 --- /dev/null +++ b/server/notifications/scenarioBuilders/partMarkedBackOrderedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const partMarkedBackOrderedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = partMarkedBackOrderedBuilder; diff --git a/server/notifications/scenarioBuilders/paymentCollectedCompletedBuilder.js b/server/notifications/scenarioBuilders/paymentCollectedCompletedBuilder.js new file mode 100644 index 000000000..7a4c01a66 --- /dev/null +++ b/server/notifications/scenarioBuilders/paymentCollectedCompletedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const paymentCollectedCompletedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = paymentCollectedCompletedBuilder; diff --git a/server/notifications/scenarioBuilders/scheduleDatesChangedBuilder.js b/server/notifications/scenarioBuilders/scheduleDatesChangedBuilder.js new file mode 100644 index 000000000..9b3fa3a46 --- /dev/null +++ b/server/notifications/scenarioBuilders/scheduleDatesChangedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const scheduledDatesChangedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = scheduledDatesChangedBuilder; diff --git a/server/notifications/scenarioBuilders/supplementImportedBuilder.js b/server/notifications/scenarioBuilders/supplementImportedBuilder.js new file mode 100644 index 000000000..aa98c55af --- /dev/null +++ b/server/notifications/scenarioBuilders/supplementImportedBuilder.js @@ -0,0 +1,7 @@ +const consoleDir = require("../../utils/consoleDir"); + +const supplementImportedBuilder = (data) => { + consoleDir(data); +}; + +module.exports = supplementImportedBuilder; diff --git a/server/notifications/utils/scenarioMapperr.js b/server/notifications/utils/scenarioMapperr.js index a2417961f..630c233a8 100644 --- a/server/notifications/utils/scenarioMapperr.js +++ b/server/notifications/utils/scenarioMapperr.js @@ -7,9 +7,22 @@ const tasksUpdatedCreatedBuilder = require("../scenarioBuilders/tasksUpdatedCreatedBuilder"); const jobStatusChangeBuilder = require("../scenarioBuilders/jobStatusChangeBuilder"); const jobAssignedToMeBuilder = require("../scenarioBuilders/jobAssignedToMeBuilder"); +const billPostedHandler = require("../scenarioBuilders/billPostedHandler"); +const newNoteAddedBuilder = require("../scenarioBuilders/newNoteAddedBuilder"); +const scheduledDatesChangedBuilder = require("../scenarioBuilders/scheduleDatesChangedBuilder"); +const jobsAddedToProductionBuilder = require("../scenarioBuilders/jobsAddedToProductionBuilder"); +const alternateTransportChangedBuilder = require("../scenarioBuilders/alternateTransportChangedBuilder"); +const paymentCollectedCompletedBuilder = require("../scenarioBuilders/paymentCollectedCompletedBuilder"); +const newMediaAddedReassignedBuilder = require("../scenarioBuilders/newMediaAddedReassignedBuilder"); +const newTimeTicketPostedBuilder = require("../scenarioBuilders/newTimeTicketPostedBuilder"); +const intakeDeliveryChecklistCompletedBuilder = require("../scenarioBuilders/intakeDeliveryChecklistCompletedBuilder"); +const supplementImportedBuilder = require("../scenarioBuilders/supplementImportedBuilder"); +const criticalPartsStatusChangedBuilder = require("../scenarioBuilders/criticalPartsStatusChangedBuilder"); +const partMarkedBackOrderedBuilder = require("../scenarioBuilders/partMarkedBackOrderedBuilder"); const notificationScenarios = [ { + // Confirmed key: "job-assigned-to-me", table: "jobs", fields: ["employee_pre", "employee_body", "employee_csr", "employee_refinish"], @@ -17,20 +30,28 @@ const notificationScenarios = [ builder: jobAssignedToMeBuilder }, { + // Confirmed key: "bill-posted", - table: "bills" - }, - { - key: "new-note-added", - table: "notes", + table: "bills", + builder: billPostedHandler, onNew: true }, { - key: "schedule-dates-changed", - table: "jobs", - fields: ["scheduled_in", "scheduled_completion", "scheduled_delivery"] + // Confirmed + key: "new-note-added", + table: "notes", + builder: newNoteAddedBuilder, + onNew: true }, { + // Confirmed + key: "schedule-dates-changed", + table: "jobs", + fields: ["scheduled_in", "scheduled_completion", "scheduled_delivery"], + builder: scheduledDatesChangedBuilder + }, + { + // Confirmed key: "tasks-updated-created", table: "tasks", fields: ["updated_at"], @@ -38,21 +59,65 @@ const notificationScenarios = [ builder: tasksUpdatedCreatedBuilder }, { + // Confirmed key: "job-status-change", table: "jobs", fields: ["status"], builder: jobStatusChangeBuilder }, - { key: "job-added-to-production", table: "jobs", fields: ["introduction"] }, - { key: "alternate-transport-changed", table: "jobs", fields: ["alt_transport"] }, - { key: "payment-collected-completed", table: "payments", onNew: true }, + { + // Confirmed + key: "job-added-to-production", + table: "jobs", + fields: ["inproduction"], + builder: jobsAddedToProductionBuilder + }, + { + // Confirmed + key: "alternate-transport-changed", + table: "jobs", + fields: ["alt_transport"], + builder: alternateTransportChangedBuilder + }, + { + key: "payment-collected-completed", + table: "payments", + onNew: true, + builder: paymentCollectedCompletedBuilder + }, + { + key: "new-time-ticket-posted", + table: "timetickets", + builder: newTimeTicketPostedBuilder + }, + { + // Confirmed, also a good test for batching as this will hit multiple scenarios + key: "intake-delivery-checklist-completed", + table: "jobs", + fields: ["intakechecklist"], + builder: intakeDeliveryChecklistCompletedBuilder + }, + { + key: "supplement-imported", + builder: supplementImportedBuilder + }, + { + key: "critical-parts-status-changed", + table: "joblines", + builder: criticalPartsStatusChangedBuilder + }, + { + key: "part-marked-back-ordered", + table: "joblines", + builder: partMarkedBackOrderedBuilder + }, // MAKE SURE YOU ARE NOT ON A LMS ENVIRONMENT - { key: "new-media-added-reassigned", table: "documents" }, - { key: "new-time-ticket-posted", table: "timetickets" }, - { key: "intake-delivery-checklist-completed", table: "jobs", fields: ["intakechecklist"] }, - { key: "supplement-imported" }, - { key: "critical-parts-status-changed", table: "joblines" }, - { key: "part-marked-back-ordered", table: "joblines" } + // Potential Callbacks + { + key: "new-media-added-reassigned", + table: "documents", + builder: newMediaAddedReassignedBuilder + } ]; /** diff --git a/server/notifications/utils/scenarioParser.js b/server/notifications/utils/scenarioParser.js index b93efe8de..e9f55201d 100644 --- a/server/notifications/utils/scenarioParser.js +++ b/server/notifications/utils/scenarioParser.js @@ -19,6 +19,7 @@ const scenarioParser = async (req, jobIdField) => { } // Step 1: Parse event data to extract necessary details. + // console.log(`1`); const eventData = await eventParser({ newData: event.data.new, oldData: event.data.old, @@ -28,6 +29,8 @@ const scenarioParser = async (req, 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 }); @@ -44,6 +47,8 @@ const scenarioParser = async (req, jobIdField) => { } // Step 3: Retrieve body shop information from the job. + // console.log(`3`); + const bodyShopId = watcherData?.job?.bodyshop?.id; const bodyShopName = watcherData?.job?.bodyshop?.shopname; @@ -52,6 +57,8 @@ const scenarioParser = async (req, jobIdField) => { } // Step 4: Determine matching scenarios based on event data. + // console.log(`4`); + const matchingScenarios = getMatchingScenarios({ ...eventData, jobWatchers, @@ -72,6 +79,8 @@ const scenarioParser = async (req, jobIdField) => { }; // 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 @@ -82,6 +91,8 @@ const scenarioParser = async (req, jobIdField) => { } // Step 6: Filter scenario watchers based on enabled notification methods. + // console.log(`6`); + finalScenarioData.matchingScenarios = finalScenarioData.matchingScenarios.map((scenario) => ({ ...scenario, scenarioWatchers: associationsData.associations @@ -111,6 +122,8 @@ const scenarioParser = async (req, jobIdField) => { } // Step 7: Trigger scenario builders for matching scenarios with eligible watchers. + // console.log(`7`); + for (const scenario of finalScenarioData.matchingScenarios) { if (isEmpty(scenario.scenarioWatchers) || !isFunction(scenario.builder)) { continue; @@ -132,6 +145,8 @@ const scenarioParser = async (req, jobIdField) => { } // Step 8: Filter scenario fields to only include changed fields. + // console.log(`8`); + const filteredScenarioFields = scenario.fields?.filter((field) => eventData.changedFieldNames.includes(field)) || []; diff --git a/server/routes/notificationsRoutes.js b/server/routes/notificationsRoutes.js index 64228382b..e2e60bdac 100644 --- a/server/routes/notificationsRoutes.js +++ b/server/routes/notificationsRoutes.js @@ -9,6 +9,7 @@ const handleTasksChange = require("../notifications/eventHandlers/handleTasksCha const handleTimeTicketsChange = require("../notifications/eventHandlers/handleTimeTicketsChange"); const handleJobsChange = require("../notifications/eventHandlers/handeJobsChange"); const handleBillsChange = require("../notifications/eventHandlers/handleBillsChange"); +const handleNotesChange = require("../notifications/eventHandlers/handleNotesChange"); const router = express.Router(); @@ -24,5 +25,6 @@ router.post("/events/handlePartsOrderChange", eventAuthorizationMiddleware, hand router.post("/events/handlePartsDispatchChange", eventAuthorizationMiddleware, handlePartsDispatchChange); router.post("/events/handleTasksChange", eventAuthorizationMiddleware, handleTasksChange); router.post("/events/handleTimeTicketsChange", eventAuthorizationMiddleware, handleTimeTicketsChange); +router.post("/events/handleNotesChange", eventAuthorizationMiddleware, handleNotesChange); module.exports = router;