diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 53854a43f..af3c906a8 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -21,7 +21,7 @@ import "./App.styles.scss"; import Eula from "../components/eula/eula.component"; import InstanceRenderMgr from "../utils/instanceRenderMgr"; import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx"; -import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx"; +import { SocketProvider } from "../contexts/SocketIO/useSocket.jsx"; import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx"; const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component")); diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index c39e6c653..c757598c6 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -3,7 +3,7 @@ import { getToken } from "@firebase/messaging"; import axios from "axios"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { useSocket } from "../../contexts/SocketIO/socketContext"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { messaging, requestForToken } from "../../firebase/firebase.utils"; import ChatPopupComponent from "../chat-popup/chat-popup.component"; import "./chat-affix.styles.scss"; diff --git a/client/src/components/chat-archive-button/chat-archive-button.component.jsx b/client/src/components/chat-archive-button/chat-archive-button.component.jsx index bb4cfdc6d..24eb9cea4 100644 --- a/client/src/components/chat-archive-button/chat-archive-button.component.jsx +++ b/client/src/components/chat-archive-button/chat-archive-button.component.jsx @@ -3,7 +3,7 @@ import { Button } from "antd"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors.js"; import { connect } from "react-redux"; diff --git a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx index 1dbabeee5..e85ed6270 100644 --- a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx +++ b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx @@ -4,7 +4,7 @@ import { Link } from "react-router-dom"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors.js"; import { connect } from "react-redux"; diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 594abd9b1..853c4851f 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -3,7 +3,7 @@ import axios from "axios"; import { useCallback, useEffect, useState } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { useSocket } from "../../contexts/SocketIO/socketContext"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; diff --git a/client/src/components/chat-label/chat-label.component.jsx b/client/src/components/chat-label/chat-label.component.jsx index 78e6821a3..6fccd1390 100644 --- a/client/src/components/chat-label/chat-label.component.jsx +++ b/client/src/components/chat-label/chat-label.component.jsx @@ -4,7 +4,7 @@ import { Input, Spin, Tag, Tooltip } from "antd"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors.js"; import { connect } from "react-redux"; diff --git a/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx b/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx index 759aef46e..8806974bf 100644 --- a/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx +++ b/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx @@ -5,7 +5,7 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { openChatByPhone } from "../../redux/messaging/messaging.actions"; import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser diff --git a/client/src/components/chat-open-button/chat-open-button.component.jsx b/client/src/components/chat-open-button/chat-open-button.component.jsx index b23fd5391..68b9cd8b2 100644 --- a/client/src/components/chat-open-button/chat-open-button.component.jsx +++ b/client/src/components/chat-open-button/chat-open-button.component.jsx @@ -7,7 +7,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { searchingForConversation } from "../../redux/messaging/messaging.selectors"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; const mapStateToProps = createStructuredSelector({ diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index ad7282c28..2ae16b129 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -12,7 +12,7 @@ import ChatConversationListComponent from "../chat-conversation-list/chat-conver import ChatConversationContainer from "../chat-conversation/chat-conversation.container"; import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import "./chat-popup.styles.scss"; diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx index e843bbff6..2a12d96db 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx @@ -8,7 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries"; import ChatTagRo from "./chat-tag-ro.component"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors.js"; import { connect } from "react-redux"; diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 1e0c91ec6..a5cc39e21 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -5,7 +5,7 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { Link } from "react-router-dom"; import { useQuery } from "@apollo/client"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; import NotificationCenterContainer from "../notification-center/notification-center.container.jsx"; import LockWrapper from "../lock-wrapper/lock-wrapper.component"; @@ -704,7 +704,11 @@ function Header({ )} {scenarioNotificationsOn && ( - setNotificationVisible(false)} /> + setNotificationVisible(false)} + unreadCount={unreadCount} + /> )} ); diff --git a/client/src/components/job-at-change/schedule-event.component.jsx b/client/src/components/job-at-change/schedule-event.component.jsx index 4a30c91b1..eb53dffd4 100644 --- a/client/src/components/job-at-change/schedule-event.component.jsx +++ b/client/src/components/job-at-change/schedule-event.component.jsx @@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { createStructuredSelector } from "reselect"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; import { setModalContext } from "../../redux/modals/modals.actions"; diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index 71e02e13e..325a16ffc 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useNavigate } from "react-router-dom"; import { createStructuredSelector } from "reselect"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries"; diff --git a/client/src/components/notification-center/notification-center.component.jsx b/client/src/components/notification-center/notification-center.component.jsx index 246721dc7..9b6b39ea2 100644 --- a/client/src/components/notification-center/notification-center.component.jsx +++ b/client/src/components/notification-center/notification-center.component.jsx @@ -18,7 +18,8 @@ const NotificationCenterComponent = ({ toggleUnreadOnly, markAllRead, loadMore, - onNotificationClick + onNotificationClick, + unreadCount }) => { const { t } = useTranslation(); @@ -61,8 +62,6 @@ const NotificationCenterComponent = ({ ); }; - const hasUnread = notifications.some((n) => !n.read); - return (
@@ -82,9 +81,9 @@ const NotificationCenterComponent = ({
diff --git a/client/src/components/notification-center/notification-center.container.jsx b/client/src/components/notification-center/notification-center.container.jsx index 6ea671791..dfadf3a84 100644 --- a/client/src/components/notification-center/notification-center.container.jsx +++ b/client/src/components/notification-center/notification-center.container.jsx @@ -3,7 +3,7 @@ import { useQuery } from "@apollo/client"; import { connect } from "react-redux"; import NotificationCenterComponent from "./notification-center.component"; import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries"; -import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors.js"; import day from "../../utils/day.js"; @@ -11,7 +11,7 @@ import day from "../../utils/day.js"; // This will be used to poll for notifications when the socket is disconnected const NOTIFICATION_POLL_INTERVAL_SECONDS = 60; -export function NotificationCenterContainer({ visible, onClose, bodyshop }) { +export function NotificationCenterContainer({ visible, onClose, bodyshop, unreadCount }) { const [showUnreadOnly, setShowUnreadOnly] = useState(false); const [notifications, setNotifications] = useState([]); const [error, setError] = useState(null); @@ -169,6 +169,7 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) { markAllRead={handleMarkAllRead} loadMore={loadMore} onNotificationClick={handleNotificationClick} + unreadCount={unreadCount} /> ); } diff --git a/client/src/components/payments-generate-link/payments-generate-link.component.jsx b/client/src/components/payments-generate-link/payments-generate-link.component.jsx index 4b26180ff..7e121621f 100644 --- a/client/src/components/payments-generate-link/payments-generate-link.component.jsx +++ b/client/src/components/payments-generate-link/payments-generate-link.component.jsx @@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect"; import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, diff --git a/client/src/components/production-board-kanban/production-board-kanban.container.jsx b/client/src/components/production-board-kanban/production-board-kanban.container.jsx index 0712ab78e..3b0c4066f 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.container.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.container.jsx @@ -12,7 +12,7 @@ import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import ProductionBoardKanbanComponent from "./production-board-kanban.component"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, diff --git a/client/src/components/production-list-table/production-list-table.container.jsx b/client/src/components/production-list-table/production-list-table.container.jsx index 88c3a3e00..373222a65 100644 --- a/client/src/components/production-list-table/production-list-table.container.jsx +++ b/client/src/components/production-list-table/production-list-table.container.jsx @@ -10,7 +10,7 @@ import { import ProductionListTable from "./production-list-table.component"; import _ from "lodash"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) { const client = useApolloClient(); diff --git a/client/src/components/profile-my/notification-settings.component.jsx b/client/src/components/profile-my/notification-settings.component.jsx index 1f365337b..01fa214e1 100644 --- a/client/src/components/profile-my/notification-settings.component.jsx +++ b/client/src/components/profile-my/notification-settings.component.jsx @@ -1,6 +1,6 @@ import { useMutation, useQuery } from "@apollo/client"; import { useEffect, useState } from "react"; -import { Button, Card, Checkbox, Form, Table } from "antd"; +import { Button, Card, Checkbox, Form, Space, Table } from "antd"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -165,14 +165,15 @@ function NotificationSettingsForm({ currentUser }) { - + - + } > diff --git a/client/src/components/profile-my/profile-my.component.jsx b/client/src/components/profile-my/profile-my.component.jsx index 43e36033c..df5a49e19 100644 --- a/client/src/components/profile-my/profile-my.component.jsx +++ b/client/src/components/profile-my/profile-my.component.jsx @@ -9,7 +9,7 @@ import { logImEXEvent, updateCurrentPassword } from "../../firebase/firebase.uti import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import NotificationSettingsForm from "./notification-settings.component.jsx"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser diff --git a/client/src/contexts/SocketIO/socketContext.jsx b/client/src/contexts/SocketIO/useSocket.jsx similarity index 99% rename from client/src/contexts/SocketIO/socketContext.jsx rename to client/src/contexts/SocketIO/useSocket.jsx index 2529dba7e..a8b28a145 100644 --- a/client/src/contexts/SocketIO/socketContext.jsx +++ b/client/src/contexts/SocketIO/useSocket.jsx @@ -452,7 +452,9 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot markNotificationRead, markAllNotificationsRead, navigate, - currentUser + currentUser, + scenarioNotificationsOn, + t ]); return ( diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 273db294e..26d10ef67 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -525,6 +525,7 @@ export const GET_JOB_BY_PK = gql` iouparent job_totals job_watchers { + id user_email } joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { diff --git a/client/src/graphql/notifications.queries.js b/client/src/graphql/notifications.queries.js index f2f9fa932..71be7b931 100644 --- a/client/src/graphql/notifications.queries.js +++ b/client/src/graphql/notifications.queries.js @@ -12,6 +12,7 @@ export const GET_NOTIFICATIONS = gql` created_at read job { + id ro_number } } diff --git a/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx b/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx index d74d6b7cf..1dd428091 100644 --- a/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx +++ b/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx @@ -9,6 +9,7 @@ import { createStructuredSelector } from "reselect"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js"; import EmployeeSearchSelectComponent from "../../components/employee-search-select/employee-search-select.component.jsx"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component.jsx"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const { Text } = Typography; @@ -19,6 +20,14 @@ const mapStateToProps = createStructuredSelector({ const JobWatcherToggle = ({ job, currentUser, bodyshop }) => { const { t } = useTranslation(); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop && bodyshop.imexshopid + }); + const userEmail = currentUser.email; const jobid = job.id; @@ -146,7 +155,7 @@ const JobWatcherToggle = ({ job, currentUser, bodyshop }) => { /> {/* Divider for UI separation */} {/* Only show team selection if there are available teams */} - {bodyshop?.employee_teams?.length > 0 && ( + {Enhanced_Payroll && bodyshop?.employee_teams?.length > 0 && ( <> {t("notifications.labels.add-watchers-team")} diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index f00551443..64725e890 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -57,7 +57,7 @@ import dayjs from "../../utils/day"; import UndefinedToNull from "../../utils/undefinedtonull"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import JobWatcherToggle from "./job-watcher-toggle.component.jsx"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 06c9b9b09..e813eaae0 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -20,7 +20,7 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import { requestForToken } from "../../firebase/firebase.utils"; -import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors"; import UpdateAlert from "../../components/update-alert/update-alert.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; diff --git a/server/email/sendemail.js b/server/email/sendemail.js index 8d306258f..26d5c8560 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -69,11 +69,14 @@ const sendServerEmail = async ({ subject, text }) => { } }, (err, info) => { - logger.log("server-email-failure", err ? "error" : "debug", null, null, { message: err?.message }); + logger.log("server-email-failure", err ? "error" : "debug", null, null, { + message: err?.message, + stack: err?.stack + }); } ); } catch (error) { - logger.log("server-email-failure", "error", null, null, { message: error?.message }); + logger.log("server-email-failure", "error", null, null, { message: error?.message, stack: error?.stack }); } }; @@ -92,11 +95,11 @@ const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachmen }, (err, info) => { // (message, type, user, record, meta - logger.log("server-email", err ? "error" : "debug", null, null, { message: err?.message }); + logger.log("server-email", err ? "error" : "debug", null, null, { message: err?.message, stack: err?.stack }); } ); } catch (error) { - logger.log("server-email-failure", "error", null, null, { message: error?.message }); + logger.log("server-email-failure", "error", null, null, { message: error?.message, stack: error?.stack }); } }; @@ -125,7 +128,8 @@ const sendEmail = async (req, res) => { cc: req.body.cc, subject: req.body.subject, templateStrings: req.body.templateStrings, - errorMessage: error?.message + errorMessage: error?.message, + errorStack: error?.stack }); } }) @@ -194,7 +198,8 @@ const sendEmail = async (req, res) => { cc: req.body.cc, subject: req.body.subject, templateStrings: req.body.templateStrings, - errorMessage: err?.message + errorMessage: err?.message, + errorStack: err?.stack }); logEmail(req, { to: req.body.to, @@ -202,7 +207,7 @@ const sendEmail = async (req, res) => { subject: req.body.subject, bodyshopid: req.body.bodyshopid }); - res.status(500).json({ success: false, errorMessage: err?.message }); + res.status(500).json({ success: false, errorMessage: err?.message, stack: err?.stack }); } } ); @@ -270,14 +275,16 @@ ${body.bounce?.bouncedRecipients.map( }, (err, info) => { logger.log("sns-error", err ? "error" : "debug", "api", null, { - errorMessage: err?.message + errorMessage: err?.message, + errorStack: err?.stack }); } ); } } catch (error) { logger.log("sns-error", "ERROR", "api", null, { - errorMessage: error?.message + errorMessage: error?.message, + errorStack: error?.stack }); } res.sendStatus(200); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 14e016089..94c76bb90 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2708,16 +2708,14 @@ exports.INSERT_AUDIT_TRAIL = ` exports.GET_JOB_WATCHERS = ` query GET_JOB_WATCHERS($jobid: uuid!) { - job_watchers_aggregate(where: { jobid: { _eq: $jobid } }) { - nodes { - user_email - user { - authid - employee { - id - first_name - last_name - } + job_watchers(where: { jobid: { _eq: $jobid } }) { + user_email + user { + authid + employee { + id + first_name + last_name } } } diff --git a/server/notifications/eventHandlers.js b/server/notifications/eventHandlers.js index cad02a7b5..45de5dc3e 100644 --- a/server/notifications/eventHandlers.js +++ b/server/notifications/eventHandlers.js @@ -22,7 +22,7 @@ async function processNotificationEvent(req, res, parserPath, successMessage) { // Call scenarioParser but don't await it; log any error that occurs. scenarioParser(req, parserPath).catch((error) => { - logger.log("notifications-error", "error", "notifications", null, { error: error?.message }); + logger.log("notifications-error", "error", "notifications", null, { message: error?.message, stack: error?.stack }); }); return res.status(200).json({ message: successMessage }); diff --git a/server/notifications/queues/appQueue.js b/server/notifications/queues/appQueue.js index 890ce73f2..2e6316a7d 100644 --- a/server/notifications/queues/appQueue.js +++ b/server/notifications/queues/appQueue.js @@ -214,7 +214,7 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => { await pubClient.del(`app:consolidate:${jobId}`); } catch (err) { - logger.log(`Consolidation error for jobId ${jobId}`, "ERROR", "notifications", "api", { + logger.log(`app-queue-consolidation-error`, "ERROR", "notifications", "api", { message: err?.message, stack: err?.stack }); @@ -237,13 +237,13 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => { addWorker.on("completed", (job) => logger.logger.debug(`Add job ${job.id} completed`)); consolidateWorker.on("completed", (job) => logger.logger.debug(`Consolidate job ${job.id} completed`)); addWorker.on("failed", (job, err) => - logger.log(`Add job ${job.id} failed:`, "ERROR", "notifications", "api", { + logger.log(`app-queue-notification-error`, "ERROR", "notifications", "api", { message: err?.message, stack: err?.stack }) ); consolidateWorker.on("failed", (job, err) => - logger.log(`Consolidate job ${job.id} failed:`, "ERROR", "notifications", "api", { + logger.log(`app-queue-consolidation-failed:`, "ERROR", "notifications", "api", { message: err?.message, stack: err?.stack }) diff --git a/server/notifications/queues/emailQueue.js b/server/notifications/queues/emailQueue.js index 22814e6a7..dfc385401 100644 --- a/server/notifications/queues/emailQueue.js +++ b/server/notifications/queues/emailQueue.js @@ -146,7 +146,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => { await pubClient.del(recipientsSet); await pubClient.del(`email:consolidate:${jobId}`); } catch (err) { - logger.log(`Email Consolidation error for jobId ${jobId}`, "ERROR", "notifications", "api", { + logger.log(`email-queue-consolidation-error`, "ERROR", "notifications", "api", { message: err?.message, stack: err?.stack }); @@ -170,13 +170,13 @@ const loadEmailQueue = async ({ pubClient, logger }) => { emailAddWorker.on("completed", (job) => logger.logger.debug(`Email add job ${job.id} completed`)); emailConsolidateWorker.on("completed", (job) => logger.logger.debug(`Email consolidate job ${job.id} completed`)); emailAddWorker.on("failed", (job, err) => - logger.log(`Email add job ${job.id} failed`, "ERROR", "notifications", "api", { + logger.log(`add-email-queue-failed`, "ERROR", "notifications", "api", { message: err?.message, stack: err?.stack }) ); emailConsolidateWorker.on("failed", (job, err) => - logger.log(`Email consolidate job ${job.id} failed:`, "ERROR", "notifications", "api", { + logger.log(`email-consolidation-job-failed`, "ERROR", "notifications", "api", { message: err?.message, stack: err?.stack }) diff --git a/server/notifications/scenarioBuilders.js b/server/notifications/scenarioBuilders.js index 316b353ea..0860f8ad0 100644 --- a/server/notifications/scenarioBuilders.js +++ b/server/notifications/scenarioBuilders.js @@ -24,8 +24,9 @@ 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?.altTransport?.old}.`; + const body = `The Alternate Transport status has been updated to ${data?.data?.alt_transport}.`; const result = { app: { jobId: data.jobId, @@ -56,8 +57,12 @@ const alternateTransportChangedBuilder = (data) => { /** * Builds notification data for bill posted events. */ +//verified const billPostedHandler = (data) => { - const body = `A bill of $${data.data.clm_total} has been posted.`; + 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, @@ -66,7 +71,8 @@ const billPostedHandler = (data) => { key: "notifications.job.billPosted", body, variables: { - clmTotal: data.data.clm_total + facing, + is_credit_memo: data?.data?.is_credit_memo }, recipients: [] }, @@ -87,6 +93,7 @@ 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 result = { @@ -119,8 +126,9 @@ const criticalPartsStatusChangedBuilder = (data) => { /** * Builds notification data for completed intake or delivery checklists. */ +// Verified const intakeDeliveryChecklistCompletedBuilder = (data) => { - const checklistType = data.changedFields.intakechecklist ? "intake" : "delivery"; + const checklistType = data?.changedFields?.intakechecklist ? "Intake" : "Delivery"; const body = `The ${checklistType.charAt(0).toUpperCase() + checklistType.slice(1)} checklist has been completed.`; const result = { app: { @@ -152,6 +160,7 @@ 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 = { @@ -183,8 +192,9 @@ const jobAssignedToMeBuilder = (data) => { /** * Builds notification data for jobs added to production. */ +// Verified const jobsAddedToProductionBuilder = (data) => { - const body = `Job has been added to production.`; + const body = `Has been added to Production.`; const result = { app: { jobId: data.jobId, @@ -212,6 +222,7 @@ 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} to ${data.changedFields.status.new}`; const result = { @@ -244,8 +255,15 @@ const jobStatusChangeBuilder = (data) => { /** * Builds notification data for new media added or reassigned events. */ +// Verified const newMediaAddedReassignedBuilder = (data) => { - const body = `New media has been added.`; + // 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, @@ -253,7 +271,10 @@ const newMediaAddedReassignedBuilder = (data) => { bodyShopId: data.bodyShopId, key: "notifications.job.newMediaAdded", body, - variables: {}, + variables: { + mediaType, + action + }, recipients: [] }, email: { @@ -274,7 +295,7 @@ const newMediaAddedReassignedBuilder = (data) => { * Builds notification data for new notes added to a job. */ const newNoteAddedBuilder = (data) => { - const body = `A new note has been added: "${data.data.text}"`; + const body = `An Note has been added: "${data.data.text}"`; const result = { app: { jobId: data.jobId, @@ -305,7 +326,10 @@ const newNoteAddedBuilder = (data) => { * Builds notification data for new time tickets posted. */ const newTimeTicketPostedBuilder = (data) => { - const body = `A new time ticket has been posted.`; + consoleDir(data); + const type = data?.data?.cost_center; + const body = `An ${type} time ticket has been posted${data?.data?.flat_rate ? " (Flat Rate)" : ""}.`.trim(); + const result = { app: { jobId: data.jobId, @@ -313,7 +337,9 @@ const newTimeTicketPostedBuilder = (data) => { bodyShopId: data.bodyShopId, key: "notifications.job.newTimeTicketPosted", body, - variables: {}, + variables: { + type + }, recipients: [] }, email: { diff --git a/server/notifications/scenarioMapperr.js b/server/notifications/scenarioMapper.js similarity index 94% rename from server/notifications/scenarioMapperr.js rename to server/notifications/scenarioMapper.js index 0b91ff43e..7b623a517 100644 --- a/server/notifications/scenarioMapperr.js +++ b/server/notifications/scenarioMapper.js @@ -32,8 +32,8 @@ const notificationScenarios = [ { key: "job-assigned-to-me", table: "jobs", - fields: ["employee_pre", "employee_body", "employee_csr", "employee_refinish"], - matchToUserFields: ["employee_pre", "employee_body", "employee_csr", "employee_refinish"], + fields: ["employee_prep", "employee_body", "employee_csr", "employee_refinish"], + matchToUserFields: ["employee_prep", "employee_body", "employee_csr", "employee_refinish"], builder: jobAssignedToMeBuilder }, { @@ -88,19 +88,17 @@ const notificationScenarios = [ // Good test for batching as this will hit multiple scenarios key: "intake-delivery-checklist-completed", table: "jobs", - fields: ["intakechecklist"], + fields: ["intakechecklist", "deliverchecklist"], builder: intakeDeliveryChecklistCompletedBuilder }, { - key: "payment-collected-completed", + key: "payment-added", table: "payments", onNew: true, builder: paymentCollectedCompletedBuilder }, { // MAKE SURE YOU ARE NOT ON A LMS ENVIRONMENT - // Potential Callbacks / Save for last - // Not question mark for Non LMS Scenario key: "new-media-added-reassigned", table: "documents", fields: ["jobid"], diff --git a/server/notifications/scenarioParser.js b/server/notifications/scenarioParser.js index 83497d357..6ec50eeb4 100644 --- a/server/notifications/scenarioParser.js +++ b/server/notifications/scenarioParser.js @@ -11,7 +11,7 @@ const eventParser = require("./eventParser"); const { client: gqlClient } = require("../graphql-client/graphql-client"); const queries = require("../graphql-client/queries"); const { isEmpty, isFunction } = require("lodash"); -const { getMatchingScenarios } = require("./scenarioMapperr"); +const { getMatchingScenarios } = require("./scenarioMapper"); const { dispatchEmailsToQueue } = require("./queues/emailQueue"); const { dispatchAppsToQueue } = require("./queues/appQueue"); @@ -59,7 +59,7 @@ const scenarioParser = async (req, jobIdField) => { }); // Transform watcher data into a simplified format with email and employee details - let jobWatchers = watcherData?.job_watchers_aggregate?.nodes?.map((watcher) => ({ + let jobWatchers = watcherData?.job_watchers?.map((watcher) => ({ email: watcher.user_email, firstName: watcher?.user?.employee?.first_name, lastName: watcher?.user?.employee?.last_name,