import { DeleteFilled, DownOutlined, ReloadOutlined } from "@ant-design/icons"; import { Button, Card, Col, Divider, Dropdown, Form, Input, InputNumber, Row, Select, Space, Statistic, Switch, Tooltip, Typography } from "antd"; import Dinero from "dinero.js"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { determineDmsType } from "../../utils/determineDMSType"; import { selectBodyshop } from "../../redux/user/user.selectors"; import i18n from "../../translations/i18n"; import dayjs from "../../utils/day"; import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component"; 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 { useSocket } from "../../contexts/SocketIO/useSocket"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useEffect, useMemo, useState } from "react"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); 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(); // Figure out DMS once and reuse const dms = useMemo(() => determineDmsType(bodyshop), [bodyshop]); // ---------------- RR Advisors (unchanged behavior) ---------------- const [advisors, setAdvisors] = useState([]); const [advLoading, setAdvLoading] = useState(false); // Normalize advisor fields coming from various shapes const getAdvisorNumber = (a) => a?.advisorId; const getAdvisorLabel = (a) => `${a?.firstName || ""} ${a?.lastName || ""}`.trim(); const fetchRrAdvisors = (refresh = false) => { if (!wsssocket) return; setAdvLoading(true); // Listen for the server's broadcast const onResult = (payload) => { try { const list = payload?.result ?? payload ?? []; setAdvisors(Array.isArray(list) ? list : []); } finally { setAdvLoading(false); wsssocket.off("rr-get-advisors:result", onResult); } }; wsssocket.once("rr-get-advisors:result", onResult); // Emit with refresh flag: server will bypass/rebuild cache when true wsssocket.emit("rr-get-advisors", { departmentType: "B", refresh }, (ack) => { if (ack?.ok) { const list = ack.result ?? []; setAdvisors(Array.isArray(list) ? list : []); } else if (ack) { // Preserve original logging semantics console.error("Something went wrong fetching DMS Advisors"); } setAdvLoading(false); wsssocket.off("rr-get-advisors:result", onResult); }); }; useEffect(() => { if (dms === "rr") fetchRrAdvisors(false); }, [dms, bodyshop?.id]); // ---------------- Payers helpers (non-RR) ---------------- const handlePayerSelect = (value, index) => { form.setFieldsValue({ payers: (form.getFieldValue("payers") || []).map((payer, mapIndex) => { if (index !== mapIndex) return payer; const cdkPayer = bodyshop.cdk_configuration.payers && bodyshop.cdk_configuration.payers.find((i) => i.name === value); if (!cdkPayer) return payer; return { ...cdkPayer, dms_acctnumber: cdkPayer.dms_acctnumber, controlnumber: job?.[cdkPayer.control_type] }; }) }); }; // ---------------- Submit (RR precedence preserved) ---------------- const handleFinish = (values) => { // RR takes precedence regardless of Fortellis split if (dms === "rr") { // values will include advisorNo (and makeOverride if provided) wsssocket.emit("rr-export-job", { bodyshopId: bodyshop?.id, jobId: job.id, job, txEnvelope: values }); } else if (Fortellis.treatment === "on") { // Fallback to existing Fortellis behavior wsssocket.emit("fortellis-export-job", { jobid: job.id, txEnvelope: { ...values, SubscriptionID: bodyshop.cdk_dealerid } }); } else { // CDK/PBS/etc. socket.emit(`${dms}-export-job`, { jobid: job.id, txEnvelope: values }); } // Scroll logs into view (original behavior) logsRef?.current?.scrollIntoView({ behavior: "smooth" }); }; return (
= (dayjs().year() + 1) % 100 ? 1900 + parseInt(job.v_model_yr, 10) : 2000 + parseInt(job.v_model_yr, 10) : job.v_model_yr)) || 2019 }-01-01` ) }} > {/* TOP ROW — bottom-aligned so the Refresh button sits flush */} {dms !== "rr" && ( )} {dms === "rr" && ( <> {/* Advisor + inline Refresh (restores original behavior with better UX) */} )} {/* CDK vehicle details (unchanged behavior) */} {bodyshop.cdk_dealerid && ( <> )} {/* Totals (unchanged) */} {/* Non-RR payers list (parity with original) */} {dms !== "rr" && ( <> {(fields, { add, remove }) => (
{fields.map((field, index) => (
} name={[field.name, "controlnumber"]} rules={[{ required: true }]} > {() => { const payers = form.getFieldValue("payers"); const row = payers?.[index]; const cdkPayer = bodyshop.cdk_configuration.payers && bodyshop.cdk_configuration.payers.find((i) => i && row && i.name === row.name); if (i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)) return
{cdkPayer && t(`jobs.fields.${cdkPayer?.control_type}`)}
; else if (i18n.exists(`jobs.fields.dms.control_type.${cdkPayer?.control_type}`)) { return (
{cdkPayer && t(`jobs.fields.dms.control_type.${cdkPayer?.control_type}`)}
); } else { return null; } }}
))} )}
)} {/* Validation gates & summary (unchanged logic) */} {() => { // 1) Sum allocated payers let totalAllocated = Dinero(); const payers = form.getFieldValue("payers") || []; payers.forEach((payer) => { totalAllocated = totalAllocated.add(Dinero({ amount: Math.round((payer?.amount || 0) * 100) })); }); // 2) Subtotal from socket.allocationsSummary (existing behavior) const totals = socket && socket.allocationsSummary ? socket.allocationsSummary.reduce( (acc, val) => ({ totalSale: acc.totalSale.add(Dinero(val.sale)), totalCost: acc.totalCost.add(Dinero(val.cost)) }), { totalSale: Dinero(), totalCost: Dinero() } ) : { totalSale: Dinero(), totalCost: Dinero() }; const discrep = totals ? totals.totalSale.subtract(totalAllocated) : Dinero(); // 3) Validation gates const advisorOk = dms !== "rr" || !!form.getFieldValue("advisorNo"); // Require at least one complete payer row for non-RR const payersOk = dms === "rr" || (payers.length > 0 && payers.every((p) => p?.name && p.dms_acctnumber && (p.amount ?? "") !== "" && p.controlnumber)); // 4) Disable rules: // - For non-RR: must have summary and zero discrepancy // - For RR: ignore discrepancy rule, but require advisor const nonRrDiscrepancyGate = dms !== "rr" && (socket.allocationsSummary ? discrep.getAmount() !== 0 : true); const disablePost = !advisorOk || !payersOk || nonRrDiscrepancyGate; return ( - = ); }}
); }