Compare commits

..

22 Commits

Author SHA1 Message Date
Dave
42427c4569 release/2025-10-17 - Refine Job Totals Logging (switch from error to warn for USA key issues, normalize case on logging level) 2025-10-17 14:56:21 -04:00
Dave Richer
d5f921ed35 Merged in feature/IO-3385-Remove-CASL-From-Rome (pull request #2631)
feature/IO-3385-Remove-CASL-From-Rome - Remove CASL Report from Rome Customers.
2025-10-15 15:48:13 +00:00
Dave
043471fdbc feature/IO-3385-Remove-CASL-From-Rome - Remove CASL Report from Rome Customers. 2025-10-15 11:46:51 -04:00
Dave Richer
54850e8ee2 Merged in feature/IO-3404-Seamless-Logout (pull request #2629)
feature/IO-3404-Seamless-Logout - Implement Seamless Logout
2025-10-15 15:33:29 +00:00
Dave
199ddc7d9e feature/IO-3404-Seamless-Logout - Implement Seamless Logout 2025-10-15 11:31:34 -04:00
Dave Richer
13a44b9a59 Merged in hotfix/2025-10-10 (pull request #2628)
Hotfix/2025 10 10 - Change BullMQ Key Delimiters based on system logs.

Approved-by: Patrick Fic
2025-10-11 01:35:51 +00:00
Dave Richer
e3337bacea Merged in hotfix/2025-10-10 (pull request #2626)
Hotfix/2025 10 10
2025-10-10 20:05:08 +00:00
Dave
8e6c809fc6 Update 2025-10-10 16:02:56 -04:00
Dave
41afedd02c Change Delemiters in BullMQ, was using an invalid : 2025-10-10 15:58:32 -04:00
Allan Carr
29df829120 Merged in feature/IO-3373-Dashboard-Component-Redux-Fix (pull request #2622)
IO-3373 Dashboard Component Refresh Cyclic object fix

Approved-by: Dave Richer
2025-10-10 16:08:21 +00:00
Allan Carr
11e0c3e507 Merged in hotfix/2025-10-09 (pull request #2624)
Hotfix/2025 10 09
2025-10-09 17:57:20 +00:00
Allan Carr
6c1c7c9c2c Merged in feature/IO-3386-carfax-rps (pull request #2623)
Feature/IO-3386 carfax rps
2025-10-09 17:55:21 +00:00
Allan Carr
d2b6054e60 Merged in feature/IO-3386-carfax-rps (pull request #2620)
IO-3368 CARFAX Adjustments for OP_CODE [PENDING APPROVAL]
2025-10-09 17:28:31 +00:00
Allan Carr
7004ed9880 IO-3373 Dashboard Component Refresh Cyclic object fix
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-10-09 09:13:03 -07:00
Dave Richer
e3df22160b Merged in release/2025-10-17 (pull request #2621)
Release/2025 10 17 into master-AIO - IO-3330, IO-3356, IO-3365, IO-3368, IO-3373, IO-3386, IO-3388, IO-3389, IO-3390, IO-3395
2025-10-08 01:22:16 +00:00
Allan Carr
34d70f6a18 Merged in feature/IO-3373-Dashboard-Component-Redux-Fix (pull request #2618)
IO-3373 Remove Cache Eviction

Approved-by: Dave Richer
2025-10-07 18:40:51 +00:00
Allan Carr
fc1bf213c7 Merged in feature/IO-3395-Postback-Payment-Date (pull request #2614)
IO-3395 Postback Payment Date

Approved-by: Dave Richer
2025-10-07 18:40:28 +00:00
Allan Carr
4dc72986d0 IO-3373 Remove Cache Eviction
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-10-07 11:07:12 -07:00
Allan Carr
f19b9cb8e1 IO-3395 Postback Payment Date
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-10-07 10:59:10 -07:00
Allan Carr
bdd5056c9a IO-3395 Postback Payment Date
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-10-06 21:35:31 -07:00
Allan Carr
4f6db827e7 IO-3395 Postback Payment Date
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-10-06 17:50:19 -07:00
Patrick Fic
15792cb0ef Merged in hotfix/2025-10-06-remove-amplitude (pull request #2612)
Hotfix/2025 10 06 remove amplitude
2025-10-06 15:33:23 +00:00
10 changed files with 91 additions and 56 deletions

View File

@@ -12,6 +12,7 @@ import GlobalLoadingBar from "../components/global-loading-bar/global-loading-ba
import { setDarkMode } from "../redux/application/application.actions";
import { selectDarkMode } from "../redux/application/application.selectors";
import { selectCurrentUser } from "../redux/user/user.selectors.js";
import { signOutStart } from "../redux/user/user.actions";
import client from "../utils/GraphQLClient";
import App from "./App";
import getTheme from "./themeProvider";
@@ -20,14 +21,13 @@ import getTheme from "./themeProvider";
const config = {
core: {
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
key: "anon" // Default key, overridden dynamically by SplitClientProvider
key: "anon"
}
};
// Custom provider to manage the Split client key based on imexshopid from Redux
function SplitClientProvider({ children }) {
const imexshopid = useSelector((state) => state.user.imexshopid); // Access imexshopid from Redux store
const splitClient = useSplitClient({ key: imexshopid || "anon" }); // Use imexshopid or fallback to "anon"
const imexshopid = useSelector((state) => state.user.imexshopid);
const splitClient = useSplitClient({ key: imexshopid || "anon" });
useEffect(() => {
if (splitClient && imexshopid) {
console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
@@ -36,40 +36,66 @@ function SplitClientProvider({ children }) {
return children;
}
const mapDispatchToProps = (dispatch) => ({
setDarkMode: (isDarkMode) => dispatch(setDarkMode(isDarkMode))
});
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
function AppContainer({ currentUser, setDarkMode }) {
const mapDispatchToProps = (dispatch) => ({
setDarkMode: (isDarkMode) => dispatch(setDarkMode(isDarkMode)),
signOutStart: () => dispatch(signOutStart())
});
function AppContainer({ currentUser, setDarkMode, signOutStart }) {
const { t } = useTranslation();
const isDarkMode = useSelector(selectDarkMode);
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
// Update data-theme attribute when dark mode changes
// Global seamless logout listener with redirect to /signin
useEffect(() => {
const handleSeamlessLogout = (event) => {
if (event.data?.type !== "seamlessLogoutRequest") return;
const requestOrigin = event.origin;
if (currentUser?.authorized !== true) {
window.parent.postMessage(
{ type: "seamlessLogoutResponse", status: "already_logged_out" },
requestOrigin || "*"
);
return;
}
signOutStart();
window.parent.postMessage({ type: "seamlessLogoutResponse", status: "logged_out" }, requestOrigin || "*");
};
window.addEventListener("message", handleSeamlessLogout);
return () => {
window.removeEventListener("message", handleSeamlessLogout);
};
}, [signOutStart, currentUser]);
// Update data-theme attribute
useEffect(() => {
document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light");
return () => document.documentElement.removeAttribute("data-theme");
}, [isDarkMode]);
// Sync Redux darkMode with localStorage on user change
// Sync darkMode with localStorage
useEffect(() => {
if (currentUser?.uid) {
const savedMode = localStorage.getItem(`dark-mode-${currentUser.uid}`);
if (savedMode !== null) {
setDarkMode(JSON.parse(savedMode));
} else {
setDarkMode(false); // default to light mode
setDarkMode(false);
}
} else {
setDarkMode(false);
}
}, [currentUser?.uid]);
}, [currentUser?.uid, setDarkMode]);
// Persist darkMode to localStorage when it or user changes
// Persist darkMode
useEffect(() => {
if (currentUser?.uid) {
localStorage.setItem(`dark-mode-${currentUser.uid}`, JSON.stringify(isDarkMode));

View File

@@ -1,5 +1,5 @@
import Icon, { SyncOutlined } from "@ant-design/icons";
import { useMutation, useQuery, useApolloClient } from "@apollo/client";
import { useMutation, useQuery } from "@apollo/client";
import { Button, Dropdown, Space } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import { useMemo, useState, useEffect } from "react";
@@ -33,7 +33,6 @@ const mapDispatchToProps = () => ({
export function DashboardGridComponent({ currentUser }) {
const { t } = useTranslation();
const client = useApolloClient();
const notification = useNotification();
// Constants for layout defaults
@@ -114,11 +113,6 @@ export function DashboardGridComponent({ currentUser }) {
return false;
}
// Note: Removed Apollo cache update to prevent triggering unwanted Redux actions
// Instead, evict the dashboard bodyshop query from cache to ensure fresh data on next fetch
client.cache.evict({ fieldName: "dashboard_bodyshops" });
client.cache.gc();
return true;
} catch (err) {
console.error(`Dashboard ${errorContext} failed`, err);
@@ -202,7 +196,7 @@ export function DashboardGridComponent({ currentUser }) {
<PageHeader
extra={
<Space>
<Button onClick={refetch}>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Dropdown menu={menu} trigger={["click"]}>

View File

@@ -15,21 +15,29 @@ export const EmailSettings = {
export const TemplateList = (type, context) => {
//const { bodyshop } = store.getState().user;
const casl = InstanceRenderManager({
imex: {
casl_authorization: {
title: i18n.t("printcenter.jobs.casl_authorization"),
description: "",
subject: i18n.t("printcenter.jobs.casl_authorization"),
key: "casl_authorization",
disabled: false,
group: "authorization",
regions: {
CA: true
}
}
},
rome: {}
});
return {
//If there's no type or the type is job, send it back.
...(!type || type === "job"
? {
casl_authorization: {
title: i18n.t("printcenter.jobs.casl_authorization"),
description: "",
subject: i18n.t("printcenter.jobs.casl_authorization"),
key: "casl_authorization",
disabled: false,
group: "authorization",
regions: {
CA: true
}
},
...casl,
fippa_authorization: {
title: i18n.t("printcenter.jobs.fippa_authorization"),
description: "",

View File

@@ -2902,6 +2902,7 @@ exports.GET_BODYSHOP_BY_ID = `
intellipay_config
state
notification_followers
timezone
}
}
`;
@@ -2993,6 +2994,7 @@ query GET_JOBID_BY_MERCHANTID_RONUMBER($merchantID: String!, $roNumber: String!)
id
intellipay_config
email
timezone
}
}
}`;
@@ -3002,6 +3004,7 @@ query GET_BODYSHOP_BY_MERCHANTID($merchantID: String!) {
bodyshops(where: {intellipay_merchant_id: {_eq: $merchantID}}) {
id
email
timezone
}
}`;

View File

@@ -48,7 +48,9 @@ const handleCommentBasedPayment = async (values, decodedComment, logger, logMeta
payer: "Customer",
type: getPaymentType(ipMapping, values.cardtype),
jobid: p.jobid,
date: moment(Date.now()),
date: moment()
.tz(bodyshop?.bodyshops_by_pk?.timezone ?? "UTC")
.format("YYYY-MM-DD"),
payment_responses: {
data: {
amount: values.total,

View File

@@ -97,7 +97,9 @@ const handleInvoiceBasedPayment = async (values, logger, logMeta, res) => {
payer: "Customer",
type: getPaymentType(ipMapping, values.cardtype),
jobid: job.id,
date: moment(Date.now())
date: moment()
.tz(bodyshop?.timezone ?? "UTC")
.format("YYYY-MM-DD")
}
});

View File

@@ -24,7 +24,7 @@ exports.totalsSsu = async function (req, res) {
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
logger.log("job-totals-ssu-USA", "DEBUG", req?.user?.email, id);
logger.log("job-totals-ssu-USA", "debug", req?.user?.email, id);
try {
const job = await client.setHeaders({ Authorization: BearerToken }).request(queries.GET_JOB_BY_PK, {
@@ -49,7 +49,7 @@ exports.totalsSsu = async function (req, res) {
res.status(200).send();
} catch (error) {
logger.log("job-totals-ssu-USA-error", "ERROR", req?.user?.email, id, {
logger.log("job-totals-ssu-USA-error", "error", req?.user?.email, id, {
jobid: id,
error: error.message,
stack: error.stack
@@ -95,7 +95,7 @@ async function TotalsServerSide(req, res) {
ret.totals.subtotal = ret.totals.subtotal.add(ret.totals.ttl_adjustment);
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_adjustment);
logger.log("job-totals-USA-ttl-adj", "DEBUG", null, job.id, {
logger.log("job-totals-USA-ttl-adj", "debug", null, job.id, {
adjAmount: ttlDifference
});
}
@@ -116,7 +116,7 @@ async function TotalsServerSide(req, res) {
ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) });
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_tax_adjustment);
logger.log("job-totals-USA-ttl-tax-adj", "DEBUG", null, job.id, {
logger.log("job-totals-USA-ttl-tax-adj", "debug", null, job.id, {
adjAmount: ttlTaxDifference
});
}
@@ -124,7 +124,7 @@ async function TotalsServerSide(req, res) {
return ret;
} catch (error) {
logger.log("job-totals-ssu-USA-error", "ERROR", req.user?.email, job.id, {
logger.log("job-totals-ssu-USA-error", "error", req.user?.email, job.id, {
jobid: job.id,
error: error.message,
stack: error.stack
@@ -142,7 +142,7 @@ async function Totals(req, res) {
const logger = req.logger;
const client = req.userGraphQLClient;
logger.log("job-totals-ssu-USA", "DEBUG", req.user.email, job.id, {
logger.log("job-totals-ssu-USA", "debug", req.user.email, job.id, {
jobid: job.id,
id: id
});
@@ -159,7 +159,7 @@ async function Totals(req, res) {
res.status(200).json(ret);
} catch (error) {
logger.log("job-totals-ssu-USA-error", "ERROR", req.user.email, job.id, {
logger.log("job-totals-ssu-USA-error", "error", req.user.email, job.id, {
jobid: job.id,
error: error.message,
stack: error.stack
@@ -240,7 +240,7 @@ async function AtsAdjustmentsIfRequired({ job, client, user }) {
job.joblines.push(newAtsLine);
}
} catch (error) {
logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, {
logger.log("job-totals-ssu-ats-error", "error", user?.email, job.id, {
jobid: job.id,
error: error.message,
stack: error.stack
@@ -258,7 +258,7 @@ async function AtsAdjustmentsIfRequired({ job, client, user }) {
job.joblines[atsLineIndex].act_price = atsAmount;
}
} catch (error) {
logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, {
logger.log("job-totals-ssu-ats-error", "error", user?.email, job.id, {
jobid: job.id,
atsLineIndex: atsLineIndex,
atsAmount: atsAmount,
@@ -1055,7 +1055,7 @@ function CalculateTaxesTotals(job, otherTotals) {
}
}
} catch (error) {
logger.log("job-totals-USA Key with issue", "error", null, job.id, {
logger.log("job-totals-USA Key with issue", "warn", null, job.id, {
key: key,
error: error.message,
stack: error.stack

View File

@@ -23,7 +23,7 @@ exports.totalsSsu = async function (req, res) {
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
logger.log("job-totals-ssu", "debug", req.user.email, id, null);
try {
const job = await client.setHeaders({ Authorization: BearerToken }).request(queries.GET_JOB_BY_PK, {
@@ -49,7 +49,7 @@ exports.totalsSsu = async function (req, res) {
res.status(200).send();
} catch (error) {
logger.log("job-totals-ssu-error", "ERROR", req.user.email, id, {
logger.log("job-totals-ssu-error", "error", req.user.email, id, {
jobid: id,
error: error.message,
stack: error.stack
@@ -73,7 +73,7 @@ async function TotalsServerSide(req, res) {
return ret;
} catch (error) {
logger.log("job-totals-ssu-error", "ERROR", req?.user?.email, job.id, {
logger.log("job-totals-ssu-error", "error", req?.user?.email, job.id, {
jobid: job.id,
error: error.message,
stack: error.stack
@@ -91,7 +91,7 @@ async function Totals(req, res) {
const logger = req.logger;
const client = req.userGraphQLClient;
logger.log("job-totals-ssu", "DEBUG", req.user.email, job.id, {
logger.log("job-totals-ssu", "debug", req.user.email, job.id, {
jobid: job.id,
id: id
});
@@ -108,7 +108,7 @@ async function Totals(req, res) {
res.status(200).json(ret);
} catch (error) {
logger.log("job-totals-ssu-error", "ERROR", req.user.email, job.id, {
logger.log("job-totals-ssu-error", "error", req.user.email, job.id, {
jobid: job.id,
error: error.message,
stack: error.stack
@@ -189,7 +189,7 @@ async function AtsAdjustmentsIfRequired({ job, client, user }) {
job.joblines.push(newAtsLine);
}
} catch (error) {
logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, {
logger.log("job-totals-ssu-ats-error", "error", user?.email, job.id, {
jobid: job.id,
error: error.message,
stack: error.stack
@@ -207,7 +207,7 @@ async function AtsAdjustmentsIfRequired({ job, client, user }) {
job.joblines[atsLineIndex].act_price = atsAmount;
}
} catch (error) {
logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, {
logger.log("job-totals-ssu-ats-error", "error", user?.email, job.id, {
jobid: job.id,
atsLineIndex: atsLineIndex,
atsAmount: atsAmount,

View File

@@ -92,7 +92,7 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
"consolidate-notifications",
{ jobId, recipients },
{
jobId: `consolidate:${jobId}`,
jobId: `consolidate-${jobId}`,
delay: APP_CONSOLIDATION_DELAY,
attempts: 3,
backoff: LOCK_EXPIRATION
@@ -288,7 +288,7 @@ const dispatchAppsToQueue = async ({ appsToDispatch }) => {
await appQueue.add(
"add-notification",
{ jobId, bodyShopId, key, variables, recipients, body, jobRoNumber },
{ jobId: `${jobId}:${Date.now()}` }
{ jobId: `${jobId}-${Date.now()}` }
);
devDebugLogger(`Added notification to queue for jobId ${jobId} with ${recipients.length} recipients`);
}

View File

@@ -86,7 +86,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
"consolidate-emails",
{ jobId, jobRoNumber, bodyShopName, bodyShopTimezone },
{
jobId: `consolidate:${jobId}`,
jobId: `consolidate-${jobId}`,
delay: EMAIL_CONSOLIDATION_DELAY,
attempts: 3,
backoff: LOCK_EXPIRATION
@@ -252,7 +252,7 @@ const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
await emailAddQueue.add(
"add-email-notification",
{ jobId, jobRoNumber, bodyShopName, bodyShopTimezone, body, recipients },
{ jobId: `${jobId}:${Date.now()}` }
{ jobId: `${jobId}-${Date.now()}` }
);
devDebugLogger(`Added email notification to queue for jobId ${jobId} with ${recipients.length} recipients`);
}