184
client/src/App/App.container.backup-2026-03-04.jsx
Normal file
184
client/src/App/App.container.backup-2026-03-04.jsx
Normal file
@@ -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 (
|
||||
<CookiesProvider>
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
input={antdInput}
|
||||
locale={enLocale}
|
||||
theme={theme}
|
||||
form={antdForm}
|
||||
table={antdTable}
|
||||
pagination={antdPagination}
|
||||
componentSize={isPhone ? "small" : isUltraWide ? "large" : "middle"}
|
||||
popupOverflow="viewport"
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<SplitFactoryProvider config={config}>
|
||||
<SplitClientProvider>
|
||||
<App />
|
||||
</SplitClientProvider>
|
||||
</SplitFactoryProvider>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
</CookiesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sentry.withProfiler(AppContainer);
|
||||
@@ -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 (
|
||||
<CookiesProvider>
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
input={antdInput}
|
||||
locale={enLocale}
|
||||
theme={theme}
|
||||
form={antdForm}
|
||||
table={antdTable}
|
||||
pagination={antdPagination}
|
||||
componentSize={isPhone ? "small" : isUltraWide ? "large" : "middle"}
|
||||
popupOverflow="viewport"
|
||||
>
|
||||
<ConfigProvider input={antdInput} locale={enLocale} theme={theme} form={antdForm}>
|
||||
<GlobalLoadingBar />
|
||||
<SplitFactoryProvider config={config}>
|
||||
<SplitClientProvider>
|
||||
|
||||
184
client/src/App/App.container.pre-rollback-2026-03-04.jsx
Normal file
184
client/src/App/App.container.pre-rollback-2026-03-04.jsx
Normal file
@@ -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 (
|
||||
<CookiesProvider>
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
input={antdInput}
|
||||
locale={enLocale}
|
||||
theme={theme}
|
||||
form={antdForm}
|
||||
table={antdTable}
|
||||
pagination={antdPagination}
|
||||
componentSize={isPhone ? "small" : isUltraWide ? "large" : "middle"}
|
||||
popupOverflow="viewport"
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<SplitFactoryProvider config={config}>
|
||||
<SplitClientProvider>
|
||||
<App />
|
||||
</SplitClientProvider>
|
||||
</SplitFactoryProvider>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
</CookiesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sentry.withProfiler(AppContainer);
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
<Table
|
||||
className={resolvedClassName}
|
||||
columns={resolvedColumns}
|
||||
scroll={resolvedScroll}
|
||||
tableLayout={resolvedTableLayout}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -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 (
|
||||
<Table
|
||||
className={resolvedClassName}
|
||||
columns={resolvedColumns}
|
||||
scroll={resolvedScroll}
|
||||
tableLayout={resolvedTableLayout}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
function ResponsiveTable(props) {
|
||||
return <Table {...props} />;
|
||||
}
|
||||
|
||||
ResponsiveTable.Summary = Table.Summary;
|
||||
|
||||
@@ -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 <LoadingSkeleton />;
|
||||
|
||||
@@ -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 ?? "?"}${
|
||||
|
||||
@@ -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 } : {})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
63
server/rr/rr-log-xml.js
Normal file
63
server/rr/rr-log-xml.js
Normal file
@@ -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
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user