Files
bodyshop/server/notifications/scenarioBuilders.js
2025-03-06 13:36:19 -05:00

638 lines
17 KiB
JavaScript

const { getJobAssignmentType, formatTaskPriority } = require("./stringHelpers");
const moment = require("moment-timezone");
const { startCase } = require("lodash");
/**
* Populates the recipients for app, email, and FCM notifications based on scenario watchers.
*
* @param {Object} data - The data object containing scenarioWatchers and bodyShopId.
* @param {Object} result - The result object to populate with recipients for app, email, and FCM notifications.
*/
const populateWatchers = (data, result) => {
data.scenarioWatchers.forEach((recipients) => {
const { user, app, fcm, email, firstName, lastName, employeeId, associationId } = recipients;
if (app === true)
result.app.recipients.push({
user,
bodyShopId: data.bodyShopId,
employeeId,
associationId
});
if (fcm === true) result.fcm.recipients.push(user);
if (email === true) result.email.recipients.push({ user, firstName, lastName });
});
};
/**
* Builds notification data for changes to alternate transport.
*/
// Verified
const alternateTransportChangedBuilder = (data) => {
const body = `The Alternate Transport status has been updated from ${data.changedFields.alt_transport?.old || "unset"} to ${data?.changedFields?.alt_transport?.new || "unset"}.`;
const result = {
app: {
jobId: data.jobId,
bodyShopId: data.bodyShopId,
jobRoNumber: data.jobRoNumber,
key: "notifications.job.alternateTransportChanged",
body,
variables: {
alternateTransport: data?.changedFields?.alt_transport?.new,
oldAlternateTransport: data?.changedFields?.alt_transport?.old
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for bill posted events.
*/
//verified
const billPostedHandler = (data) => {
const facing = data?.data?.isinhouse ? "In-House" : "External";
const body = `An ${facing} Bill has been posted${data?.data?.is_credit_memo ? " (Credit Memo)" : ""}.`.trim();
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.billPosted",
body,
variables: {
isInHouse: data?.data?.isinhouse,
isCreditMemo: data?.data?.is_credit_memo
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for changes to critical parts status.
*/
// TODO: Needs change
const criticalPartsStatusChangedBuilder = (data) => {
const body = `The critical parts status has changed to ${data?.data?.queued_for_parts ? "queued" : "not queued"}.`;
const result = {
app: {
jobId: data.jobId,
bodyShopId: data.bodyShopId,
jobRoNumber: data.jobRoNumber,
key: "notifications.job.criticalPartsStatusChanged",
body,
variables: {
queuedForParts: data?.data?.queued_for_parts,
oldQueuedForParts: data?.changedFields?.queued_for_parts?.old
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for completed intake or delivery checklists.
*/
// Verified
const intakeDeliveryChecklistCompletedBuilder = (data) => {
const checklistType = data?.changedFields?.intakechecklist ? "Intake" : "Delivery";
const body = `The ${checklistType.charAt(0).toUpperCase() + checklistType.slice(1)} checklist has been completed.`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.checklistCompleted",
body,
variables: {
checklistType,
completed: true
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for job assignment events.
*/
// Verified
const jobAssignedToMeBuilder = (data) => {
const body = `You have been assigned to ${getJobAssignmentType(data.scenarioFields?.[0])}.`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.assigned",
body,
variables: {
type: data.scenarioFields?.[0]
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for jobs added to production.
*/
// Verified
const jobsAddedToProductionBuilder = (data) => {
const body = `Has been added to Production.`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.addedToProduction",
body,
variables: {},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for job status changes.
*/
// Verified
const jobStatusChangeBuilder = (data) => {
const body = `The status has changed from ${data?.changedFields?.status?.old || "unset"} to ${data?.changedFields?.status?.new || "unset"}`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.statusChanged",
body,
variables: {
status: data.changedFields.status.new,
oldStatus: data.changedFields.status.old
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for new media added or reassigned events.
*/
// Verified
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";
// Construct the body string
const body = `An ${mediaType} has been ${action}.`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.newMediaAdded",
body,
variables: {
mediaType,
action
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for new notes added to a job.
*/
// verified
const newNoteAddedBuilder = (data) => {
const body = [
"A",
data?.data?.critical && "critical",
data?.data?.private && "private",
data?.data?.type,
"Note has been added by",
`${data.data.created_by}`
]
.filter(Boolean)
.join(" ");
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.newNoteAdded",
body,
variables: {
createdBy: data?.data?.created_by,
critical: data?.data?.critical,
type: data?.data?.type,
private: data?.data?.private
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for new time tickets posted.
*/
// Verified
const newTimeTicketPostedBuilder = (data) => {
const type = data?.data?.cost_center;
const body =
`A ${startCase(type.toLowerCase())} Time Ticket for ${data?.data?.date} has been posted${data?.data?.flat_rate ? " (Flat Rate)" : ""}.`.trim();
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.newTimeTicketPosted",
body,
variables: {
type,
date: data?.data?.date,
flatRate: data?.data?.flat_rate
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for parts marked as back-ordered.
*/
const partMarkedBackOrderedBuilder = (data) => {
const body = `A part has been marked as back-ordered.`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.partBackOrdered",
body,
variables: {},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for payment collection events.
*/
const paymentCollectedCompletedBuilder = (data) => {
const body = `Payment of $${data.data.clm_total} has been collected.`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.paymentCollected",
body,
variables: {
clmTotal: data.data.clm_total
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* 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
const fieldConfigs = {
scheduled_in: "Scheduled In",
scheduled_completion: "Scheduled Completion",
scheduled_delivery: "Scheduled Delivery"
};
// 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.";
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.scheduledDatesChanged",
body,
variables: {
scheduledIn: changedFields.scheduled_in?.new,
oldScheduledIn: changedFields.scheduled_in?.old,
scheduledCompletion: changedFields.scheduled_completion?.new,
oldScheduledCompletion: changedFields.scheduled_completion?.old,
scheduledDelivery: changedFields.scheduled_delivery?.new,
oldScheduledDelivery: changedFields.scheduled_delivery?.old
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for supplement imported events.
*/
const supplementImportedBuilder = (data) => {
const body = `A supplement of $${data.data.cieca_ttl?.data?.supp_amt || 0} has been imported.`;
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: "notifications.job.supplementImported",
body,
variables: {
suppAmt: data.data.cieca_ttl?.data?.supp_amt
},
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
/**
* Builds notification data for tasks updated or created.
*/
const tasksUpdatedCreatedBuilder = (data) => {
const momentFormat = "MM/DD/YYYY hh:mm a";
const timezone = data.bodyShopTimezone;
const taskTitle = data?.data?.title;
let body;
let variables;
if (data.isNew) {
// Created case
const priority = formatTaskPriority(data?.data?.priority);
const createdBy = data?.data?.created_by;
const dueDate = data.data.due_date ? ` due on ${moment(data.data.due_date).tz(timezone).format(momentFormat)}` : "";
const completedOnCreation = data.data.completed === true;
body = `A ${priority} Task ${taskTitle} has been created${completedOnCreation ? " and marked completed" : ""} by ${createdBy}${dueDate}`;
variables = {
isNew: data.isNew,
roNumber: data.jobRoNumber,
title: data?.data?.title,
priority: data?.data?.priority,
createdBy: data?.data?.created_by,
dueDate: data?.data?.due_date,
completed: completedOnCreation ? data?.data?.completed : undefined // Only include if true
};
} else {
// Updated case
const changedFields = data.changedFields;
const fieldNames = Object.keys(changedFields);
// Special case: Only 'completed' changed
if (fieldNames.length === 1 && changedFields.completed) {
body = `Task ${taskTitle} was marked ${changedFields.completed.new ? "complete" : "incomplete"}`;
variables = {
isNew: data.isNew,
roNumber: data.jobRoNumber,
title: data?.data?.title,
changedCompleted: data?.changedFields?.completed?.new
};
} else {
// General update case
const fieldMessages = [];
if (changedFields.description) {
fieldMessages.push("Description Updated");
}
if (changedFields.priority) {
fieldMessages.push(`Priority changed to ${formatTaskPriority(changedFields.priority.new)}`);
}
if (changedFields.due_date) {
fieldMessages.push(`Due date set to ${moment(changedFields.due_date.new).tz(timezone).format(momentFormat)}`);
}
if (changedFields.completed) {
fieldMessages.push(`Status changed to ${changedFields.completed.new ? "complete" : "incomplete"}`);
}
body =
fieldMessages.length > 0
? `Task ${taskTitle} updated: ${fieldMessages.join(", ")}`
: `Task ${taskTitle} has been updated.`;
variables = {
isNew: data.isNew,
roNumber: data.jobRoNumber,
title: data?.data?.title,
changedPriority: data?.changedFields?.priority?.new,
changedDueDate: data?.changedFields?.due_date?.new,
changedCompleted: data?.changedFields?.completed?.new
};
}
}
const result = {
app: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId,
key: data.isNew ? "notifications.job.taskCreated" : "notifications.job.taskUpdated",
body,
variables,
recipients: []
},
email: {
jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName,
body,
recipients: []
},
fcm: { recipients: [] }
};
populateWatchers(data, result);
return result;
};
module.exports = {
alternateTransportChangedBuilder,
billPostedHandler,
criticalPartsStatusChangedBuilder,
intakeDeliveryChecklistCompletedBuilder,
jobAssignedToMeBuilder,
jobsAddedToProductionBuilder,
jobStatusChangeBuilder,
newMediaAddedReassignedBuilder,
newNoteAddedBuilder,
newTimeTicketPostedBuilder,
partMarkedBackOrderedBuilder,
paymentCollectedCompletedBuilder,
scheduledDatesChangedBuilder,
supplementImportedBuilder,
tasksUpdatedCreatedBuilder
};