Merged in feature/IO-3166-Global-Notifications-Part-2 (pull request #2156)

Feature/IO-3166 Global Notifications Part 2
This commit is contained in:
Dave Richer
2025-03-06 18:39:46 +00:00
13 changed files with 207 additions and 77 deletions

View File

@@ -4,6 +4,7 @@ import { Avatar, Button, Divider, List, Popover, Select, Tooltip, Typography } f
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import EmployeeSearchSelectComponent from "../../components/employee-search-select/employee-search-select.component.jsx"; import EmployeeSearchSelectComponent from "../../components/employee-search-select/employee-search-select.component.jsx";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component.jsx"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component.jsx";
import { BiSolidTrash } from "react-icons/bi";
const { Text } = Typography; const { Text } = Typography;
@@ -42,7 +43,8 @@ export default function JobWatcherToggleComponent({
<Button <Button
type="default" type="default"
danger danger
size="small" size="medium"
icon={<BiSolidTrash />}
onClick={() => handleRemoveWatcher(watcher.user_email)} onClick={() => handleRemoveWatcher(watcher.user_email)}
disabled={adding || removing} // Optional: Disable button during mutations disabled={adding || removing} // Optional: Disable button during mutations
> >
@@ -61,20 +63,28 @@ export default function JobWatcherToggleComponent({
const popoverContent = ( const popoverContent = (
<div style={{ width: 600 }}> <div style={{ width: 600 }}>
<Button <List>
block <List.Item
type={isWatching ? "primary" : "default"} actions={[
icon={isWatching ? <EyeOutlined /> : <EyeFilled />} <Button
onClick={handleToggleSelf} type={isWatching ? "primary" : "default"}
loading={adding || removing} danger={!isWatching}
> icon={isWatching ? <EyeOutlined /> : <EyeFilled />}
{isWatching ? t("notifications.tooltips.unwatch") : t("notifications.tooltips.watch")} size="medium"
</Button> onClick={handleToggleSelf}
<Divider /> loading={adding || removing}
>
<Text type="secondary" style={{ marginBottom: 8, display: "block" }}> {isWatching ? t("notifications.labels.unwatch") : t("notifications.labels.watch")}
{t("notifications.labels.watching-issue")} </Button>
</Text> ]}
>
<List.Item.Meta>
<Text type="secondary" style={{ marginBottom: 8, display: "block" }}>
{t("notifications.labels.watching-issue")}
</Text>
</List.Item.Meta>
</List.Item>
</List>
{watcherLoading ? ( {watcherLoading ? (
<LoadingSpinner /> <LoadingSpinner />
) : jobWatchers && jobWatchers.length > 0 ? ( ) : jobWatchers && jobWatchers.length > 0 ? (
@@ -82,12 +92,16 @@ export default function JobWatcherToggleComponent({
) : ( ) : (
<Text type="secondary">{t("notifications.labels.no-watchers")}</Text> <Text type="secondary">{t("notifications.labels.no-watchers")}</Text>
)} )}
<Divider />
<Divider />
<Text type="secondary">{t("notifications.labels.add-watchers")}</Text> <Text type="secondary">{t("notifications.labels.add-watchers")}</Text>
<EmployeeSearchSelectComponent <EmployeeSearchSelectComponent
style={{ minWidth: "100%" }} style={{ minWidth: "100%" }}
options={bodyshop?.employees?.filter((e) => jobWatchers.every((w) => w.user_email !== e.user_email)) || []} options={
bodyshop?.employees?.filter((e) =>
jobWatchers.every((w) => w.user_email !== e.user_email && e.active && e.user_email)
) || []
}
placeholder={t("notifications.labels.employee-search")} placeholder={t("notifications.labels.employee-search")}
value={selectedWatcher} value={selectedWatcher}
onChange={(value) => { onChange={(value) => {
@@ -110,10 +124,9 @@ export default function JobWatcherToggleComponent({
const teamMembers = team.employee_team_members const teamMembers = team.employee_team_members
.map((member) => { .map((member) => {
const employee = bodyshop?.employees?.find((e) => e.id === member.employeeid); const employee = bodyshop?.employees?.find((e) => e.id === member.employeeid);
return employee ? employee.user_email : null; return employee?.user_email && employee?.active ? employee.user_email : null;
}) })
.filter(Boolean); .filter(Boolean);
return { return {
value: JSON.stringify(teamMembers), value: JSON.stringify(teamMembers),
label: team.name label: team.name
@@ -128,7 +141,7 @@ export default function JobWatcherToggleComponent({
return ( return (
<Popover content={popoverContent} trigger="click" open={open} onOpenChange={setOpen}> <Popover content={popoverContent} trigger="click" open={open} onOpenChange={setOpen}>
<Tooltip title={isWatching ? t("notifications.tooltips.unwatch") : t("notifications.tooltips.watch")}> <Tooltip title={t("notifications.tooltips.job-watchers")}>
<Button <Button
shape="circle" shape="circle"
type={isWatching ? "primary" : "default"} type={isWatching ? "primary" : "default"}

View File

@@ -211,6 +211,7 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
handleRemoveWatcher={handleRemoveWatcher} handleRemoveWatcher={handleRemoveWatcher}
handleWatcherSelect={handleWatcherSelect} handleWatcherSelect={handleWatcherSelect}
handleTeamSelect={handleTeamSelect} handleTeamSelect={handleTeamSelect}
currentUser={currentUser}
/> />
); );
} }

View File

@@ -3785,17 +3785,18 @@
"ro-number": "RO #{{ro_number}}", "ro-number": "RO #{{ro_number}}",
"no-watchers": "No Watchers", "no-watchers": "No Watchers",
"notification-settings-success": "Notification Settings saved successfully.", "notification-settings-success": "Notification Settings saved successfully.",
"notification-settings-failure": "Error saving Notification Settings. {{error}}" "notification-settings-failure": "Error saving Notification Settings. {{error}}",
"watch": "Watch",
"unwatch": "Unwatch"
}, },
"actions": { "actions": {
"remove": "remove" "remove": "Remove"
}, },
"aria": { "aria": {
"toggle": "Toggle Watching Job" "toggle": "Toggle Watching Job"
}, },
"tooltips": { "tooltips": {
"watch": "Watch Job", "job-watchers": "Job Watchers"
"unwatch": "Unwatch Job"
}, },
"scenarios": { "scenarios": {
"job-assigned-to-me": "Job Assigned to Me", "job-assigned-to-me": "Job Assigned to Me",

View File

@@ -3785,7 +3785,9 @@
"ro-number": "", "ro-number": "",
"no-watchers": "", "no-watchers": "",
"notification-settings-success": "", "notification-settings-success": "",
"notification-settings-failure": "" "notification-settings-failure": "",
"watch": "",
"unwatch": ""
}, },
"actions": { "actions": {
"remove": "" "remove": ""
@@ -3794,8 +3796,7 @@
"toggle": "" "toggle": ""
}, },
"tooltips": { "tooltips": {
"watch": "", "job-watchers": ""
"unwatch": ""
}, },
"scenarios": { "scenarios": {
"job-assigned-to-me": "", "job-assigned-to-me": "",

View File

@@ -3785,7 +3785,9 @@
"ro-number": "", "ro-number": "",
"no-watchers": "", "no-watchers": "",
"notification-settings-success": "", "notification-settings-success": "",
"notification-settings-failure": "" "notification-settings-failure": "",
"watch": "",
"unwatch": ""
}, },
"actions": { "actions": {
"remove": "" "remove": ""
@@ -3794,8 +3796,7 @@
"toggle": "" "toggle": ""
}, },
"tooltips": { "tooltips": {
"watch": "", "job-watchers": ""
"unwatch": ""
}, },
"scenarios": { "scenarios": {
"job-assigned-to-me": "", "job-assigned-to-me": "",

View File

@@ -6290,10 +6290,12 @@
columns: columns:
- joblineid - joblineid
- assigned_to - assigned_to
- due_date
- partsorderid - partsorderid
- completed - completed
- description - description
- billid - billid
- title
- priority - priority
retry_conf: retry_conf:
interval_sec: 10 interval_sec: 10

View File

@@ -11,6 +11,7 @@ const moment = require("moment-timezone");
const { taskEmailQueue } = require("./tasksEmailsQueue"); const { taskEmailQueue } = require("./tasksEmailsQueue");
const mailer = require("./mailer"); const mailer = require("./mailer");
const { InstanceEndpoints } = require("../utils/instanceMgr"); const { InstanceEndpoints } = require("../utils/instanceMgr");
const { formatTaskPriority } = require("../notifications/stringHelpers");
// Initialize the Tasks Email Queue // Initialize the Tasks Email Queue
const tasksEmailQueue = taskEmailQueue(); const tasksEmailQueue = taskEmailQueue();
@@ -62,16 +63,6 @@ const formatDate = (date) => {
return date ? `| Due on: ${moment(date).format("MM/DD/YYYY")}` : ""; return date ? `| Due on: ${moment(date).format("MM/DD/YYYY")}` : "";
}; };
const formatPriority = (priority) => {
if (priority === 1) {
return "High";
} else if (priority === 3) {
return "Low";
} else {
return "Medium";
}
};
/** /**
* Generate the email template arguments. * Generate the email template arguments.
* @param title * @param title
@@ -88,7 +79,7 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
const endPoints = InstanceEndpoints(); const endPoints = InstanceEndpoints();
return { return {
header: title, header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)} | Created By: ${createdBy || "N/A"}`, subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatTaskPriority(priority)} ${formatDate(dueDate)} | Created By: ${createdBy || "N/A"}`,
body: `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`, body: `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`,
dateLine dateLine
}; };
@@ -155,7 +146,7 @@ const taskAssignedEmail = async (req, res) => {
sendMail( sendMail(
"assigned", "assigned",
tasks_by_pk.assigned_to_employee.user_email, tasks_by_pk.assigned_to_employee.user_email,
`A ${formatPriority(newTask.priority)} priority task has been ${dirty ? "reassigned to" : "created for"} you - ${newTask.title}`, `A ${formatTaskPriority(newTask.priority)} priority task has been ${dirty ? "reassigned to" : "created for"} you - ${newTask.title}`,
generateEmailTemplate( generateEmailTemplate(
generateTemplateArgs( generateTemplateArgs(
newTask.title, newTask.title,
@@ -239,7 +230,7 @@ const tasksRemindEmail = async (req, res) => {
const onlyTask = groupedTasks[recipient.email][0]; const onlyTask = groupedTasks[recipient.email][0];
emailData.subject = emailData.subject =
`New ${formatPriority(onlyTask.priority)} Priority Task Reminder - ${onlyTask.title} ${onlyTask.due_date ? `- ${formatDate(onlyTask.due_date)}` : ""}`.trim(); `New ${formatTaskPriority(onlyTask.priority)} Priority Task Reminder - ${onlyTask.title} ${onlyTask.due_date ? `- ${formatDate(onlyTask.due_date)}` : ""}`.trim();
emailData.html = generateEmailTemplate( emailData.html = generateEmailTemplate(
generateTemplateArgs( generateTemplateArgs(
@@ -266,7 +257,7 @@ const tasksRemindEmail = async (req, res) => {
body: `<ul> body: `<ul>
${allTasks ${allTasks
.map((task) => .map((task) =>
`<li><a href="${InstanceEndpoints()}/manage/tasks/alltasks?taskid=${task.id}">${task.title} - Priority: ${formatPriority(task.priority)} ${task.due_date ? `${formatDate(task.due_date)}` : ""} | Bodyshop: ${task.bodyshop.shopname}</a></li>`.trim() `<li><a href="${InstanceEndpoints()}/manage/tasks/alltasks?taskid=${task.id}">${task.title} - Priority: ${formatTaskPriority(task.priority)} ${task.due_date ? `${formatDate(task.due_date)}` : ""} | Bodyshop: ${task.bodyshop.shopname}</a></li>`.trim()
) )
.join("")} .join("")}
</ul>` </ul>`

View File

@@ -2726,6 +2726,7 @@ query GET_JOB_WATCHERS($jobid: uuid!) {
bodyshop { bodyshop {
id id
shopname shopname
timezone
} }
} }
} }

View File

@@ -7,7 +7,7 @@ const graphQLClient = require("../../graphql-client/graphql-client").client;
const APP_CONSOLIDATION_DELAY_IN_MINS = (() => { const APP_CONSOLIDATION_DELAY_IN_MINS = (() => {
const envValue = process.env?.APP_CONSOLIDATION_DELAY_IN_MINS; const envValue = process.env?.APP_CONSOLIDATION_DELAY_IN_MINS;
const parsedValue = envValue ? parseInt(envValue, 10) : NaN; const parsedValue = envValue ? parseInt(envValue, 10) : NaN;
return isNaN(parsedValue) ? 1 : Math.max(1, parsedValue); // Default to 1, ensure at least 1 return isNaN(parsedValue) ? 3 : Math.max(1, parsedValue); // Default to 3, ensure at least 1
})(); })();
// Base time-related constant (in milliseconds) / DO NOT TOUCH // Base time-related constant (in milliseconds) / DO NOT TOUCH

View File

@@ -7,7 +7,7 @@ const { registerCleanupTask } = require("../../utils/cleanupManager");
const EMAIL_CONSOLIDATION_DELAY_IN_MINS = (() => { const EMAIL_CONSOLIDATION_DELAY_IN_MINS = (() => {
const envValue = process.env?.EMAIL_CONSOLIDATION_DELAY_IN_MINS; const envValue = process.env?.EMAIL_CONSOLIDATION_DELAY_IN_MINS;
const parsedValue = envValue ? parseInt(envValue, 10) : NaN; const parsedValue = envValue ? parseInt(envValue, 10) : NaN;
return isNaN(parsedValue) ? 1 : Math.max(1, parsedValue); // Default to 1, ensure at least 1 return isNaN(parsedValue) ? 3 : Math.max(1, parsedValue); // Default to 3, ensure at least 1
})(); })();
// Base time-related constant (in milliseconds) / DO NOT TOUCH // Base time-related constant (in milliseconds) / DO NOT TOUCH

View File

@@ -1,4 +1,6 @@
const { getJobAssignmentType } = require("./stringHelpers"); 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. * Populates the recipients for app, email, and FCM notifications based on scenario watchers.
@@ -26,17 +28,17 @@ const populateWatchers = (data, result) => {
*/ */
// Verified // Verified
const alternateTransportChangedBuilder = (data) => { const alternateTransportChangedBuilder = (data) => {
const body = `The Alternate Transport status has been updated to ${data?.data?.alt_transport}.`; 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 = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
jobRoNumber: data.jobRoNumber, jobRoNumber: data.jobRoNumber,
key: "notifications.job.alternateTransportChanged", key: "notifications.job.alternateTransportChanged",
body, // Same as email body body,
variables: { variables: {
alternateTransport: data.changedFields.alt_transport?.new, alternateTransport: data?.changedFields?.alt_transport?.new,
oldAlternateTransport: data.changedFields.alt_transport?.old oldAlternateTransport: data?.changedFields?.alt_transport?.old
}, },
recipients: [] recipients: []
}, },
@@ -60,7 +62,6 @@ const alternateTransportChangedBuilder = (data) => {
//verified //verified
const billPostedHandler = (data) => { const billPostedHandler = (data) => {
const facing = data?.data?.isinhouse ? "In-House" : "External"; 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 body = `An ${facing} Bill has been posted${data?.data?.is_credit_memo ? " (Credit Memo)" : ""}.`.trim();
const result = { const result = {
@@ -71,8 +72,8 @@ const billPostedHandler = (data) => {
key: "notifications.job.billPosted", key: "notifications.job.billPosted",
body, body,
variables: { variables: {
facing, isInHouse: data?.data?.isinhouse,
is_credit_memo: data?.data?.is_credit_memo isCreditMemo: data?.data?.is_credit_memo
}, },
recipients: [] recipients: []
}, },
@@ -95,7 +96,7 @@ const billPostedHandler = (data) => {
*/ */
// TODO: Needs change // TODO: Needs change
const criticalPartsStatusChangedBuilder = (data) => { const criticalPartsStatusChangedBuilder = (data) => {
const body = `The critical parts status has changed to ${data.data.queued_for_parts ? "queued" : "not queued"}.`; const body = `The critical parts status has changed to ${data?.data?.queued_for_parts ? "queued" : "not queued"}.`;
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
@@ -104,8 +105,8 @@ const criticalPartsStatusChangedBuilder = (data) => {
key: "notifications.job.criticalPartsStatusChanged", key: "notifications.job.criticalPartsStatusChanged",
body, body,
variables: { variables: {
queuedForParts: data.data.queued_for_parts, queuedForParts: data?.data?.queued_for_parts,
oldQueuedForParts: data.changedFields.queued_for_parts?.old oldQueuedForParts: data?.changedFields?.queued_for_parts?.old
}, },
recipients: [] recipients: []
}, },
@@ -162,7 +163,7 @@ const intakeDeliveryChecklistCompletedBuilder = (data) => {
*/ */
// Verified // Verified
const jobAssignedToMeBuilder = (data) => { const jobAssignedToMeBuilder = (data) => {
const body = `You have been assigned to ${getJobAssignmentType(data.scenarioFields?.[0])}`; const body = `You have been assigned to ${getJobAssignmentType(data.scenarioFields?.[0])}.`;
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
@@ -224,7 +225,7 @@ const jobsAddedToProductionBuilder = (data) => {
*/ */
// Verified // Verified
const jobStatusChangeBuilder = (data) => { const jobStatusChangeBuilder = (data) => {
const body = `The status has changed from ${data.changedFields.status.old} to ${data.changedFields.status.new}`; const body = `The status has changed from ${data?.changedFields?.status?.old || "unset"} to ${data?.changedFields?.status?.new || "unset"}`;
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
@@ -294,8 +295,19 @@ const newMediaAddedReassignedBuilder = (data) => {
/** /**
* Builds notification data for new notes added to a job. * Builds notification data for new notes added to a job.
*/ */
// verified
const newNoteAddedBuilder = (data) => { const newNoteAddedBuilder = (data) => {
const body = `An Note has been added: "${data.data.text}"`; 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 = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
@@ -304,7 +316,10 @@ const newNoteAddedBuilder = (data) => {
key: "notifications.job.newNoteAdded", key: "notifications.job.newNoteAdded",
body, body,
variables: { variables: {
text: data.data.text createdBy: data?.data?.created_by,
critical: data?.data?.critical,
type: data?.data?.type,
private: data?.data?.private
}, },
recipients: [] recipients: []
}, },
@@ -325,9 +340,11 @@ const newNoteAddedBuilder = (data) => {
/** /**
* Builds notification data for new time tickets posted. * Builds notification data for new time tickets posted.
*/ */
// Verified
const newTimeTicketPostedBuilder = (data) => { const newTimeTicketPostedBuilder = (data) => {
const type = data?.data?.cost_center; const type = data?.data?.cost_center;
const body = `An ${type} time ticket has been posted${data?.data?.flat_rate ? " (Flat Rate)" : ""}.`.trim(); const body =
`A ${startCase(type.toLowerCase())} Time Ticket for ${data?.data?.date} has been posted${data?.data?.flat_rate ? " (Flat Rate)" : ""}.`.trim();
const result = { const result = {
app: { app: {
@@ -337,7 +354,9 @@ const newTimeTicketPostedBuilder = (data) => {
key: "notifications.job.newTimeTicketPosted", key: "notifications.job.newTimeTicketPosted",
body, body,
variables: { variables: {
type type,
date: data?.data?.date,
flatRate: data?.data?.flat_rate
}, },
recipients: [] recipients: []
}, },
@@ -419,7 +438,27 @@ const paymentCollectedCompletedBuilder = (data) => {
* Builds notification data for changes to scheduled dates. * Builds notification data for changes to scheduled dates.
*/ */
const scheduledDatesChangedBuilder = (data) => { const scheduledDatesChangedBuilder = (data) => {
const body = `Scheduled dates have been updated.`; 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 = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
@@ -428,12 +467,12 @@ const scheduledDatesChangedBuilder = (data) => {
key: "notifications.job.scheduledDatesChanged", key: "notifications.job.scheduledDatesChanged",
body, body,
variables: { variables: {
scheduledIn: data.changedFields.scheduled_in?.new, scheduledIn: changedFields.scheduled_in?.new,
oldScheduledIn: data.changedFields.scheduled_in?.old, oldScheduledIn: changedFields.scheduled_in?.old,
scheduledCompletion: data.changedFields.scheduled_completion?.new, scheduledCompletion: changedFields.scheduled_completion?.new,
oldScheduledCompletion: data.changedFields.scheduled_completion?.old, oldScheduledCompletion: changedFields.scheduled_completion?.old,
scheduledDelivery: data.changedFields.scheduled_delivery?.new, scheduledDelivery: changedFields.scheduled_delivery?.new,
oldScheduledDelivery: data.changedFields.scheduled_delivery?.old oldScheduledDelivery: changedFields.scheduled_delivery?.old
}, },
recipients: [] recipients: []
}, },
@@ -486,7 +525,75 @@ const supplementImportedBuilder = (data) => {
* Builds notification data for tasks updated or created. * Builds notification data for tasks updated or created.
*/ */
const tasksUpdatedCreatedBuilder = (data) => { const tasksUpdatedCreatedBuilder = (data) => {
const body = `Tasks have been ${data.isNew ? "created" : "updated"}.`; 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 = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
@@ -494,10 +601,7 @@ const tasksUpdatedCreatedBuilder = (data) => {
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: data.isNew ? "notifications.job.taskCreated" : "notifications.job.taskUpdated", key: data.isNew ? "notifications.job.taskCreated" : "notifications.job.taskUpdated",
body, body,
variables: { variables,
isNew: data.isNew,
roNumber: data.jobRoNumber
},
recipients: [] recipients: []
}, },
email: { email: {

View File

@@ -110,6 +110,8 @@ const scenarioParser = async (req, jobIdField) => {
const bodyShopId = watcherData?.job?.bodyshop?.id; const bodyShopId = watcherData?.job?.bodyshop?.id;
const bodyShopName = watcherData?.job?.bodyshop?.shopname; const bodyShopName = watcherData?.job?.bodyshop?.shopname;
const bodyShopTimezone = watcherData?.job?.bodyshop?.timezone;
const jobRoNumber = watcherData?.job?.ro_number; const jobRoNumber = watcherData?.job?.ro_number;
const jobClaimNumber = watcherData?.job?.clm_no; const jobClaimNumber = watcherData?.job?.clm_no;
@@ -147,6 +149,7 @@ const scenarioParser = async (req, jobIdField) => {
jobWatchers, jobWatchers,
bodyShopId, bodyShopId,
bodyShopName, bodyShopName,
bodyShopTimezone,
matchingScenarios matchingScenarios
}; };
@@ -247,6 +250,7 @@ const scenarioParser = async (req, jobIdField) => {
trigger: finalScenarioData.trigger.name, trigger: finalScenarioData.trigger.name,
bodyShopId: finalScenarioData.bodyShopId, bodyShopId: finalScenarioData.bodyShopId,
bodyShopName: finalScenarioData.bodyShopName, bodyShopName: finalScenarioData.bodyShopName,
bodyShopTimezone: finalScenarioData.bodyShopTimezone,
scenarioKey: scenario.key, scenarioKey: scenario.key,
scenarioTable: scenario.table, scenarioTable: scenario.table,
scenarioFields: filteredScenarioFields, scenarioFields: filteredScenarioFields,

View File

@@ -26,6 +26,17 @@ const getJobAssignmentType = (data) => {
} }
}; };
module.exports = { const formatTaskPriority = (priority) => {
getJobAssignmentType if (priority === 1) {
return "High";
} else if (priority === 3) {
return "Low";
} else {
return "Medium";
}
};
module.exports = {
getJobAssignmentType,
formatTaskPriority
}; };