@@ -14,3 +14,5 @@ VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
TEST_USERNAME="test@imex.dev"
|
||||
TEST_PASSWORD="test123"
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
@@ -16,3 +16,5 @@ VITE_APP_COUNTRY=USA
|
||||
VITE_APP_INSTANCE=ROME
|
||||
TEST_USERNAME="test@imex.dev"
|
||||
TEST_PASSWORD="test123"
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
@@ -13,3 +13,5 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
@@ -13,3 +13,5 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||
VITE_APP_INSTANCE=ROME
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
@@ -13,3 +13,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
||||
VITE_APP_IS_TEST=true
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
@@ -13,3 +13,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
||||
VITE_APP_IS_TEST=true
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=ROME
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
2968
client/package-lock.json
generated
2968
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-browser": "^2.23.1",
|
||||
"@ant-design/pro-layout": "^7.22.6",
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
@@ -38,16 +39,17 @@
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"graphql": "^16.11.0",
|
||||
"i18next": "^25.3.6",
|
||||
"i18next": "^25.4.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.12.12",
|
||||
"libphonenumber-js": "^1.12.13",
|
||||
"logrocket": "^9.0.2",
|
||||
"markerjs2": "^2.32.6",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.2",
|
||||
"object-hash": "^3.0.0",
|
||||
"phone": "^3.1.67",
|
||||
"posthog-js": "^1.260.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.2.2",
|
||||
"raf-schd": "^4.0.3",
|
||||
@@ -59,7 +61,7 @@
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^15.6.1",
|
||||
"react-i18next": "^15.7.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -137,10 +139,10 @@
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@sentry/webpack-plugin": "^4.1.1",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"browserslist": "^4.25.3",
|
||||
@@ -150,9 +152,10 @@
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^15.15.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"lightningcss": "^1.30.1",
|
||||
"memfs": "^4.36.3",
|
||||
"os-browserify": "^0.3.0",
|
||||
"playwright": "^1.54.2",
|
||||
"playwright": "^1.55.0",
|
||||
"react-error-overlay": "^6.1.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
@@ -160,7 +163,7 @@
|
||||
"vite-plugin-babel": "^1.3.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vite-plugin-pwa": "^1.0.2",
|
||||
"vite-plugin-pwa": "^1.0.3",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"vitest": "^3.2.4",
|
||||
"workbox-window": "^7.3.0"
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } 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 { connect, useSelector } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
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 client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import getTheme from "./themeProvider";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentUser } from "../redux/user/user.selectors.js";
|
||||
import { selectDarkMode } from "../redux/application/application.selectors";
|
||||
import { setDarkMode } from "../redux/application/application.actions";
|
||||
|
||||
// Base Split configuration
|
||||
const config = {
|
||||
@@ -85,7 +85,7 @@ function AppContainer({ currentUser, setDarkMode }) {
|
||||
theme={theme}
|
||||
form={{
|
||||
validateMessages: {
|
||||
required: t("general.validation.required", { label: "{{label}}" })
|
||||
required: t("general.validation.required", { label: "${label}" })
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -22,9 +22,9 @@ export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["OpenSearch"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
splitKey: bodyshop?.imexshopid
|
||||
});
|
||||
// TODO - Client Update - Technically key is not doing anything here
|
||||
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
@@ -35,7 +35,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
|
||||
key: "home",
|
||||
title: (
|
||||
<Link to={isPartsEntry ? `/parts/` : `/manage/`}>
|
||||
<HomeFilled /> {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || ""}
|
||||
<HomeFilled /> {(bodyshop?.shopname && `(${bodyshop.shopname})`) || ""}
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -4,13 +4,8 @@ import parsePhoneNumber from "libphonenumber-js";
|
||||
import { forwardRef } from "react";
|
||||
import "./phone-form-item.styles.scss";
|
||||
|
||||
function FormItemPhone(props) {
|
||||
return (
|
||||
<Input
|
||||
// country="ca" ref={ref} className="ant-input"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
function FormItemPhone(props, ref) {
|
||||
return <Input ref={ref} {...props} />;
|
||||
}
|
||||
|
||||
export default forwardRef(FormItemPhone);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
|
||||
import { Button, Layout, Space } from "antd";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -18,17 +17,6 @@ const mapStateToProps = createStructuredSelector({
|
||||
export function GlobalFooter({ isPartsEntry }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
// Canny Not Required on Parts Entry
|
||||
if (isPartsEntry) return;
|
||||
window.Canny("initChangelog", {
|
||||
appID: "680bd2c7ee501290377f6686",
|
||||
position: "top",
|
||||
align: "left",
|
||||
theme: "light" // options: light [default], dark, auto
|
||||
});
|
||||
}, [isPartsEntry]);
|
||||
|
||||
if (isPartsEntry) {
|
||||
return (
|
||||
<Footer>
|
||||
|
||||
@@ -8,19 +8,20 @@ import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectIsPartsEntry, selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly
|
||||
jobRO: selectJobReadOnly,
|
||||
isPartsEntry: selectIsPartsEntry
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail, isPartsEntry }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [availableStatuses, setAvailableStatuses] = useState([]);
|
||||
@@ -45,25 +46,43 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
});
|
||||
};
|
||||
|
||||
// Updates available statuses based on job and bodyshop context
|
||||
useEffect(() => {
|
||||
//Figure out what scenario were in, populate accodingly
|
||||
if (job && bodyshop) {
|
||||
if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(bodyshop.md_ro_statuses.pre_production_statuses);
|
||||
} else if (bodyshop.md_ro_statuses.production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(bodyshop.md_ro_statuses.production_statuses);
|
||||
} else if (bodyshop.md_ro_statuses.post_production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(
|
||||
bodyshop.md_ro_statuses.post_production_statuses.filter(
|
||||
(s) => s !== bodyshop.md_ro_statuses.default_invoiced && s !== bodyshop.md_ro_statuses.default_exported
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log("Status didn't match any restrictions. Allowing all status changes.");
|
||||
setAvailableStatuses(bodyshop.md_ro_statuses.statuses);
|
||||
}
|
||||
if (!job || !bodyshop) return;
|
||||
|
||||
const { md_ro_statuses } = bodyshop;
|
||||
const {
|
||||
parts_statuses,
|
||||
pre_production_statuses,
|
||||
production_statuses,
|
||||
post_production_statuses,
|
||||
statuses,
|
||||
default_invoiced,
|
||||
default_exported
|
||||
} = md_ro_statuses;
|
||||
|
||||
if (isPartsEntry) {
|
||||
// Set parts-specific statuses for parts entry scenario
|
||||
setAvailableStatuses(parts_statuses);
|
||||
return;
|
||||
}
|
||||
}, [job, setAvailableStatuses, bodyshop]);
|
||||
|
||||
// Handle non-parts entry scenarios based on job status
|
||||
if (pre_production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(pre_production_statuses);
|
||||
} else if (production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(production_statuses);
|
||||
} else if (post_production_statuses.includes(job.status)) {
|
||||
// Filter out invoiced and exported statuses for post-production
|
||||
setAvailableStatuses(
|
||||
post_production_statuses.filter((status) => status !== default_invoiced && status !== default_exported)
|
||||
);
|
||||
} else {
|
||||
// Default to all statuses if no specific restrictions apply
|
||||
console.log("Status didn't match any restrictions. Allowing all status changes.");
|
||||
setAvailableStatuses(statuses);
|
||||
}
|
||||
}, [job, bodyshop, isPartsEntry, setAvailableStatuses]);
|
||||
|
||||
const statusMenu = {
|
||||
items: [
|
||||
|
||||
@@ -37,7 +37,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
.filter(
|
||||
(temp) =>
|
||||
(!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
temp.regions?.[bodyshop.region_config] ||
|
||||
(temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)) &&
|
||||
(!temp.dms || temp.dms === false)
|
||||
)
|
||||
@@ -46,7 +46,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
.filter(
|
||||
(temp) =>
|
||||
!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
temp.regions?.[bodyshop.region_config] ||
|
||||
(temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
|
||||
);
|
||||
|
||||
@@ -82,7 +82,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
variables: { id: jobId }
|
||||
},
|
||||
{
|
||||
to: job && job.ownr_ea,
|
||||
to: job?.ownr_ea,
|
||||
subject: cards.find((c) => c.key === key)?.subject
|
||||
},
|
||||
"e",
|
||||
@@ -129,7 +129,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
const columns = `repeat(${actions.length}, 1fr)`;
|
||||
|
||||
return (
|
||||
<Col key={item.key} xs={24} sm={12}>
|
||||
<Col key={item.key} xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||
<Card hoverable style={{ minHeight: 100 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<div style={{ flex: "1 1 70%", minWidth: 0 }}>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function ShopInfoContainer() {
|
||||
onFinish={handleFinish}
|
||||
initialValues={
|
||||
data
|
||||
? data.bodyshops[0].accountingconfig.ClosingPeriod
|
||||
? data?.bodyshops?.[0]?.accountingconfig?.ClosingPeriod
|
||||
? {
|
||||
...data.bodyshops[0],
|
||||
accountingconfig: {
|
||||
|
||||
@@ -96,13 +96,15 @@ export function SimplifiedPartsJobsListComponent({
|
||||
key: "status",
|
||||
|
||||
ellipsis: true,
|
||||
sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
|
||||
sorter: search?.search
|
||||
? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.parts_active_statuses)
|
||||
: true,
|
||||
sortOrder: sortcolumn === "status" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.status || t("general.labels.na");
|
||||
},
|
||||
filteredValue: filter?.status || null,
|
||||
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
|
||||
filters: bodyshop.md_ro_statuses.parts_statuses.map((s) => {
|
||||
return { text: s, value: [s] };
|
||||
}),
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
|
||||
@@ -167,7 +167,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
socketRef.current = socketInstance;
|
||||
|
||||
const handleBodyshopMessage = (message) => {
|
||||
if (!message || !message.type) return;
|
||||
if (!message?.type) return;
|
||||
switch (message.type) {
|
||||
case "alert-update":
|
||||
store.dispatch(addAlerts(message.payload));
|
||||
@@ -512,21 +512,20 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
};
|
||||
|
||||
const unsubscribe = auth.onIdTokenChanged(async (user) => {
|
||||
if (user) {
|
||||
const token = await user.getIdToken();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} else {
|
||||
initializeSocket(token).catch((err) =>
|
||||
console.error(`Something went wrong Initializing Sockets: ${err?.message || ""}`)
|
||||
);
|
||||
}
|
||||
if (!user) {
|
||||
socketRef.current?.disconnect();
|
||||
socketRef.current = null;
|
||||
setIsConnected(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await user.getIdToken();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} else {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
setIsConnected(false);
|
||||
}
|
||||
initializeSocket(token).catch((err) =>
|
||||
console.error("Something went wrong Initializing Sockets:", err?.message || "")
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { getAuth, updatePassword, updateProfile } from "@firebase/auth";
|
||||
import { getFirestore } from "@firebase/firestore";
|
||||
import { getMessaging, getToken, onMessage } from "@firebase/messaging";
|
||||
import { store } from "../redux/store";
|
||||
import * as amplitude from '@amplitude/analytics-browser';
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
||||
initializeApp(config);
|
||||
@@ -71,25 +73,33 @@ onMessage(messaging, (payload) => {
|
||||
});
|
||||
|
||||
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||
const state = stateProp || store.getState();
|
||||
const eventParams = {
|
||||
shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
|
||||
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
...additionalParams
|
||||
};
|
||||
// axios.post("/ioevent", {
|
||||
// useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
// bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
|
||||
// operationName: eventName,
|
||||
// variables: additionalParams,
|
||||
// dbevent: false,
|
||||
// env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
|
||||
// });
|
||||
// console.log(
|
||||
// "%c[Analytics]",
|
||||
// "background-color: green ;font-weight:bold;",
|
||||
// eventName,
|
||||
// eventParams
|
||||
// );
|
||||
logEvent(analytics, eventName, eventParams);
|
||||
try {
|
||||
|
||||
const state = stateProp || store.getState();
|
||||
const eventParams = {
|
||||
shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
|
||||
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
...additionalParams
|
||||
};
|
||||
// axios.post("/ioevent", {
|
||||
// useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
// bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
|
||||
// operationName: eventName,
|
||||
// variables: additionalParams,
|
||||
// dbevent: false,
|
||||
// env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
|
||||
// });
|
||||
// console.log(
|
||||
// "%c[Analytics]",
|
||||
// "background-color: green ;font-weight:bold;",
|
||||
// eventName,
|
||||
// eventParams
|
||||
// );
|
||||
logEvent(analytics, eventName, eventParams);
|
||||
amplitude.track(eventName, eventParams);
|
||||
posthog.capture(eventName, eventParams);
|
||||
|
||||
} finally {
|
||||
//If it fails, just keep going.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@ import { persistor, store } from "./redux/store";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import "./translations/i18n";
|
||||
import "./utils/CleanAxios";
|
||||
import * as amplitude from "@amplitude/analytics-browser";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
|
||||
window.global ||= window;
|
||||
|
||||
@@ -23,10 +25,10 @@ registerSW({ immediate: true });
|
||||
// Dinero.globalLocale = "en-CA";
|
||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
|
||||
amplitude.init("6228a598e57cd66875cfd41604f1f891", {});
|
||||
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);
|
||||
|
||||
const router = sentryCreateBrowserRouter(createRoutesFromElements(<Route path="*" element={<AppContainer />} />));
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
let styles =
|
||||
"font-weight: bold; font-size: 50px;color: red; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) ";
|
||||
@@ -37,7 +39,12 @@ function App() {
|
||||
return (
|
||||
<PersistGate loading={<LoadingSpinner message="Restoring your settings..." />} persistor={persistor}>
|
||||
<Provider store={store}>
|
||||
<RouterProvider router={router} />
|
||||
<PostHogProvider
|
||||
apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_KEY}
|
||||
options={{ autocapture: false, capture_exceptions: true }}
|
||||
>
|
||||
<RouterProvider router={router} />
|
||||
</PostHogProvider>
|
||||
</Provider>
|
||||
</PersistGate>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FloatButton, Layout, Spin } from "antd";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
|
||||
// import preval from "preval.macro";
|
||||
import { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { lazy, Suspense, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -23,6 +23,7 @@ import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import { selectBodyshop, selectInstanceConflict, selectPartsManagementOnly } from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import useAlertsNotifications from "../../hooks/useAlertsNotifications.jsx";
|
||||
import { selectDarkMode } from "../../redux/application/application.selectors.js";
|
||||
|
||||
const PrintCenterModalContainer = lazy(
|
||||
() => import("../../components/print-center-modal/print-center-modal.container")
|
||||
@@ -107,22 +108,26 @@ const { Content } = Layout;
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
conflict: selectInstanceConflict,
|
||||
bodyshop: selectBodyshop,
|
||||
partsManagementOnly: selectPartsManagementOnly
|
||||
partsManagementOnly: selectPartsManagementOnly,
|
||||
isDarkMode: selectDarkMode
|
||||
});
|
||||
|
||||
export function Manage({ conflict, bodyshop, partsManagementOnly }) {
|
||||
export function Manage({ conflict, bodyshop, partsManagementOnly, isDarkMode }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
const didMount = useRef(false);
|
||||
|
||||
// Centralized alerts handling (fetch + dedupe + notifications)
|
||||
useAlertsNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (didMount.current) return; // prevents dev StrictMode double-run
|
||||
didMount.current = true;
|
||||
window.Canny("initChangelog", {
|
||||
appID: "680bd2c7ee501290377f6686",
|
||||
position: "top",
|
||||
align: "left",
|
||||
theme: "light" // options: light [default], dark, auto
|
||||
theme: !isDarkMode ? "light" : "dark"
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -60,19 +60,19 @@ function SimplifiedPartsJobsDetailContainer({ setBreadcrumbs, addRecentItem, set
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)"
|
||||
}),
|
||||
ro_number: (data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
|
||||
ro_number: data.jobs_by_pk?.ro_number || t("general.labels.na")
|
||||
});
|
||||
setBreadcrumbs([
|
||||
{ link: "/parts/", label: t("titles.bc.jobs") },
|
||||
{ link: "/parts", label: t("titles.bc.parts") },
|
||||
{
|
||||
link: `/parts/jobs/${jobId}`,
|
||||
label: t("titles.bc.jobs-detail", {
|
||||
number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
|
||||
number: (data?.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
|
||||
})
|
||||
}
|
||||
]);
|
||||
|
||||
if (data && data.jobs_by_pk) {
|
||||
if (data?.jobs_by_pk) {
|
||||
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
|
||||
|
||||
addRecentItem(
|
||||
|
||||
@@ -22,7 +22,7 @@ export function SimplifiedPartsJobsPage({ setBreadcrumbs, setSelectedHeader }) {
|
||||
})
|
||||
});
|
||||
setSelectedHeader("parts-queue");
|
||||
setBreadcrumbs([{ link: "/parts", label: t("titles.bc.simplified-parts-jobs") }]);
|
||||
setBreadcrumbs([{ link: "/parts", label: t("titles.bc.parts") }]);
|
||||
}, [setBreadcrumbs, t, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FloatButton, Layout, Spin } from "antd";
|
||||
import { lazy, Suspense, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component.jsx";
|
||||
import ConflictComponent from "../../components/conflict/conflict.component.jsx";
|
||||
@@ -39,6 +39,15 @@ const mapStateToProps = createStructuredSelector({
|
||||
export function SimplifiedPartsPage({ conflict, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Redirector to strip '/parts/jobs' from path for non-detail routes
|
||||
function JobsStripRedirect() {
|
||||
// lazy import to avoid top-level import churn
|
||||
const location = useLocation();
|
||||
const { pathname, search, hash } = location;
|
||||
const target = pathname.replace("/parts/jobs", "/parts") + (search || "") + (hash || "");
|
||||
return <Navigate to={target} replace />;
|
||||
}
|
||||
|
||||
// Centralized alerts handling (fetch + dedupe + notifications)
|
||||
useAlertsNotifications();
|
||||
|
||||
@@ -67,6 +76,10 @@ export function SimplifiedPartsPage({ conflict, bodyshop }) {
|
||||
<EmailOverlayContainer />
|
||||
<PrintCenterModalContainer />
|
||||
<Routes>
|
||||
{/* Redirect legacy or relative routes that include '/jobs' segment */}
|
||||
<Route path="jobs" element={<JobsStripRedirect />} />
|
||||
<Route path="jobs/*" element={<JobsStripRedirect />} />
|
||||
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
|
||||
@@ -42,33 +42,31 @@ export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelec
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)"
|
||||
}),
|
||||
vehicle:
|
||||
data && data.vehicles_by_pk
|
||||
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
|
||||
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
|
||||
} ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}`
|
||||
: ""
|
||||
vehicle: data?.vehicles_by_pk
|
||||
? `${data.vehicles_by_pk?.v_model_yr || ""} ${
|
||||
data.vehicles_by_pk?.v_make_desc || ""
|
||||
} ${data.vehicles_by_pk?.v_model_desc || ""}`
|
||||
: ""
|
||||
});
|
||||
setSelectedHeader("vehicles");
|
||||
|
||||
const crumbs = [];
|
||||
if (isPartsEntry) crumbs.push({ link: "/parts/", label: t("titles.bc.jobs") });
|
||||
if (isPartsEntry) crumbs.push({ link: "/parts", label: t("titles.bc.parts") });
|
||||
crumbs.push({ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") });
|
||||
crumbs.push({
|
||||
link: `${basePath}/vehicles/${vehId}`,
|
||||
label: t("titles.bc.vehicle-details", {
|
||||
vehicle:
|
||||
data && data.vehicles_by_pk
|
||||
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
|
||||
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
|
||||
} ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}`
|
||||
: ""
|
||||
vehicle: data?.vehicles_by_pk
|
||||
? `${data.vehicles_by_pk?.v_model_yr || ""} ${
|
||||
data.vehicles_by_pk?.v_make_desc || ""
|
||||
} ${data.vehicles_by_pk?.v_model_desc || ""}`
|
||||
: ""
|
||||
})
|
||||
});
|
||||
|
||||
setBreadcrumbs(crumbs);
|
||||
|
||||
if (data && data.vehicles_by_pk)
|
||||
if (data?.vehicles_by_pk)
|
||||
addRecentItem(
|
||||
CreateRecentItem(
|
||||
vehId,
|
||||
|
||||
@@ -33,7 +33,7 @@ export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader, isPar
|
||||
|
||||
if (isPartsEntry) {
|
||||
setBreadcrumbs([
|
||||
{ link: "/parts/", label: t("titles.bc.jobs") },
|
||||
{ link: "/parts", label: t("titles.bc.parts") },
|
||||
{ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") }
|
||||
]);
|
||||
} else {
|
||||
|
||||
@@ -49,6 +49,8 @@ import {
|
||||
validatePasswordResetSuccess
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import * as amplitude from '@amplitude/analytics-browser';
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
const fpPromise = FingerprintJS.load();
|
||||
|
||||
@@ -83,8 +85,6 @@ export function* onCheckUserSession() {
|
||||
|
||||
export function* isUserAuthenticated() {
|
||||
try {
|
||||
logImEXEvent("redux_auth_check");
|
||||
|
||||
const user = yield getCurrentUser();
|
||||
if (!user) {
|
||||
yield put(unauthorizedUser());
|
||||
@@ -92,6 +92,8 @@ export function* isUserAuthenticated() {
|
||||
}
|
||||
|
||||
LogRocket.identify(user.email);
|
||||
amplitude.setUserId(user.email);
|
||||
posthog.identify(user.email);
|
||||
|
||||
const eulaQuery = yield client.query({
|
||||
query: QUERY_EULA,
|
||||
@@ -137,6 +139,7 @@ export function* signOutStart() {
|
||||
imexshopid: state.user.bodyshop.imexshopid,
|
||||
type: "messaging"
|
||||
});
|
||||
amplitude.reset();
|
||||
} catch {
|
||||
console.log("No FCM token. Skipping unsubscribe.");
|
||||
}
|
||||
@@ -266,11 +269,11 @@ export function* signInSuccessSaga({ payload }) {
|
||||
instanceSeg,
|
||||
...(isParts
|
||||
? [
|
||||
InstanceRenderManager({
|
||||
imex: "ImexPartsManagement",
|
||||
rome: "RomePartsManagement"
|
||||
})
|
||||
]
|
||||
InstanceRenderManager({
|
||||
imex: "ImexPartsManagement",
|
||||
rome: "RomePartsManagement"
|
||||
})
|
||||
]
|
||||
: [])
|
||||
];
|
||||
window.$crisp.push(["set", "session:segments", [segs]]);
|
||||
@@ -295,7 +298,6 @@ export function* signInSuccessSaga({ payload }) {
|
||||
|
||||
setUserId(analytics, payload.email);
|
||||
setUserProperties(analytics, payload);
|
||||
yield logImEXEvent("redux_sign_in_success");
|
||||
}
|
||||
|
||||
export function* onSendPasswordResetStart() {
|
||||
@@ -362,6 +364,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
}
|
||||
|
||||
try {
|
||||
amplitude.setGroup('Shop', payload.shopname);
|
||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
|
||||
@@ -3523,6 +3523,8 @@
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"parts_settings": "Parts Management Settings | {{app}}",
|
||||
"simplified-parts-jobs": "Parts Management | {{app}}",
|
||||
"accounting-payables": "Payables | {{app}}",
|
||||
"accounting-payments": "Payments | {{app}}",
|
||||
"accounting-receivables": "Receivables | {{app}}",
|
||||
@@ -3530,7 +3532,7 @@
|
||||
"app": "",
|
||||
"bc": {
|
||||
"simplified-parts-jobs": "Jobs",
|
||||
"parts": "Jobs",
|
||||
"parts": "Parts",
|
||||
"parts_settings": "Settings",
|
||||
"accounting-payables": "Payables",
|
||||
"accounting-payments": "Payments",
|
||||
|
||||
@@ -3523,6 +3523,9 @@
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"simplified-parts-jobs": "",
|
||||
"parts": "",
|
||||
"parts_settings": "",
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
|
||||
@@ -3523,6 +3523,9 @@
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"simplified-parts-jobs": "",
|
||||
"parts": "",
|
||||
"parts_settings": "",
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import chalk from "chalk";
|
||||
@@ -10,21 +9,23 @@ import { ViteEjsPlugin } from "vite-plugin-ejs";
|
||||
import eslint from "vite-plugin-eslint";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
|
||||
import browserslist from "browserslist";
|
||||
import { browserslistToTargets } from "lightningcss";
|
||||
|
||||
// Ensure your environment variables are set correctly for Vite 6
|
||||
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
|
||||
timeZone: "America/Los_Angeles"
|
||||
});
|
||||
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", { timeZone: "America/Los_Angeles" });
|
||||
const commitHash = child.execSync("git rev-parse HEAD").toString().trimEnd();
|
||||
process.env.VITE_GIT_COMMIT_HASH = commitHash;
|
||||
|
||||
const currentDatePST = new Date()
|
||||
.toLocaleDateString("en-US", {
|
||||
timeZone: "America/Los_Angeles",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit"
|
||||
// Resolve browserslist from package.json and map to Lightning CSS targets
|
||||
const lightningCssTargets = browserslistToTargets(
|
||||
browserslist(undefined, {
|
||||
path: process.cwd(),
|
||||
env: process.env.NODE_ENV === "production" ? "production" : "development"
|
||||
})
|
||||
);
|
||||
|
||||
const currentDatePST = new Date()
|
||||
.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles", year: "numeric", month: "2-digit", day: "2-digit" })
|
||||
.split("/")
|
||||
.reverse()
|
||||
.join("-");
|
||||
@@ -32,18 +33,21 @@ const currentDatePST = new Date()
|
||||
const getFormattedTimestamp = () =>
|
||||
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
|
||||
|
||||
export const logger = createLogger("info", {
|
||||
allowClearScreen: false
|
||||
});
|
||||
export const logger = createLogger("info", { allowClearScreen: false });
|
||||
|
||||
export default defineConfig({
|
||||
base: "/",
|
||||
plugins: [
|
||||
// Ensure all plugins are Vite 6 compatible
|
||||
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
|
||||
|
||||
// PWA only for production builds (faster dev)
|
||||
VitePWA({
|
||||
apply: "build",
|
||||
injectRegister: "auto",
|
||||
registerType: "prompt",
|
||||
workbox: {
|
||||
navigateFallbackDenylist: [/^\/api\//] // prevent caching API routes
|
||||
},
|
||||
manifest: {
|
||||
short_name: InstanceRenderManager({
|
||||
instance: process.env.VITE_APP_INSTANCE,
|
||||
@@ -94,13 +98,15 @@ export default defineConfig({
|
||||
gcm_sender_id: "103953800507"
|
||||
}
|
||||
}),
|
||||
|
||||
react(),
|
||||
eslint(),
|
||||
|
||||
// Sentry only for production builds (no dev overhead)
|
||||
sentryVitePlugin({
|
||||
apply: "build",
|
||||
org: "imex",
|
||||
reactComponentAnnotation: {
|
||||
enabled: true
|
||||
},
|
||||
reactComponentAnnotation: { enabled: true },
|
||||
release: {
|
||||
name: `${process.env.VITE_APP_IS_TEST ? "test" : "production"}-${currentDatePST}-${commitHash}`.trim()
|
||||
},
|
||||
@@ -111,10 +117,12 @@ export default defineConfig({
|
||||
})
|
||||
})
|
||||
],
|
||||
|
||||
define: {
|
||||
APP_VERSION: JSON.stringify(process.env.npm_package_version),
|
||||
__COMMIT_HASH__: JSON.stringify(commitHash)
|
||||
},
|
||||
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000,
|
||||
@@ -122,13 +130,11 @@ export default defineConfig({
|
||||
proxy: {
|
||||
"/ws": {
|
||||
target: "ws://localhost:4000",
|
||||
rewriteWsOrigin: true,
|
||||
secure: false,
|
||||
ws: true
|
||||
},
|
||||
"/wss": {
|
||||
target: "ws://localhost:4000",
|
||||
rewriteWsOrigin: true,
|
||||
secure: false,
|
||||
ws: true
|
||||
},
|
||||
@@ -148,18 +154,17 @@ export default defineConfig({
|
||||
},
|
||||
https: {
|
||||
key: await fsPromises.readFile("../certs/key.pem"),
|
||||
cert: await fsPromises.readFile("../certs/cert.pem"),
|
||||
allowHTTP1: false // Force HTTP/2
|
||||
cert: await fsPromises.readFile("../certs/cert.pem")
|
||||
}
|
||||
},
|
||||
|
||||
preview: {
|
||||
port: 6000,
|
||||
host: true,
|
||||
open: true,
|
||||
https: {
|
||||
key: await fsPromises.readFile("../certs/key.pem"),
|
||||
cert: await fsPromises.readFile("../certs/cert.pem"),
|
||||
allowHTTP1: false // Force HTTP/2
|
||||
cert: await fsPromises.readFile("../certs/cert.pem")
|
||||
},
|
||||
proxy: {
|
||||
"/ws": {
|
||||
@@ -182,7 +187,10 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
build: {
|
||||
sourcemap: true,
|
||||
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
@@ -207,8 +215,14 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
|
||||
sourcemap: true
|
||||
cssMinify: "lightningcss"
|
||||
},
|
||||
|
||||
// Strip console/debugger in prod to shrink bundles
|
||||
esbuild: {
|
||||
drop: ["console", "debugger"]
|
||||
},
|
||||
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
"react",
|
||||
@@ -231,19 +245,17 @@ export default defineConfig({
|
||||
"@firebase/util"
|
||||
],
|
||||
esbuildOptions: {
|
||||
// Update for Vite 6: Use proper file extensions
|
||||
loader: {
|
||||
".jsx": "jsx",
|
||||
".tsx": "tsx"
|
||||
}
|
||||
loader: { ".jsx": "jsx", ".tsx": "tsx" }
|
||||
}
|
||||
},
|
||||
|
||||
css: {
|
||||
transformer: "lightningcss",
|
||||
lightningcss: {
|
||||
targets: lightningCssTargets
|
||||
},
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: "modern-compiler",
|
||||
quietDeps: true // Quite Deprecation Warnings, should be disabled occasionally before major upgrades
|
||||
}
|
||||
scss: { quietDeps: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
2965
package-lock.json
generated
2965
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -18,14 +18,14 @@
|
||||
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.864.0",
|
||||
"@aws-sdk/client-elasticache": "^3.864.0",
|
||||
"@aws-sdk/client-s3": "^3.864.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.864.0",
|
||||
"@aws-sdk/client-ses": "^3.864.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.864.0",
|
||||
"@aws-sdk/lib-storage": "^3.864.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.864.0",
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.873.0",
|
||||
"@aws-sdk/client-elasticache": "^3.872.0",
|
||||
"@aws-sdk/client-s3": "^3.872.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.872.0",
|
||||
"@aws-sdk/client-ses": "^3.872.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.873.0",
|
||||
"@aws-sdk/lib-storage": "^3.872.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.872.0",
|
||||
"@opensearch-project/opensearch": "^2.13.0",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
@@ -41,7 +41,7 @@
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"crisp-status-reporter": "^1.2.2",
|
||||
"dd-trace": "^5.63.1",
|
||||
"dd-trace": "^5.63.3",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.21.1",
|
||||
@@ -63,7 +63,7 @@
|
||||
"query-string": "7.1.3",
|
||||
"recursive-diff": "^1.0.9",
|
||||
"rimraf": "^6.0.1",
|
||||
"skia-canvas": "^2.0.2",
|
||||
"skia-canvas": "^3.0.3",
|
||||
"soap": "^1.3.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-adapter": "^2.5.5",
|
||||
|
||||
@@ -7,3 +7,4 @@ exports.usageReport = require("./usageReport").default;
|
||||
exports.podium = require("./podium").default;
|
||||
exports.emsUpload = require("./emsUpload").default;
|
||||
exports.carfax = require("./carfax").default;
|
||||
exports.vehicletype = require("./vehicletype/vehicletype").default;
|
||||
126
server/data/vehicletype/cargovans.json
Normal file
126
server/data/vehicletype/cargovans.json
Normal file
@@ -0,0 +1,126 @@
|
||||
[
|
||||
"PROMASTER 1500",
|
||||
"PROMASTER 2500",
|
||||
"PROMASTER CITY",
|
||||
"NV 1500",
|
||||
"NV 200",
|
||||
"NV 2500",
|
||||
"NV 3500",
|
||||
"NV1500",
|
||||
"NV200",
|
||||
"NV2500",
|
||||
"NV3500",
|
||||
"SPRINTER",
|
||||
"E150 ECONOLINE CARGO VAN",
|
||||
"E150 ECONOLINE XL",
|
||||
"E250 ECONOLINE CARGO",
|
||||
"E250 ECONOLINE CARGO (AMALGAM)",
|
||||
"E250 ECONOLINE CARGO (INSPECT)",
|
||||
"E250 ECONOLINE CARGO VAN EXT",
|
||||
"E250 ECONOLINE SUPER CARGO VAN",
|
||||
"E350 CUTAWAY VAN",
|
||||
"E350 ECONO SD CARGO VAN EXT",
|
||||
"E350 ECONOLINE CARGO VAN",
|
||||
"E350 ECONOLINE CUTAWAY",
|
||||
"E350 ECONOLINE SD CARGO VAN",
|
||||
"E350 ECONOLINE SD XL",
|
||||
"E350 ECONOLINE SD XL EXT",
|
||||
"E350 ECONOLINE SD XLT",
|
||||
"E350 ECONOLINE SD XLT EXT",
|
||||
"E350 SD CUTAWAY",
|
||||
"E450",
|
||||
"E450 ECONOLINE",
|
||||
"E450 ECONOLINE SD",
|
||||
"E450 ECONOLINE SD CUTAWAY",
|
||||
"TRANSIT 150 WB 130 CARGO VAN",
|
||||
"TRANSIT 150 WB 130 XLT",
|
||||
"TRANSIT 150 WB 148 CARGO VAN",
|
||||
"TRANSIT 250 WB 130 CARGO VAN",
|
||||
"TRANSIT 250 WB 148 CARGO VAN",
|
||||
"TRANSIT 250 WB 148 EL CARGO",
|
||||
"TRANSIT 350 WB 148 CARGO VAN",
|
||||
"TRANSIT 350 WB 148 EL CARGO",
|
||||
"TRANSIT CONNECT XL CARGO VAN",
|
||||
"TRANSIT CONNECT XLT CARGO VAN",
|
||||
"250 TRANSIT",
|
||||
"CITY EXPRESS LS CARGO VAN",
|
||||
"CITY EXPRESS LT CARGO VAN",
|
||||
"EXPRESS 1500",
|
||||
"EXPRESS 1500 CARGO VAN",
|
||||
"EXPRESS 1500 LS",
|
||||
"EXPRESS 1500 LT",
|
||||
"EXPRESS 2500 CARGO VAN",
|
||||
"EXPRESS 2500 CARGO VAN EXT",
|
||||
"EXPRESS 2500 LS",
|
||||
"EXPRESS 2500 LT",
|
||||
"EXPRESS 3500",
|
||||
"EXPRESS 3500 CARGO VAN",
|
||||
"EXPRESS 3500 CARGO VAN EXT",
|
||||
"EXPRESS 3500 EXT",
|
||||
"EXPRESS 3500 LS",
|
||||
"EXPRESS 3500 LS EXT",
|
||||
"EXPRESS 3500 LT",
|
||||
"EXPRESS 3500 LT EXT",
|
||||
"G3500 EXPRESS CUTAWAY",
|
||||
"SAVANA 1500 CARGO VAN",
|
||||
"SAVANA 1500 SL",
|
||||
"SAVANA 1500 SLE",
|
||||
"SAVANA 2500",
|
||||
"2500 SAVANA",
|
||||
"SAVANA 2500 CARGO VAN",
|
||||
"SAVANA 2500 CARGO VAN EXT",
|
||||
"SAVANA 2500 LT",
|
||||
"SAVANA 2500 SLE",
|
||||
"SAVANA 3500",
|
||||
"SAVANA 3500 CARGO VAN",
|
||||
"SAVANA 3500 CARGO VAN EXT",
|
||||
"SAVANA 3500 EXT",
|
||||
"SAVANA 3500 LT EXT",
|
||||
"SAVANA 3500 SLE EXT",
|
||||
"SAVANA G3500 CUTAWAY",
|
||||
"SAVANA G4500 CUTAWAY",
|
||||
"EXPRESS 1500 LS CARGO VAN",
|
||||
"G20 SPORTVAN",
|
||||
"NV 3500 S V8 CARGO VAN",
|
||||
"E-150",
|
||||
"E-250",
|
||||
"E-350",
|
||||
"E-450",
|
||||
"E150",
|
||||
"E250",
|
||||
"E350",
|
||||
"TRANSIT",
|
||||
"CITY",
|
||||
"CITY EXPRESS",
|
||||
"EXPRESS",
|
||||
"EXPRESS 2500",
|
||||
"G3500",
|
||||
"SAVANA",
|
||||
"SAVANA 1500",
|
||||
"CHEVY EXPRESS G2500",
|
||||
"CLUBWAGON E350",
|
||||
"TRANSIT CONNECT",
|
||||
"SPRINTER 2500",
|
||||
"TRANSIT 150",
|
||||
"ECONOLINE E250",
|
||||
"TRANSIT 250",
|
||||
"ECONOLINE E350",
|
||||
"NV3500 HD",
|
||||
"TRANSIT 350HD",
|
||||
"ECONOLINE E150",
|
||||
"E250 ECONOLINE",
|
||||
"C/V",
|
||||
"E350 CHSCAB",
|
||||
"G1500 CHEVY EXPRESS",
|
||||
"2500 SPRINTER",
|
||||
"E150 ECONOLINE",
|
||||
"350 TRANSIT",
|
||||
"E450 CUTAWAY",
|
||||
"PROMASTER 3500",
|
||||
"CHEVY EXPRESS G3500",
|
||||
"SAVANA G3500",
|
||||
"1500 PROMASTER",
|
||||
"2500 EXPRESS",
|
||||
"3500 EXPRESS",
|
||||
"3500 SPRINTER"
|
||||
]
|
||||
33
server/data/vehicletype/passengervans.json
Normal file
33
server/data/vehicletype/passengervans.json
Normal file
@@ -0,0 +1,33 @@
|
||||
[
|
||||
"GRAND CARAVAN",
|
||||
"GRANDCARAVAN",
|
||||
"GRAND CARAVAN CREW",
|
||||
"GRAND CARAVAN CV",
|
||||
"GRAND CARAVAN CVP",
|
||||
"GRAND CARAVAN SE",
|
||||
"GRAND CARAVAN SXT",
|
||||
"CARAVAN CV",
|
||||
"SIENNA CE V6",
|
||||
"SIENNA LE V6",
|
||||
"SIENNA XLE V6",
|
||||
"SIENNA",
|
||||
"ODYSSEY",
|
||||
"SEDONA",
|
||||
"PACIFICA (NEW)",
|
||||
"QUEST",
|
||||
"CARAVAN",
|
||||
"MONTANA SV6",
|
||||
"FREESTAR",
|
||||
"UPLANDER",
|
||||
"MONTANA",
|
||||
"VOYAGER",
|
||||
"ENTOURAGE",
|
||||
"PACIFICA",
|
||||
"CARNIVAL",
|
||||
"VENTURE",
|
||||
"SAFARI",
|
||||
"VANAGON",
|
||||
"WINDSTAR",
|
||||
"TOWN&COUNTRY",
|
||||
"ROUTAN"
|
||||
]
|
||||
485
server/data/vehicletype/suvs.json
Normal file
485
server/data/vehicletype/suvs.json
Normal file
@@ -0,0 +1,485 @@
|
||||
[
|
||||
"EDGE SEL",
|
||||
"ESCAPE",
|
||||
"ESCAPE SE",
|
||||
"ESCAPE SEL",
|
||||
"ESCAPE XLT V6",
|
||||
"EXPEDITION",
|
||||
"EXPEDITION LIMITED",
|
||||
"EXPEDITION MAX",
|
||||
"EXPEDITION MAX LIMITED",
|
||||
"EXPLORER",
|
||||
"EXCURSION",
|
||||
"EXPLORER LIMITED",
|
||||
"EXPLORER PLATINUM ECOBOOST",
|
||||
"EXPLORER XLT",
|
||||
"FLEX",
|
||||
"FLEX SE",
|
||||
"ECOSPORT",
|
||||
"ESCAPE HYBRID",
|
||||
"MUSTANG MACH-E",
|
||||
"BRONCO",
|
||||
"BRONCO SPORT",
|
||||
"TRAILBLAZER",
|
||||
"BLAZER LT",
|
||||
"CHEROKEE",
|
||||
"CHEROKEE CLASSIC",
|
||||
"CHEROKEE COUNTRY",
|
||||
"CHEROKEE LIMITED",
|
||||
"CHEROKEE NORTH",
|
||||
"CHEROKEE OVERLAND",
|
||||
"CHEROKEE SPORT",
|
||||
"CHEROKEE TRAILHAWK",
|
||||
"CJ",
|
||||
"CJ7",
|
||||
"CJ7 RENEGADE",
|
||||
"COMMANDER",
|
||||
"COMMANDER LIMITED",
|
||||
"COMMANDER SPORT",
|
||||
"COMPASS",
|
||||
"COMPASS HIGH ALTITUDE",
|
||||
"COMPASS LATITUDE",
|
||||
"COMPASS LIMITED",
|
||||
"COMPASS NORTH",
|
||||
"COMPASS SPORT",
|
||||
"COMPASS TRAILHAWK",
|
||||
"GLADIATOR OVERLAND",
|
||||
"GLADIATOR RUBICON",
|
||||
"GRAND CHEROKEE LAREDO",
|
||||
"GRAND CHEROKEE LIMITED",
|
||||
"GRAND CHEROKEE OVERLAND",
|
||||
"GRAND CHEROKEE SE",
|
||||
"GRAND CHEROKEE SRT",
|
||||
"GRAND CHEROKEE SRT8",
|
||||
"GRAND CHEROKEE SUMMIT",
|
||||
"GRAND CHEROKEE TRACKHAWK",
|
||||
"GRAND CHEROKEE TRAILHAWK",
|
||||
"GRAND CHEROKEE",
|
||||
"GRANDCHEROKEE",
|
||||
"LIBERTY LIMITED",
|
||||
"LIBERTY RENEGADE",
|
||||
"LIBERTY SPORT",
|
||||
"LIBERTY",
|
||||
"PATRIOT",
|
||||
"PATRIOT HIGH ALTITUDE",
|
||||
"PATRIOT LATITUDE",
|
||||
"PATRIOT LIMITED",
|
||||
"PATRIOT NORTH",
|
||||
"PATRIOT SPORT",
|
||||
"RENEGADE LIMITED",
|
||||
"RENEGADE NORTH",
|
||||
"RENEGADE SPORT",
|
||||
"RENEGADE TRAILHAWK",
|
||||
"TJ",
|
||||
"TJ RUBICON",
|
||||
"TJ SAHARA",
|
||||
"TJ SPORT",
|
||||
"TJ UNLIMITED",
|
||||
"WRANGLER",
|
||||
"WRANGLER RUBICON",
|
||||
"WRANGLER SAHARA",
|
||||
"WRANGLER SPORT",
|
||||
"WRANGLER UNLIMITED",
|
||||
"WRANGLER UNLIMITED 70TH ANNIV",
|
||||
"WRANGLER UNLIMITED RUBICON",
|
||||
"WRANGLER UNLIMITED SAHARA",
|
||||
"WRANGLER UNLIMITED SPORT",
|
||||
"WRANGLER UNLIMITED X",
|
||||
"WRANGLER X",
|
||||
"YJ WRANGLER",
|
||||
"AVIATOR",
|
||||
"AVIATOR RESERVE",
|
||||
"MKC",
|
||||
"MKC RESERVE",
|
||||
"MKC SELECT",
|
||||
"MKT",
|
||||
"MKT ECOBOOST",
|
||||
"MKX",
|
||||
"MKX RESERVE",
|
||||
"NAUTILUS RESERVE",
|
||||
"NAUTILUS RESERVE V6",
|
||||
"NAVIGATOR",
|
||||
"NAVIGATOR L",
|
||||
"NAVIGATOR L RESERVE",
|
||||
"NAVIGATOR L SELECT",
|
||||
"NAVIGATOR RESERVE",
|
||||
"PILOT",
|
||||
"PILOT BLACK EDITION",
|
||||
"PILOT ELITE",
|
||||
"PILOT EX",
|
||||
"PILOT EX-L",
|
||||
"PILOT GRANITE",
|
||||
"PILOT LX",
|
||||
"PILOT SE",
|
||||
"PILOT SE-L",
|
||||
"PILOT TOURING",
|
||||
"DURANGO R/T",
|
||||
"DURANGO SLT PLUS",
|
||||
"DURANGO SRT",
|
||||
"DURANGO",
|
||||
"JOURNEY",
|
||||
"JOURNEY CROSSROAD",
|
||||
"JOURNEY CVP",
|
||||
"JOURNEY LIMITED",
|
||||
"JOURNEY R/T",
|
||||
"JOURNEY SXT",
|
||||
"NITRO SE",
|
||||
"NITRO",
|
||||
"K1500 SUBURBAN",
|
||||
"SUBURBAN 1500 LT",
|
||||
"SUBURBAN 1500 LTZ",
|
||||
"SUBURBAN 1500 PREMIER",
|
||||
"SUBURBAN 2500 LS",
|
||||
"TAHOE LT",
|
||||
"TRAVERSE LS",
|
||||
"TRAVERSE LT",
|
||||
"TRAVERSE PREMIER",
|
||||
"TRAX LT",
|
||||
"TRAX PREMIER",
|
||||
"UPLANDER LT EXT",
|
||||
"SUBURBAN",
|
||||
"TAHOE",
|
||||
"TRAVERSE",
|
||||
"TRAX",
|
||||
"UPLANDER",
|
||||
"YUKON",
|
||||
"YUKON DENALI",
|
||||
"YUKON XL",
|
||||
"YUKON XL DENALI",
|
||||
"EQUINOX LS",
|
||||
"EQUINOX LT",
|
||||
"EQUINOX PREMIER",
|
||||
"EQUINOX",
|
||||
"RAV4 LE",
|
||||
"RAV4 XLE",
|
||||
"HIGHLANDER SPORT V6",
|
||||
"4RUNNER SR5 V6",
|
||||
"RAV4",
|
||||
"RAV4 HYBRID",
|
||||
"RAV4 XLE HYBRID",
|
||||
"HIGHLANDER",
|
||||
"4RUNNER",
|
||||
"SEQUOIA",
|
||||
"PATHFINDER SE",
|
||||
"PATHFINDER SL",
|
||||
"PATHFINDER",
|
||||
"MURANO PLATINUM",
|
||||
"MURANO SV",
|
||||
"MURANO",
|
||||
"TUCSON",
|
||||
"TERRAIN",
|
||||
"SORENTO",
|
||||
"EDGE",
|
||||
"KICKS",
|
||||
"QASHQAI",
|
||||
"SANTA FE",
|
||||
"ARMADA",
|
||||
"TELLURIDE",
|
||||
"PALISADE",
|
||||
"SELTOS",
|
||||
"TORRENT",
|
||||
"C-HR",
|
||||
"SPORTAGE",
|
||||
"VENZA",
|
||||
"ACADIA",
|
||||
"CR-V",
|
||||
"HR-V",
|
||||
"CX-5",
|
||||
"CX-50",
|
||||
"CX-7",
|
||||
"CX-9",
|
||||
"CX-3",
|
||||
"Q3",
|
||||
"Q5",
|
||||
"Q7",
|
||||
"Q8",
|
||||
"JUKE SV",
|
||||
"JUKE",
|
||||
"ROGUE",
|
||||
"ROGUE SV",
|
||||
"XTERRA",
|
||||
"COROLLA CROSS",
|
||||
"ACADIA DENALI",
|
||||
"TAURUS X",
|
||||
"MACAN",
|
||||
"FJ CRUISER",
|
||||
"BRONCO SPORT BADLANDS",
|
||||
"ESCALADE",
|
||||
"RX 350",
|
||||
"KONA",
|
||||
"MDX",
|
||||
"RDX",
|
||||
"COOPER COUNTRYMAN",
|
||||
"V70",
|
||||
"OUTLANDER",
|
||||
"RIO5",
|
||||
"GLC300 COUPE",
|
||||
"ENCORE",
|
||||
"SRX",
|
||||
"SANTA FE SPORT",
|
||||
"NX 300",
|
||||
"WRANGLER UNLIMITE",
|
||||
"WRANGLER JK UNLIM",
|
||||
"RANGEROVER EVOQUE",
|
||||
"CROSSTREK",
|
||||
"FORESTER",
|
||||
"TIGUAN",
|
||||
"XV CROSSTREK",
|
||||
"ENDEAVOR",
|
||||
"RX 330",
|
||||
"ATLAS",
|
||||
"XC90",
|
||||
"TOUAREG",
|
||||
"STELVIO",
|
||||
"RANGE ROVER SPORT",
|
||||
"GLE350D",
|
||||
"EX35",
|
||||
"RVR",
|
||||
"MONTERO",
|
||||
"X-TRAIL",
|
||||
"GRAND VITARA",
|
||||
"TRIBUTE",
|
||||
"X3",
|
||||
"XC60",
|
||||
"GLK250 BLUETEC",
|
||||
"ENVOY",
|
||||
"ML350 BLUETEC",
|
||||
"ENVISION",
|
||||
"FX35",
|
||||
"X1",
|
||||
"VENUE",
|
||||
"TAOS",
|
||||
"KONA ELECTRIC",
|
||||
"OUTLANDER PHEV",
|
||||
"PASSPORT",
|
||||
"H3",
|
||||
"EXPLORERSPORTTRAC",
|
||||
"F-PACE",
|
||||
"ML320 BLUETEC",
|
||||
"REGAL SPORTBACK",
|
||||
"DISCOVERY SPORT",
|
||||
"RENDEZVOUS",
|
||||
"XC70",
|
||||
"COMPASS (NEW)",
|
||||
"CUBE",
|
||||
"V60 CROSS COUNTRY",
|
||||
"QX70",
|
||||
"X6",
|
||||
"ELEMENT",
|
||||
"RX 400H",
|
||||
"VUE",
|
||||
"RANGE ROVER VELAR",
|
||||
"E-PACE",
|
||||
"RAV4 PRIME",
|
||||
"LX 570",
|
||||
"GX 470",
|
||||
"EX37",
|
||||
"GLE43",
|
||||
"NAUTILUS",
|
||||
"XT6",
|
||||
"RX 450H",
|
||||
"ESCALADE ESV",
|
||||
"OUTLOOK",
|
||||
"CAYENNE",
|
||||
"XC90 PLUG-IN",
|
||||
"MODEL X",
|
||||
"MODEL Y",
|
||||
"GLC300",
|
||||
"SANTA FE HYBRID",
|
||||
"G63",
|
||||
"XV CROSSTREK HYBR",
|
||||
"JX35",
|
||||
"JIMMY",
|
||||
"TUCSON HYBRID",
|
||||
"XC40 ELECTRIC",
|
||||
"RX 300",
|
||||
"ML320",
|
||||
"WRANGLER JK UNLIMITED",
|
||||
"POLICE INTERCEPTOR UTILITY",
|
||||
"WRANGLER JK",
|
||||
"TRIBECA",
|
||||
"E-TRON SPORTBACK",
|
||||
"500X",
|
||||
"RX 350H",
|
||||
"GL350 BLUETEC",
|
||||
"WRANGLER UNLIMITED 4XE",
|
||||
"GV80",
|
||||
"GL550",
|
||||
"Q5 E",
|
||||
"H2 SUV",
|
||||
"Q5 HYBRID",
|
||||
"IONIQ 5",
|
||||
"SQ5 SPORTBACK",
|
||||
"LEVANTE",
|
||||
"TONALE",
|
||||
"GLE43 COUPE",
|
||||
"GRAND CHEROKEE WK",
|
||||
"DEFENDER",
|
||||
"NX 450H+",
|
||||
"ML400",
|
||||
"LX 600",
|
||||
"RX 450HL",
|
||||
"SORENTO HYBRID",
|
||||
"NX 350",
|
||||
"TRACKER",
|
||||
"GLE450",
|
||||
"Q5 SPORTBACK",
|
||||
"CR-V HYBRID",
|
||||
"LX 470",
|
||||
"EQS580 SUV",
|
||||
"H2",
|
||||
"EV9",
|
||||
"SORENTO PLUG-IN",
|
||||
"LYRIQ",
|
||||
"GLE550",
|
||||
"RX 500H",
|
||||
"X1 SAV",
|
||||
"E-TRON S SPORTBACK",
|
||||
"ML500",
|
||||
"GRAND HIGHLANDER HYBRID",
|
||||
"RS Q8",
|
||||
"GLS550",
|
||||
"GLS580",
|
||||
"IX",
|
||||
"CAYENNE COUPE",
|
||||
"SOLTERRA",
|
||||
"PATHFINDER HYBRID",
|
||||
"Q8 E-TRON",
|
||||
"TX 350",
|
||||
"TX 500H",
|
||||
"EQUINOX EV",
|
||||
"NAUTILUS HYBRID",
|
||||
"TRAVERSE LIMITED",
|
||||
"CX-70",
|
||||
"SANTA FE XL",
|
||||
"RENEGADE",
|
||||
"QX50",
|
||||
"ECLIPSE CROSS",
|
||||
"QX80",
|
||||
"X5",
|
||||
"X3",
|
||||
"X1",
|
||||
"X4",
|
||||
"ENCLAVE",
|
||||
"ENCORE GX",
|
||||
"CAYENNE HYBRID",
|
||||
"SOUL",
|
||||
"GX 460",
|
||||
"UX 250H",
|
||||
"XT5",
|
||||
"GLE53",
|
||||
"XT4",
|
||||
"SQ7",
|
||||
"NX 350H",
|
||||
"GLK350",
|
||||
"GLE350",
|
||||
"NX 300H",
|
||||
"NX 200T",
|
||||
"RANGE ROVER EVOQUE",
|
||||
"GLS450",
|
||||
"TERRAIN DENALI",
|
||||
"GRAND CHEROKEE L",
|
||||
"GLE400",
|
||||
"TUCSON PLUG-IN",
|
||||
"BLAZER",
|
||||
"ASCENT",
|
||||
"HIGHLANDER HYBRID",
|
||||
"ATLAS CROSS SPORT",
|
||||
"XC40",
|
||||
"VENZA HYBRID",
|
||||
"GLA45",
|
||||
"GLB250",
|
||||
"GRAND HIGHLANDER",
|
||||
"GV70",
|
||||
"NIRO",
|
||||
"NIRO EV",
|
||||
"GLA250",
|
||||
"ESCAPE PLUG-IN",
|
||||
"WAGONEER",
|
||||
"CX-30",
|
||||
"QX60",
|
||||
"GRAND CHEROKEE 4XE",
|
||||
"SPORTAGE HYBRID",
|
||||
"EV6",
|
||||
"TONALE PLUG-IN",
|
||||
"GLC43 COUPE",
|
||||
"X2",
|
||||
"RX 350L",
|
||||
"HORNET",
|
||||
"ENVISTA",
|
||||
"LEVANTE S",
|
||||
"SPORTAGE PLUG-IN",
|
||||
"ORLANDO",
|
||||
"X5 M",
|
||||
"EXPLORER HYBRID",
|
||||
"FREESTYLE",
|
||||
"CORSAIR",
|
||||
"K1500 YUKON XL",
|
||||
"RANGE ROVER",
|
||||
"SUV W/O LABOR",
|
||||
"ID.4",
|
||||
"CX-90",
|
||||
"X7",
|
||||
"CORSAIR PLUG-IN",
|
||||
"ESCALADE EXT",
|
||||
"QX55",
|
||||
"DISCOVERY",
|
||||
"BOLT EUV",
|
||||
"C40 ELECTRIC",
|
||||
"LR4",
|
||||
"GRAND WAGONEER",
|
||||
"XC60 PLUG-IN",
|
||||
"LR2",
|
||||
"EQE350 SUV",
|
||||
"COROLLA CROSS HYBRID",
|
||||
"SOUL EV",
|
||||
"GRECALE",
|
||||
"SUV W/O LABOR",
|
||||
"QX30",
|
||||
"SQ5",
|
||||
"NIRO PLUG-IN",
|
||||
"BORREGO",
|
||||
"CX-90 PLUG-IN",
|
||||
"XL-7",
|
||||
"SUV W/O LABOR",
|
||||
"SUV W/O LABOR",
|
||||
"I-PACE",
|
||||
"HORNET PLUG-IN",
|
||||
"UX 300H",
|
||||
"ML320 CDI",
|
||||
"VERACRUZ",
|
||||
"SQ8",
|
||||
"GLE53 COUPE",
|
||||
"ZDX",
|
||||
"9-7X",
|
||||
"ARIYA",
|
||||
"ASPEN",
|
||||
"AVIATOR PLUG-IN",
|
||||
"B9 TRIBECA",
|
||||
"BRAVADA",
|
||||
"ENVOY XL",
|
||||
"EQB350",
|
||||
"EQB350 SUV",
|
||||
"ESCALADE-V",
|
||||
"E-TRON",
|
||||
"FX37",
|
||||
"GL320 CDI",
|
||||
"GLADIATOR",
|
||||
"GLC43",
|
||||
"GLE450 COUPE",
|
||||
"GLE63",
|
||||
"GV60",
|
||||
"MKT TOWN CAR",
|
||||
"ML350",
|
||||
"ML550",
|
||||
"ML63",
|
||||
"NX 250",
|
||||
"Q4 E-TRON",
|
||||
"Q8 E-TRON SPORTBACK",
|
||||
"QX4",
|
||||
"QX56",
|
||||
"SANTA FE PLUG-IN",
|
||||
"UX 200",
|
||||
"WAGONEER L",
|
||||
"XB"
|
||||
]
|
||||
567
server/data/vehicletype/trucks.json
Normal file
567
server/data/vehicletype/trucks.json
Normal file
@@ -0,0 +1,567 @@
|
||||
[
|
||||
"MARK LT",
|
||||
|
||||
"F-150",
|
||||
"F-250",
|
||||
"F-350",
|
||||
"F-450",
|
||||
"F-550",
|
||||
"F-650",
|
||||
"F100 PICKUP",
|
||||
"F150 FX2 SUPERCAB",
|
||||
"F150 FX4 PICKUP",
|
||||
"F150 FX4 SUPERCAB",
|
||||
"F150 FX4 SUPERCREW",
|
||||
"F150 HARLEY DAVIDSON SUPERCAB",
|
||||
"F150 HARLEY DAVIDSON SUPERCREW",
|
||||
"F150 KING RANCH SUPERCREW",
|
||||
"F150 LARIAT FX4 SUPERCREW",
|
||||
"F150 LARIAT HARLEY DAVIDSON SC",
|
||||
"F150 LARIAT KING RANCH SUPCREW",
|
||||
"F150 LARIAT LIMITED SUPERCREW",
|
||||
"F150 LARIAT PICKUP",
|
||||
"F150 LARIAT SUPERCAB",
|
||||
"F150 LARIAT SUPERCAB (AMALGAM)",
|
||||
"F150 LARIAT SUPERCREW",
|
||||
"F150 LARIAT SUPERCREW (AMALGA)",
|
||||
"F150 LIMITED SUPERCREW",
|
||||
"F150 PICKUP",
|
||||
"F150 PLATINUM SUPERCREW",
|
||||
"F150 RAPTOR SUPERCAB",
|
||||
"F150 RAPTOR SUPERCREW",
|
||||
"F150 STX PICKUP",
|
||||
"F150 STX SUPERCAB",
|
||||
"F150 SUPERCAB",
|
||||
"F150 SUPERCREW",
|
||||
"F150 SUPERCREW (AMALGAMATED)",
|
||||
"F150 SVT RAPTOR SUPERCAB",
|
||||
"F150 XL PICKUP",
|
||||
"F150 XL SUPERCAB",
|
||||
"F150 XL SUPERCREW",
|
||||
"F150 XLT LARIAT SUPERCAB",
|
||||
"F150 XLT PICKUP",
|
||||
"F150 XLT SUPERCAB",
|
||||
"F150 XLT SUPERCREW",
|
||||
"F150 XLT SUPERCREW (AMALGAMAT)",
|
||||
"F150 XTR SUPERCAB",
|
||||
"F250 PICKUP",
|
||||
"F250 SD CREW CAB",
|
||||
"F250 SD FX4 CREW CAB",
|
||||
"F250 SD FX4 SUPERCAB",
|
||||
"F250 SD KING RANCH CREW CAB",
|
||||
"F250 SD LARIAT CREW CAB",
|
||||
"F250 SD LARIAT CREW CAB (AMAL)",
|
||||
"F250 SD LARIAT PICKUP",
|
||||
"F250 SD LARIAT SUPERCAB",
|
||||
"F250 SD LIMITED CREW CAB",
|
||||
"F250 SD PLATINUM CREW CAB",
|
||||
"F250 SD SUPERCAB",
|
||||
"F250 SD XL CREW CAB",
|
||||
"F250 SD XL PICKUP",
|
||||
"F250 SD XL SUPERCAB",
|
||||
"F250 SD XLT CREW CAB",
|
||||
"F250 SD XLT PICKUP",
|
||||
"F250 SD XLT SUPERCAB",
|
||||
"F250 SUPERCAB",
|
||||
"F250 XL CREW CAB",
|
||||
"F350 CREW CAB",
|
||||
"F350 PICKUP",
|
||||
"F350 PICKUP 2WD",
|
||||
"F350 SD CABELAS CREW CAB",
|
||||
"F350 SD CREW CAB",
|
||||
"F350 SD FX4 CREW CAB",
|
||||
"F350 SD FX4 SUPERCAB",
|
||||
"F350 SD HARLEY DAVIDSON",
|
||||
"F350 SD KING RANCH CREW CAB",
|
||||
"F350 SD LARIAT CREW CAB",
|
||||
"F350 SD LARIAT CREW CAB (AMAL)",
|
||||
"F350 SD LARIAT KING RANCH",
|
||||
"F350 SD LARIAT SUPERCAB",
|
||||
"F350 SD LIMITED CREW CAB",
|
||||
"F350 SD PICKUP",
|
||||
"F350 SD PLATINUM CREW CAB",
|
||||
"F350 SD SUPERCAB",
|
||||
"F350 SD XL CREW CAB",
|
||||
"F350 SD XL PICKUP",
|
||||
"F350 SD XL SUPERCAB",
|
||||
"F350 SD XLT CREW CAB",
|
||||
"F350 SD XLT SUPERCAB",
|
||||
"F350 SUPER DUTY",
|
||||
"F350 SUPER DUTY XL",
|
||||
"F350 XL PICKUP",
|
||||
"F450",
|
||||
"F450 Pickup",
|
||||
"F450 SD KING RANCH CREW CAB",
|
||||
"F450 SD LARIAT CREW CAB",
|
||||
"F450 SD PICKUP",
|
||||
"F450 SD PLATINUM CREW CAB",
|
||||
"F450 SD XL",
|
||||
"F450 SD XL CREW CAB",
|
||||
"F450 SD XL PICKUP",
|
||||
"F450 SD XLT CREW CAB",
|
||||
"F450 SUPER DUTY XLT",
|
||||
"F550",
|
||||
"F550 SD",
|
||||
"F550 SD XL",
|
||||
"F550 SD XL PICKUP",
|
||||
"F550 SD XLT CREW CAB",
|
||||
"F550 SD XLT SUPERCAB",
|
||||
"F550 SUPER DUTY",
|
||||
"F550 SUPER DUTY XL",
|
||||
"F550 SUPER DUTY XLT",
|
||||
"F550 SUPER DUTY XLT CREW CAB",
|
||||
"F550 XL",
|
||||
"F650 SD XLT SUPERCAB",
|
||||
"F68",
|
||||
"F750 XL",
|
||||
|
||||
"RANGER",
|
||||
"RANGER EDGE SUPERCAB",
|
||||
"RANGER FX4 SUPERCAB",
|
||||
"RANGER LARIAT SUPERCREW",
|
||||
"RANGER SPORT SUPERCAB",
|
||||
"RANGER STX SUPERCAB",
|
||||
"RANGER SUPERCAB",
|
||||
"RANGER XL",
|
||||
"RANGER XL SUPERCAB",
|
||||
"RANGER XLT",
|
||||
"RANGER XLT SUPERCAB",
|
||||
"RANGER XLT SUPERCREW",
|
||||
|
||||
"FRONTIER LE CREW CAB V6",
|
||||
"FRONTIER NISMO CREW CAB V6",
|
||||
"FRONTIER NISMO KING CAB V6",
|
||||
"FRONTIER PRO-4X CREW CAB V6",
|
||||
"FRONTIER PRO-4X KING CAB V6",
|
||||
"FRONTIER S KING CAB",
|
||||
"FRONTIER SC CREW CAB V6",
|
||||
"FRONTIER SC V6",
|
||||
"FRONTIER SE CREW CAB V6",
|
||||
"FRONTIER SE KING CAB V6",
|
||||
"FRONTIER SL CREW CAB V6",
|
||||
"FRONTIER SV CREW CAB V6",
|
||||
"FRONTIER SV KING CAB V6",
|
||||
"FRONTIER XE KING CAB",
|
||||
"FRONTIER XE KING CAB V6",
|
||||
"KING CAB",
|
||||
|
||||
"TITAN 5.6 LE CREW CAB",
|
||||
"TITAN 5.6 LE KING CAB",
|
||||
"TITAN 5.6 MIDNIGHT CREW CAB",
|
||||
"TITAN 5.6 PLATINUM RESERVE CC",
|
||||
"TITAN 5.6 PRO-4X CREW CAB",
|
||||
"TITAN 5.6 PRO-4X KING CAB",
|
||||
"TITAN 5.6 S CREW CAB",
|
||||
"TITAN 5.6 SE CREW CAB",
|
||||
"TITAN 5.6 SE KING CAB",
|
||||
"TITAN 5.6 SL CREW CAB",
|
||||
"TITAN 5.6 SV CREW CAB",
|
||||
"TITAN 5.6 SV KING CAB",
|
||||
"TITAN 5.6 XE CREW CAB",
|
||||
"TITAN 5.6 XE KING CAB",
|
||||
"TITAN XD PLATINUM CREW CAB",
|
||||
"TITAN XD PRO-4X CREW CAB",
|
||||
"TITAN XD S CREW CAB",
|
||||
"TITAN XD SL CREW CAB",
|
||||
"TITAN XD SV CREW CAB",
|
||||
|
||||
"PICKUP SR5",
|
||||
|
||||
"TACOMA",
|
||||
"TACOMA ACCESS CAB",
|
||||
"TACOMA DOUBLE CAB V6",
|
||||
"TACOMA LIMITED DOUBLE CAB V6",
|
||||
"TACOMA PRERUNNER DOUBLE CAB V6",
|
||||
"TACOMA PRERUNNER V6 ACCESS CAB",
|
||||
"TACOMA PRERUNNER XTRACAB",
|
||||
"TACOMA PRERUNNER XTRACAB V6",
|
||||
"TACOMA SR5 DOUBLE CAB V6",
|
||||
"TACOMA SR5 V6 ACCESS CAB",
|
||||
"TACOMA SR5 V6 XTRACAB",
|
||||
"TACOMA V6 ACCESS CAB",
|
||||
"TACOMA XTRACAB",
|
||||
"TACOMA XTRACAB V6",
|
||||
"TUNDRA ACCESS CAB V8",
|
||||
"TUNDRA DOUBLE CAB V8",
|
||||
"TUNDRA LIMITED ACCESS CAB V8",
|
||||
"TUNDRA LIMITED SR5 DBLCAB V8",
|
||||
"TUNDRA LIMITED V8",
|
||||
"TUNDRA LIMITED V8 CREWMAX",
|
||||
"TUNDRA LIMITED V8 DOUBLE CAB",
|
||||
"TUNDRA PLATINUM V8 CREWMAX",
|
||||
"TUNDRA SR DOUBLE CAB V8",
|
||||
"TUNDRA SR V8",
|
||||
"TUNDRA SR5 DOUBLE CAB V8",
|
||||
"TUNDRA SR5 TRD DOUBLE CAB V8",
|
||||
"TUNDRA SR5 V8 CREWMAX",
|
||||
"TUNDRA V8",
|
||||
"TUNDRA V8 CREWMAX",
|
||||
"XTRACAB LONG BOX",
|
||||
|
||||
"AVALANCHE 1500",
|
||||
"AVALANCHE 1500 LS",
|
||||
"AVALANCHE 1500 LS Z71",
|
||||
"AVALANCHE 1500 LT",
|
||||
"AVALANCHE 1500 LT Z71",
|
||||
"AVALANCHE 1500 LTZ",
|
||||
"C/R 10/1500 4+CAB",
|
||||
"C/R 10/1500 PICKUP",
|
||||
"C/R 20/2500 4+CAB",
|
||||
"C/R 20/2500 PICKUP",
|
||||
"C3500",
|
||||
|
||||
"COLORADO",
|
||||
"COLORADO EXT CAB",
|
||||
"COLORADO LS",
|
||||
"COLORADO LS CREW CAB",
|
||||
"COLORADO LS EXT CAB",
|
||||
"COLORADO LT",
|
||||
"COLORADO LT CREW CAB",
|
||||
"COLORADO LT EXT CAB",
|
||||
"COLORADO WT CREW CAB",
|
||||
"COLORADO WT EXT CAB",
|
||||
"COLORADO Z71 CREW CAB",
|
||||
"COLORADO Z71 EXT CAB",
|
||||
"COLORADO ZR2 CREW CAB",
|
||||
"COLORADO ZR2 EXT CAB",
|
||||
|
||||
"HHR LS PANEL",
|
||||
"K/V 10/1500 4+CAB",
|
||||
"K/V 10/1500 PICKUP",
|
||||
"K/V 20/2500 4+CAB",
|
||||
"K/V 20/2500 PICKUP",
|
||||
"K/V 30/3500 4+CAB",
|
||||
"Pickup K3500",
|
||||
"Pickup Silverado C2500 HD",
|
||||
"S10 4+CAB",
|
||||
"S10 LS 4+CAB",
|
||||
"SILVERADO 1500",
|
||||
"SILVERADO 1500 CHEYENNE CREW",
|
||||
"SILVERADO 1500 CREW CAB",
|
||||
"SILVERADO 1500 CREW CAB (AMAL)",
|
||||
"SILVERADO 1500 CUST TRAIL DC",
|
||||
"SILVERADO 1500 CUSTOM CREW CAB",
|
||||
"SILVERADO 1500 CUSTOM DC",
|
||||
"SILVERADO 1500 CUSTOM TRAIL CC",
|
||||
"SILVERADO 1500 DOUBLE (AMALGA)",
|
||||
"SILVERADO 1500 EXT CAB",
|
||||
"SILVERADO 1500 HD LS CREW CAB",
|
||||
"SILVERADO 1500 HD LT CREW CAB",
|
||||
"SILVERADO 1500 HIGH COUNTRY CC",
|
||||
"SILVERADO 1500 HYBRID CREW CAB",
|
||||
"SILVERADO 1500 LS",
|
||||
"SILVERADO 1500 LS CREW CAB",
|
||||
"SILVERADO 1500 LS DOUBLE CAB",
|
||||
"SILVERADO 1500 LS EXT CAB",
|
||||
"SILVERADO 1500 LT",
|
||||
"SILVERADO 1500 LT CC (AMALGAM)",
|
||||
"SILVERADO 1500 LT CREW CAB",
|
||||
"SILVERADO 1500 LT DOUBLE CAB",
|
||||
"SILVERADO 1500 LT EXT CAB",
|
||||
"SILVERADO 1500 LT TRAIL CC",
|
||||
"SILVERADO 1500 LT TRAIL DC",
|
||||
"SILVERADO 1500 LTZ CREW CAB",
|
||||
"SILVERADO 1500 LTZ DOUBLE CAB",
|
||||
"SILVERADO 1500 LTZ EXT CAB",
|
||||
"SILVERADO 1500 RST CREW CAB",
|
||||
"SILVERADO 1500 RST DOUBLE CAB",
|
||||
"SILVERADO 1500 SS EXT CAB",
|
||||
"SILVERADO 1500 WT",
|
||||
"SILVERADO 1500 WT CREW CAB",
|
||||
"SILVERADO 1500 WT DOUBLE CAB",
|
||||
"SILVERADO 1500 WT EXT CAB",
|
||||
"SILVERADO 2500 EXT CAB",
|
||||
"SILVERADO 2500 HD",
|
||||
"SILVERADO 2500 HD CREW CAB",
|
||||
"SILVERADO 2500 HD EXT CAB",
|
||||
"SILVERADO 2500 HD HC CREW CAB",
|
||||
"SILVERADO 2500 HD LS CREW CAB",
|
||||
"SILVERADO 2500 HD LS EXT CAB",
|
||||
"SILVERADO 2500 HD LT",
|
||||
"SILVERADO 2500 HD LT CREW CAB",
|
||||
"SILVERADO 2500 HD LT DBL CAB",
|
||||
"SILVERADO 2500 HD LT EXT CAB",
|
||||
"SILVERADO 2500 HD LTZ CREW CAB",
|
||||
"SILVERADO 2500 HD LTZ DBL CAB",
|
||||
"SILVERADO 2500 HD LTZ EXT CAB",
|
||||
"SILVERADO 2500 HD WT",
|
||||
"SILVERADO 2500 HD WT CREW CAB",
|
||||
"SILVERADO 2500 HD WT DBL CAB",
|
||||
"SILVERADO 2500 HD WT EXT CAB",
|
||||
"SILVERADO 3500",
|
||||
"SILVERADO 3500 CREW CAB",
|
||||
"SILVERADO 3500 CREW CAB (AMAL)",
|
||||
"SILVERADO 3500 EXT CAB",
|
||||
"SILVERADO 3500 HC CREW CAB",
|
||||
"SILVERADO 3500 HD (AMALGAMATE)",
|
||||
"SILVERADO 3500 LS",
|
||||
"SILVERADO 3500 LS CREW CAB",
|
||||
"SILVERADO 3500 LS EXT CAB",
|
||||
"SILVERADO 3500 LT CREW CAB",
|
||||
"SILVERADO 3500 LT DOUBLE CAB",
|
||||
"SILVERADO 3500 LT EXT CAB",
|
||||
"SILVERADO 3500 LTZ CREW CAB",
|
||||
"SILVERADO 3500 LTZ EXT CAB",
|
||||
"SILVERADO 3500 WT CREW CAB",
|
||||
"Silverado 3500HD",
|
||||
|
||||
"B250 SPORTSMAN",
|
||||
|
||||
"DAKOTA CLUB CAB",
|
||||
"DAKOTA LARAMIE V8 CLUB CAB",
|
||||
"DAKOTA LARAMIE V8 QUAD CAB",
|
||||
"DAKOTA QUAD CAB",
|
||||
"DAKOTA SLT CREW CAB",
|
||||
"DAKOTA SLT EXT CAB",
|
||||
"DAKOTA SLT PLUS QUAD CAB",
|
||||
"DAKOTA SLT PLUS V8 CLUB CAB",
|
||||
"DAKOTA SLT PLUS V8 QUAD CAB",
|
||||
"DAKOTA SLT QUAD CAB",
|
||||
"DAKOTA SLT V8 CLUB CAB",
|
||||
"DAKOTA SLT V8 CREW CAB",
|
||||
"DAKOTA SLT V8 EXT CAB",
|
||||
"DAKOTA SLT V8 QUAD CAB",
|
||||
"DAKOTA SPORT V8",
|
||||
"DAKOTA SPORT V8 CLUB CAB",
|
||||
"DAKOTA SPORT V8 QUAD CAB",
|
||||
"DAKOTA ST CLUB CAB",
|
||||
"DAKOTA ST QUAD CAB",
|
||||
"DAKOTA ST V8 QUAD CAB",
|
||||
"DAKOTA SXT CREW CAB",
|
||||
"DAKOTA SXT EXT CAB",
|
||||
"DAKOTA SXT V8 CREW CAB",
|
||||
"DAKOTA SXT V8 EXT CAB",
|
||||
"DAKOTA V8 CLUB CAB",
|
||||
"DAKOTA V8 QUAD CAB",
|
||||
|
||||
"RAM 1500",
|
||||
"RAM 1500 BIG HORN CREW CAB",
|
||||
"RAM 1500 BIG HORN QUAD CAB",
|
||||
"RAM 1500 CLUB CAB",
|
||||
"RAM 1500 CREW CAB (AMALGAMATE)",
|
||||
"RAM 1500 EXPRESS",
|
||||
"RAM 1500 LARAMIE CREW (AMALGA)",
|
||||
"RAM 1500 LARAMIE CREW CAB",
|
||||
"RAM 1500 LARAMIE LONGHORN CREW",
|
||||
"RAM 1500 LARAMIE MEGA CAB",
|
||||
"RAM 1500 LARAMIE QUAD CAB",
|
||||
"RAM 1500 LARAMIE SLT QUAD CAB",
|
||||
"RAM 1500 LIMITED CREW CAB",
|
||||
"RAM 1500 LONGHORN CREW CAB",
|
||||
"RAM 1500 OUTDOORSMAN CREW CAB",
|
||||
"RAM 1500 OUTDOORSMAN QC (AMAL)",
|
||||
"RAM 1500 OUTDOORSMAN QUAD CAB",
|
||||
"RAM 1500 QUAD CAB",
|
||||
"RAM 1500 R/T",
|
||||
"RAM 1500 REBEL CREW CAB",
|
||||
"RAM 1500 REBEL QUAD CAB",
|
||||
"RAM 1500 SLT",
|
||||
"RAM 1500 SLT CREW (AMALGAMATE)",
|
||||
"RAM 1500 SLT CREW CAB",
|
||||
"RAM 1500 SLT MEGA CAB",
|
||||
"RAM 1500 SLT QUAD (AMALGAMATE)",
|
||||
"RAM 1500 SLT QUAD CAB",
|
||||
"RAM 1500 SPORT",
|
||||
"RAM 1500 SPORT CLUB CAB",
|
||||
"RAM 1500 SPORT CREW CAB",
|
||||
"RAM 1500 SPORT CREW CAB (AMAL)",
|
||||
"RAM 1500 SPORT QUAD CAB",
|
||||
"RAM 1500 ST",
|
||||
"RAM 1500 ST CREW CAB",
|
||||
"RAM 1500 ST QUAD CAB",
|
||||
"RAM 1500 SXT CREW CAB",
|
||||
"RAM 1500 SXT QUAD CAB",
|
||||
"RAM 1500 TRADESMAN CREW CAB",
|
||||
"RAM 1500 TRADESMAN QUAD CAB",
|
||||
"RAM 1500 TRX QUAD CAB",
|
||||
"RAM 2500",
|
||||
"RAM 2500 BIG HORN CREW CAB",
|
||||
"RAM 2500 BIG HORN MEGA CAB",
|
||||
"RAM 2500 CLUB CAB",
|
||||
"RAM 2500 LARAMIE CREW CAB",
|
||||
"RAM 2500 LARAMIE LONGHORN CREW",
|
||||
"RAM 2500 LARAMIE LONGHORN MEGA",
|
||||
"RAM 2500 LARAMIE MEGA CAB",
|
||||
"RAM 2500 LARAMIE QUAD CAB",
|
||||
"RAM 2500 LARAMIE SLT",
|
||||
"RAM 2500 LARAMIE SLT QUAD CAB",
|
||||
"RAM 2500 LIMITED CREW CAB",
|
||||
"RAM 2500 OUTDOORSMAN CREW CAB",
|
||||
"RAM 2500 POWER WAGON CREW CAB",
|
||||
"RAM 2500 QUAD CAB",
|
||||
"RAM 2500 SLT",
|
||||
"RAM 2500 SLT CREW CAB",
|
||||
"RAM 2500 SLT MEGA CAB",
|
||||
"RAM 2500 SLT QUAD CAB",
|
||||
"RAM 2500 SLT QUAD CAB (AMALGA)",
|
||||
"RAM 2500 SPORT QUAD CAB",
|
||||
"RAM 2500 ST",
|
||||
"RAM 2500 ST CREW CAB",
|
||||
"RAM 2500 ST QUAD CAB",
|
||||
"RAM 2500 SXT QUAD CAB",
|
||||
"RAM 2500 TRADESMAN",
|
||||
"RAM 2500 TRADESMAN CREW CAB",
|
||||
"RAM 2500 TRX CREW CAB",
|
||||
"RAM 2500 TRX QUAD CAB",
|
||||
"RAM 3500",
|
||||
"RAM 3500 4WD",
|
||||
"RAM 3500 BIG HORN CREW CAB",
|
||||
"RAM 3500 CREW CAB",
|
||||
"RAM 3500 CREW CAB (AMALGAMATE)",
|
||||
"RAM 3500 LARAMIE CREW CAB",
|
||||
"RAM 3500 LARAMIE LONGHORN CREW",
|
||||
"RAM 3500 LARAMIE LONGHORN MEGA",
|
||||
"RAM 3500 LARAMIE MEGA CAB",
|
||||
"RAM 3500 LARAMIE QUAD CAB",
|
||||
"RAM 3500 LARAMIE SLT",
|
||||
"RAM 3500 LARAMIE SLT QUAD CAB",
|
||||
"RAM 3500 LIMITED MEGA CAB",
|
||||
"RAM 3500 LONGHORN CREW CAB",
|
||||
"RAM 3500 QUAD CAB",
|
||||
"RAM 3500 SLT",
|
||||
"RAM 3500 SLT CREW CAB",
|
||||
"RAM 3500 SLT MEGA CAB",
|
||||
"RAM 3500 SLT QUAD CAB",
|
||||
"RAM 3500 SPORT QUAD CAB",
|
||||
"RAM 3500 ST",
|
||||
"RAM 3500 ST CREW CAB",
|
||||
"RAM 3500 ST QUAD CAB",
|
||||
"RAM 3500 TRX QUAD CAB",
|
||||
"RAM 4500",
|
||||
"RAM 4500 CREW CAB",
|
||||
"RAM 5500",
|
||||
"RAM 5500 CREW CAB",
|
||||
"W250 TURBO DIESEL",
|
||||
|
||||
"C Series 5500",
|
||||
"C/R 1500 4+CAB",
|
||||
"C/R 1500 PICKUP",
|
||||
"C/R 1500 SIERRA SL EXT CAB",
|
||||
"C/R 3500",
|
||||
"C/R 3500 PICKUP",
|
||||
"CANYON ALL TERRAIN CREW CAB",
|
||||
"CANYON CREW CAB",
|
||||
"CANYON DENALI CREW CAB",
|
||||
"CANYON EXT CAB",
|
||||
"CANYON SL",
|
||||
"CANYON SL EXT CAB",
|
||||
"CANYON SLE",
|
||||
"CANYON SLE CREW CAB",
|
||||
"CANYON SLE EXT CAB",
|
||||
"CANYON SLT CREW CAB",
|
||||
"CANYON SLT CREW CAB (AMALGAMA)",
|
||||
"K/V 1500 4+CAB",
|
||||
"K/V 1500 PICKUP",
|
||||
"K/V 2500 4+CAB",
|
||||
"K/V 2500 PICKUP",
|
||||
"K/V 3500 SIERRA SL CREW CAB",
|
||||
"K/V 3500 SIERRA SLE CREW CAB",
|
||||
"SIERRA 1500 AT4 CREW CAB",
|
||||
"SIERRA 1500 AT4 DOUBLE CAB",
|
||||
"SIERRA 1500 CREW CAB",
|
||||
"SIERRA 1500 CREW CAB (AMALGAM)",
|
||||
"SIERRA 1500 DENALI CREW CAB",
|
||||
"SIERRA 1500 DENALI EXT CAB",
|
||||
"SIERRA 1500 DOUBLE CAB",
|
||||
"SIERRA 1500 ELEVATION CREW CAB",
|
||||
"SIERRA 1500 ELEVATION DC",
|
||||
"SIERRA 1500 EXT CAB",
|
||||
"SIERRA 1500 HD CREW CAB",
|
||||
"SIERRA 1500 HD SLE CREW CAB",
|
||||
"SIERRA 1500 HD SLT CREW CAB",
|
||||
"SIERRA 1500 NEVADA EDITION",
|
||||
"SIERRA 1500 PICKUP",
|
||||
"SIERRA 1500 SL CREW CAB",
|
||||
"SIERRA 1500 SL EXT CAB",
|
||||
"SIERRA 1500 SL PICKUP",
|
||||
"SIERRA 1500 SLE CREW CAB",
|
||||
"SIERRA 1500 SLE DC (AMALGAMAT)",
|
||||
"SIERRA 1500 SLE DOUBLE CAB",
|
||||
"SIERRA 1500 SLE EXT CAB",
|
||||
"SIERRA 1500 SLE EXT CAB (AMAL)",
|
||||
"SIERRA 1500 SLE PICKUP",
|
||||
"SIERRA 1500 SLT CREW (AMALGAM)",
|
||||
"SIERRA 1500 SLT CREW CAB",
|
||||
"SIERRA 1500 SLT DOUBLE CAB",
|
||||
"SIERRA 1500 SLT EXT CAB",
|
||||
"SIERRA 1500 WT CREW CAB",
|
||||
"SIERRA 1500 WT EXT CAB",
|
||||
"SIERRA 1500 WT PICKUP",
|
||||
"SIERRA 2500 EXT CAB",
|
||||
"SIERRA 2500 HD AT4 CREW CAB",
|
||||
"SIERRA 2500 HD CREW CAB",
|
||||
"SIERRA 2500 HD DENALI CREW CAB",
|
||||
"SIERRA 2500 HD DOUBLE CAB",
|
||||
"SIERRA 2500 HD EXT CAB",
|
||||
"SIERRA 2500 HD PICKUP",
|
||||
"SIERRA 2500 HD SL EXT CAB",
|
||||
"SIERRA 2500 HD SL PICKUP",
|
||||
"SIERRA 2500 HD SLE CREW CAB",
|
||||
"SIERRA 2500 HD SLE DOUBLE CAB",
|
||||
"SIERRA 2500 HD SLE EXT CAB",
|
||||
"SIERRA 2500 HD SLE PICKUP",
|
||||
"SIERRA 2500 HD SLT CREW CAB",
|
||||
"SIERRA 2500 HD SLT DOUBLE CAB",
|
||||
"SIERRA 2500 HD SLT EXT CAB",
|
||||
"SIERRA 2500 HD WT CREW CAB",
|
||||
"SIERRA 2500 HD WT DOUBLE CAB",
|
||||
"SIERRA 2500 HD WT EXT CAB",
|
||||
"SIERRA 2500 HD WT PICKUP",
|
||||
"SIERRA 2500 SLE EXT CAB",
|
||||
"SIERRA 3500 AT4 CREW CAB",
|
||||
"SIERRA 3500 CREW CAB",
|
||||
"SIERRA 3500 DENALI CREW CAB",
|
||||
"SIERRA 3500 EXT CAB",
|
||||
"SIERRA 3500 PICKUP",
|
||||
"SIERRA 3500 SL CREW CAB",
|
||||
"SIERRA 3500 SLE",
|
||||
"SIERRA 3500 SLE CREW CAB",
|
||||
"SIERRA 3500 SLE EXT CAB",
|
||||
"SIERRA 3500 SLT CREW CAB",
|
||||
"SIERRA 3500 WT CREW CAB",
|
||||
"SONOMA",
|
||||
"SONOMA CREW CAB",
|
||||
"SONOMA EXT CAB",
|
||||
|
||||
"1500",
|
||||
"1500 Classic",
|
||||
"Pickup 1500",
|
||||
"Pickup 3500",
|
||||
"ProMaster 1500",
|
||||
|
||||
"RIDGELINE",
|
||||
"RIDGELINE BLACK EDITION",
|
||||
"RIDGELINE DX",
|
||||
"RIDGELINE EX-L",
|
||||
"RIDGELINE LX",
|
||||
"RIDGELINE RT",
|
||||
"RIDGELINE RTL",
|
||||
"RIDGELINE RTS",
|
||||
"RIDGELINE RTX",
|
||||
"RIDGELINE SE",
|
||||
"RIDGELINE SPORT",
|
||||
"RIDGELINE TOURING",
|
||||
"RIDGELINE VP",
|
||||
|
||||
"TITAN",
|
||||
"TACOMA",
|
||||
"TUNDRA",
|
||||
"AVALANCE",
|
||||
"COLORADO",
|
||||
"SILVERADO",
|
||||
"SILVERADO 1500",
|
||||
"SILVERADO 2500",
|
||||
"SILVERADO 3500",
|
||||
"DAKOTA",
|
||||
"RAM 1500",
|
||||
"RAM 2500",
|
||||
"RAM 3500",
|
||||
"RAM 4500",
|
||||
"RAM 5500",
|
||||
"CANYON",
|
||||
"SIERRA 1500",
|
||||
"SIERRA 2500",
|
||||
"SIERRA 3500",
|
||||
"SONOMA",
|
||||
"1500"
|
||||
]
|
||||
39
server/data/vehicletype/vehicletype.js
Normal file
39
server/data/vehicletype/vehicletype.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const logger = require("../../utils/logger");
|
||||
const TrucksList = require("./trucks.json");
|
||||
const CargoVanList = require("./cargovans.json");
|
||||
const PassengerVanList = require("./passengervans.json");
|
||||
const SuvList = require("./suvs.json");
|
||||
|
||||
|
||||
const vehicletype = async (req, res) => {
|
||||
try {
|
||||
const { model } = req.body;
|
||||
if (!model || model.trim() === "") {
|
||||
res.status(400).json({ success: false, error: "Please provide a model" });
|
||||
} else {
|
||||
|
||||
const type = getVehicleType(model.trim())
|
||||
res.status(200).json({ success: true, ...type });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("vehicletype-error", "ERROR", req?.user?.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
res.status(500).json({ error: error.message, stack: error.stack });
|
||||
}
|
||||
};
|
||||
|
||||
function getVehicleType(model) {
|
||||
const inTrucks = TrucksList.includes(model.toUpperCase());
|
||||
const inPV = PassengerVanList.includes(model.toUpperCase());
|
||||
const inSuv = SuvList.includes(model.toUpperCase());
|
||||
const inCv = CargoVanList.includes(model.toUpperCase());
|
||||
|
||||
if (inTrucks) return { type: "TK", match: true };
|
||||
else if (inPV) return { type: "PC", match: true };
|
||||
else if (inSuv) return { type: "SUV", match: true };
|
||||
else if (inCv) return { type: "VN", match: true };
|
||||
else return { type: "PC", match: false };
|
||||
}
|
||||
exports.default = vehicletype;
|
||||
@@ -35,6 +35,11 @@
|
||||
"headerMargin": "135"
|
||||
},
|
||||
"md_ro_statuses": {
|
||||
"parts_statuses": [
|
||||
"Open",
|
||||
"In Progress",
|
||||
"Completed"
|
||||
],
|
||||
"statuses": [
|
||||
"Open",
|
||||
"Scheduled",
|
||||
@@ -54,6 +59,10 @@
|
||||
"Void"
|
||||
],
|
||||
"default_void": "Void",
|
||||
"parts_active_statuses": [
|
||||
"Open",
|
||||
"In Progress"
|
||||
],
|
||||
"active_statuses": [
|
||||
"Open",
|
||||
"Scheduled",
|
||||
|
||||
@@ -181,7 +181,7 @@ const partsManagementProvisioning = async (req, res) => {
|
||||
headerMargin: DefaultNewShop.logo_img_path.headerMargin
|
||||
},
|
||||
features: {
|
||||
allAccess: true, // TODO: should be false?
|
||||
allAccess: false,
|
||||
partsManagementOnly: true
|
||||
},
|
||||
md_ro_statuses: DefaultNewShop.md_ro_statuses,
|
||||
@@ -191,14 +191,14 @@ const partsManagementProvisioning = async (req, res) => {
|
||||
md_messaging_presets: DefaultNewShop.md_messaging_presets,
|
||||
md_rbac: DefaultNewShop.md_rbac,
|
||||
md_classes: DefaultNewShop.md_classes,
|
||||
md_ins_cos: DefaultNewShop.md_ins_cos, // TODO need?
|
||||
md_categories: DefaultNewShop.md_categories, // TODO need?
|
||||
md_labor_rates: DefaultNewShop.md_labor_rates, // TODO need?
|
||||
md_payment_types: DefaultNewShop.md_payment_types, // TODO need?
|
||||
md_hour_split: DefaultNewShop.md_hour_split, // TODO need?
|
||||
md_ccc_rates: DefaultNewShop.md_ccc_rates, // TODO need?
|
||||
appt_alt_transport: DefaultNewShop.appt_alt_transport, // TODO need?
|
||||
md_jobline_presets: DefaultNewShop.md_jobline_presets, // TODO need?
|
||||
md_ins_cos: DefaultNewShop.md_ins_cos,
|
||||
md_categories: DefaultNewShop.md_categories,
|
||||
md_labor_rates: DefaultNewShop.md_labor_rates,
|
||||
md_payment_types: DefaultNewShop.md_payment_types,
|
||||
md_hour_split: DefaultNewShop.md_hour_split,
|
||||
md_ccc_rates: DefaultNewShop.md_ccc_rates,
|
||||
appt_alt_transport: DefaultNewShop.appt_alt_transport,
|
||||
md_jobline_presets: DefaultNewShop.md_jobline_presets,
|
||||
vendors: {
|
||||
data: p.vendors.map((v) => ({
|
||||
name: v.name,
|
||||
|
||||
@@ -14,10 +14,8 @@ const {
|
||||
} = require("../partsManagement.queries");
|
||||
|
||||
// Defaults
|
||||
const FALLBACK_DEFAULT_ORDER_STATUS = "OPEN";
|
||||
const FALLBACK_DEFAULT_ORDER_STATUS = "Open";
|
||||
|
||||
// Config: include labor lines and labor in totals (default true)
|
||||
const INCLUDE_LABOR = true;
|
||||
/**
|
||||
* Fetches the default order status for a bodyshop.
|
||||
* @param {string} shopId - The bodyshop UUID.
|
||||
@@ -76,8 +74,6 @@ const extractJobData = (rq) => {
|
||||
category: doc.DocumentType || null,
|
||||
classType: doc.DocumentStatus || null,
|
||||
comment: doc.Comment || null,
|
||||
// TODO: This causes the job to be read only in the UI
|
||||
// date_exported: doc.TransmitDateTime || null,
|
||||
asgn_no: asgn.AssignmentNumber || null,
|
||||
asgn_type: asgn.AssignmentType || null,
|
||||
asgn_date: asgn.AssignmentDate || null,
|
||||
@@ -85,7 +81,8 @@ const extractJobData = (rq) => {
|
||||
scheduled_in: ev.RepairEvent?.RequestedPickUpDateTime || null,
|
||||
scheduled_completion: ev.RepairEvent?.TargetCompletionDateTime || null,
|
||||
clm_no: ci.ClaimNum || null,
|
||||
status: ci.ClaimStatus || null,
|
||||
// status: ci.ClaimStatus || null, Proper, setting it default for now
|
||||
status: FALLBACK_DEFAULT_ORDER_STATUS,
|
||||
policy_no: ci.PolicyInfo?.PolicyInfo?.PolicyNum || ci.PolicyInfo?.PolicyNum || null,
|
||||
ded_amt: parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0)
|
||||
};
|
||||
@@ -329,10 +326,6 @@ const extractJobLines = (rq) => {
|
||||
const refinishInfo = line.RefinishLaborInfo || {};
|
||||
const subletInfo = line.SubletInfo || {};
|
||||
|
||||
let jobLineType = "PART";
|
||||
if (Object.keys(subletInfo).length > 0) jobLineType = "SUBLET";
|
||||
else if (Object.keys(laborInfo).length > 0 && Object.keys(partInfo).length === 0) jobLineType = "LABOR";
|
||||
|
||||
const base = {
|
||||
line_no: parseInt(line.LineNum || 0, 10),
|
||||
unq_seq: parseInt(line.UniqueSequenceNum || 0, 10),
|
||||
@@ -341,134 +334,87 @@ const extractJobLines = (rq) => {
|
||||
notes: line.LineMemo || null
|
||||
};
|
||||
|
||||
if (jobLineType === "PART") {
|
||||
const lineOut = { ...base };
|
||||
|
||||
// Manual line flag coercion
|
||||
if (line.ManualLineInd !== undefined) {
|
||||
lineOut.manual_line =
|
||||
line.ManualLineInd === true ||
|
||||
line.ManualLineInd === 1 ||
|
||||
line.ManualLineInd === "1" ||
|
||||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y");
|
||||
} else {
|
||||
lineOut.manual_line = null;
|
||||
}
|
||||
|
||||
// Parts (preferred) or Sublet (fallback when no PartInfo)
|
||||
const hasPart = Object.keys(partInfo).length > 0;
|
||||
const hasSublet = Object.keys(subletInfo).length > 0;
|
||||
|
||||
if (hasPart) {
|
||||
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
|
||||
// Push the part line with ONLY part pricing/fields
|
||||
out.push({
|
||||
...base,
|
||||
part_type: partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null,
|
||||
part_qty: parseFloat(partInfo.Quantity || 0) || 1,
|
||||
oem_partno: partInfo.OEMPartNum || partInfo.PartNum || null,
|
||||
db_price: price,
|
||||
act_price: price,
|
||||
// Tax flag from PartInfo.TaxableInd when provided
|
||||
...(partInfo.TaxableInd !== undefined &&
|
||||
lineOut.part_type = partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null;
|
||||
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
|
||||
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
|
||||
lineOut.db_price = isNaN(price) ? 0 : price;
|
||||
lineOut.act_price = isNaN(price) ? 0 : price;
|
||||
|
||||
// Tax flag from PartInfo.TaxableInd when provided
|
||||
if (
|
||||
partInfo.TaxableInd !== undefined &&
|
||||
(typeof partInfo.TaxableInd === "string" ||
|
||||
typeof partInfo.TaxableInd === "number" ||
|
||||
typeof partInfo.TaxableInd === "boolean")
|
||||
? {
|
||||
tax_part:
|
||||
partInfo.TaxableInd === true ||
|
||||
partInfo.TaxableInd === 1 ||
|
||||
partInfo.TaxableInd === "1" ||
|
||||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y")
|
||||
}
|
||||
: {}),
|
||||
// Manual line flag coercion
|
||||
...(line.ManualLineInd !== undefined
|
||||
? {
|
||||
manual_line:
|
||||
line.ManualLineInd === true ||
|
||||
line.ManualLineInd === 1 ||
|
||||
line.ManualLineInd === "1" ||
|
||||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||
}
|
||||
: { manual_line: null })
|
||||
});
|
||||
|
||||
// If labor is present on the same damage line, split it to a separate LABOR jobline
|
||||
// TODO: Verify with patrick this is desired.
|
||||
if (INCLUDE_LABOR) {
|
||||
const hrs = parseFloat(laborInfo.LaborHours || 0);
|
||||
const amt = parseFloat(laborInfo.LaborAmt || 0);
|
||||
const hasLabor =
|
||||
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
|
||||
(!isNaN(hrs) && hrs !== 0) ||
|
||||
(!isNaN(amt) && amt !== 0);
|
||||
if (hasLabor) {
|
||||
out.push({
|
||||
...base,
|
||||
// tweak unq_seq to avoid collisions in later upserts
|
||||
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 400000,
|
||||
mod_lbr_ty: laborInfo.LaborType || null,
|
||||
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
|
||||
lbr_op: laborInfo.LaborOperation || null,
|
||||
lbr_amt: isNaN(amt) ? 0 : amt,
|
||||
...(line.ManualLineInd !== undefined
|
||||
? {
|
||||
manual_line:
|
||||
line.ManualLineInd === true ||
|
||||
line.ManualLineInd === 1 ||
|
||||
line.ManualLineInd === "1" ||
|
||||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||
}
|
||||
: { manual_line: null })
|
||||
});
|
||||
}
|
||||
) {
|
||||
lineOut.tax_part =
|
||||
partInfo.TaxableInd === true ||
|
||||
partInfo.TaxableInd === 1 ||
|
||||
partInfo.TaxableInd === "1" ||
|
||||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
|
||||
}
|
||||
} else if (jobLineType === "SUBLET") {
|
||||
out.push({
|
||||
...base,
|
||||
part_type: "PAS",
|
||||
part_qty: 1,
|
||||
act_price: parseFloat(subletInfo.SubletAmount || 0),
|
||||
// Manual line flag
|
||||
...(line.ManualLineInd !== undefined
|
||||
? {
|
||||
manual_line:
|
||||
line.ManualLineInd === true ||
|
||||
line.ManualLineInd === 1 ||
|
||||
line.ManualLineInd === "1" ||
|
||||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||
}
|
||||
: { manual_line: null })
|
||||
});
|
||||
} else if (INCLUDE_LABOR) {
|
||||
// Labor-only line (only when enabled)
|
||||
out.push({
|
||||
...base,
|
||||
mod_lbr_ty: laborInfo.LaborType || null,
|
||||
mod_lb_hrs: parseFloat(laborInfo.LaborHours || 0),
|
||||
lbr_op: laborInfo.LaborOperation || null,
|
||||
lbr_amt: parseFloat(laborInfo.LaborAmt || 0),
|
||||
...(line.ManualLineInd !== undefined
|
||||
? {
|
||||
manual_line:
|
||||
line.ManualLineInd === true ||
|
||||
line.ManualLineInd === 1 ||
|
||||
line.ManualLineInd === "1" ||
|
||||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||
}
|
||||
: { manual_line: null })
|
||||
});
|
||||
} else if (hasSublet) {
|
||||
const amt = parseFloat(subletInfo.SubletAmount || 0);
|
||||
lineOut.part_type = "PAS"; // Sublet as parts-as-service
|
||||
lineOut.part_qty = 1;
|
||||
lineOut.act_price = isNaN(amt) ? 0 : amt;
|
||||
}
|
||||
|
||||
// Add a separate refinish labor line if present and enabled
|
||||
if (INCLUDE_LABOR && Object.keys(refinishInfo).length > 0) {
|
||||
const hrs = parseFloat(refinishInfo.LaborHours || 0);
|
||||
const amt = parseFloat(refinishInfo.LaborAmt || 0);
|
||||
if (!isNaN(hrs) || !isNaN(amt)) {
|
||||
out.push({
|
||||
...base,
|
||||
// tweak unq_seq to avoid collisions in later upserts
|
||||
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 500000,
|
||||
line_desc: base.line_desc || "Refinish",
|
||||
mod_lbr_ty: "LAR",
|
||||
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
|
||||
lbr_op: refinishInfo.LaborOperation || null,
|
||||
lbr_amt: isNaN(amt) ? 0 : amt,
|
||||
...(line.ManualLineInd !== undefined
|
||||
? {
|
||||
manual_line:
|
||||
line.ManualLineInd === true ||
|
||||
line.ManualLineInd === 1 ||
|
||||
line.ManualLineInd === "1" ||
|
||||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||
}
|
||||
: { manual_line: null })
|
||||
});
|
||||
}
|
||||
// Primary labor (if present) recorded on the same line
|
||||
const hrs = parseFloat(laborInfo.LaborHours || 0);
|
||||
const amt = parseFloat(laborInfo.LaborAmt || 0);
|
||||
const hasLabor =
|
||||
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
|
||||
(!isNaN(hrs) && hrs !== 0) ||
|
||||
(!isNaN(amt) && amt !== 0);
|
||||
if (hasLabor) {
|
||||
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
|
||||
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
|
||||
lineOut.lbr_op = laborInfo.LaborOperation || null;
|
||||
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
|
||||
}
|
||||
|
||||
// Refinish labor (if present) recorded on the same line using secondary labor fields
|
||||
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
||||
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
||||
const hasRefinish =
|
||||
Object.keys(refinishInfo).length > 0 &&
|
||||
((refinishInfo.LaborType && String(refinishInfo.LaborType).length > 0) ||
|
||||
!isNaN(rHrs) ||
|
||||
!isNaN(rAmt) ||
|
||||
!!refinishInfo.LaborOperation);
|
||||
if (hasRefinish) {
|
||||
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
|
||||
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
|
||||
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
|
||||
// Aggregate refinish labor amount into the total labor amount for the line
|
||||
if (!isNaN(rAmt)) {
|
||||
lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
|
||||
}
|
||||
if (refinishInfo.PaintStagesNum !== undefined) lineOut.paint_stg = refinishInfo.PaintStagesNum;
|
||||
if (refinishInfo.PaintTonesNum !== undefined) lineOut.paint_tone = refinishInfo.PaintTonesNum;
|
||||
}
|
||||
|
||||
out.push(lineOut);
|
||||
}
|
||||
|
||||
return out;
|
||||
@@ -517,14 +463,14 @@ const computeLinesTotal = (joblines = []) => {
|
||||
let parts = 0;
|
||||
let labor = 0;
|
||||
for (const jl of joblines) {
|
||||
if (jl && jl.part_type) {
|
||||
if (jl?.part_type) {
|
||||
const qty = Number.isFinite(jl.part_qty) ? jl.part_qty : 1;
|
||||
const price = Number.isFinite(jl.act_price) ? jl.act_price : 0;
|
||||
parts += price * (qty || 1);
|
||||
} else if (!jl.part_type && Number.isFinite(jl.act_price)) {
|
||||
parts += jl.act_price;
|
||||
}
|
||||
if (INCLUDE_LABOR && Number.isFinite(jl.lbr_amt)) {
|
||||
if (Number.isFinite(jl.lbr_amt)) {
|
||||
labor += jl.lbr_amt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,22 +6,23 @@ const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
||||
const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
|
||||
|
||||
const {
|
||||
GET_JOB_BY_CLAIM,
|
||||
GET_JOB_BY_ID,
|
||||
UPDATE_JOB_BY_ID,
|
||||
DELETE_JOBLINES_BY_IDS,
|
||||
INSERT_JOBLINES
|
||||
SOFT_DELETE_JOBLINES_BY_IDS,
|
||||
INSERT_JOBLINES,
|
||||
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ
|
||||
} = require("../partsManagement.queries");
|
||||
|
||||
/**
|
||||
* Finds a job by shop ID and claim number.
|
||||
* @param shopId
|
||||
* @param claimNum
|
||||
* @param jobId
|
||||
* @param logger
|
||||
* @returns {Promise<*|null>}
|
||||
*/
|
||||
const findJob = async (shopId, claimNum, logger) => {
|
||||
const findJob = async (shopId, jobId, logger) => {
|
||||
try {
|
||||
const { jobs } = await client.request(GET_JOB_BY_CLAIM, { shopid: shopId, clm_no: claimNum });
|
||||
const { jobs } = await client.request(GET_JOB_BY_ID, { shopid: shopId, jobid: jobId });
|
||||
return jobs?.[0] || null;
|
||||
} catch (err) {
|
||||
logger.log("parts-job-lookup-failed", "error", null, null, { error: err });
|
||||
@@ -56,13 +57,13 @@ const extractUpdatedJobData = (rq) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts updated job lines from the request payload, mirroring the AddRq splitting rules:
|
||||
* - PART lines carry only part pricing (act_price) and related fields
|
||||
* - If LaborInfo exists on a part line, add a separate LABOR line at unq_seq + 400000
|
||||
* - If RefinishLaborInfo exists, add a separate LABOR line at unq_seq + 500000 with mod_lbr_ty=LAR
|
||||
* - SUBLET lines become PAS part_type with act_price=SubletAmount
|
||||
* Extracts updated job lines from the request payload without splitting parts and labor:
|
||||
* - Keep part and labor on the same jobline
|
||||
* - Aggregate RefinishLabor into secondary labor fields and add its amount to lbr_amt
|
||||
* - SUBLET-only lines become PAS part_type with act_price = SubletAmount
|
||||
* Accepts currentJobLineNotes map for notes merging.
|
||||
*/
|
||||
const extractUpdatedJobLines = (addsChgs = {}, jobId) => {
|
||||
const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {}) => {
|
||||
const linesIn = Array.isArray(addsChgs.DamageLineInfo) ? addsChgs.DamageLineInfo : [addsChgs.DamageLineInfo || {}];
|
||||
|
||||
const coerceManual = (val) =>
|
||||
@@ -84,78 +85,99 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId) => {
|
||||
unq_seq: parseInt(line.UniqueSequenceNum || 0, 10),
|
||||
status: line.LineStatusCode || null,
|
||||
line_desc: line.LineDesc || null,
|
||||
notes: line.LineMemo || null,
|
||||
// notes will be set below
|
||||
manual_line: line.ManualLineInd !== undefined ? coerceManual(line.ManualLineInd) : null
|
||||
};
|
||||
|
||||
const lineOut = { ...base };
|
||||
|
||||
// --- Notes merge logic ---
|
||||
const unqSeq = lineOut.unq_seq;
|
||||
const currentNotes = currentJobLineNotes?.[unqSeq] || null;
|
||||
const newNotes = line.LineMemo || null;
|
||||
if (newNotes && currentNotes) {
|
||||
if (currentNotes === newNotes) {
|
||||
lineOut.notes = currentNotes;
|
||||
} else if (currentNotes.includes(newNotes)) {
|
||||
lineOut.notes = currentNotes;
|
||||
} else {
|
||||
lineOut.notes = `${currentNotes} | ${newNotes}`;
|
||||
}
|
||||
} else if (newNotes) {
|
||||
lineOut.notes = newNotes;
|
||||
} else if (currentNotes) {
|
||||
lineOut.notes = currentNotes;
|
||||
} else {
|
||||
lineOut.notes = null;
|
||||
}
|
||||
// --- End notes merge logic ---
|
||||
|
||||
const hasPart = Object.keys(partInfo).length > 0;
|
||||
const hasLaborOnly = Object.keys(laborInfo).length > 0 && !hasPart && Object.keys(subletInfo).length === 0;
|
||||
const hasSublet = Object.keys(subletInfo).length > 0;
|
||||
|
||||
if (hasPart) {
|
||||
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
|
||||
out.push({
|
||||
...base,
|
||||
part_type: partInfo.PartType ? String(partInfo.PartType).toUpperCase() : null,
|
||||
part_qty: parseFloat(partInfo.Quantity || 0) || 1,
|
||||
oem_partno: partInfo.OEMPartNum || partInfo.PartNum || null,
|
||||
db_price: isNaN(price) ? 0 : price,
|
||||
act_price: isNaN(price) ? 0 : price
|
||||
});
|
||||
lineOut.part_type = partInfo.PartType ? String(partInfo.PartType).toUpperCase() : null;
|
||||
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
|
||||
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
|
||||
lineOut.db_price = isNaN(price) ? 0 : price;
|
||||
lineOut.act_price = isNaN(price) ? 0 : price;
|
||||
|
||||
// Split any attached labor on the part line into a derived labor jobline
|
||||
const hrs = parseFloat(laborInfo.LaborHours || 0);
|
||||
const amt = parseFloat(laborInfo.LaborAmt || 0);
|
||||
const hasLabor =
|
||||
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
|
||||
(!isNaN(hrs) && hrs !== 0) ||
|
||||
(!isNaN(amt) && amt !== 0);
|
||||
if (hasLabor) {
|
||||
out.push({
|
||||
...base,
|
||||
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 400000,
|
||||
mod_lbr_ty: laborInfo.LaborType || null,
|
||||
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
|
||||
lbr_op: laborInfo.LaborOperation || null,
|
||||
lbr_amt: isNaN(amt) ? 0 : amt
|
||||
});
|
||||
// Optional: taxability flag for parts
|
||||
if (
|
||||
partInfo.TaxableInd !== undefined &&
|
||||
(typeof partInfo.TaxableInd === "string" ||
|
||||
typeof partInfo.TaxableInd === "number" ||
|
||||
typeof partInfo.TaxableInd === "boolean")
|
||||
) {
|
||||
lineOut.tax_part =
|
||||
partInfo.TaxableInd === true ||
|
||||
partInfo.TaxableInd === 1 ||
|
||||
partInfo.TaxableInd === "1" ||
|
||||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
|
||||
}
|
||||
} else if (hasSublet) {
|
||||
out.push({
|
||||
...base,
|
||||
part_type: "PAS",
|
||||
part_qty: 1,
|
||||
act_price: parseFloat(subletInfo.SubletAmount || 0) || 0
|
||||
});
|
||||
const amt = parseFloat(subletInfo.SubletAmount || 0);
|
||||
lineOut.part_type = "PAS";
|
||||
lineOut.part_qty = 1;
|
||||
lineOut.act_price = isNaN(amt) ? 0 : amt;
|
||||
}
|
||||
|
||||
// Labor-only line (no PartInfo): still upsert as a labor entry
|
||||
if (hasLaborOnly) {
|
||||
out.push({
|
||||
...base,
|
||||
mod_lbr_ty: laborInfo.LaborType || null,
|
||||
mod_lb_hrs: parseFloat(laborInfo.LaborHours || 0) || 0,
|
||||
lbr_op: laborInfo.LaborOperation || null,
|
||||
lbr_amt: parseFloat(laborInfo.LaborAmt || 0) || 0
|
||||
});
|
||||
// Primary labor on same line
|
||||
const hrs = parseFloat(laborInfo.LaborHours || 0);
|
||||
const amt = parseFloat(laborInfo.LaborAmt || 0);
|
||||
const hasLabor =
|
||||
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
|
||||
(!isNaN(hrs) && hrs !== 0) ||
|
||||
(!isNaN(amt) && amt !== 0);
|
||||
if (hasLabor) {
|
||||
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
|
||||
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
|
||||
lineOut.lbr_op = laborInfo.LaborOperation || null;
|
||||
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
|
||||
}
|
||||
|
||||
// Separate refinish labor line
|
||||
if (Object.keys(refinishInfo).length > 0) {
|
||||
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
||||
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
||||
if (!isNaN(rHrs) || !isNaN(rAmt)) {
|
||||
out.push({
|
||||
...base,
|
||||
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 500000,
|
||||
line_desc: base.line_desc || "Refinish",
|
||||
mod_lbr_ty: "LAR",
|
||||
mod_lb_hrs: isNaN(rHrs) ? 0 : rHrs,
|
||||
lbr_op: refinishInfo.LaborOperation || null,
|
||||
lbr_amt: isNaN(rAmt) ? 0 : rAmt
|
||||
});
|
||||
// Refinish labor on same line using secondary fields; aggregate amount into lbr_amt
|
||||
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
||||
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
||||
const hasRefinish =
|
||||
Object.keys(refinishInfo).length > 0 &&
|
||||
((refinishInfo.LaborType && String(refinishInfo.LaborType).length > 0) ||
|
||||
!isNaN(rHrs) ||
|
||||
!isNaN(rAmt) ||
|
||||
!!refinishInfo.LaborOperation);
|
||||
if (hasRefinish) {
|
||||
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
|
||||
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
|
||||
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
|
||||
if (!isNaN(rAmt)) {
|
||||
lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
|
||||
}
|
||||
if (refinishInfo.PaintStagesNum !== undefined) lineOut.paint_stg = refinishInfo.PaintStagesNum;
|
||||
if (refinishInfo.PaintTonesNum !== undefined) lineOut.paint_tone = refinishInfo.PaintTonesNum;
|
||||
}
|
||||
|
||||
out.push(lineOut);
|
||||
}
|
||||
|
||||
return out;
|
||||
@@ -192,28 +214,42 @@ const partsManagementVehicleDamageEstimateChgRq = async (req, res) => {
|
||||
if (!rq) return res.status(400).send("Missing <VehicleDamageEstimateChgRq>");
|
||||
|
||||
const shopId = rq.ShopID;
|
||||
const claimNum = rq.ClaimInfo?.ClaimNum;
|
||||
const jobId = rq.JobID;
|
||||
|
||||
if (!shopId || !claimNum) return res.status(400).send("Missing ShopID or ClaimNum");
|
||||
if (!shopId || !jobId) return res.status(400).send("Missing ShopID or JobID");
|
||||
|
||||
const job = await findJob(shopId, jobId, logger);
|
||||
|
||||
const job = await findJob(shopId, claimNum, logger);
|
||||
if (!job) return res.status(404).send("Job not found");
|
||||
|
||||
// --- Get updated lines and their unq_seq ---
|
||||
const linesIn = Array.isArray(rq.AddsChgs?.DamageLineInfo)
|
||||
? rq.AddsChgs.DamageLineInfo
|
||||
: [rq.AddsChgs?.DamageLineInfo || {}];
|
||||
const updatedSeqs = Array.from(
|
||||
new Set((linesIn || []).map((l) => parseInt(l?.UniqueSequenceNum || 0, 10)).filter((v) => Number.isInteger(v)))
|
||||
);
|
||||
let currentJobLineNotes = {};
|
||||
if (updatedSeqs.length > 0) {
|
||||
const resp = await client.request(GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ, { jobid: job.id, unqSeqs: updatedSeqs });
|
||||
if (resp?.joblines) {
|
||||
for (const jl of resp.joblines) {
|
||||
currentJobLineNotes[jl.unq_seq] = jl.notes;
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- End fetch current notes ---
|
||||
|
||||
const updatedJobData = extractUpdatedJobData(rq);
|
||||
const updatedLines = extractUpdatedJobLines(rq.AddsChgs, job.id);
|
||||
const updatedLines = extractUpdatedJobLines(rq.AddsChgs, job.id, currentJobLineNotes);
|
||||
const deletedLineIds = extractDeletions(rq.Deletions);
|
||||
|
||||
await client.request(UPDATE_JOB_BY_ID, { id: job.id, job: updatedJobData });
|
||||
|
||||
// Build a set of unq_seq that will be updated (replaced). We delete them first to avoid duplicates.
|
||||
const updatedSeqs = Array.from(
|
||||
new Set((updatedLines || []).map((l) => l && l.unq_seq).filter((v) => Number.isInteger(v)))
|
||||
);
|
||||
|
||||
if ((deletedLineIds && deletedLineIds.length) || (updatedSeqs && updatedSeqs.length)) {
|
||||
if (deletedLineIds?.length || updatedSeqs?.length) {
|
||||
const allToDelete = Array.from(new Set([...(deletedLineIds || []), ...(updatedSeqs || [])]));
|
||||
if (allToDelete.length) {
|
||||
await client.request(DELETE_JOBLINES_BY_IDS, { jobid: job.id, unqSeqs: allToDelete });
|
||||
await client.request(SOFT_DELETE_JOBLINES_BY_IDS, { jobid: job.id, unqSeqs: allToDelete });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,18 @@ const GET_JOB_BY_CLAIM = `
|
||||
}
|
||||
`;
|
||||
|
||||
const GET_JOB_BY_ID = `
|
||||
query GetJobByID($shopid: uuid!, $jobid: uuid!) {
|
||||
jobs(
|
||||
where: { shopid: { _eq: $shopid }, id: { _eq: $jobid } }
|
||||
order_by: { created_at: desc }
|
||||
limit: 1
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const UPDATE_JOB_BY_ID = `
|
||||
mutation UpdateJobById($id: uuid!, $job: jobs_set_input!) {
|
||||
update_jobs_by_pk(pk_columns: { id: $id }, _set: $job) {
|
||||
@@ -52,50 +64,12 @@ const UPDATE_JOB_BY_ID = `
|
||||
}
|
||||
`;
|
||||
|
||||
const UPSERT_JOBLINES = `
|
||||
mutation UpsertJoblines($joblines: [joblines_insert_input!]!) {
|
||||
insert_joblines(
|
||||
objects: $joblines
|
||||
on_conflict: {
|
||||
constraint: joblines_pkey
|
||||
update_columns: [
|
||||
jobid
|
||||
status
|
||||
line_desc
|
||||
part_type
|
||||
part_qty
|
||||
oem_partno
|
||||
db_price
|
||||
act_price
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
lbr_op
|
||||
lbr_amt
|
||||
notes
|
||||
manual_line
|
||||
]
|
||||
}
|
||||
) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DELETE_JOBLINES_BY_JOBID = `
|
||||
mutation DeleteJoblinesByJobId($jobid: uuid!) {
|
||||
delete_joblines(where: { jobid: { _eq: $jobid } }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DELETE_JOBLINES_BY_IDS = `
|
||||
mutation DeleteJoblinesByIds($jobid: uuid!, $unqSeqs: [Int!]!) {
|
||||
delete_joblines(
|
||||
where: {
|
||||
jobid: { _eq: $jobid },
|
||||
unq_seq: { _in: $unqSeqs }
|
||||
}
|
||||
// Soft delete joblines by marking removed=true instead of hard-deleting
|
||||
const SOFT_DELETE_JOBLINES_BY_IDS = `
|
||||
mutation SoftDeleteJoblinesByIds($jobid: uuid!, $unqSeqs: [Int!]!) {
|
||||
update_joblines(
|
||||
where: { jobid: { _eq: $jobid }, unq_seq: { _in: $unqSeqs } },
|
||||
_set: { removed: true }
|
||||
) {
|
||||
affected_rows
|
||||
}
|
||||
@@ -233,6 +207,15 @@ const DELETE_AUDIT_TRAIL_BY_SHOP = `
|
||||
}
|
||||
`;
|
||||
|
||||
const GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ = `
|
||||
query GetJoblinesNotesByJobIdUnqSeq($jobid: uuid!, $unqSeqs: [Int!]!) {
|
||||
joblines(where: { jobid: { _eq: $jobid }, unq_seq: { _in: $unqSeqs }, removed: { _neq: true } }) {
|
||||
unq_seq
|
||||
notes
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
GET_BODYSHOP_STATUS,
|
||||
GET_VEHICLE_BY_SHOP_VIN,
|
||||
@@ -240,9 +223,7 @@ module.exports = {
|
||||
INSERT_JOB_WITH_LINES,
|
||||
GET_JOB_BY_CLAIM,
|
||||
UPDATE_JOB_BY_ID,
|
||||
UPSERT_JOBLINES,
|
||||
DELETE_JOBLINES_BY_JOBID,
|
||||
DELETE_JOBLINES_BY_IDS,
|
||||
SOFT_DELETE_JOBLINES_BY_IDS,
|
||||
INSERT_JOBLINES,
|
||||
CHECK_EXTERNAL_SHOP_ID,
|
||||
CREATE_SHOP,
|
||||
@@ -258,5 +239,7 @@ module.exports = {
|
||||
GET_JOBS_BY_SHOP,
|
||||
DELETE_JOBLINES_BY_JOB_IDS,
|
||||
DELETE_JOBS_BY_IDS,
|
||||
DELETE_AUDIT_TRAIL_BY_SHOP
|
||||
DELETE_AUDIT_TRAIL_BY_SHOP,
|
||||
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ,
|
||||
GET_JOB_BY_ID
|
||||
};
|
||||
|
||||
@@ -44,11 +44,20 @@ const generateSignedUploadUrls = async (req, res) => {
|
||||
for (const filename of filenames) {
|
||||
const key = filename;
|
||||
const client = new S3Client({ region: InstanceRegion() });
|
||||
const command = new PutObjectCommand({
|
||||
|
||||
// Check if filename indicates PDF and set content type accordingly
|
||||
const isPdf = filename.toLowerCase().endsWith('.pdf');
|
||||
const commandParams = {
|
||||
Bucket: imgproxyDestinationBucket,
|
||||
Key: key,
|
||||
StorageClass: "INTELLIGENT_TIERING"
|
||||
});
|
||||
};
|
||||
|
||||
if (isPdf) {
|
||||
commandParams.ContentType = "application/pdf";
|
||||
}
|
||||
|
||||
const command = new PutObjectCommand(commandParams);
|
||||
const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 });
|
||||
signedUrls.push({ filename, presignedUrl, key });
|
||||
}
|
||||
|
||||
@@ -73,37 +73,23 @@ const processCanvasRequest = async (req, res) => {
|
||||
// Default width and height
|
||||
const width = isNumber(w) && w > 0 ? w : 500;
|
||||
const height = isNumber(h) && h > 0 ? h : 275;
|
||||
|
||||
const configuration = getChartConfiguration(keys, values, override);
|
||||
|
||||
let canvas = null;
|
||||
let ctx = null;
|
||||
let chart = null;
|
||||
let chartImage = null;
|
||||
|
||||
try {
|
||||
// Create the canvas
|
||||
canvas = new Canvas(width, height);
|
||||
ctx = canvas.getContext("2d");
|
||||
const canvas = new Canvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Render the chart
|
||||
chart = new Chart(ctx, configuration);
|
||||
|
||||
// Generate and send the image
|
||||
chartImage = (await canvas.toBuffer("image/png")).toString("base64");
|
||||
const chartImage = (await canvas.toBuffer("image/png")).toString("base64");
|
||||
res.status(200).send(`data:image/png;base64,${chartImage}`);
|
||||
} catch (error) {
|
||||
// Log the error and send the response
|
||||
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
||||
res.status(500).send("Failed to generate canvas.");
|
||||
res.status(500).send("Error generating canvas");
|
||||
} finally {
|
||||
// Cleanup resources
|
||||
if (chart) {
|
||||
chart.destroy();
|
||||
}
|
||||
ctx = null;
|
||||
canvas = null;
|
||||
chartImage = null;
|
||||
chart?.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,15 +104,18 @@ const enqueueRequest = (req, res) => {
|
||||
};
|
||||
|
||||
const processNextInQueue = async () => {
|
||||
while (requestQueue.length > 0) {
|
||||
const { req, res } = requestQueue.shift();
|
||||
try {
|
||||
await processCanvasRequest(req, res);
|
||||
} catch (err) {
|
||||
console.error("canvas-queue-error", "error", "jsr", null, { error: err.message });
|
||||
try {
|
||||
while (requestQueue.length > 0) {
|
||||
const { req, res } = requestQueue.shift();
|
||||
try {
|
||||
await processCanvasRequest(req, res);
|
||||
} catch (err) {
|
||||
console.error("canvas-queue-error", "error", "jsr", null, { error: err.message });
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
isProcessing = false;
|
||||
};
|
||||
|
||||
exports.canvastest = function (req, res) {
|
||||
@@ -134,7 +123,10 @@ exports.canvastest = function (req, res) {
|
||||
};
|
||||
|
||||
exports.canvas = async (req, res) => {
|
||||
if (isProcessing || !enqueueRequest(req, res)) return;
|
||||
isProcessing = true;
|
||||
processNextInQueue().catch((err) => console.error("canvas-processing-error", { error: err.message }));
|
||||
if (!enqueueRequest(req, res)) return;
|
||||
|
||||
if (!isProcessing) {
|
||||
isProcessing = true;
|
||||
processNextInQueue().catch((err) => console.error("canvas-processing-error", { error: err.message }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ if (typeof PARTS_MANAGEMENT_INTEGRATION_SECRET === "string" && PARTS_MANAGEMENT_
|
||||
* Route to handle Vehicle Damage Estimate Change Request
|
||||
*/
|
||||
router.post(
|
||||
"/parts-management/VehicleDamageEstimateChqRq",
|
||||
"/parts-management/VehicleDamageEstimateChgRq",
|
||||
express.raw({ type: "application/xml", limit: XML_BODY_LIMIT }), // Parse XML body
|
||||
partsManagementIntegrationMiddleware,
|
||||
partsManagementVehicleDamageEstimateChqRq
|
||||
|
||||
@@ -144,6 +144,9 @@ router.post("/emsupload", validateFirebaseIdTokenMiddleware, data.emsUpload);
|
||||
// Redis Cache Routes
|
||||
router.post("/bodyshop-cache", eventAuthorizationMiddleware, updateBodyshopCache);
|
||||
|
||||
// Estimate Scrubber Vehicle Type
|
||||
router.post("/es/vehicletype", data.vehicletype);
|
||||
|
||||
// Health Check for docker-compose-cluster load balancer, only available in development
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
router.get("/health", (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user