Merge branch 'feature/IO-2776-cdk-fortellis' into feature/Reynolds-and-Reynolds-DMS-API-Integration

This commit is contained in:
Dave
2025-09-12 14:58:50 -04:00
25 changed files with 59374 additions and 1478 deletions

View File

@@ -7,6 +7,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
import { SyncOutlined } from "@ant-design/icons";
import { pageLimit } from "../../utils/config";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { useSocket } from "../../contexts/SocketIO/useSocket";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -22,13 +24,28 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummar
export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]);
const {
treatments: { Fortellis }
} = useSplitTreatments({
attributes: {},
names: ["Fortellis"],
splitKey: bodyshop.imexshopid
});
const { socket: wsssocket } = useSocket();
useEffect(() => {
if (socket.connected) {
socket.emit("cdk-calculate-allocations", jobId, (ack) => {
if (Fortellis.treatment === "on") {
wsssocket.emit("fortellis-calculate-allocations", jobId, (ack) => {
setAllocationsSummary(ack);
socket.allocationsSummary = ack;
});
} else {
if (socket.connected) {
socket.emit("cdk-calculate-allocations", jobId, (ack) => {
setAllocationsSummary(ack);
socket.allocationsSummary = ack;
});
}
}
}, [socket, socket.connected, jobId]);
@@ -76,8 +93,12 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
extra={
<Button
onClick={() => {
socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
}}
if (Fortellis.treatment === "on") {
socket.emit("fortellis-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
} else {
socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack));
}
}}
>
<SyncOutlined />
</Button>

View File

@@ -1,8 +1,10 @@
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Checkbox, Col, Table } from "antd";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { useSocket } from "../../contexts/SocketIO/useSocket";
import { socket } from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { alphaSort } from "../../utils/sorters";
@@ -15,42 +17,125 @@ const mapDispatchToProps = () => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector);
export function DmsCustomerSelector({ bodyshop }) {
export function DmsCustomerSelector({ bodyshop, jobid }) {
const { t } = useTranslation();
const [customerList, setcustomerList] = useState([]);
const [open, setOpen] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null);
const [dmsType, setDmsType] = useState("cdk");
socket.on("cdk-select-customer", (customerList) => {
setOpen(true);
setDmsType("cdk");
setcustomerList(customerList);
});
socket.on("pbs-select-customer", (customerList) => {
setOpen(true);
setDmsType("pbs");
setcustomerList(customerList);
const {
treatments: { Fortellis }
} = useSplitTreatments({
attributes: {},
names: ["Fortellis"],
splitKey: bodyshop.imexshopid
});
const { socket: wsssocket } = useSocket();
useEffect(() => {
if (Fortellis.treatment === "on") {
const handleFortellisSelectCustomer = (customerList) => {
setOpen(true);
setDmsType("cdk");
setcustomerList(customerList);
};
wsssocket.on("fortellis-select-customer", handleFortellisSelectCustomer);
return () => {
wsssocket.off("fortellis-select-customer", handleFortellisSelectCustomer);
};
} else {
const handleCdkSelectCustomer = (customerList) => {
setOpen(true);
setDmsType("cdk");
setcustomerList(customerList);
};
const handlePbsSelectCustomer = (customerList) => {
setOpen(true);
setDmsType("pbs");
setcustomerList(customerList);
};
socket.on("cdk-select-customer", handleCdkSelectCustomer);
socket.on("pbs-select-customer", handlePbsSelectCustomer);
return () => {
socket.off("cdk-select-customer", handleCdkSelectCustomer);
socket.off("pbs-select-customer", handlePbsSelectCustomer);
};
}
}, []);
const onUseSelected = () => {
setOpen(false);
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
if (Fortellis.treatment === "on") {
wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: selectedCustomer, jobid });
} else {
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
}
setSelectedCustomer(null);
};
const onUseGeneric = () => {
setOpen(false);
socket.emit(`${dmsType}-selected-customer`, bodyshop.cdk_configuration.generic_customer_number);
if (Fortellis.treatment === "on") {
wsssocket.emit(`fortellis-selected-customer`, {
selectedCustomerId: bodyshop.cdk_configuration.generic_customer_number,
jobid
});
} else {
socket.emit(`${dmsType}-selected-customer`, bodyshop.cdk_configuration.generic_customer_number);
}
setSelectedCustomer(null);
};
const onCreateNew = () => {
setOpen(false);
socket.emit(`${dmsType}-selected-customer`, null);
if (Fortellis.treatment === "on") {
wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: null, jobid });
} else {
socket.emit(`${dmsType}-selected-customer`, null);
}
setSelectedCustomer(null);
};
const fortellisColumns = [
{
title: t("jobs.fields.dms.id"),
dataIndex: "customerId",
key: "id"
},
{
title: t("jobs.fields.dms.vinowner"),
dataIndex: "vinOwner",
key: "vinOwner",
render: (text, record) => <Checkbox disabled checked={record.vinOwner} />
},
{
title: t("jobs.fields.dms.name1"),
dataIndex: ["customerName", "firstName"],
key: "firstName",
sorter: (a, b) => alphaSort(a.customerName?.firstName, b.customerName?.firstName)
},
{
title: t("jobs.fields.dms.name1"),
dataIndex: ["customerName", "lastName"],
key: "lastName",
sorter: (a, b) => alphaSort(a.customerName?.lastName, b.customerName?.lastName)
},
{
title: t("jobs.fields.dms.address"),
key: "address",
render: (record) =>
`${record.postalAddress?.addressLine1} ${record.postalAddress?.addressLine2 ? `, ${record.postalAddress?.addressLine2}` : ""},
${record.postalAddress?.city} ${record.postalAddress?.state} ${record.postalAddress?.postalCode} ${
record.postalAddress?.country
}`
}
];
const cdkColumns = [
{
title: t("jobs.fields.dms.id"),
@@ -117,13 +202,13 @@ export function DmsCustomerSelector({ bodyshop }) {
</div>
)}
pagination={{ position: "top" }}
columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)}
columns={dmsType === "cdk" ? (Fortellis.treatment === "on" ? fortellisColumns : cdkColumns) : pbsColumns}
rowKey={(record) => (dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId)}
dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{
onSelect: (record) => {
setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId);
setSelectedCustomer(dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId);
},
type: "radio",
selectedRowKeys: [selectedCustomer]

View File

@@ -26,6 +26,8 @@ import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { useSocket } from "../../contexts/SocketIO/useSocket";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -36,8 +38,17 @@ const mapDispatchToProps = () => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
const {
treatments: { Fortellis }
} = useSplitTreatments({
attributes: {},
names: ["Fortellis"],
splitKey: bodyshop.imexshopid
});
const [form] = Form.useForm();
const { t } = useTranslation();
const { socket: wsssocket } = useSocket();
const handlePayerSelect = (value, index) => {
form.setFieldsValue({
@@ -58,10 +69,21 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
};
const handleFinish = (values) => {
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
jobid: job.id,
txEnvelope: values
});
//TODO: Add this as a split instead.
if (Fortellis.treatment === "on") {
wsssocket.emit("fortellis-export-job", {
jobid: job.id,
txEnvelope: {
...values,
SubscriptionID: "5b527d7d-baf3-40bc-adae-e7a541e37363" //bodyshop.cdk_dealerid
}
});
} else {
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
jobid: job.id,
txEnvelope: values
});
}
console.log(logsRef);
if (logsRef) {
console.log("executing", logsRef);
@@ -74,6 +96,16 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
return (
<Card title={t("jobs.labels.dms.postingform")}>
<Button
onClick={() =>
wsssocket.emit("fortellis-export-job", {
txEnvelope: { test: 1, test2: 2, SubscriptionID: "5b527d7d-baf3-40bc-adae-e7a541e37363" },
jobid: job.id
})
}
>
Test
</Button>
<Form
form={form}
layout="vertical"
@@ -151,7 +183,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
}
]}
>
<Input disabled />
<Input />
</Form.Item>
<Form.Item
name="dms_model"
@@ -162,14 +194,14 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
}
]}
>
<Input disabled />
<Input />
</Form.Item>
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
<DateTimePicker isDateOnly />
</Form.Item>
</LayoutFormRow>
<Space>
<DmsCdkMakes form={form} socket={socket} job={job} />
<DmsCdkMakes form={form} job={job} />
<DmsCdkMakesRefetch />
<Form.Item name="dms_unsold" label={t("jobs.fields.dms.dms_unsold")} initialValue={false}>
<Switch />

View File

@@ -21,6 +21,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -54,6 +56,14 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
const search = queryString.parse(useLocation().search);
const { jobId } = search;
const notification = useNotification();
const {
treatments: { Fortellis }
} = useSplitTreatments({
attributes: {},
names: ["Fortellis"],
splitKey: bodyshop.imexshopid
});
const { socket: wsssocket } = useSocket();
const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, {
variables: { id: jobId },
@@ -84,45 +94,75 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "warn",
message: "Reconnected to CDK Export Service"
}
];
});
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({ message: err.message });
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("export-success", (payload) => {
notification.success({
message: t("jobs.successes.exported")
});
insertAuditTrail({
jobid: payload,
operation: AuditTrailMapping.jobexported(),
type: "jobexported"
});
history("/manage/accounting/receivables");
});
if (Fortellis.treatment === "on") {
wsssocket.emit("set-log-level", logLevel);
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
const handleLogEvent = (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
};
const handleExportSuccess = (payload) => {
notification.success({
message: t("jobs.successes.exported")
});
insertAuditTrail({
jobid: payload,
operation: AuditTrailMapping.jobexported(),
type: "jobexported"
});
history("/manage/accounting/receivables");
};
wsssocket.on("fortellis-log-event", handleLogEvent);
wsssocket.on("export-success", handleExportSuccess);
return () => {
wsssocket.off("fortellis-log-event", handleLogEvent);
wsssocket.off("export-success", handleExportSuccess);
};
} else {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "warn",
message: "Reconnected to CDK Export Service"
}
];
});
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({ message: err.message });
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("export-success", (payload) => {
notification.success({
message: t("jobs.successes.exported")
});
insertAuditTrail({
jobid: payload,
operation: AuditTrailMapping.jobexported(),
type: "jobexported"
});
history("/manage/accounting/receivables");
});
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
}
}, []);
if (loading) return <LoadingSpinner />;
@@ -136,6 +176,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
return (
<div>
{Fortellis.treatment === "on" && (
<AlertComponent message="Posting to Fortellis" type="warning" showIcon closable />
)}
<Row gutter={[16, 16]}>
<Col md={24} lg={10}>
<DmsAllocationsSummary
@@ -157,7 +200,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
<DmsPostForm socket={socket} jobId={jobId} job={data && data.jobs_by_pk} logsRef={logsRef} />
</Col>
<DmsCustomerSelector />
<DmsCustomerSelector jobid={jobId} />
<Col span={24}>
<div ref={logsRef}>

View File