{day(notification.created_at).fromNow()}
diff --git a/client/src/contexts/SocketIO/useSocket.jsx b/client/src/contexts/SocketIO/useSocket.jsx
index a68a03e7e..ee51def17 100644
--- a/client/src/contexts/SocketIO/useSocket.jsx
+++ b/client/src/contexts/SocketIO/useSocket.jsx
@@ -299,7 +299,9 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
.catch((e) => console.error(`Error marking notification read: ${e?.message || ""}`));
}}
>
- {t("notifications.labels.notification-popup-title", { ro_number: jobRoNumber })}
+ {t("notifications.labels.notification-popup-title", {
+ ro_number: jobRoNumber || t("general.labels.na")
+ })}
),
description: (
diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml
index 4a780b262..40f1de070 100644
--- a/hasura/metadata/tables.yaml
+++ b/hasura/metadata/tables.yaml
@@ -3303,7 +3303,7 @@
request_transform:
body:
action: transform
- template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body.event.session_variables.x-hasura-user-id}}\r\n }, \r\n \"op\": \"UPDATE\",\r\n \"data\": {\r\n \"old\": {\r\n \"id\": {{$body.event.data.old.id}},\r\n \"jobid\": {{$body.event.data.old.jobid}},\r\n \"critical\": {{$body.event.data.old.critical}},\r\n \"status\": {{$body.event.data.old.status}}\r\n },\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"jobid\": {{$body.event.data.new.jobid}},\r\n \"critical\": {{$body.event.data.old.critical}},\r\n \"status\": {{$body.event.data.new.status}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_joblines\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"joblines\"\r\n }\r\n}\r\n"
+ template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body?.event?.session_variables?.x-hasura-user-id ?? \"Internal\"}}\r\n }, \r\n \"op\": \"UPDATE\",\r\n \"data\": {\r\n \"old\": {\r\n \"id\": {{$body.event.data.old.id}},\r\n \"jobid\": {{$body.event.data.old.jobid}},\r\n \"critical\": {{$body.event.data.old.critical}},\r\n \"status\": {{$body.event.data.old.status}}\r\n },\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"jobid\": {{$body.event.data.new.jobid}},\r\n \"critical\": {{$body.event.data.new.critical}},\r\n \"status\": {{$body.event.data.new.status}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_joblines\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"joblines\"\r\n }\r\n}\r\n"
method: POST
query_params: {}
template_engine: Kriti
@@ -4573,7 +4573,7 @@
request_transform:
body:
action: transform
- template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body.event.session_variables.x-hasura-user-id}}\r\n }, \r\n \"op\": \"UPDATE\",\r\n \"data\": {\r\n \"old\": {\r\n \"id\": {{$body.event.data.old.id}},\r\n \"ro_number\": {{$body.event.data.old.ro_number}},\r\n \"queued_for_parts\": {{$body.event.data.old.queued_for_parts}},\r\n \"employee_prep\": {{$body.event.data.old.employee_prep}},\r\n \"clm_total\": {{$body.event.data.old.clm_total}},\r\n \"towin\": {{$body.event.data.old.towin}},\r\n \"employee_body\": {{$body.event.data.old.employee_body}},\r\n \"converted\": {{$body.event.data.old.converted}},\r\n \"scheduled_in\": {{$body.event.data.old.scheduled_in}},\r\n \"scheduled_completion\": {{$body.event.data.old.scheduled_completion}},\r\n \"scheduled_delivery\": {{$body.event.data.old.scheduled_delivery}},\r\n \"actual_delivery\": {{$body.event.data.old.actual_delivery}},\r\n \"actual_completion\": {{$body.event.data.old.actual_completion}},\r\n \"alt_transport\": {{$body.event.data.old.alt_transport}},\r\n \"date_exported\": {{$body.event.data.old.date_exported}},\r\n \"status\": {{$body.event.data.old.status}},\r\n \"employee_csr\": {{$body.event.data.old.employee_csr}},\r\n \"actual_in\": {{$body.event.data.old.actual_in}},\r\n \"deliverchecklist\": {{$body.event.data.old.deliverchecklist}},\r\n \"comment\": {{$body.event.data.old.comment}},\r\n \"employee_refinish\": {{$body.event.data.old.employee_refinish}},\r\n \"inproduction\": {{$body.event.data.old.inproduction}},\r\n \"production_vars\": {{$body.event.data.old.production_vars}},\r\n \"intakechecklist\": {{$body.event.data.old.intakechecklist}},\r\n \"cieca_ttl\": {{$body.event.data.old.cieca_ttl}},\r\n \"date_invoiced\": {{$body.event.data.old.date_invoiced}}\r\n },\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"ro_number\": {{$body.event.data.old.ro_number}},\r\n \"queued_for_parts\": {{$body.event.data.new.queued_for_parts}},\r\n \"employee_prep\": {{$body.event.data.new.employee_prep}},\r\n \"clm_total\": {{$body.event.data.new.clm_total}},\r\n \"towin\": {{$body.event.data.new.towin}},\r\n \"employee_body\": {{$body.event.data.new.employee_body}},\r\n \"converted\": {{$body.event.data.new.converted}},\r\n \"scheduled_in\": {{$body.event.data.new.scheduled_in}},\r\n \"scheduled_completion\": {{$body.event.data.new.scheduled_completion}},\r\n \"scheduled_delivery\": {{$body.event.data.new.scheduled_delivery}},\r\n \"actual_delivery\": {{$body.event.data.new.actual_delivery}},\r\n \"actual_completion\": {{$body.event.data.new.actual_completion}},\r\n \"alt_transport\": {{$body.event.data.new.alt_transport}},\r\n \"date_exported\": {{$body.event.data.new.date_exported}},\r\n \"status\": {{$body.event.data.new.status}},\r\n \"employee_csr\": {{$body.event.data.new.employee_csr}},\r\n \"actual_in\": {{$body.event.data.new.actual_in}},\r\n \"deliverchecklist\": {{$body.event.data.new.deliverchecklist}},\r\n \"comment\": {{$body.event.data.new.comment}},\r\n \"employee_refinish\": {{$body.event.data.new.employee_refinish}},\r\n \"inproduction\": {{$body.event.data.new.inproduction}},\r\n \"production_vars\": {{$body.event.data.new.production_vars}},\r\n \"intakechecklist\": {{$body.event.data.new.intakechecklist}},\r\n \"cieca_ttl\": {{$body.event.data.new.cieca_ttl}},\r\n \"date_invoiced\": {{$body.event.data.new.date_invoiced}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_jobs\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"jobs\"\r\n }\r\n}\r\n"
+ template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body?.event?.session_variables?.x-hasura-user-id ?? \"Internal\"}}\r\n }, \r\n \"op\": \"UPDATE\",\r\n \"data\": {\r\n \"old\": {\r\n \"id\": {{$body.event.data.old.id}},\r\n \"ro_number\": {{$body.event.data.old.ro_number}},\r\n \"queued_for_parts\": {{$body.event.data.old.queued_for_parts}},\r\n \"employee_prep\": {{$body.event.data.old.employee_prep}},\r\n \"clm_total\": {{$body.event.data.old.clm_total}},\r\n \"towin\": {{$body.event.data.old.towin}},\r\n \"employee_body\": {{$body.event.data.old.employee_body}},\r\n \"converted\": {{$body.event.data.old.converted}},\r\n \"scheduled_in\": {{$body.event.data.old.scheduled_in}},\r\n \"scheduled_completion\": {{$body.event.data.old.scheduled_completion}},\r\n \"scheduled_delivery\": {{$body.event.data.old.scheduled_delivery}},\r\n \"actual_delivery\": {{$body.event.data.old.actual_delivery}},\r\n \"actual_completion\": {{$body.event.data.old.actual_completion}},\r\n \"alt_transport\": {{$body.event.data.old.alt_transport}},\r\n \"date_exported\": {{$body.event.data.old.date_exported}},\r\n \"status\": {{$body.event.data.old.status}},\r\n \"employee_csr\": {{$body.event.data.old.employee_csr}},\r\n \"actual_in\": {{$body.event.data.old.actual_in}},\r\n \"deliverchecklist\": {{$body.event.data.old.deliverchecklist}},\r\n \"comment\": {{$body.event.data.old.comment}},\r\n \"employee_refinish\": {{$body.event.data.old.employee_refinish}},\r\n \"inproduction\": {{$body.event.data.old.inproduction}},\r\n \"production_vars\": {{$body.event.data.old.production_vars}},\r\n \"intakechecklist\": {{$body.event.data.old.intakechecklist}},\r\n \"cieca_ttl\": {{$body.event.data.old.cieca_ttl}},\r\n \"date_invoiced\": {{$body.event.data.old.date_invoiced}}\r\n },\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"ro_number\": {{$body.event.data.old.ro_number}},\r\n \"queued_for_parts\": {{$body.event.data.new.queued_for_parts}},\r\n \"employee_prep\": {{$body.event.data.new.employee_prep}},\r\n \"clm_total\": {{$body.event.data.new.clm_total}},\r\n \"towin\": {{$body.event.data.new.towin}},\r\n \"employee_body\": {{$body.event.data.new.employee_body}},\r\n \"converted\": {{$body.event.data.new.converted}},\r\n \"scheduled_in\": {{$body.event.data.new.scheduled_in}},\r\n \"scheduled_completion\": {{$body.event.data.new.scheduled_completion}},\r\n \"scheduled_delivery\": {{$body.event.data.new.scheduled_delivery}},\r\n \"actual_delivery\": {{$body.event.data.new.actual_delivery}},\r\n \"actual_completion\": {{$body.event.data.new.actual_completion}},\r\n \"alt_transport\": {{$body.event.data.new.alt_transport}},\r\n \"date_exported\": {{$body.event.data.new.date_exported}},\r\n \"status\": {{$body.event.data.new.status}},\r\n \"employee_csr\": {{$body.event.data.new.employee_csr}},\r\n \"actual_in\": {{$body.event.data.new.actual_in}},\r\n \"deliverchecklist\": {{$body.event.data.new.deliverchecklist}},\r\n \"comment\": {{$body.event.data.new.comment}},\r\n \"employee_refinish\": {{$body.event.data.new.employee_refinish}},\r\n \"inproduction\": {{$body.event.data.new.inproduction}},\r\n \"production_vars\": {{$body.event.data.new.production_vars}},\r\n \"intakechecklist\": {{$body.event.data.new.intakechecklist}},\r\n \"cieca_ttl\": {{$body.event.data.new.cieca_ttl}},\r\n \"date_invoiced\": {{$body.event.data.new.date_invoiced}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_jobs\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"jobs\"\r\n }\r\n}\r\n"
method: POST
query_params: {}
template_engine: Kriti
diff --git a/server/notifications/queues/emailQueue.js b/server/notifications/queues/emailQueue.js
index fe25c99fa..9a07a0db1 100644
--- a/server/notifications/queues/emailQueue.js
+++ b/server/notifications/queues/emailQueue.js
@@ -118,12 +118,12 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
const details = await pubClient.hgetall(detailsKey);
const firstName = details.firstName || "User";
const multipleUpdateString = messages.length > 1 ? "Updates" : "Update";
- const subject = `${multipleUpdateString} for job ${jobRoNumber} at ${bodyShopName}`;
+ const subject = `${multipleUpdateString} for job ${jobRoNumber || "N/A"} at ${bodyShopName}`;
const emailBody = generateEmailTemplate({
- header: `${multipleUpdateString} for Job ${jobRoNumber}`,
+ header: `${multipleUpdateString} for Job ${jobRoNumber || "N/A"}`,
subHeader: `Dear ${firstName},`,
body: `
- There have been updates to job ${jobRoNumber} at ${bodyShopName}:
+ There have been updates to job ${jobRoNumber || "N/A"} at ${bodyShopName}:
${messages.map((msg) => `- ${msg}
`).join("")}
@@ -224,7 +224,7 @@ const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
if (!jobId || !jobRoNumber || !bodyShopName || !body || !recipients.length) {
logger.logger.warn(
`Skipping email dispatch for jobId ${jobId} due to missing data: ` +
- `jobRoNumber=${jobRoNumber}, bodyShopName=${bodyShopName}, body=${body}, recipients=${recipients.length}`
+ `jobRoNumber=${jobRoNumber || "N/A"}, bodyShopName=${bodyShopName}, body=${body}, recipients=${recipients.length}`
);
continue;
}
diff --git a/server/notifications/scenarioBuilders.js b/server/notifications/scenarioBuilders.js
index 1355c437b..18cf8cfe9 100644
--- a/server/notifications/scenarioBuilders.js
+++ b/server/notifications/scenarioBuilders.js
@@ -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: {
diff --git a/server/notifications/scenarioMapper.js b/server/notifications/scenarioMapper.js
index 8a3923997..c125f3952 100644
--- a/server/notifications/scenarioMapper.js
+++ b/server/notifications/scenarioMapper.js
@@ -95,7 +95,7 @@ const notificationScenarios = [
builder: intakeDeliveryChecklistCompletedBuilder
},
{
- key: "payment-added",
+ key: "payment-collected-completed",
table: "payments",
onNew: true,
builder: paymentCollectedCompletedBuilder
@@ -110,9 +110,10 @@ const notificationScenarios = [
{
key: "critical-parts-status-changed",
table: "joblines",
- fields: ["critical"],
- onlyTruthyValues: ["critical"],
- builder: criticalPartsStatusChangedBuilder
+ fields: ["status"],
+ onlyTruthyValues: ["status"],
+ builder: criticalPartsStatusChangedBuilder,
+ filterCallback: ({ eventData }) => !eventData?.data?.critical
},
{
key: "part-marked-back-ordered",