Merged in feature/IO-3096-GlobalNotifications (pull request #2184)

Feature/IO-3096 GlobalNotifications
This commit is contained in:
Dave Richer
2025-03-11 17:18:43 +00:00
5 changed files with 125 additions and 24 deletions

View File

@@ -50,13 +50,69 @@ const handleBillsChange = async (req, res) =>
/**
* Handle documents change notifications.
* Processes both old and new job IDs if the document was moved between jobs.
*
* @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.");
const handleDocumentsChange = async (req, res) => {
const { logger } = req;
const newJobId = req.body?.event?.data?.new?.jobid;
const oldJobId = req.body?.event?.data?.old?.jobid;
// If jobid changed (document moved between jobs), we need to notify both jobs
if (oldJobId && newJobId && oldJobId !== newJobId) {
// Process notification for new job ID
scenarioParser(req, "req.body.event.new.jobid").catch((error) => {
logger.log("notifications-error", "error", "notifications", null, {
message: error?.message,
stack: error?.stack
});
});
// Create a modified request for old job ID
const oldJobReq = {
body: {
...req.body,
event: {
...req.body.event,
data: {
new: {
...req.body.event.data.old,
// Add a flag to indicate this document was moved away
_documentMoved: true,
_movedToJob: newJobId
},
old: null
}
}
},
logger,
sessionUtils: req.sessionUtils
};
// Process notification for old job ID using the modified request
scenarioParser(oldJobReq, "req.body.event.new.jobid").catch((error) => {
logger.log("notifications-error", "error", "notifications", null, {
message: error?.message,
stack: error?.stack
});
});
return res.status(200).json({ message: "Documents Change Notifications Event Handled for both jobs." });
}
// Otherwise just process the new job ID
scenarioParser(req, "req.body.event.new.jobid").catch((error) => {
logger.log("notifications-error", "error", "notifications", null, {
message: error?.message,
stack: error?.stack
});
});
return res.status(200).json({ message: "Documents Change Notifications Event Handled." });
};
/**
* Handle job lines change notifications.

View File

@@ -258,8 +258,19 @@ const jobStatusChangeBuilder = (data) => {
const newMediaAddedReassignedBuilder = (data) => {
// Determine if it's an image or document
const mediaType = data?.data?.type?.startsWith("image") ? "Image" : "Document";
// Determine if it's added or updated
const action = data.isNew ? "added" : "updated";
// Determine the action
let action;
if (data?.data?._documentMoved) {
action = "moved to another Job"; // Special case for document moved from this job
} else if (data.isNew) {
action = "added"; // New media
} else if (data.changedFields?.jobid && data.changedFields.jobid.old !== data.changedFields.jobid.new) {
action = "moved to this Job";
} else {
action = "updated";
}
// Construct the body string
const body = `An ${mediaType} has been ${action}.`;
@@ -272,7 +283,8 @@ const newMediaAddedReassignedBuilder = (data) => {
body,
variables: {
mediaType,
action
action,
movedToJob: data?.data?._movedToJob
},
recipients: []
},
@@ -452,7 +464,6 @@ const paymentCollectedCompletedBuilder = (data) => {
* Builds notification data for changes to scheduled dates.
*/
const scheduledDatesChangedBuilder = (data) => {
const momentFormat = "MM/DD/YYYY hh:mm a";
const changedFields = data.changedFields;
// Define field configurations
@@ -462,16 +473,38 @@ const scheduledDatesChangedBuilder = (data) => {
scheduled_delivery: "Scheduled Delivery"
};
// Helper function to format date and time with "at"
const formatDateTime = (date) => {
if (!date) return "unset";
const formatted = moment(date).tz(data.bodyShopTimezone);
const datePart = formatted.format("MM/DD/YYYY");
const timePart = formatted.format("hh:mm a");
return `${datePart} at ${timePart}`;
};
// Build field messages dynamically
const fieldMessages = Object.entries(fieldConfigs)
.filter(([field]) => changedFields[field]) // Only include changed fields
.map(([field, label]) => {
const { old, new: newValue } = changedFields[field];
const formatDate = (date) => (date ? moment(date).tz(data.bodyShopTimezone).format(momentFormat) : "unset");
return `${label} changed from ${formatDate(old)} to ${formatDate(newValue)}`;
});
const body = fieldMessages.length > 0 ? fieldMessages.join(", ") + "." : "Scheduled dates have been updated.";
// Case 1: Scheduled date cancelled (from value to null)
if (old && !newValue) {
return `${label} was cancelled (previously ${formatDateTime(old)}).`;
}
// Case 2: Scheduled date set (from null to value)
else if (!old && newValue) {
return `${label} was set to ${formatDateTime(newValue)}.`;
}
// Case 3: Scheduled date changed (from value to value)
else if (old && newValue) {
return `${label} changed from ${formatDateTime(old)} to ${formatDateTime(newValue)}.`;
}
return ""; // Fallback, though this shouldn't happen with the filter
})
.filter(Boolean); // Remove any empty strings
const body = fieldMessages.length > 0 ? fieldMessages.join(" ") : "Scheduled dates have been updated.";
const result = {
app: {

View File

@@ -37,10 +37,11 @@ const scenarioParser = async (req, jobIdField) => {
// Step 1: Validate we know what user committed the action that fired the parser
// console.log("Step 1");
const hasuraUserRole = event?.session_variables?.["x-hasura-role"];
const hasuraUserId = event?.session_variables?.["x-hasura-user-id"];
// Bail if we don't know who started the scenario
if (!hasuraUserId) {
if (hasuraUserRole === "user" && !hasuraUserId) {
logger.log("No Hasura user ID found, skipping notification parsing", "info", "notifications");
return;
}
@@ -84,7 +85,7 @@ const scenarioParser = async (req, jobIdField) => {
authId: watcher?.user?.authid
}));
if (FILTER_SELF_FROM_WATCHERS) {
if (FILTER_SELF_FROM_WATCHERS && hasuraUserRole === "user") {
jobWatchers = jobWatchers.filter((watcher) => watcher.authId !== hasuraUserId);
}