|
|
|
|
@@ -1,6 +1,9 @@
|
|
|
|
|
const { getJobAssignmentType, formatTaskPriority } = require("./stringHelpers");
|
|
|
|
|
const moment = require("moment-timezone");
|
|
|
|
|
const { startCase } = require("lodash");
|
|
|
|
|
const Dinero = require("dinero.js");
|
|
|
|
|
|
|
|
|
|
Dinero.globalRoundingMode = "HALF_EVEN";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Populates the recipients for app, email, and FCM notifications based on scenario watchers.
|
|
|
|
|
@@ -26,7 +29,6 @@ const populateWatchers = (data, result) => {
|
|
|
|
|
/**
|
|
|
|
|
* 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 = {
|
|
|
|
|
@@ -59,7 +61,6 @@ const alternateTransportChangedBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* 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();
|
|
|
|
|
@@ -94,9 +95,10 @@ const billPostedHandler = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* 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 body = `A Critical Job Line status has been changed."`;
|
|
|
|
|
|
|
|
|
|
const result = {
|
|
|
|
|
app: {
|
|
|
|
|
jobId: data.jobId,
|
|
|
|
|
@@ -105,8 +107,7 @@ const criticalPartsStatusChangedBuilder = (data) => {
|
|
|
|
|
key: "notifications.job.criticalPartsStatusChanged",
|
|
|
|
|
body,
|
|
|
|
|
variables: {
|
|
|
|
|
queuedForParts: data?.data?.queued_for_parts,
|
|
|
|
|
oldQueuedForParts: data?.changedFields?.queued_for_parts?.old
|
|
|
|
|
joblineId: data?.data?.id // If we want to deeplink to the jobline
|
|
|
|
|
},
|
|
|
|
|
recipients: []
|
|
|
|
|
},
|
|
|
|
|
@@ -127,7 +128,6 @@ const criticalPartsStatusChangedBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* 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.`;
|
|
|
|
|
@@ -161,7 +161,6 @@ const intakeDeliveryChecklistCompletedBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* Builds notification data for job assignment events.
|
|
|
|
|
*/
|
|
|
|
|
// Verified
|
|
|
|
|
const jobAssignedToMeBuilder = (data) => {
|
|
|
|
|
const body = `You have been assigned to ${getJobAssignmentType(data.scenarioFields?.[0])}.`;
|
|
|
|
|
const result = {
|
|
|
|
|
@@ -193,7 +192,6 @@ const jobAssignedToMeBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* Builds notification data for jobs added to production.
|
|
|
|
|
*/
|
|
|
|
|
// Verified
|
|
|
|
|
const jobsAddedToProductionBuilder = (data) => {
|
|
|
|
|
const body = `Has been added to Production.`;
|
|
|
|
|
const result = {
|
|
|
|
|
@@ -223,7 +221,6 @@ const jobsAddedToProductionBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* 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 = {
|
|
|
|
|
@@ -256,7 +253,6 @@ const jobStatusChangeBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* 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";
|
|
|
|
|
@@ -295,7 +291,6 @@ const newMediaAddedReassignedBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* Builds notification data for new notes added to a job.
|
|
|
|
|
*/
|
|
|
|
|
// verified
|
|
|
|
|
const newNoteAddedBuilder = (data) => {
|
|
|
|
|
const body = [
|
|
|
|
|
"A",
|
|
|
|
|
@@ -340,7 +335,6 @@ const newNoteAddedBuilder = (data) => {
|
|
|
|
|
/**
|
|
|
|
|
* Builds notification data for new time tickets posted.
|
|
|
|
|
*/
|
|
|
|
|
// Verified
|
|
|
|
|
const newTimeTicketPostedBuilder = (data) => {
|
|
|
|
|
const type = data?.data?.cost_center;
|
|
|
|
|
const body =
|
|
|
|
|
@@ -407,7 +401,22 @@ const partMarkedBackOrderedBuilder = (data) => {
|
|
|
|
|
* Builds notification data for payment collection events.
|
|
|
|
|
*/
|
|
|
|
|
const paymentCollectedCompletedBuilder = (data) => {
|
|
|
|
|
const body = `Payment of $${data.data.clm_total} has been collected.`;
|
|
|
|
|
const momentFormat = "MM/DD/YYYY";
|
|
|
|
|
const timezone = data.bodyShopTimezone;
|
|
|
|
|
|
|
|
|
|
// Format amount using Dinero.js
|
|
|
|
|
const amountDinero = Dinero({
|
|
|
|
|
amount: Math.round((data.data.amount || 0) * 100) // Convert to cents, default to 0 if missing
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const amountFormatted = amountDinero.toFormat();
|
|
|
|
|
|
|
|
|
|
const payer = data.data.payer;
|
|
|
|
|
const paymentType = data.data.type;
|
|
|
|
|
const paymentDate = moment(data.data.date).tz(timezone).format(momentFormat);
|
|
|
|
|
|
|
|
|
|
const body = `Payment of ${amountFormatted} has been collected from ${payer} via ${paymentType} on ${paymentDate}`;
|
|
|
|
|
|
|
|
|
|
const result = {
|
|
|
|
|
app: {
|
|
|
|
|
jobId: data.jobId,
|
|
|
|
|
@@ -416,7 +425,10 @@ const paymentCollectedCompletedBuilder = (data) => {
|
|
|
|
|
key: "notifications.job.paymentCollected",
|
|
|
|
|
body,
|
|
|
|
|
variables: {
|
|
|
|
|
clmTotal: data.data.clm_total
|
|
|
|
|
amount: data.data.amount,
|
|
|
|
|
payer: data.data.payer,
|
|
|
|
|
type: data.data.type,
|
|
|
|
|
date: data.data.date
|
|
|
|
|
},
|
|
|
|
|
recipients: []
|
|
|
|
|
},
|
|
|
|
|
@@ -491,20 +503,94 @@ const scheduledDatesChangedBuilder = (data) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds notification data for supplement imported events.
|
|
|
|
|
* Builds notification data for tasks updated or created.
|
|
|
|
|
*/
|
|
|
|
|
const supplementImportedBuilder = (data) => {
|
|
|
|
|
const body = `A supplement of $${data.data.cieca_ttl?.data?.supp_amt || 0} has been imported.`;
|
|
|
|
|
const tasksUpdatedCreatedBuilder = (data) => {
|
|
|
|
|
const momentFormat = "MM/DD/YYYY hh:mm a";
|
|
|
|
|
const timezone = data.bodyShopTimezone;
|
|
|
|
|
const taskTitle = data?.data?.title ? `"${data.data.title}"` : "Unnamed Task";
|
|
|
|
|
|
|
|
|
|
let body;
|
|
|
|
|
let variables;
|
|
|
|
|
|
|
|
|
|
if (data.isNew) {
|
|
|
|
|
// Created case
|
|
|
|
|
const priority = formatTaskPriority(data?.data?.priority);
|
|
|
|
|
const createdBy = data?.data?.created_by || "Unknown"; // Fallback for undefined 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);
|
|
|
|
|
const oldTitle = changedFields.title ? `"${changedFields.title.old || "Unnamed Task"}"` : taskTitle;
|
|
|
|
|
|
|
|
|
|
// Special case: Only 'completed' changed
|
|
|
|
|
if (fieldNames.length === 1 && changedFields.completed) {
|
|
|
|
|
body = `Task ${oldTitle} 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.title) {
|
|
|
|
|
fieldMessages.push(`Task ${oldTitle} changed title to "${changedFields.title.new || "Unnamed Task"}"`);
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
? fieldMessages.length === 1 && changedFields.title
|
|
|
|
|
? fieldMessages[0] // If only title changed, use it standalone
|
|
|
|
|
: `Task ${oldTitle} updated: ${fieldMessages.join(", ")}`
|
|
|
|
|
: `Task ${oldTitle} has been updated.`;
|
|
|
|
|
variables = {
|
|
|
|
|
isNew: data.isNew,
|
|
|
|
|
roNumber: data.jobRoNumber,
|
|
|
|
|
title: data?.data?.title,
|
|
|
|
|
changedTitleOld: data?.changedFields?.title?.old,
|
|
|
|
|
changedTitleNew: data?.changedFields?.title?.new,
|
|
|
|
|
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: "notifications.job.supplementImported",
|
|
|
|
|
key: data.isNew ? "notifications.job.taskCreated" : "notifications.job.taskUpdated",
|
|
|
|
|
body,
|
|
|
|
|
variables: {
|
|
|
|
|
suppAmt: data.data.cieca_ttl?.data?.supp_amt
|
|
|
|
|
},
|
|
|
|
|
variables,
|
|
|
|
|
recipients: []
|
|
|
|
|
},
|
|
|
|
|
email: {
|
|
|
|
|
@@ -522,86 +608,19 @@ const supplementImportedBuilder = (data) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds notification data for tasks updated or created.
|
|
|
|
|
* Builds notification data for supplement imported events.
|
|
|
|
|
* TODO: This is an advanced case and will be done later
|
|
|
|
|
*/
|
|
|
|
|
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 supplementImportedBuilder = (data) => {
|
|
|
|
|
const body = `A supplement has been imported.`;
|
|
|
|
|
const result = {
|
|
|
|
|
app: {
|
|
|
|
|
jobId: data.jobId,
|
|
|
|
|
jobRoNumber: data.jobRoNumber,
|
|
|
|
|
bodyShopId: data.bodyShopId,
|
|
|
|
|
key: data.isNew ? "notifications.job.taskCreated" : "notifications.job.taskUpdated",
|
|
|
|
|
key: "notifications.job.supplementImported",
|
|
|
|
|
body,
|
|
|
|
|
variables,
|
|
|
|
|
variables: {},
|
|
|
|
|
recipients: []
|
|
|
|
|
},
|
|
|
|
|
email: {
|
|
|
|
|
|