/* © 2026 K2B NETWORK SOLUTIONS LLC. All rights reserved. */ // ════════════════════════════════════════════════════════════════════════════ // DealCorrections — RC23 (lane corr): correcting a POSTED deal on the screens. // Deal Backend / Deal Review: "Unwind deal", "Cancel product", "Refund customer"; Funding: "Reverse funding"; the deal's correction // history. The office only (Admin / F&I, Office Manager, Accounting — DMS.can("dealPost")). Every dialog asks for the reason and has a // CONFIRM step: "Review" asks the SERVER what the command would do (POST /api/deals/correction/preview — the command run and rolled // back) and lists exactly what will be reversed, with the amounts; "Confirm" sends the command itself (DMSCore unwindDeal / // reverseDealFunding / refundDeal / cancelDealProduct). Nothing is computed or booked on this station. // ════════════════════════════════════════════════════════════════════════════ const dcx$ = (c) => (Number(c || 0) / 100).toLocaleString("en-US", { style: "currency", currency: "USD" }); const dcxD$ = (v) => (+v || 0).toLocaleString("en-US", { style: "currency", currency: "USD" }); const DCX_TITLES = { unwind: "Unwind deal", refund: "Refund customer", "product-cancel": "Cancel product", "fund-reverse": "Reverse funding" }; const DCX_KINDS = { unwind: "Deal unwound", refund: "Refund to the customer", "product-cancel": "Product cancelled", "fund-reverse": "Funding reversed", "held-applied": "Held money applied" }; const dcxToday = () => { const n = new Date(); return n.getFullYear() + "-" + String(n.getMonth() + 1).padStart(2, "0") + "-" + String(n.getDate()).padStart(2, "0"); }; const dcxPosted = (d) => !!(d && d.postedToGL && !d.imported); const dcxIsCash = (d) => !!(window.DMS && window.DMS.isCashDeal ? window.DMS.isCashDeal(d) : /cash/i.test(String((d && d.dealType) || ""))); const dcxProducts = (d) => (window.DealCalc && window.DealCalc.productsOf ? window.DealCalc.productsOf(d) : []); /** the lender fundings still on the books (not reversed) — what "Reverse funding" can pick */ function dcxLiveFundings(d) { const list = Array.isArray(d && d.fundings) ? d.fundings : []; const gone = new Set(list.filter(x => x && x.kind === "reversal").map(x => String(x.reverses))); return list.filter(x => x && x.kind === "lender" && x.jeId && !gone.has(String(x.jeId))); } const dcxMay = () => !!(window.DMS && window.DMS.can && window.DMS.can("dealPost")); const DealCorrectionModal = ({ op, deal: d, onClose }) => { const D = window.DMS; const prods = dcxProducts(d).filter(p => !p.cancelled); const funds = dcxLiveFundings(d); const [f, setF] = React.useState(() => ({ reason: "", amount: "", date: dcxToday(), method: "Check", ref: "", productId: prods[0] ? prods[0].id : "", cancelDate: dcxToday(), customerRefund: "", chargebackToDealer: "", refundTo: dcxIsCash(d) ? "customer" : "lender", fundingId: funds[0] ? funds[0].jeId : "" })); const [pv, setPv] = React.useState(null); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(""); const set = (k) => (e) => { const v = e && e.target ? e.target.value : e; setF(x => ({ ...x, [k]: v })); setPv(null); setErr(""); }; const held = D && D.dealHeldCents ? D.dealHeldCents(d) : 0; const body = () => D.correctionBody(op, d.id, f); const review = async () => { if (String(f.reason || "").trim().length < 3) { setErr("Type the reason — it goes on the deal's correction history and the audit trail."); return; } setBusy(true); setErr(""); const r = await D.correctionPreview(op, body()); setBusy(false); if (!r.ok) { setPv(r.ownerOverride ? { ok: true, steps: [], journals: [], overrideOnly: true } : null); setErr(r.msg + (r.ownerOverride ? " As the owner you may still confirm — you will be asked for an Owner override (your code and the reason)." : "")); return; } setPv(r); }; const confirm = async () => { setBusy(true); setErr(""); let r; if (op === "unwind") r = await D.unwindDeal(d.id, f.reason); else if (op === "fund-reverse") r = await D.reverseDealFunding(d.id, f.fundingId, f.reason); else if (op === "refund") r = await D.refundDeal(d.id, { amount: f.amount, date: f.date, method: f.method, ref: f.ref, reason: f.reason }); else r = await D.cancelDealProduct(d.id, { productId: f.productId, cancelDate: f.cancelDate, customerRefund: f.customerRefund, chargebackToDealer: f.chargebackToDealer, refundTo: f.refundTo, reason: f.reason }); setBusy(false); if (!r || !r.ok) { setErr((r && r.msg) || "The server refused this correction."); setPv(null); return; } window.k2bToast && window.k2bToast(r.msg + ((r.warnings || []).length ? " ⚠ " + r.warnings.join(" ") : ""), "ok", 9000); onClose && onClose(r); }; const lbl = { fontSize: 10.5, color: "var(--jb-text-dim)", fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.3 }; const inp = { width: "100%", boxSizing: "border-box", font: "13px var(--jb-mono)", border: "1px solid #C8CCD2", borderRadius: 3, padding: "4px 6px", background: "#FFFFE1" }; const fld = (k, label, o) => ( ); const th = { padding: "3px 6px", textAlign: "left", fontSize: 10.5, color: "var(--jb-text-dim)", borderBottom: "1px solid var(--jb-border)", fontWeight: 700 }; const td = { padding: "3px 6px", fontSize: 12, fontFamily: "var(--jb-mono)" }; const intro = { unwind: "Every journal entry this deal's posting made is reversed by a linked reversing entry (dated the original day if that month is open, else today). The vehicle goes back to stock, the trade-in comes out of stock (only if it is still on the lot, untouched), the commission accrual and the rebate claim are reversed, and what the customer paid is HELD for the customer until the deal is posted again or the money is refunded. The deal, its documents and payments stay on record — it becomes \"Unwound\" so you can change it and post it again.", "fund-reverse": "The lender's funding entry is reversed: the money goes back into contracts in transit (205). Getting the money back to the lender (or netting it from their next funding) is your step with the lender.", refund: `Pays back money held for the customer (never more than what is held: ${dcx$(held)}). A check is written through Check Writing (the next check number) — print it there.`, "product-cancel": "Cancels ONE sold product without unwinding the deal: the dealer's share of the income is given back (F&I income), the provider's part comes off the product payable, and the refund is owed to the customer (held, then Refund customer) or to the lender as a payoff reduction. The salesperson's product bonus is charged back. The deal keeps the product line, marked cancelled.", }[op]; return ( onClose && onClose(null)} buttons={<> {err || (pv ? "Check the list above, then confirm." : "Fill in the reason, then Review.")} onClose && onClose(null)}>Cancel }>
{intro}
{op === "refund" &&
{fld("amount", "Amount to pay back", { money: true, ph: (held / 100).toFixed(2) })}{fld("date", "Date", { date: true })} {fld("method", "How", { options: (D.RECEIPT_METHODS || ["Cash", "Check", "Card", "Wire / ACH", "Other"]).map(m => [m, m === "Check" ? "Check (Check Writing)" : m]) })} {f.method !== "Check" && fld("ref", "Reference", { ph: "optional" })}
} {op === "product-cancel" && <>
{fld("productId", "Product", { flex: 2, options: prods.length ? prods.map(p => [p.id, `${p.name} — price ${dcx$(p.priceCents)}, cost ${dcx$(p.costCents)}`]) : [["", "No product left to cancel"]] })} {fld("cancelDate", "Cancelled on", { date: true })}
{fld("customerRefund", "Customer refund (from the provider's form)", { money: true, ph: "0.00" })} {fld("chargebackToDealer", "Dealer chargeback (our share)", { money: true, ph: "0.00" })}
The refund goes to {!dcxIsCash(d) && }
} {op === "fund-reverse" &&
{fld("fundingId", "Funding to reverse", { options: funds.length ? funds.map(x => [x.jeId, `${x.jeId} — ${dcxD$(x.amount)}${x.ref ? " · ref " + x.ref : ""}${x.date ? " · " + x.date : ""}`]) : [["", "No lender funding on this deal"]] })}
}