feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration -VIN Ownership checks
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Checkbox, Col, message, Table } from "antd";
|
||||
import { Alert, Button, Checkbox, Col, message, Table } from "antd";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -26,7 +26,9 @@ function normalizeRrList(list) {
|
||||
row.name ||
|
||||
[row.CustomerName?.FirstName, row.CustomerName?.LastName].filter(Boolean).join(" ").trim() ||
|
||||
(custNo ? String(custNo) : "");
|
||||
return custNo ? { custNo: String(custNo), name } : null;
|
||||
if (!custNo) return null;
|
||||
const vinOwner = !!(row.vinOwner ?? row.isVehicleOwner);
|
||||
return { custNo: String(custNo), name, vinOwner };
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
@@ -49,14 +51,29 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
const { socket: wsssocket } = useSocket();
|
||||
const dms = useMemo(() => determineDmsType(bodyshop), [bodyshop]);
|
||||
|
||||
// --- owner set (RR only) ---
|
||||
const rrOwnerSet = useMemo(() => {
|
||||
return new Set(
|
||||
(Array.isArray(customerList) ? customerList : [])
|
||||
.filter((c) => c?.vinOwner || c?.isVehicleOwner)
|
||||
.map((c) => String(c.custNo))
|
||||
);
|
||||
}, [customerList]);
|
||||
const rrHasVinOwner = rrOwnerSet.size > 0;
|
||||
|
||||
useEffect(() => {
|
||||
// RR takes precedence
|
||||
if (dms === "rr") {
|
||||
const handleRrSelectCustomer = (list) => {
|
||||
const normalized = normalizeRrList(list);
|
||||
|
||||
setOpen(true);
|
||||
setDmsType("rr");
|
||||
setcustomerList(normalizeRrList(list));
|
||||
setSelectedCustomer(null);
|
||||
setcustomerList(normalized);
|
||||
|
||||
// PRESELECT VIN OWNER (first one if multiple)
|
||||
const firstOwner = normalized.find((r) => r.vinOwner)?.custNo;
|
||||
setSelectedCustomer(firstOwner ? String(firstOwner) : null);
|
||||
};
|
||||
|
||||
wsssocket.on("rr-select-customer", handleRrSelectCustomer);
|
||||
@@ -98,14 +115,30 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
}
|
||||
}, [dms, Fortellis?.treatment, wsssocket]);
|
||||
|
||||
// Safety: if owner info arrives later or list changes, keep the owner preselected.
|
||||
useEffect(() => {
|
||||
if (dmsType !== "rr" || !rrHasVinOwner) return;
|
||||
const firstOwner = (customerList.find((c) => c.vinOwner) || {}).custNo;
|
||||
if (firstOwner && String(selectedCustomer) !== String(firstOwner)) {
|
||||
setSelectedCustomer(String(firstOwner));
|
||||
}
|
||||
}, [dmsType, rrHasVinOwner, customerList]);
|
||||
|
||||
const onUseSelected = () => {
|
||||
if (!selectedCustomer) {
|
||||
message.warning(t("general.actions.select"));
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is a VIN owner, only allow owner selection
|
||||
if (dmsType === "rr" && rrHasVinOwner && !rrOwnerSet.has(String(selectedCustomer))) {
|
||||
message.warning(
|
||||
"This VIN is already assigned in Reynolds. Only the VIN owner can be selected. To choose a different customer, change ownership in Reynolds first."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dmsType === "rr") {
|
||||
// RR now mirrors others: send selection and close
|
||||
wsssocket.emit("rr-selected-customer", { jobId: jobid, custNo: String(selectedCustomer) }, (ack) => {
|
||||
if (ack?.ok) {
|
||||
setOpen(false);
|
||||
@@ -127,6 +160,7 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
};
|
||||
|
||||
const onUseGeneric = () => {
|
||||
if (dmsType === "rr" && rrHasVinOwner) return;
|
||||
const generic = bodyshop.cdk_configuration?.generic_customer_number || null;
|
||||
if (dmsType === "rr") {
|
||||
if (generic) {
|
||||
@@ -146,11 +180,11 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
};
|
||||
|
||||
const onCreateNew = () => {
|
||||
// Exact parity with Fortellis: ask server to create immediately
|
||||
if (dmsType === "rr" && rrHasVinOwner) return;
|
||||
|
||||
if (dmsType === "rr") {
|
||||
wsssocket.emit("rr-selected-customer", { jobId: jobid, create: true }, (ack) => {
|
||||
if (ack?.ok) {
|
||||
// Optionally preselect returned custNo
|
||||
if (ack.custNo) setSelectedCustomer(String(ack.custNo));
|
||||
setOpen(false);
|
||||
message.success(t("dms.messages.customerCreated"));
|
||||
@@ -161,7 +195,6 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-RR unchanged
|
||||
setOpen(false);
|
||||
if (Fortellis.treatment === "on") {
|
||||
wsssocket.emit("fortellis-selected-customer", { selectedCustomerId: null, jobid });
|
||||
@@ -245,7 +278,18 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
|
||||
const rrColumns = [
|
||||
{ title: t("jobs.fields.dms.id"), dataIndex: "custNo", key: "custNo" },
|
||||
{ title: t("jobs.fields.dms.name1"), dataIndex: "name", key: "name", sorter: (a, b) => alphaSort(a?.name, b?.name) }
|
||||
{
|
||||
title: t("jobs.fields.dms.name1"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
sorter: (a, b) => alphaSort(a?.name, b?.name)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.vinowner"),
|
||||
dataIndex: "vinOwner",
|
||||
key: "vinOwner",
|
||||
render: (_t, r) => <Checkbox disabled checked={!!(r.vinOwner ?? r.isVehicleOwner)} />
|
||||
}
|
||||
];
|
||||
|
||||
if (!open) return null;
|
||||
@@ -266,18 +310,40 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
? (record) => record.id?.value || record.customerId
|
||||
: (record) => record.ContactId;
|
||||
|
||||
const rrDisableRow = (record) => {
|
||||
if (dmsType !== "rr") return false;
|
||||
if (!rrHasVinOwner) return false;
|
||||
return !rrOwnerSet.has(String(record.custNo));
|
||||
};
|
||||
|
||||
return (
|
||||
<Col span={24}>
|
||||
<Table
|
||||
title={() => (
|
||||
<div>
|
||||
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
||||
{t("jobs.actions.dms.useselected")}
|
||||
</Button>
|
||||
<Button onClick={onUseGeneric} disabled={!bodyshop.cdk_configuration?.generic_customer_number}>
|
||||
{t("jobs.actions.dms.usegeneric")}
|
||||
</Button>
|
||||
<Button onClick={onCreateNew}>{t("jobs.actions.dms.createnewcustomer")}</Button>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
||||
{t("jobs.actions.dms.useselected")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onUseGeneric}
|
||||
disabled={dmsType === "rr" ? rrHasVinOwner : !bodyshop.cdk_configuration?.generic_customer_number}
|
||||
>
|
||||
{t("jobs.actions.dms.usegeneric")}
|
||||
</Button>
|
||||
<Button onClick={onCreateNew} disabled={dmsType === "rr" ? rrHasVinOwner : false}>
|
||||
{t("jobs.actions.dms.createnewcustomer")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{dmsType === "rr" && rrHasVinOwner && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="VIN ownership enforced"
|
||||
description="This VIN is already assigned in Reynolds. Only the VIN owner is selectable here. To use a different customer, please change the vehicle ownership in Reynolds first, then return to complete the export."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
pagination={{ position: "top" }}
|
||||
@@ -295,7 +361,10 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
setSelectedCustomer(key ? String(key) : null);
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: selectedCustomer ? [selectedCustomer] : []
|
||||
selectedRowKeys: selectedCustomer ? [selectedCustomer] : [],
|
||||
getCheckboxProps: (record) => ({
|
||||
disabled: rrDisableRow(record)
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
Reference in New Issue
Block a user