diff --git a/client/src/App/App.container.backup-2026-03-04.jsx b/client/src/App/App.container.backup-2026-03-04.jsx
new file mode 100644
index 000000000..bcfc39fa1
--- /dev/null
+++ b/client/src/App/App.container.backup-2026-03-04.jsx
@@ -0,0 +1,184 @@
+import { ApolloProvider } from "@apollo/client/react";
+import * as Sentry from "@sentry/react";
+import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
+import { ConfigProvider, Grid } from "antd";
+import enLocale from "antd/es/locale/en_US";
+import { useEffect, useMemo } from "react";
+import { CookiesProvider } from "react-cookie";
+import { useTranslation } from "react-i18next";
+import { useDispatch, useSelector } from "react-redux";
+import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
+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";
+
+// Base Split configuration
+const config = {
+ core: {
+ authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
+ key: "anon"
+ }
+};
+
+function SplitClientProvider({ children }) {
+ const imexshopid = useSelector((state) => state.user.imexshopid);
+ const splitClient = useSplitClient({ key: imexshopid || "anon" });
+
+ useEffect(() => {
+ if (import.meta.env.DEV && splitClient && imexshopid) {
+ console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
+ }
+ }, [splitClient, imexshopid]);
+
+ return children;
+}
+
+function AppContainer() {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ const currentUser = useSelector(selectCurrentUser);
+ const isDarkMode = useSelector(selectDarkMode);
+ const screens = Grid.useBreakpoint();
+ const isPhone = !screens.md;
+ const isUltraWide = Boolean(screens.xxxl);
+
+ const theme = useMemo(() => {
+ const baseTheme = getTheme(isDarkMode);
+
+ return {
+ ...baseTheme,
+ token: {
+ ...(baseTheme.token || {}),
+ screenXXXL: 2160
+ },
+ components: {
+ ...(baseTheme.components || {}),
+ Table: {
+ ...(baseTheme.components?.Table || {}),
+ cellFontSizeSM: isPhone ? 12 : 13,
+ cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14,
+ cellFontSize: isUltraWide ? 15 : 14,
+ cellPaddingInlineSM: isPhone ? 8 : 10,
+ cellPaddingInlineMD: isPhone ? 10 : 14,
+ cellPaddingInline: isUltraWide ? 20 : 16,
+ cellPaddingBlockSM: isPhone ? 8 : 10,
+ cellPaddingBlockMD: isPhone ? 10 : 12,
+ cellPaddingBlock: isUltraWide ? 14 : 12,
+ selectionColumnWidth: isPhone ? 44 : 52
+ }
+ }
+ };
+ }, [isDarkMode, isPhone, isUltraWide]);
+
+ const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []);
+ const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []);
+ const antdPagination = useMemo(
+ () => ({
+ showSizeChanger: !isPhone,
+ totalBoundaryShowSizeChanger: 100
+ }),
+ [isPhone]
+ );
+
+ const antdForm = useMemo(
+ () => ({
+ validateMessages: {
+ required: t("general.validation.required", { label: "${label}" })
+ }
+ }),
+ [t]
+ );
+
+ // Global seamless logout listener with redirect to /signin
+ useEffect(() => {
+ const handleSeamlessLogout = (event) => {
+ if (event.data?.type !== "seamlessLogoutRequest") return;
+
+ // Only accept messages from the parent window
+ if (event.source !== window.parent) return;
+
+ const targetOrigin = event.origin || "*";
+
+ if (currentUser?.authorized !== true) {
+ window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "already_logged_out" }, targetOrigin);
+ return;
+ }
+
+ dispatch(signOutStart());
+ window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "logged_out" }, targetOrigin);
+ };
+
+ window.addEventListener("message", handleSeamlessLogout);
+ return () => {
+ window.removeEventListener("message", handleSeamlessLogout);
+ };
+ }, [dispatch, currentUser?.authorized]);
+
+ // Update data-theme attribute (no cleanup to avoid transient style churn)
+ useEffect(() => {
+ document.documentElement.dataset.theme = isDarkMode ? "dark" : "light";
+ }, [isDarkMode]);
+
+ // Sync darkMode with localStorage
+ useEffect(() => {
+ const uid = currentUser?.uid;
+
+ if (!uid) {
+ dispatch(setDarkMode(false));
+ return;
+ }
+
+ const key = `dark-mode-${uid}`;
+ const raw = localStorage.getItem(key);
+
+ if (raw == null) {
+ dispatch(setDarkMode(false));
+ return;
+ }
+
+ try {
+ dispatch(setDarkMode(Boolean(JSON.parse(raw))));
+ } catch {
+ dispatch(setDarkMode(false));
+ }
+ }, [currentUser?.uid, dispatch]);
+
+ // Persist darkMode
+ useEffect(() => {
+ const uid = currentUser?.uid;
+ if (!uid) return;
+
+ localStorage.setItem(`dark-mode-${uid}`, JSON.stringify(isDarkMode));
+ }, [isDarkMode, currentUser?.uid]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Sentry.withProfiler(AppContainer);
diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx
index bcfc39fa1..8abf59242 100644
--- a/client/src/App/App.container.jsx
+++ b/client/src/App/App.container.jsx
@@ -1,7 +1,7 @@
import { ApolloProvider } from "@apollo/client/react";
import * as Sentry from "@sentry/react";
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
-import { ConfigProvider, Grid } from "antd";
+import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import { useEffect, useMemo } from "react";
import { CookiesProvider } from "react-cookie";
@@ -43,47 +43,10 @@ function AppContainer() {
const currentUser = useSelector(selectCurrentUser);
const isDarkMode = useSelector(selectDarkMode);
- const screens = Grid.useBreakpoint();
- const isPhone = !screens.md;
- const isUltraWide = Boolean(screens.xxxl);
- const theme = useMemo(() => {
- const baseTheme = getTheme(isDarkMode);
-
- return {
- ...baseTheme,
- token: {
- ...(baseTheme.token || {}),
- screenXXXL: 2160
- },
- components: {
- ...(baseTheme.components || {}),
- Table: {
- ...(baseTheme.components?.Table || {}),
- cellFontSizeSM: isPhone ? 12 : 13,
- cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14,
- cellFontSize: isUltraWide ? 15 : 14,
- cellPaddingInlineSM: isPhone ? 8 : 10,
- cellPaddingInlineMD: isPhone ? 10 : 14,
- cellPaddingInline: isUltraWide ? 20 : 16,
- cellPaddingBlockSM: isPhone ? 8 : 10,
- cellPaddingBlockMD: isPhone ? 10 : 12,
- cellPaddingBlock: isUltraWide ? 14 : 12,
- selectionColumnWidth: isPhone ? 44 : 52
- }
- }
- };
- }, [isDarkMode, isPhone, isUltraWide]);
+ const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []);
- const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []);
- const antdPagination = useMemo(
- () => ({
- showSizeChanger: !isPhone,
- totalBoundaryShowSizeChanger: 100
- }),
- [isPhone]
- );
const antdForm = useMemo(
() => ({
@@ -159,16 +122,7 @@ function AppContainer() {
return (
-
+
diff --git a/client/src/App/App.container.pre-rollback-2026-03-04.jsx b/client/src/App/App.container.pre-rollback-2026-03-04.jsx
new file mode 100644
index 000000000..bcfc39fa1
--- /dev/null
+++ b/client/src/App/App.container.pre-rollback-2026-03-04.jsx
@@ -0,0 +1,184 @@
+import { ApolloProvider } from "@apollo/client/react";
+import * as Sentry from "@sentry/react";
+import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
+import { ConfigProvider, Grid } from "antd";
+import enLocale from "antd/es/locale/en_US";
+import { useEffect, useMemo } from "react";
+import { CookiesProvider } from "react-cookie";
+import { useTranslation } from "react-i18next";
+import { useDispatch, useSelector } from "react-redux";
+import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
+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";
+
+// Base Split configuration
+const config = {
+ core: {
+ authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
+ key: "anon"
+ }
+};
+
+function SplitClientProvider({ children }) {
+ const imexshopid = useSelector((state) => state.user.imexshopid);
+ const splitClient = useSplitClient({ key: imexshopid || "anon" });
+
+ useEffect(() => {
+ if (import.meta.env.DEV && splitClient && imexshopid) {
+ console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
+ }
+ }, [splitClient, imexshopid]);
+
+ return children;
+}
+
+function AppContainer() {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ const currentUser = useSelector(selectCurrentUser);
+ const isDarkMode = useSelector(selectDarkMode);
+ const screens = Grid.useBreakpoint();
+ const isPhone = !screens.md;
+ const isUltraWide = Boolean(screens.xxxl);
+
+ const theme = useMemo(() => {
+ const baseTheme = getTheme(isDarkMode);
+
+ return {
+ ...baseTheme,
+ token: {
+ ...(baseTheme.token || {}),
+ screenXXXL: 2160
+ },
+ components: {
+ ...(baseTheme.components || {}),
+ Table: {
+ ...(baseTheme.components?.Table || {}),
+ cellFontSizeSM: isPhone ? 12 : 13,
+ cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14,
+ cellFontSize: isUltraWide ? 15 : 14,
+ cellPaddingInlineSM: isPhone ? 8 : 10,
+ cellPaddingInlineMD: isPhone ? 10 : 14,
+ cellPaddingInline: isUltraWide ? 20 : 16,
+ cellPaddingBlockSM: isPhone ? 8 : 10,
+ cellPaddingBlockMD: isPhone ? 10 : 12,
+ cellPaddingBlock: isUltraWide ? 14 : 12,
+ selectionColumnWidth: isPhone ? 44 : 52
+ }
+ }
+ };
+ }, [isDarkMode, isPhone, isUltraWide]);
+
+ const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []);
+ const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []);
+ const antdPagination = useMemo(
+ () => ({
+ showSizeChanger: !isPhone,
+ totalBoundaryShowSizeChanger: 100
+ }),
+ [isPhone]
+ );
+
+ const antdForm = useMemo(
+ () => ({
+ validateMessages: {
+ required: t("general.validation.required", { label: "${label}" })
+ }
+ }),
+ [t]
+ );
+
+ // Global seamless logout listener with redirect to /signin
+ useEffect(() => {
+ const handleSeamlessLogout = (event) => {
+ if (event.data?.type !== "seamlessLogoutRequest") return;
+
+ // Only accept messages from the parent window
+ if (event.source !== window.parent) return;
+
+ const targetOrigin = event.origin || "*";
+
+ if (currentUser?.authorized !== true) {
+ window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "already_logged_out" }, targetOrigin);
+ return;
+ }
+
+ dispatch(signOutStart());
+ window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "logged_out" }, targetOrigin);
+ };
+
+ window.addEventListener("message", handleSeamlessLogout);
+ return () => {
+ window.removeEventListener("message", handleSeamlessLogout);
+ };
+ }, [dispatch, currentUser?.authorized]);
+
+ // Update data-theme attribute (no cleanup to avoid transient style churn)
+ useEffect(() => {
+ document.documentElement.dataset.theme = isDarkMode ? "dark" : "light";
+ }, [isDarkMode]);
+
+ // Sync darkMode with localStorage
+ useEffect(() => {
+ const uid = currentUser?.uid;
+
+ if (!uid) {
+ dispatch(setDarkMode(false));
+ return;
+ }
+
+ const key = `dark-mode-${uid}`;
+ const raw = localStorage.getItem(key);
+
+ if (raw == null) {
+ dispatch(setDarkMode(false));
+ return;
+ }
+
+ try {
+ dispatch(setDarkMode(Boolean(JSON.parse(raw))));
+ } catch {
+ dispatch(setDarkMode(false));
+ }
+ }, [currentUser?.uid, dispatch]);
+
+ // Persist darkMode
+ useEffect(() => {
+ const uid = currentUser?.uid;
+ if (!uid) return;
+
+ localStorage.setItem(`dark-mode-${uid}`, JSON.stringify(isDarkMode));
+ }, [isDarkMode, currentUser?.uid]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Sentry.withProfiler(AppContainer);
diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss
index aedfbd15c..5f66030b5 100644
--- a/client/src/App/App.styles.scss
+++ b/client/src/App/App.styles.scss
@@ -471,34 +471,34 @@
// padding: 0;
//}
-/* globally allow shrink inside table cells */
-.prod-list-table .ant-table-cell,
-.prod-list-table .ant-table-cell > * {
- min-width: 0;
-}
-
-/* common AntD offenders */
-.prod-list-table > .ant-table-cell .ant-space,
-.ant-table-cell .ant-space-item {
- min-width: 0;
-}
-
-/* Keep your custom header content on the left, push AntD sorter to the far right */
-.prod-list-table .ant-table-column-sorters {
- display: flex !important;
- align-items: center;
- width: 100%;
-}
-
-.prod-list-table .ant-table-column-title {
- flex: 1 1 auto;
- min-width: 0; /* allows ellipsis to work */
-}
-
-.prod-list-table .ant-table-column-sorter {
- margin-left: auto;
- flex: 0 0 auto;
-}
+///* globally allow shrink inside table cells */
+//.prod-list-table .ant-table-cell,
+//.prod-list-table .ant-table-cell > * {
+// min-width: 0;
+//}
+//
+///* common AntD offenders */
+//.prod-list-table > .ant-table-cell .ant-space,
+//.ant-table-cell .ant-space-item {
+// min-width: 0;
+//}
+//
+///* Keep your custom header content on the left, push AntD sorter to the far right */
+//.prod-list-table .ant-table-column-sorters {
+// display: flex !important;
+// align-items: center;
+// width: 100%;
+//}
+//
+//.prod-list-table .ant-table-column-title {
+// flex: 1 1 auto;
+// min-width: 0; /* allows ellipsis to work */
+//}
+//
+//.prod-list-table .ant-table-column-sorter {
+// margin-left: auto;
+// flex: 0 0 auto;
+//}
.global-search-autocomplete-fix {
diff --git a/client/src/components/responsive-table/responsive-table.component.backup-2026-03-04.jsx b/client/src/components/responsive-table/responsive-table.component.backup-2026-03-04.jsx
new file mode 100644
index 000000000..94ed7fc02
--- /dev/null
+++ b/client/src/components/responsive-table/responsive-table.component.backup-2026-03-04.jsx
@@ -0,0 +1,99 @@
+import { Grid, Table } from "antd";
+import { useMemo } from "react";
+import "./responsive-table.styles.scss";
+
+function ResponsiveTable({ className, columns, mobileColumnKeys, scroll, tableLayout, ...rest }) {
+ const screens = Grid.useBreakpoint();
+ const isPhone = !screens.md;
+ const isCompactViewport = !screens.lg;
+ const prefersHorizontalScroll = isPhone || isCompactViewport;
+ const isResponsiveFilteringEnabled = ["1", "true", "yes", "on"].includes(
+ String(import.meta.env.VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING || "")
+ .trim()
+ .toLowerCase()
+ );
+
+ const resolvedColumns = useMemo(() => {
+ if (
+ !isResponsiveFilteringEnabled ||
+ !Array.isArray(columns) ||
+ !isPhone ||
+ !Array.isArray(mobileColumnKeys) ||
+ mobileColumnKeys.length === 0
+ ) {
+ return columns;
+ }
+
+ const visibleColumnKeys = new Set(mobileColumnKeys);
+ const filteredColumns = columns.filter((column) => {
+ const key = column?.key ?? column?.dataIndex;
+
+ // Keep columns with no stable key to avoid accidental loss.
+ if (key == null) return true;
+
+ if (Array.isArray(key)) {
+ return key.some((part) => visibleColumnKeys.has(part));
+ }
+
+ return visibleColumnKeys.has(key);
+ });
+
+ return filteredColumns.length > 0 ? filteredColumns : columns;
+ }, [columns, isPhone, isResponsiveFilteringEnabled, mobileColumnKeys]);
+
+ const resolvedScroll = useMemo(() => {
+ if (prefersHorizontalScroll) {
+ if (scroll == null) {
+ return { x: "max-content" };
+ }
+
+ if (typeof scroll !== "object" || Array.isArray(scroll)) {
+ return scroll;
+ }
+
+ const { x, ...baseScroll } = scroll;
+
+ return { ...baseScroll, x: x ?? "max-content" };
+ }
+
+ if (scroll == null) {
+ // Explicitly override ConfigProvider table.scroll desktop defaults.
+ return {};
+ }
+
+ if (typeof scroll !== "object" || Array.isArray(scroll)) {
+ return scroll;
+ }
+
+ const { x, ...desktopScroll } = scroll;
+
+ // On desktop we prefer fitting columns with ellipsis over forced horizontal scroll.
+ if (x == null) {
+ return desktopScroll;
+ }
+
+ return desktopScroll;
+ }, [prefersHorizontalScroll, scroll]);
+
+ const resolvedTableLayout = tableLayout ?? (prefersHorizontalScroll ? "auto" : "fixed");
+ const responsiveClassName = prefersHorizontalScroll ? undefined : "responsive-table-fit";
+ const resolvedClassName = [responsiveClassName, className].filter(Boolean).join(" ");
+
+ return (
+
+ );
+}
+
+ResponsiveTable.Summary = Table.Summary;
+ResponsiveTable.Column = Table.Column;
+ResponsiveTable.ColumnGroup = Table.ColumnGroup;
+ResponsiveTable.SELECTION_COLUMN = Table.SELECTION_COLUMN;
+ResponsiveTable.EXPAND_COLUMN = Table.EXPAND_COLUMN;
+
+export default ResponsiveTable;
diff --git a/client/src/components/responsive-table/responsive-table.component.jsx b/client/src/components/responsive-table/responsive-table.component.jsx
index 94ed7fc02..c6424db82 100644
--- a/client/src/components/responsive-table/responsive-table.component.jsx
+++ b/client/src/components/responsive-table/responsive-table.component.jsx
@@ -1,93 +1,7 @@
-import { Grid, Table } from "antd";
-import { useMemo } from "react";
-import "./responsive-table.styles.scss";
+import { Table } from "antd";
-function ResponsiveTable({ className, columns, mobileColumnKeys, scroll, tableLayout, ...rest }) {
- const screens = Grid.useBreakpoint();
- const isPhone = !screens.md;
- const isCompactViewport = !screens.lg;
- const prefersHorizontalScroll = isPhone || isCompactViewport;
- const isResponsiveFilteringEnabled = ["1", "true", "yes", "on"].includes(
- String(import.meta.env.VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING || "")
- .trim()
- .toLowerCase()
- );
-
- const resolvedColumns = useMemo(() => {
- if (
- !isResponsiveFilteringEnabled ||
- !Array.isArray(columns) ||
- !isPhone ||
- !Array.isArray(mobileColumnKeys) ||
- mobileColumnKeys.length === 0
- ) {
- return columns;
- }
-
- const visibleColumnKeys = new Set(mobileColumnKeys);
- const filteredColumns = columns.filter((column) => {
- const key = column?.key ?? column?.dataIndex;
-
- // Keep columns with no stable key to avoid accidental loss.
- if (key == null) return true;
-
- if (Array.isArray(key)) {
- return key.some((part) => visibleColumnKeys.has(part));
- }
-
- return visibleColumnKeys.has(key);
- });
-
- return filteredColumns.length > 0 ? filteredColumns : columns;
- }, [columns, isPhone, isResponsiveFilteringEnabled, mobileColumnKeys]);
-
- const resolvedScroll = useMemo(() => {
- if (prefersHorizontalScroll) {
- if (scroll == null) {
- return { x: "max-content" };
- }
-
- if (typeof scroll !== "object" || Array.isArray(scroll)) {
- return scroll;
- }
-
- const { x, ...baseScroll } = scroll;
-
- return { ...baseScroll, x: x ?? "max-content" };
- }
-
- if (scroll == null) {
- // Explicitly override ConfigProvider table.scroll desktop defaults.
- return {};
- }
-
- if (typeof scroll !== "object" || Array.isArray(scroll)) {
- return scroll;
- }
-
- const { x, ...desktopScroll } = scroll;
-
- // On desktop we prefer fitting columns with ellipsis over forced horizontal scroll.
- if (x == null) {
- return desktopScroll;
- }
-
- return desktopScroll;
- }, [prefersHorizontalScroll, scroll]);
-
- const resolvedTableLayout = tableLayout ?? (prefersHorizontalScroll ? "auto" : "fixed");
- const responsiveClassName = prefersHorizontalScroll ? undefined : "responsive-table-fit";
- const resolvedClassName = [responsiveClassName, className].filter(Boolean).join(" ");
-
- return (
-
- );
+function ResponsiveTable(props) {
+ return ;
}
ResponsiveTable.Summary = Table.Summary;
diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
index 7d6daed41..52397b90e 100644
--- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
+++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
@@ -47,7 +47,7 @@ export function TimeTicketModalComponent({
} = useTreatmentsWithConfig({
attributes: {},
names: ["Enhanced_Payroll"],
- splitKey: bodyshop.imexshopid
+ splitKey: bodyshop?.imexshopid
});
const [loadLineTicketData, { loading, data: lineTicketData, refetch }] = useLazyQuery(GET_LINE_TICKET_BY_PK, {
@@ -347,7 +347,7 @@ export function LaborAllocationContainer({
} = useTreatmentsWithConfig({
attributes: {},
names: ["Enhanced_Payroll"],
- splitKey: bodyshop.imexshopid
+ splitKey: bodyshop?.imexshopid
});
if (loading) return ;
diff --git a/server/rr/rr-customers.js b/server/rr/rr-customers.js
index 0895bf5b4..7c5fe671f 100644
--- a/server/rr/rr-customers.js
+++ b/server/rr/rr-customers.js
@@ -1,6 +1,7 @@
const { RRClient } = require("./lib/index.cjs");
const { getRRConfigFromBodyshop } = require("./rr-config");
const CreateRRLogEvent = require("./rr-logger-event");
+const { withRRRequestXml } = require("./rr-log-xml");
const InstanceManager = require("../utils/instanceMgr").default;
/**
@@ -217,14 +218,24 @@ const createRRCustomer = async ({ bodyshop, job, overrides = {}, socket }) => {
try {
response = await client.insertCustomer(safePayload, opts);
// Very noisy; only show when log level is cranked to SILLY
- CreateRRLogEvent(socket, "SILLY", "{CU} insertCustomer: raw response", { response });
+ CreateRRLogEvent(
+ socket,
+ "SILLY",
+ "{CU} insertCustomer: raw response",
+ withRRRequestXml(response, { response })
+ );
} catch (e) {
- CreateRRLogEvent(socket, "ERROR", "RR insertCustomer transport error", {
- message: e?.message,
- code: e?.code,
- status: e?.meta?.status || e?.status,
- payload: safePayload
- });
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ "RR insertCustomer transport error",
+ withRRRequestXml(e, {
+ message: e?.message,
+ code: e?.code,
+ status: e?.meta?.status || e?.status,
+ payload: safePayload
+ })
+ );
throw e;
}
@@ -233,12 +244,17 @@ const createRRCustomer = async ({ bodyshop, job, overrides = {}, socket }) => {
let customerNo = data?.dmsRecKey;
if (!customerNo) {
- CreateRRLogEvent(socket, "ERROR", "RR insertCustomer returned no dmsRecKey/custNo", {
- status: trx?.status,
- statusCode: trx?.statusCode,
- message: trx?.message,
- data
- });
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ "RR insertCustomer returned no dmsRecKey/custNo",
+ withRRRequestXml(response, {
+ status: trx?.status,
+ statusCode: trx?.statusCode,
+ message: trx?.message,
+ data
+ })
+ );
throw new Error(
`RR insertCustomer returned no dmsRecKey (status=${trx?.status ?? "?"} code=${trx?.statusCode ?? "?"}${
diff --git a/server/rr/rr-export-logs.js b/server/rr/rr-export-logs.js
index 184d54a4f..7dff70f6c 100644
--- a/server/rr/rr-export-logs.js
+++ b/server/rr/rr-export-logs.js
@@ -1,6 +1,7 @@
const { GraphQLClient } = require("graphql-request");
const queries = require("../graphql-client/queries");
const CreateRRLogEvent = require("./rr-logger-event");
+const { extractRRXmlPair } = require("./rr-log-xml");
/** Get bearer token from the socket (same approach used elsewhere) */
const getAuthToken = (socket) =>
@@ -178,11 +179,23 @@ const insertRRFailedExportLog = async ({ socket, jobId, job, bodyshop, error, cl
const client = new GraphQLClient(endpoint, {});
client.setHeaders({ Authorization: `Bearer ${token}` });
+ const { requestXml, responseXml } = extractRRXmlPair(error);
+ const xmlFromError =
+ requestXml || responseXml
+ ? {
+ ...(requestXml ? { request: requestXml } : {}),
+ ...(responseXml ? { response: responseXml } : {})
+ }
+ : undefined;
+
const meta = buildRRExportMeta({
result,
extra: {
error: error?.message || String(error),
- classification: classification || undefined
+ classification: classification || undefined,
+ ...(requestXml ? { requestXml } : {}),
+ ...(responseXml ? { responseXml } : {}),
+ ...(xmlFromError && !result?.xml ? { xml: xmlFromError } : {})
}
});
diff --git a/server/rr/rr-job-export.js b/server/rr/rr-job-export.js
index 9cc074ab6..a5248bcf2 100644
--- a/server/rr/rr-job-export.js
+++ b/server/rr/rr-job-export.js
@@ -1,6 +1,7 @@
const { buildRRRepairOrderPayload } = require("./rr-job-helpers");
const { buildClientAndOpts } = require("./rr-lookup");
const CreateRRLogEvent = require("./rr-logger-event");
+const { withRRRequestXml } = require("./rr-log-xml");
const { extractRrResponsibilityCenters } = require("./rr-responsibility-centers");
const CdkCalculateAllocations = require("./rr-calculate-allocations").default;
const { resolveRROpCodeFromBodyshop } = require("./rr-utils");
@@ -147,10 +148,7 @@ const createMinimalRRRepairOrder = async (args) => {
const response = await client.createRepairOrder(payload, finalOpts);
- CreateRRLogEvent(socket, "INFO", "RR minimal Repair Order created", {
- payload,
- response
- });
+ CreateRRLogEvent(socket, "INFO", "RR minimal Repair Order created", withRRRequestXml(response, { payload, response }));
const data = response?.data || null;
const statusBlocks = response?.statusBlocks || {};
@@ -327,7 +325,7 @@ const updateRRRepairOrderWithFullData = async (args) => {
// Without this, Reynolds won't recognize the OpCode when we send rogg operations
// The rolabor section tells Reynolds "these jobs exist" even with minimal data
- CreateRRLogEvent(socket, "INFO", "Sending full data for early RO (using create with roNo)", {
+ CreateRRLogEvent(socket, "INFO", "Preparing full data for early RO (using create with roNo)", {
roNo: String(roNo),
hasRolabor: !!payload.rolabor,
hasRogg: !!payload.rogg,
@@ -338,10 +336,18 @@ const updateRRRepairOrderWithFullData = async (args) => {
// Reynolds will merge this with the existing RO header
const response = await client.createRepairOrder(payload, finalOpts);
- CreateRRLogEvent(socket, "INFO", "RR Repair Order full data sent", {
- payload,
- response
- });
+ CreateRRLogEvent(
+ socket,
+ "INFO",
+ "Sending full data for early RO (using create with roNo)",
+ withRRRequestXml(response, {
+ roNo: String(roNo),
+ hasRolabor: !!payload.rolabor,
+ hasRogg: !!payload.rogg,
+ payload,
+ response
+ })
+ );
const data = response?.data || null;
const statusBlocks = response?.statusBlocks || {};
@@ -501,10 +507,7 @@ const exportJobToRR = async (args) => {
const response = await client.createRepairOrder(payload, finalOpts);
- CreateRRLogEvent(socket, "INFO", "RR raw Repair Order created", {
- payload,
- response
- });
+ CreateRRLogEvent(socket, "INFO", "RR raw Repair Order created", withRRRequestXml(response, { payload, response }));
const data = response?.data || null;
const statusBlocks = response?.statusBlocks || {};
@@ -603,10 +606,15 @@ const finalizeRRRepairOrder = async (args) => {
const rrRes = await client.updateRepairOrder(payload, finalOpts);
- CreateRRLogEvent(socket, "SILLY", "RR Repair Order finalized", {
- payload,
- response: rrRes
- });
+ CreateRRLogEvent(
+ socket,
+ "SILLY",
+ "RR Repair Order finalized",
+ withRRRequestXml(rrRes, {
+ payload,
+ response: rrRes
+ })
+ );
const data = rrRes?.data || null;
const statusBlocks = rrRes?.statusBlocks || {};
diff --git a/server/rr/rr-log-xml.js b/server/rr/rr-log-xml.js
new file mode 100644
index 000000000..0761d3418
--- /dev/null
+++ b/server/rr/rr-log-xml.js
@@ -0,0 +1,63 @@
+/**
+ * Extract request/response XML from RR response/result shapes.
+ * @param rrObj
+ * @returns {{requestXml: string|null, responseXml: string|null}}
+ */
+const extractRRXmlPair = (rrObj) => {
+ const xml = rrObj?.xml ?? rrObj?.meta?.xml;
+
+ let requestXml = null;
+ let responseXml = null;
+
+ if (typeof xml === "string") {
+ requestXml = xml;
+ } else {
+ if (typeof xml?.request === "string") requestXml = xml.request;
+ else if (typeof xml?.req === "string") requestXml = xml.req;
+ else if (typeof xml?.starXml === "string") requestXml = xml.starXml;
+ if (typeof xml?.response === "string") responseXml = xml.response;
+ }
+
+ if (!requestXml && typeof rrObj?.requestXml === "string") requestXml = rrObj.requestXml;
+ if (!requestXml && typeof rrObj?.meta?.requestXml === "string") requestXml = rrObj.meta.requestXml;
+ if (!requestXml && typeof rrObj?.meta?.reqXml === "string") requestXml = rrObj.meta.reqXml;
+ if (!requestXml && typeof rrObj?.meta?.request === "string") requestXml = rrObj.meta.request;
+ if (!responseXml && typeof rrObj?.responseXml === "string") responseXml = rrObj.responseXml;
+ if (!responseXml && typeof rrObj?.meta?.responseXml === "string") responseXml = rrObj.meta.responseXml;
+ if (!responseXml && typeof rrObj?.meta?.resXml === "string") responseXml = rrObj.meta.resXml;
+ if (!responseXml && typeof rrObj?.meta?.response === "string") responseXml = rrObj.meta.response;
+
+ // If wrapped HTTP response data contains raw XML, surface it.
+ if (!responseXml && typeof rrObj?.response?.data === "string") {
+ const xmlData = rrObj.response.data.trim();
+ if (xmlData.startsWith("<")) responseXml = xmlData;
+ }
+
+ // Try one level down when errors are wrapped.
+ if ((!requestXml || !responseXml) && rrObj?.cause && rrObj.cause !== rrObj) {
+ const nested = extractRRXmlPair(rrObj.cause);
+ if (!requestXml) requestXml = nested.requestXml;
+ if (!responseXml) responseXml = nested.responseXml;
+ }
+
+ return { requestXml, responseXml };
+};
+
+/**
+ * Add Reynolds request/response XML to RR log metadata when available.
+ * @param rrObj
+ * @param meta
+ * @returns {*}
+ */
+const withRRRequestXml = (rrObj, meta = {}) => {
+ const { requestXml, responseXml } = extractRRXmlPair(rrObj);
+ const xmlMeta = {};
+ if (requestXml) xmlMeta.requestXml = requestXml;
+ if (responseXml) xmlMeta.responseXml = responseXml;
+ return Object.keys(xmlMeta).length ? { ...meta, ...xmlMeta } : meta;
+};
+
+module.exports = {
+ extractRRXmlPair,
+ withRRRequestXml
+};
diff --git a/server/rr/rr-register-socket-events.js b/server/rr/rr-register-socket-events.js
index bdb51580e..92db91d99 100644
--- a/server/rr/rr-register-socket-events.js
+++ b/server/rr/rr-register-socket-events.js
@@ -12,6 +12,7 @@ const { createRRCustomer } = require("./rr-customers");
const { ensureRRServiceVehicle } = require("./rr-service-vehicles");
const { classifyRRVendorError } = require("./rr-errors");
const { markRRExportSuccess, insertRRFailedExportLog } = require("./rr-export-logs");
+const { withRRRequestXml } = require("./rr-log-xml");
const {
makeVehicleSearchPayloadFromJob,
ownersFromVinBlocks,
@@ -48,46 +49,6 @@ const resolveJobId = (explicit, payload, job) => explicit || payload?.jobId || j
*/
const resolveVin = ({ tx, job }) => tx?.jobData?.vin || job?.v_vin || null;
-/**
- * Extract request/response XML from RR response/result shapes.
- * @param rrObj
- * @returns {{requestXml: string|null, responseXml: string|null}}
- */
-const extractRRXmlPair = (rrObj) => {
- const xml = rrObj?.xml;
-
- let requestXml = null;
- let responseXml = null;
-
- if (typeof xml === "string") {
- requestXml = xml;
- } else {
- if (typeof xml?.request === "string") requestXml = xml.request;
- else if (typeof xml?.req === "string") requestXml = xml.req;
- else if (typeof xml?.starXml === "string") requestXml = xml.starXml;
- if (typeof xml?.response === "string") responseXml = xml.response;
- }
-
- if (!requestXml && typeof rrObj?.requestXml === "string") requestXml = rrObj.requestXml;
- if (!responseXml && typeof rrObj?.responseXml === "string") responseXml = rrObj.responseXml;
-
- return { requestXml, responseXml };
-};
-
-/**
- * Add Reynolds request/response XML to RR log metadata when available.
- * @param rrObj
- * @param meta
- * @returns {*}
- */
-const withRRRequestXml = (rrObj, meta = {}) => {
- const { requestXml, responseXml } = extractRRXmlPair(rrObj);
- const xmlMeta = {};
- if (requestXml) xmlMeta.requestXml = requestXml;
- if (responseXml) xmlMeta.responseXml = responseXml;
- return Object.keys(xmlMeta).length ? { ...meta, ...xmlMeta } : meta;
-};
-
/**
* Sort vehicle owners first in the list, preserving original order otherwise.
* @param list
@@ -279,7 +240,12 @@ const rrMultiCustomerSearch = async ({ bodyshop, job, socket, redisHelpers }) =>
const multiResponse = await rrCombinedSearch(bodyshop, q);
- CreateRRLogEvent(socket, "SILLY", "Multi Customer Search - raw combined search", { response: multiResponse });
+ CreateRRLogEvent(
+ socket,
+ "SILLY",
+ "Multi Customer Search - raw combined search",
+ withRRRequestXml(multiResponse, { response: multiResponse })
+ );
if (fromVin) {
const multiBlocks = Array.isArray(multiResponse?.data) ? multiResponse.data : [];
@@ -300,7 +266,7 @@ const rrMultiCustomerSearch = async ({ bodyshop, job, socket, redisHelpers }) =>
const norm = normalizeCustomerCandidates(multiResponse, { ownersSet });
merged.push(...norm);
} catch (e) {
- CreateRRLogEvent(socket, "WARN", "Multi-search subquery failed", { kind: q.kind, error: e.message });
+ CreateRRLogEvent(socket, "WARN", "Multi-search subquery failed", withRRRequestXml(e, { kind: q.kind, error: e.message }));
}
}
@@ -348,7 +314,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
count: decorated.length
});
} catch (e) {
- CreateRRLogEvent(socket, "ERROR", "RR combined lookup error", { error: e.message, jobid });
+ CreateRRLogEvent(socket, "ERROR", "RR combined lookup error", withRRRequestXml(e, { error: e.message, jobid }));
cb?.({ jobid, error: e.message });
}
});
@@ -425,7 +391,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
fromCache
});
} catch (err) {
- CreateRRLogEvent(socket, "ERROR", "rr-get-advisors: failed", { error: err?.message });
+ CreateRRLogEvent(socket, "ERROR", "rr-get-advisors: failed", withRRRequestXml(err, { error: err?.message }));
ack?.({ ok: false, error: err?.message || "get advisors failed" });
}
});
@@ -496,11 +462,16 @@ const registerRREvents = ({ socket, redisHelpers }) => {
anyOwner: decorated.some((c) => c.vinOwner || c.isVehicleOwner)
});
} catch (error) {
- CreateRRLogEvent(socket, "ERROR", `Error during RR early RO creation (prepare)`, {
- error: error.message,
- stack: error.stack,
- jobid: rid
- });
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ `Error during RR early RO creation (prepare)`,
+ withRRRequestXml(error, {
+ error: error.message,
+ stack: error.stack,
+ jobid: rid
+ })
+ );
try {
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: error.message });
@@ -597,7 +568,12 @@ const registerRREvents = ({ socket, redisHelpers }) => {
if (vehQ && vehQ.kind === "vin" && job?.v_vin) {
const vinResponse = await rrCombinedSearch(bodyshop, vehQ);
- CreateRRLogEvent(socket, "SILLY", `VIN owner pre-check response (early RO)`, { response: vinResponse });
+ CreateRRLogEvent(
+ socket,
+ "SILLY",
+ `VIN owner pre-check response (early RO)`,
+ withRRRequestXml(vinResponse, { response: vinResponse })
+ );
const vinBlocks = Array.isArray(vinResponse?.data) ? vinResponse.data : [];
@@ -630,9 +606,14 @@ const registerRREvents = ({ socket, redisHelpers }) => {
}
}
} catch (e) {
- CreateRRLogEvent(socket, "WARN", `VIN owner pre-check failed; continuing with selected customer (early RO)`, {
- error: e?.message
- });
+ CreateRRLogEvent(
+ socket,
+ "WARN",
+ `VIN owner pre-check failed; continuing with selected customer (early RO)`,
+ withRRRequestXml(e, {
+ error: e?.message
+ })
+ );
}
// Cache final/effective customer selection
@@ -905,14 +886,19 @@ const registerRREvents = ({ socket, redisHelpers }) => {
} catch (error) {
const cls = classifyRRVendorError(error);
- CreateRRLogEvent(socket, "ERROR", `Error during RR early RO creation (customer-selected)`, {
- error: error.message,
- vendorStatusCode: cls.vendorStatusCode,
- code: cls.errorCode,
- friendly: cls.friendlyMessage,
- stack: error.stack,
- jobid: rid
- });
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ `Error during RR early RO creation (customer-selected)`,
+ withRRRequestXml(error, {
+ error: error.message,
+ vendorStatusCode: cls.vendorStatusCode,
+ code: cls.errorCode,
+ friendly: cls.friendlyMessage,
+ stack: error.stack,
+ jobid: rid
+ })
+ );
try {
if (!bodyshop || !job) {
@@ -1097,7 +1083,28 @@ const registerRREvents = ({ socket, redisHelpers }) => {
roNo: job.dms_id
});
+ CreateRRLogEvent(
+ socket,
+ "SILLY",
+ "{4.1} RR RO update response received",
+ withRRRequestXml(result, {
+ dmsRoNo: job.dms_id,
+ success: !!result?.success
+ })
+ );
+
if (!result?.success) {
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ "RR Repair Order update failed",
+ withRRRequestXml(result, {
+ jobId: rid,
+ dmsRoNo: job.dms_id,
+ roStatus: result?.roStatus,
+ statusBlocks: result?.statusBlocks
+ })
+ );
throw new Error(result?.roStatus?.message || "Failed to update RR Repair Order");
}
@@ -1154,11 +1161,16 @@ const registerRREvents = ({ socket, redisHelpers }) => {
anyOwner: decorated.some((c) => c.vinOwner || c.isVehicleOwner)
});
} catch (error) {
- CreateRRLogEvent(socket, "ERROR", `Error during RR export (prepare)`, {
- error: error.message,
- stack: error.stack,
- jobid: rid
- });
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ `Error during RR export (prepare)`,
+ withRRRequestXml(error, {
+ error: error.message,
+ stack: error.stack,
+ jobid: rid
+ })
+ );
try {
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: error.message });
@@ -1220,7 +1232,12 @@ const registerRREvents = ({ socket, redisHelpers }) => {
if (vehQ && vehQ.kind === "vin" && job?.v_vin) {
const vinResponse = await rrCombinedSearch(bodyshop, vehQ);
- CreateRRLogEvent(socket, "SILLY", `VIN owner pre-check response`, { response: vinResponse });
+ CreateRRLogEvent(
+ socket,
+ "SILLY",
+ `VIN owner pre-check response`,
+ withRRRequestXml(vinResponse, { response: vinResponse })
+ );
const vinBlocks = Array.isArray(vinResponse?.data) ? vinResponse.data : [];
@@ -1253,9 +1270,14 @@ const registerRREvents = ({ socket, redisHelpers }) => {
}
}
} catch (e) {
- CreateRRLogEvent(socket, "WARN", `VIN owner pre-check failed; continuing with selected customer`, {
- error: e?.message
- });
+ CreateRRLogEvent(
+ socket,
+ "WARN",
+ `VIN owner pre-check failed; continuing with selected customer`,
+ withRRRequestXml(e, {
+ error: e?.message
+ })
+ );
}
// Cache final/effective customer selection
@@ -1532,14 +1554,19 @@ const registerRREvents = ({ socket, redisHelpers }) => {
} catch (error) {
const cls = classifyRRVendorError(error);
- CreateRRLogEvent(socket, "ERROR", `Error during RR export (selected-customer)`, {
- error: error.message,
- vendorStatusCode: cls.vendorStatusCode,
- code: cls.errorCode,
- friendly: cls.friendlyMessage,
- stack: error.stack,
- jobid: rid
- });
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ `Error during RR export (selected-customer)`,
+ withRRRequestXml(error, {
+ error: error.message,
+ vendorStatusCode: cls.vendorStatusCode,
+ code: cls.errorCode,
+ friendly: cls.friendlyMessage,
+ stack: error.stack,
+ jobid: rid
+ })
+ );
try {
if (!bodyshop || !job) {
@@ -1707,14 +1734,19 @@ const registerRREvents = ({ socket, redisHelpers }) => {
}
} catch (error) {
const cls = classifyRRVendorError(error);
- CreateRRLogEvent(socket, "ERROR", `Error during RR finalize`, {
- error: error.message,
- vendorStatusCode: cls.vendorStatusCode,
- code: cls.errorCode,
- friendly: cls.friendlyMessage,
- stack: error.stack,
- jobid: rid
- });
+ CreateRRLogEvent(
+ socket,
+ "ERROR",
+ `Error during RR finalize`,
+ withRRRequestXml(error, {
+ error: error.message,
+ vendorStatusCode: cls.vendorStatusCode,
+ code: cls.errorCode,
+ friendly: cls.friendlyMessage,
+ stack: error.stack,
+ jobid: rid
+ })
+ );
try {
if (!bodyshop || !job) {
diff --git a/server/rr/rr-service-vehicles.js b/server/rr/rr-service-vehicles.js
index 597a2ea90..72c9bd559 100644
--- a/server/rr/rr-service-vehicles.js
+++ b/server/rr/rr-service-vehicles.js
@@ -1,5 +1,6 @@
const { buildClientAndOpts, rrCombinedSearch } = require("./rr-lookup");
const CreateRRLogEvent = require("./rr-logger-event");
+const { withRRRequestXml } = require("./rr-log-xml");
/**
* Pick and normalize VIN from inputs
* @param vin
@@ -168,9 +169,12 @@ const ensureRRServiceVehicle = async (args = {}) => {
if (bodyshop) {
const combinedSearchResponse = await rrCombinedSearch(bodyshop, { kind: "vin", vin: vinStr, maxResults: 50 });
- CreateRRLogEvent(socket, "silly", "{SV} Preflight combined search by VIN: raw response", {
- response: combinedSearchResponse
- });
+ CreateRRLogEvent(
+ socket,
+ "silly",
+ "{SV} Preflight combined search by VIN: raw response",
+ withRRRequestXml(combinedSearchResponse, { response: combinedSearchResponse })
+ );
owners = ownersFromCombined(combinedSearchResponse, vinStr);
}
@@ -194,10 +198,15 @@ const ensureRRServiceVehicle = async (args = {}) => {
}
} catch (e) {
// Preflight shouldn't be fatal; log and continue to insert (idempotency will still be handled)
- CreateRRLogEvent(socket, "warn", "{SV} VIN preflight lookup failed; continuing to insert", {
- vin: vinStr,
- error: e?.message
- });
+ CreateRRLogEvent(
+ socket,
+ "warn",
+ "{SV} VIN preflight lookup failed; continuing to insert",
+ withRRRequestXml(e, {
+ vin: vinStr,
+ error: e?.message
+ })
+ );
}
// Vendor says: MODEL DESCRIPTION HAS MAXIMUM LENGTH OF 20
@@ -271,7 +280,7 @@ const ensureRRServiceVehicle = async (args = {}) => {
try {
const res = await client.insertServiceVehicle(insertPayload, insertOpts);
- CreateRRLogEvent(socket, "silly", "{SV} insertServiceVehicle: raw response", { res });
+ CreateRRLogEvent(socket, "silly", "{SV} insertServiceVehicle: raw response", withRRRequestXml(res, { res }));
const data = res?.data ?? {};
const svId = data?.dmsRecKey || data?.svId || undefined;
@@ -309,11 +318,16 @@ const ensureRRServiceVehicle = async (args = {}) => {
};
}
- CreateRRLogEvent(socket, "error", "{SV} insertServiceVehicle: failure", {
- message: e?.message,
- code: e?.code,
- status: e?.meta?.status || e?.status
- });
+ CreateRRLogEvent(
+ socket,
+ "error",
+ "{SV} insertServiceVehicle: failure",
+ withRRRequestXml(e, {
+ message: e?.message,
+ code: e?.code,
+ status: e?.meta?.status || e?.status
+ })
+ );
throw e;
}