feature/IO-3096-GlobalNotifications - Checkpoint/Refactor cleanup
This commit is contained in:
@@ -128,18 +128,19 @@ function NotificationSettingsForm({ currentUser }) {
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: <ColumnHeaderCheckbox channel="fcm" form={form} disabled onHeaderChange={() => setIsDirty(true)} />,
|
||||
dataIndex: "fcm",
|
||||
key: "fcm",
|
||||
align: "center",
|
||||
render: (_, record) => (
|
||||
<Form.Item name={[record.key, "fcm"]} valuePropName="checked" noStyle>
|
||||
<Checkbox disabled />
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
// TODO: Disabled for now until FCM is implemented.
|
||||
// {
|
||||
// title: <ColumnHeaderCheckbox channel="fcm" form={form} disabled onHeaderChange={() => setIsDirty(true)} />,
|
||||
// dataIndex: "fcm",
|
||||
// key: "fcm",
|
||||
// align: "center",
|
||||
// render: (_, record) => (
|
||||
// <Form.Item name={[record.key, "fcm"]} valuePropName="checked" noStyle>
|
||||
// <Checkbox disabled />
|
||||
// </Form.Item>
|
||||
// )
|
||||
// }
|
||||
];
|
||||
|
||||
const dataSource = notificationScenarios.map((scenario) => ({ key: scenario }));
|
||||
|
||||
@@ -1952,6 +1952,27 @@
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
event_triggers:
|
||||
- name: notifications_docuemtns
|
||||
definition:
|
||||
enable_manual: false
|
||||
update:
|
||||
columns:
|
||||
- jobid
|
||||
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/handleDocumentsChange'
|
||||
version: 2
|
||||
- table:
|
||||
name: email_audit_trail
|
||||
schema: public
|
||||
@@ -3213,6 +3234,28 @@
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
event_triggers:
|
||||
- name: notifications_joblines
|
||||
definition:
|
||||
enable_manual: false
|
||||
insert:
|
||||
columns: '*'
|
||||
update:
|
||||
columns:
|
||||
- critical
|
||||
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/handleJobLinesChange'
|
||||
- table:
|
||||
name: joblines_status
|
||||
schema: public
|
||||
@@ -5662,6 +5705,25 @@
|
||||
- active:
|
||||
_eq: true
|
||||
event_triggers:
|
||||
- name: notifications_payments
|
||||
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/handlePaymentsChange'
|
||||
version: 2
|
||||
- name: os_payments
|
||||
definition:
|
||||
delete:
|
||||
|
||||
140
server/notifications/eventHandlers.js
Normal file
140
server/notifications/eventHandlers.js
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @fileoverview Notification event handlers.
|
||||
* This module exports functions to handle various notification events.
|
||||
* Each handler optionally calls the scenarioParser and logs errors if they occur,
|
||||
* then returns a JSON response with a success message.
|
||||
*/
|
||||
|
||||
const scenarioParser = require("./scenarioParser");
|
||||
|
||||
/**
|
||||
* Processes a notification event by invoking the scenario parser.
|
||||
* The scenarioParser is intentionally not awaited so that the response is sent immediately.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @param {string} parserPath - The key path to be passed to scenarioParser.
|
||||
* @param {string} successMessage - The message to return on success.
|
||||
* @returns {Promise<Object>} A promise that resolves to an Express JSON response.
|
||||
*/
|
||||
async function processNotificationEvent(req, res, parserPath, successMessage) {
|
||||
const { logger } = req;
|
||||
|
||||
// Call scenarioParser but don't await it; log any error that occurs.
|
||||
scenarioParser(req, parserPath).catch((error) => {
|
||||
logger.log("notifications-error", "error", "notifications", null, { error: error?.message });
|
||||
});
|
||||
|
||||
return res.status(200).json({ message: successMessage });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle job change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handleJobsChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.id", "Job Notifications Event Handled.");
|
||||
|
||||
/**
|
||||
* Handle bills change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handleBillsChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.jobid", "Bills Changed Notification Event Handled.");
|
||||
|
||||
/**
|
||||
* Handle documents change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handleDocumentsChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.jobid", "Documents Change Notifications Event Handled.");
|
||||
|
||||
/**
|
||||
* Handle job lines change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handleJobLinesChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.jobid", "JobLines Change Notifications Event Handled.");
|
||||
|
||||
/**
|
||||
* Handle notes change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handleNotesChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.jobid", "Notes Changed Notification Event Handled.");
|
||||
|
||||
/**
|
||||
* Handle parts dispatch change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Object} JSON response with a success message.
|
||||
*/
|
||||
const handlePartsDispatchChange = (req, res) => res.status(200).json({ message: "Parts Dispatch change handled." });
|
||||
|
||||
/**
|
||||
* Handle parts order change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Object} JSON response with a success message.
|
||||
*/
|
||||
const handlePartsOrderChange = (req, res) => res.status(200).json({ message: "Parts Order change handled." });
|
||||
|
||||
/**
|
||||
* Handle payments change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handlePaymentsChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.jobid", "Payments Changed Notification Event Handled.");
|
||||
|
||||
/**
|
||||
* Handle tasks change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handleTasksChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.jobid", "Tasks Notifications Event Handled.");
|
||||
|
||||
/**
|
||||
* Handle time tickets change notifications.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - Express response object.
|
||||
* @returns {Promise<Object>} JSON response with a success message.
|
||||
*/
|
||||
const handleTimeTicketsChange = async (req, res) =>
|
||||
processNotificationEvent(req, res, "req.body.event.new.jobid", "Time Tickets Changed Notification Event Handled.");
|
||||
|
||||
module.exports = {
|
||||
handleJobsChange,
|
||||
handleBillsChange,
|
||||
handleDocumentsChange,
|
||||
handleJobLinesChange,
|
||||
handleNotesChange,
|
||||
handlePartsDispatchChange,
|
||||
handlePartsOrderChange,
|
||||
handlePaymentsChange,
|
||||
handleTasksChange,
|
||||
handleTimeTicketsChange
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
const scenarioParser = require("../utils/scenarioParser");
|
||||
|
||||
const handleJobsChange = async (req, res) => {
|
||||
const { logger } = req;
|
||||
scenarioParser(req, `req.body.event.new.id`).catch((e) => {
|
||||
console.dir(e);
|
||||
logger.log("notifications-error", "error", "notifications", null, { error: e?.message });
|
||||
});
|
||||
return res.status(200).json({ message: "Job Notifications Event Handled." });
|
||||
};
|
||||
//
|
||||
module.exports = handleJobsChange;
|
||||
@@ -1,52 +0,0 @@
|
||||
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 | }
|
||||
@@ -1,13 +0,0 @@
|
||||
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;
|
||||
@@ -1,5 +0,0 @@
|
||||
const handlePartsDispatchChange = (req, res) => {
|
||||
return res.status(200).json({ message: "Parts Dispatch change handled." });
|
||||
};
|
||||
|
||||
module.exports = handlePartsDispatchChange;
|
||||
@@ -1,5 +0,0 @@
|
||||
const handlePartsOrderChange = (req, res) => {
|
||||
return res.status(200).json({ message: "Parts Order change handled." });
|
||||
};
|
||||
|
||||
module.exports = handlePartsOrderChange;
|
||||
@@ -1,11 +0,0 @@
|
||||
const scenarioParser = require("../utils/scenarioParser");
|
||||
|
||||
const handleTasksChange = 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: "Tasks Notifications Event Handled." });
|
||||
};
|
||||
//
|
||||
module.exports = handleTasksChange;
|
||||
@@ -1,13 +0,0 @@
|
||||
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;
|
||||
@@ -1,3 +1,23 @@
|
||||
/**
|
||||
* Parses an event by comparing old and new data to determine which fields have changed.
|
||||
*
|
||||
* @async
|
||||
* @function eventParser
|
||||
* @param {Object} params - The parameters for parsing the event.
|
||||
* @param {Object} params.oldData - The previous state of the data. If not provided, the data is considered new.
|
||||
* @param {Object} params.newData - The new state of the data.
|
||||
* @param {string} params.trigger - The trigger that caused the event.
|
||||
* @param {string} params.table - The name of the table where the event occurred.
|
||||
* @param {string} [params.jobIdField] - The field name or key path (e.g., "req.body.event.new.jobid") used to extract the job ID.
|
||||
* @returns {Promise<Object>} An object containing:
|
||||
* - {@link changedFieldNames}: An array of field names that have changed.
|
||||
* - {@link changedFields}: An object mapping changed field names to their new values (or `null` if the field was removed).
|
||||
* - {boolean} isNew - Indicates if the event is for new data (i.e., no oldData exists).
|
||||
* - {Object} data - The new data.
|
||||
* - {string} trigger - The event trigger.
|
||||
* - {string} table - The table name.
|
||||
* - {string|null} jobId - The extracted job ID, if available.
|
||||
*/
|
||||
const eventParser = async ({ oldData, newData, trigger, table, jobIdField }) => {
|
||||
const isNew = !oldData;
|
||||
let changedFields = {};
|
||||
@@ -4,7 +4,7 @@ require("dotenv").config({
|
||||
});
|
||||
const Queue = require("better-queue");
|
||||
|
||||
const logger = require("../../utils/logger");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const notificationsEmailQueue = () =>
|
||||
new Queue(
|
||||
79
server/notifications/scenarioBuilders.js
Normal file
79
server/notifications/scenarioBuilders.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const consoleDir = require("../utils/consoleDir");
|
||||
|
||||
const alternateTransportChangedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const billPostedHandler = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const criticalPartsStatusChangedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const intakeDeliveryChecklistCompletedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const jobAssignedToMeBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const jobsAddedToProductionBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const jobStatusChangeBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const newMediaAddedReassignedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const newNoteAddedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const newTimeTicketPostedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const partMarkedBackOrderedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const paymentCollectedCompletedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const scheduledDatesChangedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const supplementImportedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
const tasksUpdatedCreatedBuilder = async (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
alternateTransportChangedBuilder,
|
||||
billPostedHandler,
|
||||
criticalPartsStatusChangedBuilder,
|
||||
intakeDeliveryChecklistCompletedBuilder,
|
||||
jobAssignedToMeBuilder,
|
||||
jobsAddedToProductionBuilder,
|
||||
jobStatusChangeBuilder,
|
||||
newMediaAddedReassignedBuilder,
|
||||
newNoteAddedBuilder,
|
||||
newTimeTicketPostedBuilder,
|
||||
partMarkedBackOrderedBuilder,
|
||||
paymentCollectedCompletedBuilder,
|
||||
scheduledDatesChangedBuilder,
|
||||
supplementImportedBuilder,
|
||||
tasksUpdatedCreatedBuilder
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const alternateTransportChangedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = alternateTransportChangedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const billPostedHandler = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = billPostedHandler;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const criticalPartsStatusChangedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = criticalPartsStatusChangedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const intakeDeliveryChecklistCompletedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = intakeDeliveryChecklistCompletedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const jobAssignedToMeBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = jobAssignedToMeBuilder;
|
||||
@@ -1,6 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
const jobStatusChangeBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = jobStatusChangeBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const jobsAddedToProductionBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = jobsAddedToProductionBuilder;
|
||||
@@ -1,6 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
const newMediaAddedReassignedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = newMediaAddedReassignedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const newNoteAddedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = newNoteAddedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const newTimeTicketPostedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = newTimeTicketPostedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const partMarkedBackOrderedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = partMarkedBackOrderedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const paymentCollectedCompletedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = paymentCollectedCompletedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const scheduledDatesChangedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = scheduledDatesChangedBuilder;
|
||||
@@ -1,7 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
const supplementImportedBuilder = (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = supplementImportedBuilder;
|
||||
@@ -1,47 +0,0 @@
|
||||
const consoleDir = require("../../utils/consoleDir");
|
||||
|
||||
// node-app | {
|
||||
// node-app | trigger: 'notifications_tasks',
|
||||
// node-app | bodyShopId: '71f8494c-89f0-43e0-8eb2-820b52d723bc',
|
||||
// node-app | bodyShopName: 'Rome Online Collision DEMO',
|
||||
// node-app | scenarioKey: 'tasks-updated-created',
|
||||
// node-app | scenarioTable: 'tasks',
|
||||
// node-app | scenarioFields: [ 'updated_at' ],
|
||||
// node-app | scenarioBuilder: [AsyncFunction: tasksUpdatedCreatedBuilder],
|
||||
// node-app | scenarioWatchers: [ { user: 'dave@imex.dev', email: true, app: true, fcm: undefined } ],
|
||||
// node-app | jobId: 'ec1c26c7-b0ea-493f-9bba-30efc291e0fa',
|
||||
// node-app | isNew: false,
|
||||
// node-app | changedFieldNames: [ 'description', 'updated_at' ],
|
||||
// node-app | changedFields: {
|
||||
// node-app | description: 'sadasdasdasdsadssdsaddddsdsddddddddddddsdsdddsddddddddddd',
|
||||
// node-app | updated_at: '2025-02-10T23:02:21.244722+00:00'
|
||||
// node-app | },
|
||||
// node-app | data: {
|
||||
// node-app | assigned_to: '5e4f78a2-0f23-4e7e-920c-02a4e016b398',
|
||||
// node-app | billid: null,
|
||||
// node-app | bodyshopid: '71f8494c-89f0-43e0-8eb2-820b52d723bc',
|
||||
// node-app | completed: false,
|
||||
// node-app | completed_at: null,
|
||||
// node-app | created_at: '2025-02-09T20:02:46.839271+00:00',
|
||||
// node-app | created_by: 'dave@imex.dev',
|
||||
// node-app | deleted: false,
|
||||
// node-app | deleted_at: null,
|
||||
// node-app | description: 'sadasdasdasdsadssdsaddddsdsddddddddddddsdsdddsddddddddddd',
|
||||
// node-app | due_date: null,
|
||||
// node-app | id: 'ca1c49a9-3c26-46cb-bebd-4b93f02cad2a',
|
||||
// node-app | jobid: 'ec1c26c7-b0ea-493f-9bba-30efc291e0fa',
|
||||
// node-app | joblineid: '84b5bbf9-ab57-4c77-abb0-8fdd8709c9ff',
|
||||
// node-app | partsorderid: null,
|
||||
// node-app | priority: 2,
|
||||
// node-app | remind_at: null,
|
||||
// node-app | remind_at_sent: null,
|
||||
// node-app | title: 'sd',
|
||||
// node-app | updated_at: '2025-02-10T23:02:21.244722+00:00'
|
||||
// node-app | }
|
||||
// node-app | }
|
||||
|
||||
const tasksUpdatedCreatedBuilder = async (data) => {
|
||||
consoleDir(data);
|
||||
};
|
||||
|
||||
module.exports = tasksUpdatedCreatedBuilder;
|
||||
@@ -1,28 +1,35 @@
|
||||
// Key: scenario name
|
||||
// Table: table name to check for changes
|
||||
// Fields: fields to check for changes
|
||||
// OnNew: whether the scenario should be triggered on new data
|
||||
// Builder: function to handle the scenario
|
||||
|
||||
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 {
|
||||
jobAssignedToMeBuilder,
|
||||
billPostedHandler,
|
||||
newNoteAddedBuilder,
|
||||
scheduledDatesChangedBuilder,
|
||||
tasksUpdatedCreatedBuilder,
|
||||
jobStatusChangeBuilder,
|
||||
jobsAddedToProductionBuilder,
|
||||
alternateTransportChangedBuilder,
|
||||
newTimeTicketPostedBuilder,
|
||||
intakeDeliveryChecklistCompletedBuilder,
|
||||
paymentCollectedCompletedBuilder,
|
||||
newMediaAddedReassignedBuilder,
|
||||
criticalPartsStatusChangedBuilder,
|
||||
supplementImportedBuilder,
|
||||
partMarkedBackOrderedBuilder
|
||||
} = require("./scenarioBuilders");
|
||||
|
||||
/**
|
||||
* An array of notification scenario definitions.
|
||||
*
|
||||
* Each scenario object can include the following properties:
|
||||
* - key {string}: The unique scenario name.
|
||||
* - table {string}: The table name to check for changes.
|
||||
* - fields {Array<string>}: Fields to check for changes.
|
||||
* - matchToUserFields {Array<string>}: Fields used to match scenarios to user data.
|
||||
* - onNew {boolean|Array<boolean>}: Indicates whether the scenario should be triggered on new data.
|
||||
* - onlyTrue {Array<string>}: Specifies fields that must be true for the scenario to match.
|
||||
* - builder {Function}: A function to handle the scenario.
|
||||
*/
|
||||
const notificationScenarios = [
|
||||
{
|
||||
// Confirmed
|
||||
key: "job-assigned-to-me",
|
||||
table: "jobs",
|
||||
fields: ["employee_pre", "employee_body", "employee_csr", "employee_refinish"],
|
||||
@@ -30,28 +37,24 @@ const notificationScenarios = [
|
||||
builder: jobAssignedToMeBuilder
|
||||
},
|
||||
{
|
||||
// Confirmed
|
||||
key: "bill-posted",
|
||||
table: "bills",
|
||||
builder: billPostedHandler,
|
||||
onNew: true
|
||||
},
|
||||
{
|
||||
// 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"],
|
||||
@@ -59,26 +62,35 @@ const notificationScenarios = [
|
||||
builder: tasksUpdatedCreatedBuilder
|
||||
},
|
||||
{
|
||||
// Confirmed
|
||||
key: "job-status-change",
|
||||
table: "jobs",
|
||||
fields: ["status"],
|
||||
builder: jobStatusChangeBuilder
|
||||
},
|
||||
{
|
||||
// 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: "new-time-ticket-posted",
|
||||
table: "timetickets",
|
||||
builder: newTimeTicketPostedBuilder
|
||||
},
|
||||
{
|
||||
// Good test for batching as this will hit multiple scenarios
|
||||
key: "intake-delivery-checklist-completed",
|
||||
table: "jobs",
|
||||
fields: ["intakechecklist"],
|
||||
builder: intakeDeliveryChecklistCompletedBuilder
|
||||
},
|
||||
{
|
||||
key: "payment-collected-completed",
|
||||
table: "payments",
|
||||
@@ -86,37 +98,32 @@ const notificationScenarios = [
|
||||
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
|
||||
// MAKE SURE YOU ARE NOT ON A LMS ENVIRONMENT
|
||||
// Potential Callbacks / Save for last
|
||||
// Not question mark for Non LMS Scenario
|
||||
key: "new-media-added-reassigned",
|
||||
table: "documents",
|
||||
fields: ["jobid"],
|
||||
builder: newMediaAddedReassignedBuilder
|
||||
},
|
||||
{
|
||||
key: "critical-parts-status-changed",
|
||||
table: "joblines",
|
||||
fields: ["critical"],
|
||||
onlyTrue: ["critical"],
|
||||
builder: criticalPartsStatusChangedBuilder
|
||||
},
|
||||
// -------------- Difficult ---------------
|
||||
// Holding off on this one for now
|
||||
{
|
||||
key: "supplement-imported",
|
||||
builder: supplementImportedBuilder
|
||||
},
|
||||
// This one may be tricky as the jobid is not directly in the event data
|
||||
{
|
||||
key: "part-marked-back-ordered",
|
||||
table: "joblines",
|
||||
builder: partMarkedBackOrderedBuilder
|
||||
},
|
||||
// MAKE SURE YOU ARE NOT ON A LMS ENVIRONMENT
|
||||
// Potential Callbacks
|
||||
{
|
||||
key: "new-media-added-reassigned",
|
||||
table: "documents",
|
||||
builder: newMediaAddedReassignedBuilder
|
||||
}
|
||||
];
|
||||
|
||||
@@ -128,9 +135,10 @@ const notificationScenarios = [
|
||||
* - table: an object with a `name` property (e.g. { name: "tasks", schema: "public" })
|
||||
* - changedFieldNames: an array of changed field names (e.g. [ "description", "updated_at" ])
|
||||
* - isNew: boolean indicating whether the record is new or updated
|
||||
* - trigger: the trigger information (if needed for extra filtering)
|
||||
* - data: the new data object (used to check field values)
|
||||
* - (other properties may be added such as jobWatchers, bodyShopId, etc.)
|
||||
*
|
||||
* @returns {Array} An array of matching scenario objects.
|
||||
* @returns {Array<Object>} An array of matching scenario objects.
|
||||
*/
|
||||
function getMatchingScenarios(eventData) {
|
||||
return notificationScenarios.filter((scenario) => {
|
||||
@@ -159,6 +167,18 @@ function getMatchingScenarios(eventData) {
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyTrue logic:
|
||||
// If a scenario defines an onlyTrue array, then at least one of those fields must have changed
|
||||
// and its new value (from eventData.data) must be non-falsey.
|
||||
if (scenario.onlyTrue && Array.isArray(scenario.onlyTrue) && scenario.onlyTrue.length > 0) {
|
||||
const hasTruthyChange = scenario.onlyTrue.some(
|
||||
(field) => eventData.changedFieldNames.includes(field) && Boolean(eventData.data[field])
|
||||
);
|
||||
if (!hasTruthyChange) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
/**
|
||||
* @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 { client: gqlClient } = require("../graphql-client/graphql-client");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const { isEmpty, isFunction } = require("lodash");
|
||||
const { getMatchingScenarios } = require("./scenarioMapperr");
|
||||
|
||||
@@ -8,8 +14,33 @@ const { getMatchingScenarios } = require("./scenarioMapperr");
|
||||
* Parses an event and determines matching scenarios for notifications.
|
||||
* Queries job watchers and notification settings before triggering scenario builders.
|
||||
*
|
||||
* <p>This function performs the following steps:
|
||||
* <ol>
|
||||
* <li>Parse event data to extract necessary details using {@link eventParser}.</li>
|
||||
* <li>Query job watchers for the given job ID using a GraphQL client.</li>
|
||||
* <li>Retrieve body shop information from the job.</li>
|
||||
* <li>Determine matching scenarios based on event data.</li>
|
||||
* <li>Query notification settings for job watchers.</li>
|
||||
* <li>Filter scenario watchers based on enabled notification methods.</li>
|
||||
* <li>Trigger scenario builders for matching scenarios with eligible watchers.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @async
|
||||
* @function scenarioParser
|
||||
* @param {Object} req - The request object containing event data.
|
||||
* Expected properties:
|
||||
* <pre>
|
||||
* {
|
||||
* body: {
|
||||
* event: { data: { new: Object, old: Object } },
|
||||
* trigger: Object,
|
||||
* table: string
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param {string} jobIdField - The field used to identify the job ID.
|
||||
* @returns {Promise<void>} 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;
|
||||
@@ -30,7 +61,6 @@ 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
|
||||
});
|
||||
@@ -48,7 +78,6 @@ 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;
|
||||
|
||||
@@ -58,7 +87,6 @@ const scenarioParser = async (req, jobIdField) => {
|
||||
|
||||
// Step 4: Determine matching scenarios based on event data.
|
||||
// console.log(`4`);
|
||||
|
||||
const matchingScenarios = getMatchingScenarios({
|
||||
...eventData,
|
||||
jobWatchers,
|
||||
@@ -80,7 +108,6 @@ 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
|
||||
@@ -92,7 +119,6 @@ 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
|
||||
@@ -123,7 +149,6 @@ 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;
|
||||
@@ -146,7 +171,6 @@ 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)) || [];
|
||||
|
||||
@@ -2,14 +2,18 @@ const express = require("express");
|
||||
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||
const { subscribe, unsubscribe, sendNotification } = require("../firebase/firebase-handler");
|
||||
const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMIddleware");
|
||||
|
||||
const handlePartsOrderChange = require("../notifications/eventHandlers/handlePartsOrderChange");
|
||||
const handlePartsDispatchChange = require("../notifications/eventHandlers/handlePartsDispatchChange");
|
||||
const handleTasksChange = require("../notifications/eventHandlers/handleTasksChange");
|
||||
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 {
|
||||
handleJobsChange,
|
||||
handleBillsChange,
|
||||
handlePartsOrderChange,
|
||||
handlePartsDispatchChange,
|
||||
handleTasksChange,
|
||||
handleTimeTicketsChange,
|
||||
handleNotesChange,
|
||||
handlePaymentsChange,
|
||||
handleDocumentsChange,
|
||||
handleJobLinesChange
|
||||
} = require("../notifications/eventHandlers");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -26,5 +30,8 @@ router.post("/events/handlePartsDispatchChange", eventAuthorizationMiddleware, h
|
||||
router.post("/events/handleTasksChange", eventAuthorizationMiddleware, handleTasksChange);
|
||||
router.post("/events/handleTimeTicketsChange", eventAuthorizationMiddleware, handleTimeTicketsChange);
|
||||
router.post("/events/handleNotesChange", eventAuthorizationMiddleware, handleNotesChange);
|
||||
router.post("/events/handlePaymentsChange", eventAuthorizationMiddleware, handlePaymentsChange);
|
||||
router.post("/events/handleDocumentsChange", eventAuthorizationMiddleware, handleDocumentsChange);
|
||||
router.post("/events/handleJobLinesChange", eventAuthorizationMiddleware, handleJobLinesChange);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user