Compare commits

...

28 Commits

Author SHA1 Message Date
Patrick Fic
4043bd3d33 Merged in feature/IO-3722-disable-contact-fortellis (pull request #3286)
IO-3722 Remove delivery date for bypass vehicles.

Approved-by: Dave Richer
2026-05-28 18:33:50 +00:00
Patrick Fic
c1c0b35c8f IO-3722 Remove delivery date for bypass vehicles. 2026-05-28 11:32:23 -07:00
Patrick Fic
4fd2f034a3 Merged in feature/IO-3722-disable-contact-fortellis (pull request #3283)
IO-3722 Remove customer lookup by Vehicle Owner.

Approved-by: Dave Richer
2026-05-28 16:55:21 +00:00
Patrick Fic
aa3b303fe9 IO-3722 Remove customer lookup by Vehicle Owner. 2026-05-28 09:53:40 -07:00
Patrick Fic
bd25245290 Merged in feature/IO-3722-disable-contact-fortellis (pull request #3281)
IO-3722 Fix undefined customer ref.
2026-05-27 21:19:02 +00:00
Patrick Fic
468ed23f73 IO-3722 Fix undefined customer ref. 2026-05-27 14:18:31 -07:00
Patrick Fic
6472b053ed Merged in feature/IO-3722-disable-contact-fortellis (pull request #3280)
Resolve inversed if statement.
2026-05-27 19:54:30 +00:00
Patrick Fic
322ebd3bc7 Resolve inversed if statement. 2026-05-27 12:46:09 -07:00
Patrick Fic
169070594c Merged in feature/IO-3722-disable-contact-fortellis (pull request #3279)
IO-3722 Add additional await.
2026-05-27 19:42:38 +00:00
Patrick Fic
0f800c5a4c IO-3722 Add additional await. 2026-05-27 12:40:41 -07:00
Dave Richer
0974e69a50 Merged in feature/IO-3722-disable-contact-fortellis (pull request #3277)
IO-3722 Disable contact API calls for Fortellis.
2026-05-27 18:36:51 +00:00
Patrick FIc
345a470731 IO-3722 Disable contact API calls for Fortellis. 2026-05-27 10:31:33 -07:00
Dave Richer
ebde2f1581 Merged in release/2026-05-22 (pull request #3257)
Release/2026 05 22
2026-05-25 12:45:19 +00:00
Allan Carr
a45808eb94 Merged in feature/IO-3710-Visual-Board-Vehicle-Color (pull request #3255)
IO-3710 Visual Board Vehicle Color

Approved-by: Dave Richer
2026-05-20 23:57:28 +00:00
Allan Carr
a2389b1f26 IO-3710 Visual Board Vehicle Color
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-05-20 13:42:35 -07:00
Dave
ab606a4266 release/2026-05-22 - Remove uncessary require 2026-05-20 14:46:52 -04:00
Patrick Fic
da317704c4 Merged in feature/IO-3712-disable-analytics (pull request #3252)
IO-3712 Disable analytics in client side.

Approved-by: Dave Richer
2026-05-20 18:10:39 +00:00
Patrick Fic
771573409f IO-3712 Disable analytics in client side. 2026-05-20 10:48:56 -07:00
Allan Carr
cb9ccb7e77 Merged in feature/IO-3707-Deduct-From-Labor-Enhanced-Payrol (pull request #3249)
IO-3707 Deduct from Labor Enhanced Payroll

Approved-by: Dave Richer
2026-05-20 16:54:09 +00:00
Allan Carr
a5d00d562c Merged in feature/IO-3699-Alt-Part-No-Expose (pull request #3250)
Feature/IO-3699 Alt Part No Expose

Approved-by: Dave Richer
2026-05-20 16:53:36 +00:00
Allan Carr
bdeeea0406 Merged in feature/IO-3710-Visual-Board-Vehicle-Color (pull request #3248)
IO-3710 Visual Board - Vehicle Color

Approved-by: Dave Richer
2026-05-20 16:52:10 +00:00
Allan Carr
297d8afa8a IO-3699 Prettier Run
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-05-19 18:44:36 -07:00
Allan Carr
3a12597c45 IO-3699 Alt Part # Exposed
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-05-19 18:43:44 -07:00
Allan Carr
72c96f14eb IO-3707 Prettier Run
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-05-19 18:35:26 -07:00
Allan Carr
de9d47272c IO-3707 Deduct from Labor Enhanced Payroll
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-05-19 18:34:19 -07:00
Allan Carr
3fd51f0140 IO-3710 Visual Board - Vehicle Color
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-05-19 16:01:38 -07:00
Dave
84ec68f142 release/2026-05-22 - Remove uncessary require 2026-05-14 11:03:40 -04:00
Patrick Fic
22af37e8f1 Merged in feature/IO-3692-remove-patrick (pull request #3246)
IO-3692 Remove patrick's email from all codebase.

Approved-by: Dave Richer
2026-05-14 15:02:06 +00:00
16 changed files with 334 additions and 279 deletions

View File

@@ -1,6 +1,6 @@
import { useSplitClient } from "@splitsoftware/splitio-react"; import { useSplitClient } from "@splitsoftware/splitio-react";
import { Button, Result } from "antd"; import { Button, Result } from "antd";
import LogRocket from "logrocket"; //import LogRocket from "logrocket";
import { lazy, Suspense, useEffect, useState } from "react"; import { lazy, Suspense, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -101,13 +101,13 @@ export function App({
client.setAttribute("imexshopid", bodyshop.imexshopid); client.setAttribute("imexshopid", bodyshop.imexshopid);
if (client.getTreatment("LogRocket_Tracking") === "on") { if (client.getTreatment("LogRocket_Tracking") === "on") {
console.log("LR Start"); // console.log("LR Start");
LogRocket.init( // LogRocket.init(
InstanceRenderMgr({ // InstanceRenderMgr({
imex: "gvfvfw/bodyshopapp", // imex: "gvfvfw/bodyshopapp",
rome: "rome-online/rome-online" // rome: "rome-online/rome-online"
}) // })
); // );
} }
} }
}, [bodyshop, client, currentUser.authorized]); }, [bodyshop, client, currentUser.authorized]);

View File

@@ -495,7 +495,9 @@ export function BillEnterModalLinesComponent({
{Enhanced_Payroll.treatment === "on" ? ( {Enhanced_Payroll.treatment === "on" ? (
<Space> <Space>
{t("joblines.fields.assigned_team", { name: employeeTeamName?.name })} {t("joblines.fields.assigned_team", { name: employeeTeamName?.name })}
{`${jobline.mod_lb_hrs} units/${t(`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`)}`} {jobline
? `${jobline.mod_lb_hrs} units/${t(`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`)}`
: null}
</Space> </Space>
) : null} ) : null}
@@ -506,10 +508,7 @@ export function BillEnterModalLinesComponent({
rules={[{ required: true }]} rules={[{ required: true }]}
name={[record.name, "lbr_adjustment", "mod_lbr_ty"]} name={[record.name, "lbr_adjustment", "mod_lbr_ty"]}
> >
<Select <Select allowClear options={CiecaSelect(false, true)} />
allowClear
options={CiecaSelect(false, true)}
/>
</Form.Item> </Form.Item>
{Enhanced_Payroll.treatment === "on" ? ( {Enhanced_Payroll.treatment === "on" ? (

View File

@@ -67,22 +67,25 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow> <LayoutFormRow grow>
<Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty"> <Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty">
<Select allowClear options={[ <Select
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") }, allowClear
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") }, options={[
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") }, { value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") }, { value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") }, { value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") }, { value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") }, { value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") }, { value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") }, { value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") }, { value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") }, { value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") }, { value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") }, { value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") } { value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
]} /> { value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
]}
/>
</Form.Item> </Form.Item>
<Form.Item label={t("joblines.fields.op_code_desc")} name="op_code_desc"> <Form.Item label={t("joblines.fields.op_code_desc")} name="op_code_desc">
<Input /> <Input />
@@ -128,21 +131,27 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("joblines.fields.part_type")} name="part_type"> <Form.Item label={t("joblines.fields.part_type")} name="part_type">
<Select allowClear options={[ <Select
{ value: "PAA", label: t("joblines.fields.part_types.PAA") }, allowClear
{ value: "PAC", label: t("joblines.fields.part_types.PAC") }, options={[
{ value: "PAE", label: t("joblines.fields.part_types.PAE") }, { value: "PAA", label: t("joblines.fields.part_types.PAA") },
{ value: "PAL", label: t("joblines.fields.part_types.PAL") }, { value: "PAC", label: t("joblines.fields.part_types.PAC") },
{ value: "PAM", label: t("joblines.fields.part_types.PAM") }, { value: "PAE", label: t("joblines.fields.part_types.PAE") },
{ value: "PAN", label: t("joblines.fields.part_types.PAN") }, { value: "PAL", label: t("joblines.fields.part_types.PAL") },
{ value: "PAO", label: t("joblines.fields.part_types.PAO") }, { value: "PAM", label: t("joblines.fields.part_types.PAM") },
{ value: "PAR", label: t("joblines.fields.part_types.PAR") }, { value: "PAN", label: t("joblines.fields.part_types.PAN") },
{ value: "PAS", label: t("joblines.fields.part_types.PAS") } { value: "PAO", label: t("joblines.fields.part_types.PAO") },
]} /> { value: "PAR", label: t("joblines.fields.part_types.PAR") },
{ value: "PAS", label: t("joblines.fields.part_types.PAS") }
]}
/>
</Form.Item> </Form.Item>
<Form.Item label={t("joblines.fields.oem_partno")} name="oem_partno"> <Form.Item label={t("joblines.fields.oem_partno")} name="oem_partno">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("joblines.fields.alt_partno")} name="alt_partno">
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.part_qty")} label={t("joblines.fields.part_qty")}
name="part_qty" name="part_qty"

View File

@@ -80,14 +80,14 @@ const ModelInfoToolTip = ({ metadata, cardSettings }) =>
<Col span={24}> <Col span={24}>
<EllipsesToolTip <EllipsesToolTip
title={ title={
metadata.v_model_yr || metadata.v_make_desc || metadata.v_model_desc metadata.v_model_yr || metadata.v_make_desc || metadata.v_model_desc || metadata.v_color
? `${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}` ? `${metadata.v_model_yr || ""} ${metadata.v_color || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`
: null : null
} }
kiosk={cardSettings.kiosk} kiosk={cardSettings.kiosk}
> >
{metadata.v_model_yr || metadata.v_make_desc || metadata.v_model_desc ? ( {metadata.v_model_yr || metadata.v_make_desc || metadata.v_model_desc || metadata.v_color ? (
`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}` `${metadata.v_model_yr || ""} ${metadata.v_color || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`
) : ( ) : (
<span>&nbsp;</span> <span>&nbsp;</span>
)} )}

View File

@@ -140,13 +140,11 @@ const productionListColumnsData = ({ technician, state, activeStatuses, data, bo
sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
technician ? ( technician ? (
<>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ <>{`${record.v_model_yr || ""} ${record.v_color || ""}${record.v_make_desc || ""} ${record.v_model_desc || ""} ${record.plate_no || ""}`}</>
record.v_color || ""
} ${record.plate_no || ""}`}</>
) : ( ) : (
<Link to={`/manage/vehicles/${record.vehicleid}`}>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ <Link
record.v_model_desc || "" to={`/manage/vehicles/${record.vehicleid}`}
} ${record.v_color || ""} ${record.plate_no || ""}`}</Link> >{`${record.v_model_yr || ""} ${record.v_color || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${record.plate_no || ""}`}</Link>
) )
}, },
{ {
@@ -621,7 +619,7 @@ const productionListColumnsData = ({ technician, state, activeStatuses, data, bo
sortOrder: state.sortedInfo.columnKey === "dms_id" && state.sortedInfo.order sortOrder: state.sortedInfo.columnKey === "dms_id" && state.sortedInfo.order
} }
] ]
: []), : [])
]; ];
}; };
export default productionListColumnsData; export default productionListColumnsData;

View File

@@ -157,36 +157,36 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Col> </Col>
{HasFeatureAccess({ featureName: "export", bodyshop }) && {HasFeatureAccess({ featureName: "export", bodyshop }) &&
ClosingPeriod.treatment === "on" && ( ClosingPeriod.treatment === "on" && (
<Col xs={24} sm={12} xl={8}> <Col xs={24} sm={12} xl={8}>
<Form.Item <Form.Item
key="ClosingPeriod" key="ClosingPeriod"
name={["accountingconfig", "ClosingPeriod"]} name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} label={t("bodyshop.fields.closingperiod")}
> >
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} /> <DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
</Form.Item> </Form.Item>
</Col> </Col>
)} )}
{HasFeatureAccess({ featureName: "export", bodyshop }) && {HasFeatureAccess({ featureName: "export", bodyshop }) &&
ADPPayroll.treatment === "on" && ( ADPPayroll.treatment === "on" && (
<Col xs={24} sm={12} xl={8}> <Col xs={24} sm={12} xl={8}>
<Form.Item <Form.Item
key="companyCode" key="companyCode"
name={["accountingconfig", "companyCode"]} name={["accountingconfig", "companyCode"]}
label={t("bodyshop.fields.companycode")} label={t("bodyshop.fields.companycode")}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
</Col> </Col>
)} )}
{HasFeatureAccess({ featureName: "export", bodyshop }) && {HasFeatureAccess({ featureName: "export", bodyshop }) &&
ADPPayroll.treatment === "on" && ( ADPPayroll.treatment === "on" && (
<Col xs={24} sm={12} xl={8}> <Col xs={24} sm={12} xl={8}>
<Form.Item key="batchID" name={["accountingconfig", "batchID"]} label={t("bodyshop.fields.batchid")}> <Form.Item key="batchID" name={["accountingconfig", "batchID"]} label={t("bodyshop.fields.batchid")}>
<Input /> <Input />
</Form.Item> </Form.Item>
</Col> </Col>
)} )}
{HasFeatureAccess({ featureName: "export", bodyshop }) && !hasDMSKey && ( {HasFeatureAccess({ featureName: "export", bodyshop }) && !hasDMSKey && (
<> <>
<Col xs={24} sm={12} xl={8}> <Col xs={24} sm={12} xl={8}>
@@ -512,6 +512,15 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
> >
<InputNumber min={0} max={100} suffix="%" /> <InputNumber min={0} max={100} suffix="%" />
</Form.Item> </Form.Item>
{bodyshop.cdk_dealerid && (
<Form.Item
label={t("bodyshop.fields.dms.disablecontact")}
valuePropName="checked"
name={["cdk_configuration", "disablecontact"]}
>
<Switch />
</Form.Item>
)}
{bodyshop.pbs_serialnumber && ( {bodyshop.pbs_serialnumber && (
<Form.Item <Form.Item
label={t("bodyshop.fields.dms.disablecontactvehiclecreation")} label={t("bodyshop.fields.dms.disablecontactvehiclecreation")}

View File

@@ -44,9 +44,7 @@ export default function VehiclesListComponent({ loading, vehicles, total, refetc
key: "description", key: "description",
render: (text, record) => { render: (text, record) => {
return ( return (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ <span>{`${record.v_model_yr || ""} ${record.v_color || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""} `}</span>
record.v_model_desc || ""
} ${record.v_color || ""}`}</span>
); );
} }
}, },
@@ -111,7 +109,13 @@ export default function VehiclesListComponent({ loading, vehicles, total, refetc
> >
<ResponsiveTable <ResponsiveTable
loading={loading} loading={loading}
pagination={{ placement: "top", pageSize: currentPageSize, current: currentPage, showSizeChanger: true, total: total }} pagination={{
placement: "top",
pageSize: currentPageSize,
current: currentPage,
showSizeChanger: true,
total: total
}}
columns={columns} columns={columns}
mobileColumnKeys={["v_vin", "description", "plate_no"]} mobileColumnKeys={["v_vin", "description", "plate_no"]}
rowKey="id" rowKey="id"

View File

@@ -1,9 +1,9 @@
import { getAnalytics, logEvent } from "@firebase/analytics"; //import { getAnalytics, logEvent } from "@firebase/analytics";
import { initializeApp } from "@firebase/app"; import { initializeApp } from "@firebase/app";
import { getAuth, updatePassword, updateProfile } from "@firebase/auth"; import { getAuth, updatePassword, updateProfile } from "@firebase/auth";
import { getFirestore } from "@firebase/firestore"; import { getFirestore } from "@firebase/firestore";
import { getMessaging, getToken, onMessage } from "@firebase/messaging"; import { getMessaging, getToken, onMessage } from "@firebase/messaging";
import { store } from "../redux/store"; //import { store } from "../redux/store";
//import * as amplitude from '@amplitude/analytics-browser'; //import * as amplitude from '@amplitude/analytics-browser';
// import posthog from 'posthog-js' // import posthog from 'posthog-js'
@@ -12,7 +12,7 @@ initializeApp(config);
export const auth = getAuth(); export const auth = getAuth();
export const firestore = getFirestore(); export const firestore = getFirestore();
export const analytics = getAnalytics(); //export const analytics = getAnalytics();
//export default firebase; //export default firebase;
export const getCurrentUser = () => { export const getCurrentUser = () => {
@@ -72,34 +72,36 @@ onMessage(messaging, (payload) => {
// ... // ...
}); });
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { // eslint-disable-next-line no-unused-vars
try { export const logImEXEvent = (eventName, additionalParams, _stateProp = null) => {
const state = stateProp || store.getState(); // Disabled as a part of IO-3712.
// try {
// const state = stateProp || store.getState();
const eventParams = { // const eventParams = {
shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null, // shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null, // user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
partsManagementOnly: state?.user?.partsManagementOnly, // partsManagementOnly: state?.user?.partsManagementOnly,
...additionalParams // ...additionalParams
}; // };
// axios.post("/ioevent", { // // axios.post("/ioevent", {
// useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null, // // useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
// bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null, // // bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
// operationName: eventName, // // operationName: eventName,
// variables: additionalParams, // // variables: additionalParams,
// dbevent: false, // // dbevent: false,
// env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}` // // env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
// }); // // });
// console.log( // // console.log(
// "%c[Analytics]", // // "%c[Analytics]",
// "background-color: green ;font-weight:bold;", // // "background-color: green ;font-weight:bold;",
// eventName, // // eventName,
// eventParams // // eventParams
// ); // // );
logEvent(analytics, eventName, eventParams); // logEvent(analytics, eventName, eventParams);
//amplitude.track(eventName, eventParams); // //amplitude.track(eventName, eventParams);
//posthog.capture(eventName, eventParams); // //posthog.capture(eventName, eventParams);
} finally { // } finally {
//If it fails, just keep going. // //If it fails, just keep going.
} // }
}; };

View File

@@ -14,8 +14,8 @@ import reportWebVitals from "./reportWebVitals";
import "./translations/i18n"; import "./translations/i18n";
import "./utils/CleanAxios"; import "./utils/CleanAxios";
// import * as amplitude from "@amplitude/analytics-browser"; // import * as amplitude from "@amplitude/analytics-browser";
import { PostHogProvider } from "posthog-js/react"; //import { PostHogProvider } from "posthog-js/react";
import posthog from "posthog-js"; //import posthog from "posthog-js";
import { StrictMode } from "react"; import { StrictMode } from "react";
window.global ||= window; window.global ||= window;
@@ -44,11 +44,11 @@ Dinero.globalRoundingMode = "HALF_EVEN";
// // } // // }
// }); // });
posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, { // posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
autocapture: false, // autocapture: false,
capture_exceptions: true, // capture_exceptions: true,
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST // api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST
}); // });
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter); const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);
@@ -70,9 +70,7 @@ function App() {
return ( return (
<Provider store={store}> <Provider store={store}>
<PersistGate loading={<LoadingSpinner message="Restoring your settings..." />} persistor={persistor}> <PersistGate loading={<LoadingSpinner message="Restoring your settings..." />} persistor={persistor}>
<PostHogProvider client={posthog}>
<RouterProvider router={router} /> <RouterProvider router={router} />
</PostHogProvider>
</PersistGate> </PersistGate>
</Provider> </Provider>
); );

View File

@@ -1,5 +1,5 @@
import FingerprintJS from "@fingerprintjs/fingerprintjs"; import FingerprintJS from "@fingerprintjs/fingerprintjs";
import { setUserId, setUserProperties } from "@firebase/analytics"; //import { setUserId, setUserProperties } from "@firebase/analytics";
import { import {
checkActionCode, checkActionCode,
confirmPasswordReset, confirmPasswordReset,
@@ -9,14 +9,13 @@ import {
} from "@firebase/auth"; } from "@firebase/auth";
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "@firebase/firestore"; import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "@firebase/firestore";
import { getToken } from "@firebase/messaging"; import { getToken } from "@firebase/messaging";
import * as Sentry from "@sentry/react"; // import * as Sentry from "@sentry/react";
import { notification } from "antd"; import { notification } from "antd";
import axios from "axios"; import axios from "axios";
import i18next from "i18next"; import i18next from "i18next";
import LogRocket from "logrocket"; //import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects"; import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import { import {
analytics,
auth, auth,
firestore, firestore,
getCurrentUser, getCurrentUser,
@@ -49,7 +48,7 @@ import {
validatePasswordResetSuccess validatePasswordResetSuccess
} from "./user.actions"; } from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
import posthog from "posthog-js"; //import posthog from "posthog-js";
import { bodyshopHasDmsKey, determineDMSTypeByBodyshop, DMS_MAP } from "../../utils/dmsUtils"; import { bodyshopHasDmsKey, determineDMSTypeByBodyshop, DMS_MAP } from "../../utils/dmsUtils";
const fpPromise = FingerprintJS.load(); const fpPromise = FingerprintJS.load();
@@ -91,9 +90,9 @@ export function* isUserAuthenticated() {
return; return;
} }
LogRocket.identify(user.email); //LogRocket.identify(user.email);
//amplitude.setUserId(user.email); //amplitude.setUserId(user.email);
posthog.identify(user.email); //posthog.identify(user.email);
const eulaQuery = yield client.query({ const eulaQuery = yield client.query({
query: QUERY_EULA, query: QUERY_EULA,
@@ -234,7 +233,7 @@ export function* onSignInSuccess() {
} }
export function* signInSuccessSaga({ payload }) { export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email); //LogRocket.identify(payload.email);
try { try {
window.$crisp?.push(["set", "user:nickname", [payload.displayName || payload.email]]); window.$crisp?.push(["set", "user:nickname", [payload.displayName || payload.email]]);
@@ -279,17 +278,17 @@ export function* signInSuccessSaga({ payload }) {
console.log("Error updating Crisp settings.", error); console.log("Error updating Crisp settings.", error);
} }
try { // try {
Sentry.setUser({ // Sentry.setUser({
email: payload.email, // email: payload.email,
username: payload.displayName || payload.email // username: payload.displayName || payload.email
}); // });
} catch (error) { // } catch (error) {
console.log("Error setting Sentry user.", error); // console.log("Error setting Sentry user.", error);
} // }
setUserId(analytics, payload.email); // setUserId(analytics, payload.email);
setUserProperties(analytics, payload); // setUserProperties(analytics, payload);
yield; yield;
} }

View File

@@ -370,6 +370,7 @@
"cashierid": "Cashier ID", "cashierid": "Cashier ID",
"default_journal": "Default Journal", "default_journal": "Default Journal",
"disablebillwip": "Disable bill WIP for A/P Posting", "disablebillwip": "Disable bill WIP for A/P Posting",
"disablecontact": "Disable Contact Updates/Creation",
"disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation", "disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation",
"dms_acctnumber": "DMS Account #", "dms_acctnumber": "DMS Account #",
"dms_control_override": "Static Control # Override", "dms_control_override": "Static Control # Override",

View File

@@ -370,6 +370,7 @@
"cashierid": "", "cashierid": "",
"default_journal": "", "default_journal": "",
"disablebillwip": "", "disablebillwip": "",
"disablecontact": "",
"disablecontactvehiclecreation": "", "disablecontactvehiclecreation": "",
"dms_acctnumber": "", "dms_acctnumber": "",
"dms_control_override": "", "dms_control_override": "",

View File

@@ -370,6 +370,7 @@
"cashierid": "", "cashierid": "",
"default_journal": "", "default_journal": "",
"disablebillwip": "", "disablebillwip": "",
"disablecontact": "",
"disablecontactvehiclecreation": "", "disablecontactvehiclecreation": "",
"dms_acctnumber": "", "dms_acctnumber": "",
"dms_control_override": "", "dms_control_override": "",

View File

@@ -1,73 +1,71 @@
const { isString } = require("lodash"); const { isString } = require("lodash");
const { sendServerEmail } = require("../email/sendemail"); const { sendServerEmail } = require("../email/sendemail");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const { raw } = require("express");
const SUPPORT_EMAIL = "support@imexsystems.ca"; const SUPPORT_EMAIL = "support@imexsystems.ca";
const safeJsonParse = (maybeJson) => { const safeJsonParse = (maybeJson) => {
if (!isString(maybeJson)) return null; if (!isString(maybeJson)) return null;
try { try {
return JSON.parse(maybeJson); return JSON.parse(maybeJson);
} catch { } catch {
return null; return null;
} }
}; };
const handleBillAiFeedback = async (req, res) => { const handleBillAiFeedback = async (req, res) => {
try { try {
const rating = req.body?.rating; const rating = req.body?.rating;
const comments = isString(req.body?.comments) ? req.body?.comments?.trim() : ""; const comments = isString(req.body?.comments) ? req.body?.comments?.trim() : "";
const billFormValues = safeJsonParse(req.body?.billFormValues); const billFormValues = safeJsonParse(req.body?.billFormValues);
const rawAIData = safeJsonParse(req.body?.rawAIData); const rawAIData = safeJsonParse(req.body?.rawAIData);
const jobid = billFormValues?.jobid || billFormValues?.jobId || "unknown"; const jobid = billFormValues?.jobid || billFormValues?.jobId || "unknown";
const shopname = req.body?.shopname || "unknown"; const shopname = req.body?.shopname || "unknown";
const subject = `Bill AI Feedback (${rating === "up" ? "+" : "-"}) Shop=${shopname} jobid=${jobid}`; const subject = `Bill AI Feedback (${rating === "up" ? "+" : "-"}) Shop=${shopname} jobid=${jobid}`;
const text = [ const text = [
`User: ${req?.user?.email || "unknown"}`, `User: ${req?.user?.email || "unknown"}`,
`Rating: ${rating}`, `Rating: ${rating}`,
comments ? `Comments: ${comments}` : "Comments: (none)", comments ? `Comments: ${comments}` : "Comments: (none)",
"", "",
"Form Values (User):", "Form Values (User):",
JSON.stringify(billFormValues, null, 4), JSON.stringify(billFormValues, null, 4),
"", "",
"Raw AI Data:", "Raw AI Data:",
JSON.stringify(rawAIData, null, 4) JSON.stringify(rawAIData, null, 4)
] ]
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
const attachments = []; const attachments = [];
if (req.file?.buffer) { if (req.file?.buffer) {
attachments.push({ attachments.push({
filename: req.file.originalname || `bill-${jobid}.pdf`, filename: req.file.originalname || `bill-${jobid}.pdf`,
content: req.file.buffer, content: req.file.buffer,
contentType: req.file.mimetype || "application/pdf" contentType: req.file.mimetype || "application/pdf"
}); });
}
await sendServerEmail({
to: [SUPPORT_EMAIL],
subject,
type: "text",
text,
attachments
});
return res.json({ success: true });
} catch (error) {
logger.log("bill-ai-feedback-error", "ERROR", req?.user?.email, null, {
message: error?.message,
stack: error?.stack
});
return res.status(500).json({ message: "Failed to submit feedback" });
} }
await sendServerEmail({
to: [SUPPORT_EMAIL],
subject,
type: "text",
text,
attachments
});
return res.json({ success: true });
} catch (error) {
logger.log("bill-ai-feedback-error", "ERROR", req?.user?.email, null, {
message: error?.message,
stack: error?.stack
});
return res.status(500).json({ message: "Failed to submit feedback" });
}
}; };
module.exports = { module.exports = {
handleBillAiFeedback handleBillAiFeedback
}; };

View File

@@ -15,7 +15,7 @@ const _ = require("lodash");
const moment = require("moment-timezone"); const moment = require("moment-timezone");
const replaceSpecialRegex = /[^a-zA-Z0-9 ]+/g; const replaceSpecialRegex = /[^a-zA-Z0-9 ]+/g;
const bypassCustomerId = "bypass";
// Helper function to handle FortellisApiError logging // Helper function to handle FortellisApiError logging
function handleFortellisApiError(socket, error, functionName, additionalDetails = {}) { function handleFortellisApiError(socket, error, functionName, additionalDetails = {}) {
if (error instanceof FortellisApiError) { if (error instanceof FortellisApiError) {
@@ -95,7 +95,8 @@ async function FortellisJobExport({ socket, redisHelpers, txEnvelope, jobid }) {
defaultFortellisTTL defaultFortellisTTL
); );
let DMSVehCustomer; let DMSVehCustomerFromVehicle;
//let DMSVehCustomer;
if (!DMSVid.newId) { if (!DMSVid.newId) {
CreateFortellisLogEvent(socket, "DEBUG", `{2.1} Querying the Vehicle using the DMSVid: ${DMSVid.vehiclesVehId}`); CreateFortellisLogEvent(socket, "DEBUG", `{2.1} Querying the Vehicle using the DMSVid: ${DMSVid.vehiclesVehId}`);
const DMSVeh = await QueryDmsVehicleById({ socket, redisHelpers, JobData, DMSVid }); const DMSVeh = await QueryDmsVehicleById({ socket, redisHelpers, JobData, DMSVid });
@@ -106,46 +107,66 @@ async function FortellisJobExport({ socket, redisHelpers, txEnvelope, jobid }) {
DMSVeh, DMSVeh,
defaultFortellisTTL defaultFortellisTTL
); );
DMSVehCustomerFromVehicle = DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");
const DMSVehCustomerFromVehicle = // //Add in contact bypass for Fortellis.
DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT"); // if (!JobData.bodyshop.cdk_configuration.disablecontact) {
// const DMSVehCustomerFromVehicle =
// DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT");
if (DMSVehCustomerFromVehicle?.id && DMSVehCustomerFromVehicle.id.value) { // if (DMSVehCustomerFromVehicle?.id && DMSVehCustomerFromVehicle.id.value) {
CreateFortellisLogEvent( // CreateFortellisLogEvent(
socket, // socket,
"DEBUG", // "DEBUG",
`{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomerFromVehicle.id.value}` // `{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomerFromVehicle.id.value}`
); // );
DMSVehCustomer = await QueryDmsCustomerById({ // DMSVehCustomer = await QueryDmsCustomerById({
socket, // socket,
redisHelpers, // redisHelpers,
JobData, // JobData,
CustomerId: DMSVehCustomerFromVehicle.id.value // CustomerId: DMSVehCustomerFromVehicle.id.value
}); // });
await setSessionTransactionData( // await setSessionTransactionData(
socket.id, // socket.id,
getTransactionType(jobid), // getTransactionType(jobid),
FortellisCacheEnums.DMSVehCustomer, // FortellisCacheEnums.DMSVehCustomer,
DMSVehCustomer, // DMSVehCustomer,
defaultFortellisTTL // defaultFortellisTTL
); // );
} // }
// }
} }
CreateFortellisLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`); CreateFortellisLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`);
if (JobData.bodyshop.cdk_configuration.disablecontact) {
//Just go straight to posting.
await FortellisSelectedCustomer({ socket, redisHelpers, selectedCustomerId: bypassCustomerId, jobid });
} else {
const DMSCustList = await QueryDmsCustomerByName({ socket, redisHelpers, JobData });
await setSessionTransactionData(
socket.id,
getTransactionType(jobid),
FortellisCacheEnums.DMSCustList,
DMSCustList,
defaultFortellisTTL
);
const DMSCustList = await QueryDmsCustomerByName({ socket, redisHelpers, JobData }); socket.emit("fortellis-select-customer",
await setSessionTransactionData( //Removed to save one one API call while disputing with fortellis.
socket.id, // [
getTransactionType(jobid), // // ...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []),
FortellisCacheEnums.DMSCustList, // ...DMSCustList
DMSCustList, // ]
defaultFortellisTTL DMSVehCustomerFromVehicle ?
); DMSCustList.map(c => {
//if customer id is the same as the current assigned owner on the vehicle id, set it as vinowner true. )
socket.emit("fortellis-select-customer", [ if (DMSVehCustomerFromVehicle?.id?.value === c.customerId) {
...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []), return { ...c, vinOwner: true }
...DMSCustList } else {
]); return c
}
}) : DMSCustList
);
}
} catch (error) { } catch (error) {
CreateFortellisLogEvent(socket, "ERROR", `Error in FortellisJobExport - ${error} `, { CreateFortellisLogEvent(socket, "ERROR", `Error in FortellisJobExport - ${error} `, {
error: error.message, error: error.message,
@@ -218,36 +239,40 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
}); });
return; return;
} }
//Bypass only the customer creation. We still need to create the vehicle and update it to post the service history later on.
let DMSCust; let DMSCust;
if (selectedCustomerId) { if (!JobData.bodyshop.cdk_configuration.disablecontact) {
CreateFortellisLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`); if (selectedCustomerId) {
CreateFortellisLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`);
//Get cust list from Redis. Return the item //Get cust list from Redis. Return the item
const DMSCustList = const DMSCustList =
(await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCustList)) || []; (await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCustList)) || [];
const existingCustomerInDMSCustList = DMSCustList.find((c) => c.customerId === selectedCustomerId); const existingCustomerInDMSCustList = DMSCustList.find((c) => c.customerId === selectedCustomerId);
DMSCust = existingCustomerInDMSCustList || { DMSCust = existingCustomerInDMSCustList || {
customerId: selectedCustomerId //This is the fall back in case it is the generic customer. customerId: selectedCustomerId //This is the fall back in case it is the generic customer.
}; };
await setSessionTransactionData( await setSessionTransactionData(
socket.id, socket.id,
getTransactionType(jobid), getTransactionType(jobid),
FortellisCacheEnums.DMSCust, FortellisCacheEnums.DMSCust,
DMSCust, DMSCust,
defaultFortellisTTL defaultFortellisTTL
); );
} else { } else {
CreateFortellisLogEvent(socket, "DEBUG", `{3.2} Creating new customer.`); CreateFortellisLogEvent(socket, "DEBUG", `{3.2} Creating new customer.`);
const DMSCustomerInsertResponse = await InsertDmsCustomer({ socket, redisHelpers, JobData }); const DMSCustomerInsertResponse = await InsertDmsCustomer({ socket, redisHelpers, JobData });
DMSCust = { customerId: DMSCustomerInsertResponse.data }; DMSCust = { customerId: DMSCustomerInsertResponse.data };
await setSessionTransactionData( await setSessionTransactionData(
socket.id, socket.id,
getTransactionType(jobid), getTransactionType(jobid),
FortellisCacheEnums.DMSCust, FortellisCacheEnums.DMSCust,
DMSCust, DMSCust,
defaultFortellisTTL defaultFortellisTTL
); );
}
}else{
DMSCust = { customerId: bypassCustomerId };
} }
let DMSVeh; let DMSVeh;
@@ -258,8 +283,12 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
DMSVeh = await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVeh); DMSVeh = await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVeh);
CreateFortellisLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`); CreateFortellisLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`);
//If it's a bypass scenario, skip this all.
//Check to see if the vehicle needs to be updated - i.e. the owner is not the selected customer. //Check to see if the vehicle needs to be updated - i.e. the owner is not the selected customer.
if (!DMSVeh?.owners.find((o) => o.id.value === DMSCust.customerId && o.id.assigningPartyId === "CURRENT")) { if (
selectedCustomerId !== bypassCustomerId &&
!DMSVeh?.owners.find((o) => o.id.value === DMSCust.customerId && o.id.assigningPartyId === "CURRENT")
) {
DMSVeh = await UpdateDmsVehicle({ DMSVeh = await UpdateDmsVehicle({
socket, socket,
redisHelpers, redisHelpers,
@@ -782,12 +811,14 @@ async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMS
// "chassis": "", // "chassis": "",
// "color": "", // "color": "",
// "dealerBodyStyle": "", // "dealerBodyStyle": "",
deliveryDate: ...(DMSCust?.customerId !== bypassCustomerId && {
txEnvelope.dms_unsold === true deliveryDate:
? "" txEnvelope.dms_unsold === true
: moment() ? ""
: moment()
// .tz(JobData.bodyshop.timezone) // .tz(JobData.bodyshop.timezone)
.format("YYYY-MM-DD"), .format("YYYY-MM-DD"),
}),
// "deliveryMileage": 4, // "deliveryMileage": 4,
// "doorsQuantity": 4, // "doorsQuantity": 4,
// "engineNumber": "", // "engineNumber": "",
@@ -902,14 +933,17 @@ async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMS
// "warrantyExpDate": "2015-01-12", // "warrantyExpDate": "2015-01-12",
// "wheelbase": "" // "wheelbase": ""
}, },
owners: [ // Owners is not required. Exclude it if we are bypassing.
{ ...(DMSCust?.customerId !== bypassCustomerId && {
id: { owners: [
assigningPartyId: "CURRENT", {
value: DMSCust.customerId id: {
assigningPartyId: "CURRENT",
value: DMSCust.customerId
}
} }
} ]
] })
//"inventoryAccount": "237" //"inventoryAccount": "237"
} }
}); });
@@ -1009,12 +1043,14 @@ async function UpdateDmsVehicle({ socket, redisHelpers, JobData, DMSVeh, DMSCust
modelAbrev: txEnvelope.dms_model modelAbrev: txEnvelope.dms_model
} }
: {}), : {}),
deliveryDate: ...(DMSCust?.customerId !== bypassCustomerId && {
txEnvelope.dms_unsold === true deliveryDate:
? "" txEnvelope.dms_unsold === true
: moment(DMSVehToSend.vehicle.deliveryDate) ? ""
: moment(DMSVehToSend.vehicle.deliveryDate)
//.tz(JobData.bodyshop.timezone) //.tz(JobData.bodyshop.timezone)
.toISOString() .toISOString()
})
}, },
owners: ids owners: ids
} }

View File

@@ -501,7 +501,7 @@ async function CalculateRatesTotals({ job, client }) {
? job.cieca_pfl[property.toUpperCase()].lbr_adjp ? job.cieca_pfl[property.toUpperCase()].lbr_adjp
: job.cieca_pfl[property.toUpperCase()].lbr_adjp * 100; //Adjust lbr_adjp to whole number : job.cieca_pfl[property.toUpperCase()].lbr_adjp * 100; //Adjust lbr_adjp to whole number
} else { } else {
if (job.cieca_pfl["LAB"].lbr_adjp) { if (job.cieca_pfl["LAB"] && job.cieca_pfl["LAB"].lbr_adjp) {
adjp = adjp =
Math.abs(job.cieca_pfl["LAB"].lbr_adjp) > 1 Math.abs(job.cieca_pfl["LAB"].lbr_adjp) > 1
? job.cieca_pfl["LAB"].lbr_adjp ? job.cieca_pfl["LAB"].lbr_adjp