/* eslint-disable */
/* © 2026 K2B NETWORK SOLUTIONS LLC. All rights reserved.
Proprietary and confidential. Unauthorized copying, distribution, or use of
this file, via any medium, is strictly prohibited. Original work product. */
// JBREW DMS — Desking Tool (DESK). Modern 4-square work tool.
// • Trade · Price · Down · Payment, with three live payment scenarios.
// • Lenders pulled from the shared Banks registry (window.jbLenders).
// • F&I products pulled from the shared catalog (window.jbProducts) as a
// toggle-able checklist — service contracts + all ancillary products.
// • Its own Settings (defaults: doc fee, tax, pack, terms/APRs, comm).
// • Print menu: Customer Presentation, 4-Square Worksheet, As-Is, Release
// of Liability, Buyer's Order.
// • Push to Cash Deal / Push to Finance hands the worked deal to its closing screen (ONE PATH).
// • Saved Scenarios — 💾 snapshots the COMPLETE desk under a name into
// DMS.deskScenarios (server-synced); 📂 lists / reopens / deletes them
// from any workstation.
// No demo presets (B-05): with no unit / customer picked the desk starts
// BLANK — nothing invented ever reaches the Buyer's Order or a printed form.
const DESK_EMPTY_UNIT = { yr: "", mk: "", mdl: "", stock: "", vin: "", msrp: 0, cost: 0, miles: 0, type: "", preset: true };
const DESK_EMPTY_CUST = { name: "", dealId: null, credit: 0, dti: "", income: 0, preset: true };
// Negatives render "-$1,234.00" (sign before the currency symbol), never "$-1,234.00".
const dt$ = (n) => { const v = +n || 0; return (v < 0 ? "-$" : "$") + Math.abs(v).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); };
const dtRound2 = (n) => Math.round(n * 100) / 100;
const dtPmt = (rate, n, pv) => { if (n <= 0) return 0; if (rate === 0) return pv / n; const r = rate / 12; return (pv * r) / (1 - Math.pow(1 + r, -n)); };
// ── Lease math (standard single-pay-per-month closed-end lease) ─────────────
// Money factor MF = APR / 2400 (a .00250 MF = 6.00% APR)
// Residual = MSRP × residual%
// Depreciation = (adjusted cap cost − residual) / term
// Rent charge = (adjusted cap cost + residual) × MF
// Base payment = depreciation + rent charge (tax added on the payment)
const dtAprToMf = (apr) => (+apr || 0) / 2400;
const dtMfToApr = (mf) => (+mf || 0) * 2400;
// Typical GM closed-end residuals by term at 12k/yr, adjusted for mileage.
const DT_RESID_BASE = { 24: 68, 27: 66, 30: 64, 36: 60, 39: 58, 42: 56, 48: 51, 60: 42 };
const dtResidualPct = (term, milesPerYr) => {
const terms = Object.keys(DT_RESID_BASE).map(Number).sort((a, b) => a - b);
let base = DT_RESID_BASE[term];
if (base == null) { // interpolate off the nearest terms
const lo = terms.filter(t => t <= term).pop() ?? terms[0];
const hi = terms.find(t => t >= term) ?? terms[terms.length - 1];
base = lo === hi ? DT_RESID_BASE[lo] : DT_RESID_BASE[lo] + (DT_RESID_BASE[hi] - DT_RESID_BASE[lo]) * ((term - lo) / (hi - lo));
}
// GM-style mileage adjustment: ±1 point per 2,500 miles off 12k/yr.
return Math.max(10, base - ((+milesPerYr || 12000) - 12000) / 2500);
};
// The full closed-end lease engine lives in LeaseCalc.jsx.txt (loaded first):
// window.jbLeaseCalc — GMF-aware: tier MF adds, dealer participation markup,
// single-pay band reductions w/ MF floor, mileage residual adds, capitalized
// vs upfront fee flags, and all three tax methods.
// GM Financial program data loads from LeaseRates.js as window.GMF_LEASE:
// { program (rate sheet + tier/mileage/fee tables), residualGuide, usedProgram }.
const GMF_TIERS = ["A+/A1", "A2", "A3", "B1", "B2", "B3"];
const GMF_MILES = [7500, 10000, 12000, 15000];
const GMF_USED_TERMS = [24, 36, 48];
const dtGmfData = () => window.GMF_LEASE || { program: {}, residualGuide: [], usedProgram: {} };
const dtGmfTierLabel = (t) => { const f = (dtGmfData().program.tierFico || {})[t]; return f ? `${t} · FICO ${f}` : t; };
const dtNormLsc = (s) => ({ gap: false, vsc: false, all: false, resid: null, sp: false, aprIn: false, autoMf: false, autoRv: false, ...s });
// Compact display for a titling entity's legal name:
// "ACAR Leasing Ltd. d/b/a GM Financial Leasing" → "ACAR Leasing Ltd. (GMF Leasing)"
const dtEntityShort = (name) => {
const parts = String(name || "").split(/\s+d\/b\/a\s+/i);
return parts.length > 1 ? `${parts[0]} (${parts[1].replace(/GM Financial/i, "GMF")})` : String(name || "");
};
/* ── GMF Program Picker — searchable model/trim modal ──
Two sources: the current-month rate sheet (MY2025 · MF + residual for
24/27/36/39/48) and the ALG-style residual guide (MY2026-27 + the odd
30/42-month terms · residual only). Picking auto-fills every option. */
const GmfProgramPicker = ({ onPick, onClose }) => {
const [q, setQ] = React.useState("");
const [src, setSrc] = React.useState("rates"); // "rates" | "guide"
const G = dtGmfData();
const qq = q.trim().toLowerCase();
const inp = { fontSize: 12.5, padding: "6px 8px", border: "1px solid #C8CCD2", borderRadius: 4 };
const rateRows = (G.program.rates || []).map(r => ({
source: "rates", model: r.model, modelYear: r.modelYear, leaseCash: +r.leaseCash || 0,
label: `${r.modelYear} ${r.model}`, t36: r.t36 || {},
}));
const guideRows = (G.residualGuide || []).map(g => ({
source: "guide", section: g.section, model: g.model, code: g.code, trim: g.trim,
modelYear: (String(g.section || "").match(/\d{4}/) || [""])[0],
label: `${g.section} ${g.model} ${g.trim} (${g.code})`, rv36: g.rv ? g.rv["36"] : null,
}));
const all = src === "rates" ? rateRows : guideRows;
const rows = (qq ? all.filter(r => r.label.toLowerCase().includes(qq)) : all).slice(0, 400);
return (
Cancel}>
setQ(e.target.value)}
placeholder="🔍 Search model / trim… (e.g. EQUINOX, TRAX, SILVERADO)"
style={{ ...inp, flex: 1, fontSize: 13.5, padding: "8px 10px", background: "#FFFEF4", border: "2px solid var(--jb-navy, #1B66A8)" }} />
{[["rates", `Rate Sheet — MF + residual (${rateRows.length})`], ["guide", `Residual Guide — RV only, 30/42 mo + MY26-27 (${guideRows.length})`]].map(([k, lab]) => (
setSrc(k)}
style={src === k ? { background: "var(--jb-navy, #1B66A8)", color: "#fff", fontWeight: 700 } : {}}>{lab}
))}
{G.program.effective ? `Program effective ${G.program.effective}` : ""}
MY Model / Trim
{src === "rates"
? <>36-mo MF 36-mo RV% Lease Cash >
: <>Code 36-mo RV% >}
{rows.length === 0 ? (
No program rows match — change the search or switch source.
) : rows.map((r, i) => (
onPick(r)} style={{ background: i % 2 ? "var(--jb-surface-2)" : "#fff", cursor: "pointer" }}>
{r.modelYear}
{r.source === "rates" ? r.model : `${r.model} · ${r.trim}`}
{r.source === "rates"
? <>{r.t36.mf != null ? (+r.t36.mf).toFixed(5) : "—"}
{r.t36.rv != null ? r.t36.rv + "%" : "—"}
{r.leaseCash ? dt$(r.leaseCash) : "—"} >
: <>{r.code}
{r.rv36 != null ? r.rv36 + "%" : "—"} >}
))}
Rate-sheet picks fill MF + residual for 24/27/36/39/48-month options. Residual-guide picks fill
residual only (incl. 30/42-month) — the money factor stays as entered. Autofilled fields show a
GMF badge and go manual the moment you edit them.
);
};
// ── Desking defaults (editable in the in-tool Settings panel) ──────────────
// Lease-desk tax rate default — the customer's COMBINED LOCAL rate the store
// actually collects on lease monthly receipts (owner-confirmed, matches the
// live RouteOne worksheet: 60.41 / 633.43 = 9.537%). Kept separate from the
// finance-desk rate (taxRate, 7.225) — both stay user-editable.
const DT_LEASE_TAX_DEFAULT = 9.537;
const DESK_DEFAULTS = { docFee: 0, taxRate: 9.537, pack: 500, commPct: 25, cashDown: 2500, rebate: 1000, t1: 75, a1: 6.99, t2: 72, a2: 6.49, t3: 60, a3: 5.99,
// lease defaults
acqFee: 650, dispFee: 395, milesPerYr: 12000, leaseTaxOnPayment: true, lt1: 24, lt2: 36, lt3: 39, lmf1: 0.00225, lmf2: 0.00195, lmf3: 0.00185 };
function loadDeskSettings() {
try {
const s = { ...DESK_DEFAULTS, ...JSON.parse(localStorage.getItem("jb_desk_settings") || "{}") };
// One-time migration: doc fee now defaults to ZERO. Stores that still
// carry the old shipped default (299) and never chose it get zeroed.
if (s.docFee === 299 && !localStorage.getItem("jb_desk_docfee_v2")) {
s.docFee = 0; localStorage.setItem("jb_desk_docfee_v2", "1"); saveDeskSettings(s);
}
return s;
} catch (e) { return { ...DESK_DEFAULTS }; }
}
function saveDeskSettings(s) { try { localStorage.setItem("jb_desk_settings", JSON.stringify(s)); } catch (e) {} }
// Working-desk persistence — the in-progress deal survives navigating in/out.
// The blob is keyed to the deal it was worked on: switching to a DIFFERENT
// deal starts a fresh desk instead of restoring the previous deal's numbers.
function dtLoadWork() {
try {
const w = JSON.parse(localStorage.getItem("jb_desk_work") || "null") || {};
let d = window.jbDeskDeal;
if (!d) { try { d = JSON.parse(localStorage.getItem("jb_desk_deal") || "null"); } catch (e) {} }
const curDeal = d && d.id ? d.id : null;
if ((w.dealId || null) !== curDeal) return { dealId: curDeal }; // new deal → clean desk
return w;
} catch (e) { return {}; }
}
// ── Payment options A–D ──────────────────────────────────────────────────
// Every desk carries FOUR option slots (finance `sc` + lease `lsc`); a shared
// visibility list (scOn) decides which letters are shown. Letters are stable:
// removing C hides C (D keeps its letter). Each slot owns its term / rate /
// products / DOWN PAYMENT. Older blobs (3 slots + one global cashDown) migrate
// to A/B/C with down = that cashDown, D hidden as a copy of C.
const DT_LETTERS = "ABCD";
const DT_MAX_OPTS = 4;
const dtNormSc = (s) => ({ gap: false, vsc: false, all: false, ...s });
function dtMigrateSc(arr, down, defaults, norm = dtNormSc) {
const src = (Array.isArray(arr) && arr.length) ? arr : defaults;
const out = src.slice(0, DT_MAX_OPTS).map(s => { const n = norm(s || {}); return { ...n, down: n.down != null && n.down !== "" ? +n.down || 0 : (+down || 0) }; });
while (out.length < DT_MAX_OPTS) out.push({ ...out[out.length - 1] });
return out;
}
// Printed option comparison — one EQUAL-WIDTH column per visible option, the
// same rows for every option. heads = ["A", "B", …]; sel = position of the ★
// option; rows = [[label, [cell per option], strong?]]. No placeholder columns.
function dtOptGridHtml(heads, sel, rows) {
const n = Math.max(1, heads.length);
const labW = 26, colW = (100 - labW) / n;
const td = (i, inner, strong) => `
{ e.currentTarget.__dkcDown = true; }}
onClick={(e) => { e.stopPropagation(); const d = e.currentTarget.__dkcDown; e.currentTarget.__dkcDown = false; if (d) onClose(); }} />
e.stopPropagation()}>{children}
>
);
const dkcKind = (k) => k === "err" ? ["✕", "var(--jb-red)"] : k === "warn" ? ["!", "#B07000"] : k === "info" ? ["i", "var(--jb-blue)"] : ["✓", "var(--jb-green)"];
const dkcStore = (k, v) => { try { if (v === undefined) return localStorage.getItem(k); localStorage.setItem(k, v); } catch (e) {} return null; };
// Box size of a desk window → density (row height / type scale). Shared by the
// desk and the read-only Sales view so both fit the screen the same way.
const dkfDensOf = (b) => b.h < 600 ? "tight" : b.h < 690 ? "compact" : b.h < 880 ? "normal" : (b.h >= 1200 && b.w >= 2000) ? "xl" : "roomy";
function dkfUseBox(ref) {
const [box, setBox] = React.useState({ w: 1300, h: 700 });
React.useLayoutEffect(() => {
const el = ref.current; if (!el) return undefined;
const measure = () => { const r = el.getBoundingClientRect(); const w = Math.round(r.width), h = Math.round(r.height); if (w && h) setBox(b => (b.w === w && b.h === h) ? b : { w, h }); };
measure();
if (!window.ResizeObserver) { window.addEventListener("resize", measure); return () => window.removeEventListener("resize", measure); }
const ro = new ResizeObserver(measure); ro.observe(el); return () => ro.disconnect();
}, []);
return box;
}
// A label with a long and a short form; CSS shows the short one in a narrow profit block.
const dkfLS = (long, short) => <>
{long} {short} >;
// A program's term list plus the stored term when it is off the list (so a select shows the real term).
const dkfTermList = (list, t) => (list.includes(t) ? list.slice() : list.concat([t])).sort((a, b) => a - b);
// Messages: every toast (k2btoast event) is kept for the screen's own status
// line instead of floating over the figures (the desk and the Sales view hide
// the toast stack while they are open). Toasts already on screen when the
// screen opens (e.g. a sign-in sync warning) are adopted.
function dkfUseMsgs() {
const [msgs, setMsgs] = React.useState([]);
const [, tick] = React.useReducer(n => n + 1, 0);
React.useEffect(() => {
const add = (msg, kind, ttl) => setMsgs(a => a.concat([{ id: Math.random().toString(36).slice(2), msg: String(msg), kind: kind || "ok", ttl: +ttl || 2400, at: Date.now() }]).slice(-8));
try {
const host = document.querySelector("[data-k2b-toasts]");
if (host) Array.from(host.children).forEach(c => {
const ic = c.children[0] ? c.children[0].textContent.trim() : "", tx = c.children[1] ? c.children[1].textContent.trim() : "";
if (tx) add(tx.replace(/\+\d+$/, ""), ic === "✕" ? "err" : ic === "!" ? "warn" : ic === "i" ? "info" : "ok", 6000);
});
} catch (e) {}
const on = (e) => { const d = (e && e.detail) || {}; if (d.msg) add(d.msg, d.kind, d.ttl); };
window.addEventListener("k2btoast", on);
return () => window.removeEventListener("k2btoast", on);
}, []);
const last = msgs.length ? msgs[msgs.length - 1] : null;
const hold = last ? Math.max(4000, last.ttl + 1500) : 0;
React.useEffect(() => { if (!last) return undefined; const t = setTimeout(tick, hold + 50); return () => clearTimeout(t); }, [last ? last.id : null]);
return { msgs, last, fresh: !!last && (Date.now() - last.at < hold) };
}
// Put the cursor in a desk box (by data-f / data-opt selector) and flash it.
function dkfFocusBox(root, sel) {
try {
const host = root && root.querySelector(sel); if (!host) return false;
const inp = host.matches("input") ? host : host.querySelector("input:not([type='checkbox']), select");
const el = inp || host;
if (el.focus) el.focus();
if (inp && inp.select) inp.select();
const fl = host.closest(".dkc-f, .dkc-c") || host;
fl.classList.remove("dkc-flash"); void fl.offsetWidth; fl.classList.add("dkc-flash");
setTimeout(() => { try { fl.classList.remove("dkc-flash"); } catch (e) {} }, 950);
return true;
} catch (e) { return false; }
}
// ── RC27 (dsk): DEAL COST LINES (the Profit panel) — helpers ──
// A line: { id, type, desc, amount ("595.00"), side "front" | "back", weOwe, passed (lender fee only), kind, points, lender, auto }.
// The checks and the money are DealCalc's (= the server's lib/deal-calc.js): costAddsOf / profitPlan.
const dkpId = () => "CA" + Date.now().toString(36).toUpperCase() + Math.random().toString(36).slice(2, 6).toUpperCase();
const dkpDealOf = (dealId) => { try { return dealId && window.DMS && window.DMS.deals ? (window.DMS.deals().find(x => x.id === dealId) || null) : null; } catch (e) { return null; } };
const dkpDealCostAdds = (dealId) => { const d = dkpDealOf(dealId); return d && Array.isArray(d.costAdds) ? JSON.parse(JSON.stringify(d.costAdds)) : []; };
const dkpPosted = (d) => !!d && !d.imported && (d.postedToGL === true || (window.DealCalc ? window.DealCalc.isPostedDeal(d) : false));
const DKP_TYPES = () => (window.DealCalc ? window.DealCalc.COST_ADD_ORDER.map(k => [k, window.DealCalc.COST_ADD_TYPES[k]]) : []);
const DKP_EMPTY = { any: false, lines: [], problems: [], dpaCents: 0, feeCustCents: 0, feeDealerCents: 0, frontCents: 0, backCents: 0, weOweCents: 0, accrueCents: 0 };
const dkpCa = (list) => (window.DealCalc && Array.isArray(list) && list.length ? window.DealCalc.costAddsOf({ costAdds: list }) : DKP_EMPTY);
// RC27 (dsk) fix round (audit): the PACK the posting uses — the unit's own pack in Inventory ($0.00 when Inventory has none), exactly what
// the server's posting reads (deal-calc postPlan: the RC21 pay plan, pack counts). null = the unit is not in Inventory (the desk pack stands).
const dkpInvPackOf = (stock) => {
if (!stock || !window.DMS || !window.DMS._unit) return null;
let u = null; try { u = window.DMS._unit(stock); } catch (e) { u = null; }
if (!u) return null;
const c = Math.round((+u.pack || 0) * 100);
return isFinite(c) && c > 0 ? c / 100 : 0;
};
const DkfDeskSheet = ({ onClose, go }) => {
const settingsRef = React.useRef(loadDeskSettings());
const S = settingsRef.current;
const [unit, setUnitState] = React.useState(dtLoadUnit);
const [cust, setCust] = React.useState(dtLoadCust);
const [picker, setPicker] = React.useState(null); // "unit" | "cust"
const [settingsOpen, setSettingsOpen] = React.useState(false);
const [printOpen, setPrintOpen] = React.useState(false);
// Live registries
const [, regForce] = React.useReducer(n => n + 1, 0);
React.useEffect(() => {
const offs = [];
if (window.jbLenders) offs.push(window.jbLenders.subscribe(regForce));
if (window.jbProducts) offs.push(window.jbProducts.subscribe(regForce));
return () => offs.forEach(f => f && f());
}, []);
// Titling entities (role: lienholder/lessor, e.g. AmeriCredit / ACAR Leasing)
// stay out of the funding-lender menu — they're auto-selected per deal type.
const lenderList = (window.jbLenders ? window.jbLenders.list().filter(l => l.active && !l.role) : []);
const catalog = (window.jbProducts ? window.jbProducts.list().filter(p => p.active) : []);
const [, dtForce] = React.useReducer(n => n + 1, 0);
React.useEffect(() => (window.DMS && window.DMS.subscribe) ? window.DMS.subscribe(dtForce) : undefined, []);
const unitCheck = (window.DMS && window.DMS.unitOpenItems && unit.stock)
? window.DMS.unitOpenItems(unit.stock, { exceptDeal: cust.dealId }) : { clean: true, items: [] };
const setUnit = (u) => {
const n = dtNormUnit(u); setUnitState(n);
window.jbDeskUnit = { ...n, list: n.msrp };
try { localStorage.setItem("jb_desk_unit", JSON.stringify(window.jbDeskUnit)); } catch (e) {}
if (n.msrp) setPrice(n.msrp);
setMsrpOv(n.msrp || 0);
// Inception miles seed from the unit's odometer — the engine auto-applies
// the GMF retro adjustment when it lands in the 1,250–7,500 mi window.
setInceptionMiles(+n.miles || 0);
setPicker(null);
window.k2bToast && window.k2bToast(`Now desking ${n.yr} ${n.mk} ${n.mdl} · Stk ${n.stock}.`, "ok");
};
const pickCust = (c) => {
setCust(c);
dkpLoadedRef.current = c.dealId || null; setCostAdds(dkpDealCostAdds(c.dealId)); // RC27 (dsk): the picked deal's cost lines
window.jbDeskDeal = c.dealId ? { id: c.dealId, buyer: c.name } : { buyer: c.name };
try { localStorage.setItem("jb_desk_deal", JSON.stringify(window.jbDeskDeal)); } catch (e) {}
if (c.dealId) { try { localStorage.setItem("jb_fw_deal", c.dealId); } catch (e) {} }
// Carry the deal's vehicle onto the desk automatically — picking a deal
// should never require re-picking the unit it was written on.
if (c.dealId && window.DMS && window.DMS.deals) {
const d = window.DMS.deals().find(x => x.id === c.dealId);
const u = d && d.stock ? (window.DMS.inventory() || []).find(x => x.stock === d.stock) : null;
if (u) {
const n = dtNormUnit({ ...u, list: u.asking });
setUnitState(n);
window.jbDeskUnit = { ...n, list: n.msrp };
try { localStorage.setItem("jb_desk_unit", JSON.stringify(window.jbDeskUnit)); } catch (e) {}
if (d.salePrice) setPrice(+d.salePrice); else if (n.msrp) setPrice(n.msrp);
setMsrpOv(n.msrp || 0);
setInceptionMiles(+n.miles || 0);
}
}
setPicker(null);
window.k2bToast && window.k2bToast(`Customer set: ${c.name}${c.dealId ? " · deal " + c.dealId + " (unit carried over)" : ""}.`, "ok");
};
const W = React.useRef(dtLoadWork()).current; // restore the in-progress desk
const [price, setPrice] = React.useState(() => W.price != null ? W.price : (unit.msrp || 0));
// MSRP override — residual $ keys off THIS, never the sale price, so the
// desk can discount the sell price while MSRP stays fixed for the residual
// calc. Seeded from the unit's sticker; re-seeds whenever the unit changes.
const [msrpOv, setMsrpOv] = React.useState(() => W.msrpOv != null ? W.msrpOv : (unit.msrp || 0));
const msrpVal = +msrpOv || unit.msrp || price;
const [docFee, setDocFee] = React.useState(W.docFee != null ? W.docFee : S.docFee);
const [taxRate, setTaxRate] = React.useState(W.taxRate != null ? W.taxRate : S.taxRate);
const [tradeAllow, setTradeAllow] = React.useState(W.tradeAllow || 0);
const [tradeAcv, setTradeAcv] = React.useState(W.tradeAcv || 0);
const [tradePayoff, setTradePayoff] = React.useState(W.tradePayoff || 0);
const [hasTrade, setHasTrade] = React.useState(!!W.hasTrade);
const [tradeDesc, setTradeDesc] = React.useState(W.tradeDesc || "");
// Payoff lender (bank the trade payoff goes to) — from the CRM lead / deal,
// editable here, carried to the Finance Deal + Buyer's Order.
const [tradeLender, setTradeLender] = React.useState(W.tradeLender || "");
const removeTrade = () => { setHasTrade(false); setTradeAllow(0); setTradeAcv(0); setTradePayoff(0); setTradeDesc(""); setTradeLender(""); window.k2bToast && window.k2bToast("No trade on this deal.", "ok"); };
const loadAppraisal = () => {
const ap = (window.DMS && window.DMS.appraisals && window.DMS.appraisals()[0]) || null;
if (!ap) { window.k2bToast && window.k2bToast("No saved appraisals — use \u201c+ Enter manually,\u201d or run Trade Appraisal.", "warn", 3600); return; }
setHasTrade(true);
setTradeDesc(`${[ap.year, ap.make, ap.model].filter(Boolean).join(" ").toUpperCase()}${ap.mileage ? " \u00b7 " + Number(ap.mileage).toLocaleString() + " mi" : ""}`);
setTradeAllow(+ap.allowance || 0); setTradePayoff(+ap.payoff || 0);
setTradeAcv(ap.acv != null ? (+ap.acv || 0) : (+ap.allowance || 0));
if (ap.lender || ap.payoffLender || ap.lienholder) setTradeLender(String(ap.lender || ap.payoffLender || ap.lienholder).toUpperCase());
window.k2bToast && window.k2bToast("Trade loaded from latest appraisal.", "ok");
};
const addManualTrade = () => { setHasTrade(true); setTradeDesc(""); setTradeAllow(0); setTradePayoff(0); setTradeAcv(0); setTradeLender(""); };
// Seed the trade from THE deal (CRM → Desk It carries tradeDesc / payoff /
// lender / appraisal onto it). Runs once per desk when the work blob is
// fresh — dtLoadWork returns only { dealId } (hasTrade undefined) whenever
// the desk opens on a DIFFERENT deal than the saved one. A deal "has a
// trade" when any of allowance / payoff / description / appraisal is set.
const tradeInitRef = React.useRef(false);
React.useEffect(() => {
if (tradeInitRef.current || W.hasTrade != null) return; tradeInitRef.current = true;
const d = cust.dealId && window.DMS && window.DMS.deals ? window.DMS.deals().find(x => x.id === cust.dealId) : null;
if (d && (+d.tradeAllow > 0 || +d.tradePayoff > 0 || d.tradeDesc || d.tradeYrMkMdl || d.tradeVeh || d.tradeApprId)) {
setHasTrade(true); setTradeAllow(+d.tradeAllow || 0); setTradePayoff(+d.tradePayoff || 0);
setTradeAcv(+d.tradeACV || +d.tradeAcv || +d.tradeAllow || 0); setTradeDesc(d.tradeYrMkMdl || d.tradeDesc || d.tradeVeh || "");
setTradeLender(String(d.tradeLender || "").toUpperCase());
}
}, [cust.dealId]);
// Down payment lives PER OPTION now (sc[i].down / lsc[i].down) — the legacy
// single cashDown only seeds migration of an older work blob / saved scenario.
const legacyDown = W.cashDown != null ? +W.cashDown || 0 : S.cashDown;
const [rebateIn, setRebate] = React.useState(W.rebate != null ? W.rebate : S.rebate); // as entered (new units)
// Rebates apply to NEW units only. When the unit turns used the entered
// rebate is moved here (NOT applied anywhere) so the user can review it on
// the way back to new; rebateReview = [{ key, label, amount }] pending Apply.
const [rebateHeld, setRebateHeld] = React.useState(W.rebateHeld != null ? +W.rebateHeld || 0 : 0);
const [leaseCashHeld, setLeaseCashHeld] = React.useState(W.leaseCashHeld != null ? +W.leaseCashHeld || 0 : 0);
const [rebateReview, setRebateReview] = React.useState(Array.isArray(W.rebateReview) ? W.rebateReview : []);
const [lender, setLender] = React.useState(() => W.lender || (window.jbLenders && window.jbLenders.activeNames()[0]) || "GM Financial");
const [pack, setPack] = React.useState(W.pack != null ? W.pack : S.pack);
// RC27 (dsk) fix round (audit): the desk pack FOLLOWS the unit's Inventory pack (what the posting books) unless a manager typed a pack
// by hand (a what-if — the Profit panel then also shows what the posting will book with the Inventory pack).
const [packHand, setPackHand] = React.useState(() => !!W.packHand);
const dkpInvPack = dkpInvPackOf(unit.stock);
React.useEffect(() => {
if (dkpInvPack != null && !packHand && Math.round((+pack || 0) * 100) !== Math.round(dkpInvPack * 100)) setPack(dkpInvPack);
}, [unit.stock, dkpInvPack, packHand, pack]);
const setPackByHand = (v) => { setPackHand(true); setPack(v); };
const [commPct, setCommPct] = React.useState(W.commPct != null ? W.commPct : S.commPct);
// Credit profile: blank until the customer record / F&I supplies it (B-05).
const [fico, setFico] = React.useState(() => W.fico != null ? W.fico : (+cust.credit || 0));
const [income, setIncome] = React.useState(() => W.income != null ? W.income : (+cust.income || 0));
const [dti, setDti] = React.useState(() => W.dti != null ? W.dti : String(cust.dti || ""));
// Re-sync the credit profile ONLY when a different customer is picked —
// never on mount (that was silently resetting edited FICO/income/DTI).
const custSyncRef = React.useRef(false);
React.useEffect(() => { if (!custSyncRef.current) { custSyncRef.current = true; return; } setFico(+cust.credit || 0); setIncome(+cust.income || 0); setDti(String(cust.dti || "")); }, [cust]);
// Finance options A–D: { term, apr, gap, vsc, all, down }. Always 4 entries;
// scOn says which letters are visible (A + B always on). Letters are stable.
const [sc, setSc] = React.useState(() => dtMigrateSc(W.sc, legacyDown, [
{ term: S.t1, apr: S.a1, gap: false, vsc: false, all: false },
{ term: S.t2, apr: S.a2, gap: true, vsc: false, all: false },
{ term: S.t3, apr: S.a3, gap: true, vsc: true, all: false }]));
const setScK = (i, k, v) => setSc(arr => arr.map((s, j) => j === i ? { ...s, [k]: v } : s));
const [scOn, setScOn] = React.useState(() => dtMigrateOn(W.scOn, W.sc));
// ── FINANCE vs LEASE ──────────────────────────────────────────
// One desk, two quote types. Lease scenarios carry their own term, money
// factor and residual %; everything else (price, trade, products) is shared.
const [mode, setMode] = React.useState(W.mode === "lease" ? "lease" : "finance");
const [milesPerYr, setMilesPerYr] = React.useState(W.milesPerYr != null ? W.milesPerYr : S.milesPerYr);
// GMF program mode — ON by default; MF/residuals autofill off the rate sheet.
const [gmfOn, setGmfOn] = React.useState(W.gmfOn != null ? !!W.gmfOn : true);
const [gmfNewUsed, setGmfNewUsed] = React.useState(W.gmfNewUsed === "used" ? "used" : "new");
const [gmfPick, setGmfPick] = React.useState(W.gmfPick || null);
const [gmfTier, setGmfTier] = React.useState(W.gmfTier || "A+/A1");
const [gmfLeaseCash, setGmfLeaseCash] = React.useState(W.gmfLeaseCash != null ? +W.gmfLeaseCash || 0 : 0);
const [gmfPickerOpen, setGmfPickerOpen] = React.useState(false);
const gmfFees = (dtGmfData().program.fees || {});
const [acqFee, setAcqFee] = React.useState(W.acqFee != null ? W.acqFee
: (W.gmfOn === false ? S.acqFee : (+gmfFees.acquisition || 0)));
const [acqFeeCapped, setAcqFeeCapped] = React.useState(W.acqFeeCapped != null ? !!W.acqFeeCapped : true);
const [docFeeCapped, setDocFeeCapped] = React.useState(W.docFeeCapped != null ? !!W.docFeeCapped : true);
const [titleLicFee, setTitleLicFee] = React.useState(W.titleLicFee != null ? W.titleLicFee : 0);
const [titleLicCapped, setTitleLicCapped] = React.useState(W.titleLicCapped != null ? !!W.titleLicCapped : true);
const [capRed, setCapRed] = React.useState(W.capRed != null ? W.capRed : 0);
const [taxMethod, setTaxMethod] = React.useState(W.taxMethod || (S.leaseTaxOnPayment === false ? "upfront_on_payment_stream" : "monthly"));
// Lease tax rate is SEPARATE from the finance-desk rate: the finance desk
// keeps the dealership rate (7.225); the lease desk defaults to the
// customer's combined local rate collected on lease receipts (9.537 —
// validated against the live RouteOne deal). Both editable, both persisted.
const [leaseTaxRate, setLeaseTaxRate] = React.useState(W.leaseTaxRate != null ? W.leaseTaxRate : DT_LEASE_TAX_DEFAULT);
// Mileage factor group — extra mileage purchased up front, inception
// (odometer) miles, and their $/mi rates. inceptionAdjust: null = AUTO
// (engine applies the GMF 1,250–7,500 mi retro window), true/false = forced.
const [extraMiles, setExtraMiles] = React.useState(W.extraMiles != null ? W.extraMiles : 0);
const [extraMileageRate, setExtraMileageRate] = React.useState(W.extraMileageRate != null ? W.extraMileageRate : 0.20);
const [inceptionMiles, setInceptionMiles] = React.useState(W.inceptionMiles != null ? W.inceptionMiles : (+unit.miles || 0));
const [inceptionAdjust, setInceptionAdjust] = React.useState(W.inceptionAdjust !== undefined ? W.inceptionAdjust : null);
const [inceptionRate, setInceptionRate] = React.useState(W.inceptionRate != null ? W.inceptionRate : 0.20);
const [secDep, setSecDep] = React.useState(W.secDep != null ? W.secDep : null); // null = auto (B2/B3 rule)
const [dispFee, setDispFee] = React.useState(W.dispFee != null ? W.dispFee
: (gmfFees.disposition != null ? +gmfFees.disposition : (S.dispFee != null ? S.dispFee : 395)));
const [excessRate, setExcessRate] = React.useState(W.excessRate != null ? W.excessRate
: (gmfFees.excessMileagePerMile != null ? +gmfFees.excessMileagePerMile : 0.25));
const [purchOptFee, setPurchOptFee] = React.useState(W.purchOptFee != null ? W.purchOptFee : (+gmfFees.purchaseOption || 0));
const [dealerMarkupMf, setDealerMarkupMf] = React.useState(W.dealerMarkupMf != null ? W.dealerMarkupMf : 0);
const [leaseExpand, setLeaseExpand] = React.useState(null); // option index w/ open line-item breakdown
const [lsc, setLsc] = React.useState(() => dtMigrateSc(W.lsc, legacyDown, [
dtNormLsc({ term: S.lt1, mf: S.lmf1 }),
dtNormLsc({ term: S.lt2, mf: S.lmf2 }),
dtNormLsc({ term: S.lt3, mf: S.lmf3, vsc: true })], dtNormLsc));
const setLscK = (i, k, v) => setLsc(arr => arr.map((s, j) => j === i ? { ...s, [k]: v } : s));
// Which option (A–D) is SELECTED for presentation — drives the highlight,
// the hero payment, prints, and what goes to F&I. Click a row to choose.
// Always a VISIBLE option.
const [selSc, setSelScRaw] = React.useState(() => { const on = dtMigrateOn(W.scOn, W.sc); return (W.selSc >= 0 && W.selSc < DT_MAX_OPTS && on[W.selSc]) ? W.selSc : 1; });
const setSelSc = (i) => { if (scOn[i]) setSelScRaw(i); };
// Visible option indexes, in letter order — the ONLY options any total,
// print or push ever reads.
const visIdx = [0, 1, 2, 3].filter(i => scOn[i]);
const addOption = () => {
const i = scOn.findIndex(v => !v);
if (i < 0) return;
// New option = copy of the previous VISIBLE option (term, rate, products, down).
const prev = [0, 1, 2, 3].filter(j => scOn[j] && j < i).pop();
const src = prev != null ? prev : 1;
setSc(arr => arr.map((s, j) => j === i ? { ...arr[src] } : s));
setLsc(arr => arr.map((s, j) => j === i ? { ...arr[src] } : s));
setScOn(on => on.map((v, j) => j === i ? true : v));
window.k2bToast && window.k2bToast(`Option ${DT_LETTERS[i]} added (copy of ${DT_LETTERS[src]}).`, "ok");
};
const removeOption = (i) => {
if (i < 2) return; // A and B always stay
const on = scOn.map((v, j) => j === i ? false : v);
setScOn(on);
if (selSc === i) setSelScRaw([0, 1, 2, 3].filter(j => on[j] && j < i).pop() ?? 1);
window.k2bToast && window.k2bToast(`Option ${DT_LETTERS[i]} removed — it no longer prints or pushes.`, "ok");
};
// F&I products — selected map keyed by product id → chosen retail price.
const [selected, setSelected] = React.useState(W.selected || {}); // { [id]: price }
const toggleProduct = (p) => setSelected(m => { const n = { ...m }; if (n[p.id] != null) delete n[p.id]; else n[p.id] = p.retail; return n; });
const setProductPrice = (id, v) => setSelected(m => ({ ...m, [id]: v }));
const selectedProducts = catalog.filter(p => selected[p.id] != null);
// ── RC27 (dsk): the deal's COST LINES (Profit panel). Saved with the deal (deal.costAdds — the server posts them) and in the working
// desk / saved scenarios. A posted deal's lines are its own (frozen — the panel shows them read-only).
const [costAdds, setCostAdds] = React.useState(() => (Array.isArray(W.costAdds) && W.dealId === (cust.dealId || null)) ? W.costAdds : dkpDealCostAdds(cust.dealId));
const dkpLoadedRef = React.useRef(cust.dealId || null);
const dkpDeal = dkpDealOf(cust.dealId);
const dkpIsPosted = dkpPosted(dkpDeal);
const dkpLines = dkpIsPosted ? (Array.isArray(dkpDeal.costAdds) ? dkpDeal.costAdds : []) : costAdds;
const CA = dkpCa(dkpLines);
const setCaK = (i, k, v) => setCostAdds(arr => arr.map((l, j) => {
if (j !== i) return l;
if (k === "type") { const T = (window.DealCalc && window.DealCalc.COST_ADD_TYPES[v]) || {}; return Object.assign({}, l, { type: v, side: T.side || "front", weOwe: v === "weOwe", passed: false, kind: v === "lenderFee" ? (l.kind || "acquisition") : undefined, auto: false }); }
return Object.assign({}, l, { [k]: v }, k === "amount" || k === "desc" || k === "passed" || k === "side" || k === "kind" ? { auto: false } : {});
}));
const addCostLine = (type) => setCostAdds(arr => arr.length >= ((window.DealCalc && window.DealCalc.MAX_COST_ADDS) || 20) ? arr
: arr.concat([{ id: dkpId(), type, desc: "", amount: "", side: (window.DealCalc && window.DealCalc.COST_ADD_TYPES[type] || {}).side || "front", weOwe: type === "weOwe", passed: false, kind: type === "lenderFee" ? "acquisition" : undefined }]));
// a lender fee taken off by hand is not suggested again for that lender on this desk
const [dkpFeeOff, setDkpFeeOff] = React.useState(() => (W.dkpFeeOff && W.dealId === (cust.dealId || null)) ? String(W.dkpFeeOff) : "");
const removeCostLine = (i) => setCostAdds(arr => { const l = arr[i]; if (l && l.type === "lenderFee") setDkpFeeOff(String(l.lender || lender || "")); return arr.filter((x, j) => j !== i); });
// ── Saved scenarios — snapshot / reopen the COMPLETE desk (server-synced
// via DMS.deskScenarios so any workstation can pick the deal back up). ──
const [scenModal, setScenModal] = React.useState(null); // "save" | "list" | null
const [scenName, setScenName] = React.useState("");
const collectScenario = () => JSON.parse(JSON.stringify({
unit, cust, price, msrpOv, docFee, taxRate,
hasTrade, tradeDesc, tradeAllow, tradeAcv, tradePayoff, tradeLender,
cashDown, rebate: rebateIn, rebateHeld, leaseCashHeld, lender, pack, packHand, commPct,
fico, income, dti, sc, scOn, selSc, selected,
mode, milesPerYr, acqFee, capRed, lsc,
gmfOn, gmfNewUsed, gmfPick, gmfTier, gmfLeaseCash,
acqFeeCapped, docFeeCapped, titleLicFee, titleLicCapped,
taxMethod, secDep, dispFee, excessRate, purchOptFee, dealerMarkupMf,
leaseTaxRate, extraMiles, extraMileageRate, inceptionMiles, inceptionAdjust, inceptionRate,
costAdds, // RC27 (dsk)
}));
const restoreScenario = (st) => {
if (!st) { window.k2bToast && window.k2bToast("Scenario has no saved state.", "warn"); return; }
tradeInitRef.current = true; // keep the deal-seed effect from clobbering the restored trade
// Restore customer with credit fields matching the saved desk, so the
// customer-resync effect re-derives the SAME fico / income / DTI.
const c = { ...(st.cust || {}), credit: st.fico, income: st.income, dti: st.dti };
setCust(c);
window.jbDeskDeal = c.dealId ? { id: c.dealId, buyer: c.name } : { buyer: c.name || "" };
try { localStorage.setItem("jb_desk_deal", JSON.stringify(window.jbDeskDeal)); } catch (e) {}
if (st.unit && st.unit.stock) {
setUnitState(st.unit);
window.jbDeskUnit = { ...st.unit, list: st.unit.msrp };
try { localStorage.setItem("jb_desk_unit", JSON.stringify(window.jbDeskUnit)); } catch (e) {}
}
setPrice(+st.price || 0); setDocFee(+st.docFee || 0); setTaxRate(+st.taxRate || 0);
setMsrpOv(st.msrpOv != null ? +st.msrpOv || 0 : ((st.unit && +st.unit.msrp) || 0));
setHasTrade(!!st.hasTrade); setTradeDesc(st.tradeDesc || ""); setTradeLender(st.tradeLender || "");
setTradeAllow(+st.tradeAllow || 0); setTradeAcv(+st.tradeAcv || 0); setTradePayoff(+st.tradePayoff || 0);
// Rebate: the scenario's own figure; its held (used-unit) rebate comes back
// as held — never applied — and no review chip carries over.
const stUsed = (st.unit && st.unit.newUsed === "U") || (st.mode === "lease" && st.gmfOn !== false && st.gmfNewUsed === "used");
nuPrevRef.current = !!stUsed; // restored status is the baseline — no transition
const stReb = +st.rebate || 0, stLc = +st.gmfLeaseCash || 0;
setRebate(stUsed ? 0 : stReb); setRebateHeld(stUsed && stReb ? stReb : (+st.rebateHeld || 0));
setLeaseCashHeld(stUsed && stLc ? stLc : (+st.leaseCashHeld || 0)); setRebateReview([]);
if (st.lender) setLender(st.lender);
setPack(+st.pack || 0); setPackHand(!!st.packHand); setCommPct(+st.commPct || 0);
setFico(+st.fico || 0); setIncome(+st.income || 0); setDti(String(st.dti || ""));
// Options A–D (older 3-option scenarios migrate: down = their cashDown).
const stDown = +st.cashDown || 0;
if (Array.isArray(st.sc) && st.sc.length) setSc(dtMigrateSc(st.sc, stDown, st.sc));
const stOn = dtMigrateOn(st.scOn, st.sc);
setScOn(stOn);
setSelScRaw(st.selSc >= 0 && st.selSc < DT_MAX_OPTS && stOn[st.selSc] ? st.selSc : 1);
setSelected(st.selected || {});
setMode(st.mode === "lease" ? "lease" : "finance");
setMilesPerYr(st.milesPerYr != null ? st.milesPerYr : S.milesPerYr);
setAcqFee(st.acqFee != null ? st.acqFee : S.acqFee);
setCapRed(+st.capRed || 0);
if (Array.isArray(st.lsc) && st.lsc.length) setLsc(dtMigrateSc(st.lsc, stDown, st.lsc, dtNormLsc));
// GMF program state — every field the lease desk carries.
setGmfOn(st.gmfOn != null ? !!st.gmfOn : true);
setGmfNewUsed(st.gmfNewUsed === "used" ? "used" : "new");
setGmfPick(st.gmfPick || null);
setGmfTier(st.gmfTier || "A+/A1");
setGmfLeaseCash(stUsed ? 0 : (+st.gmfLeaseCash || 0));
setAcqFeeCapped(st.acqFeeCapped != null ? !!st.acqFeeCapped : true);
setDocFeeCapped(st.docFeeCapped != null ? !!st.docFeeCapped : true);
setTitleLicFee(+st.titleLicFee || 0);
setTitleLicCapped(st.titleLicCapped != null ? !!st.titleLicCapped : true);
setTaxMethod(st.taxMethod || "monthly");
setSecDep(st.secDep != null ? st.secDep : null);
setDispFee(st.dispFee != null ? +st.dispFee : 395);
setExcessRate(st.excessRate != null ? +st.excessRate : 0.25);
setPurchOptFee(+st.purchOptFee || 0);
setDealerMarkupMf(+st.dealerMarkupMf || 0);
// Mileage factor group + the lease-specific tax rate.
setLeaseTaxRate(st.leaseTaxRate != null ? +st.leaseTaxRate : DT_LEASE_TAX_DEFAULT);
setExtraMiles(+st.extraMiles || 0);
setExtraMileageRate(st.extraMileageRate != null ? +st.extraMileageRate : 0.20);
setInceptionMiles(st.inceptionMiles != null ? +st.inceptionMiles : ((st.unit && +st.unit.miles) || 0));
setInceptionAdjust(st.inceptionAdjust !== undefined ? st.inceptionAdjust : null);
setInceptionRate(st.inceptionRate != null ? +st.inceptionRate : 0.20);
// RC27 (dsk): the scenario's cost lines (a posted deal keeps its own — the panel shows the deal's)
dkpLoadedRef.current = c.dealId || null; setCostAdds(Array.isArray(st.costAdds) ? JSON.parse(JSON.stringify(st.costAdds)) : []);
};
const openSaveScenario = () => {
const d = new Date();
const who = (!cust.preset && cust.name) ? cust.name : "Walk-in";
const veh = `${unit.yr || ""} ${unit.mk || ""} ${unit.mdl || ""}`.replace(/\s+/g, " ").trim();
setScenName(`${who} — ${veh || unit.stock || "No unit"} — ${d.getMonth() + 1}/${d.getDate()}`);
setScenModal("save");
};
const saveScenario = () => {
const name = scenName.trim() || "Untitled scenario";
const r = (window.DMS && window.DMS.saveDeskScenario)
? window.DMS.saveDeskScenario({ id: "DS-" + Date.now(), name, state: collectScenario(), view: dkfViewSafe() })
: { ok: false, msg: "DMS core not loaded." };
if (r && r.ok) { setScenModal(null); window.k2bToast && window.k2bToast("Scenario saved", "ok"); }
else window.k2bToast && window.k2bToast((r && r.msg) || "Could not save scenario.", "warn");
};
// ── Computed ──
// New vs used: from the desked unit (inventory type) — and, on a lease, the
// GMF Used (CPO) program. Used units carry NO new-vehicle rebate / lease cash:
// `rebate` below is the EFFECTIVE rebate every figure, print and push reads.
const isUsedUnit = unit.newUsed === "U" || (mode === "lease" && gmfOn && gmfNewUsed === "used");
const rebate = isUsedUnit ? 0 : (+rebateIn || 0);
const leaseCashEff = (!isUsedUnit && gmfOn && gmfNewUsed === "new") ? (+gmfLeaseCash || 0) : 0;
// New ↔ used transitions (unit pick, deal pick, scenario restore, GMF
// New / Used (CPO) buttons, finance ↔ lease with a GMF used program):
// → used: the entered rebate / GMF lease cash move to HELD (not applied);
// → new : nothing is re-applied — an amber review chip offers Apply / Discard.
// All figures read the effective `rebate` above, so they recalc in the same
// render the status flips (this effect only tidies the stored inputs).
const nuPrevRef = React.useRef(null);
React.useEffect(() => {
const prev = nuPrevRef.current; nuPrevRef.current = isUsedUnit;
if (prev === isUsedUnit) return;
if (isUsedUnit) {
const r = +rebateIn || 0, lc = +gmfLeaseCash || 0;
if (r > 0) { setRebateHeld(r); setRebate(0); }
if (lc > 0) { setLeaseCashHeld(lc); setGmfLeaseCash(0); }
setRebateReview([]);
if (prev === false && (r > 0 || lc > 0) && window.k2bToast)
window.k2bToast(`Used unit — ${[r > 0 ? "rebate " + dt$(r) : null, lc > 0 ? "GMF lease cash " + dt$(lc) : null].filter(Boolean).join(" + ")} removed from every option (held for review if it goes back to new).`, "warn", 4800);
} else if (prev === true) {
const items = [];
if (rebateHeld > 0) items.push({ key: "rebate", label: "Rebate", amount: dtRound2(rebateHeld) });
// GMF program data for the picked new model outranks the held lease cash.
const progCash = gmfOn && gmfPick && +gmfPick.leaseCash > 0 ? +gmfPick.leaseCash : 0;
const lc = progCash || leaseCashHeld;
if (lc > 0) items.push({ key: "leaseCash", label: progCash ? "GMF lease cash (program)" : "GMF lease cash", amount: dtRound2(lc) });
setRebateReview(items);
}
}, [isUsedUnit]);
const reviewApply = (it) => {
if (it.key === "rebate") { setRebate(it.amount); setRebateHeld(0); }
else { setGmfLeaseCash(it.amount); setLeaseCashHeld(0); }
setRebateReview(list => list.filter(x => x.key !== it.key));
window.k2bToast && window.k2bToast(`${it.label} ${dt$(it.amount)} applied to every option.`, "ok");
};
const reviewDiscard = (it) => {
if (it.key === "rebate") setRebateHeld(0); else setLeaseCashHeld(0);
setRebateReview(list => list.filter(x => x.key !== it.key));
};
// Per-option down payment (finance + lease each keep their own per letter).
const optDown = (i, lease = mode === "lease") => +(((lease ? lsc : sc)[i]) || {}).down || 0;
const cashDown = optDown(selSc); // the SELECTED option's down
const setCashDown = (v) => (mode === "lease" ? setLscK : setScK)(selSc, "down", +v || 0);
// Tax: taxable = price + doc − trade allowance. The rebate is applied AFTER
// tax (not a pre-tax reduction), so dropping it moves amount financed by
// exactly the rebate amount.
const taxable = price + docFee - tradeAllow;
const tax = Math.max(0, taxable) * (taxRate / 100);
const tradeEquity = tradeAllow - tradePayoff;
// Trade equity split (retail + lease alike): positive equity is DOWN money;
// negative equity (payoff > allowance) is ROLLED INTO the loan / cap cost.
const negEquity = hasTrade ? Math.max(0, dtRound2(tradePayoff - tradeAllow)) : 0;
const posEquity = hasTrade ? Math.max(0, dtRound2(tradeAllow - tradePayoff)) : 0;
// Per-scenario product loads — GAP / Warranty(VSC) priced from the catalog
// (or the picked price when selected); "All" = every selected F&I product.
// The "Warranty" pill uses the service contract ticked in the checklist when
// one is, else the first active one in the catalog (same for GAP).
const isGapProd = (p) => /gap/i.test((p.name || "") + " " + (p.category || ""));
const isVscProd = (p) => !isGapProd(p) && /vsc|service contract|warrant/i.test((p.name || "") + " " + (p.category || ""));
const gapProd = catalog.find(p => isGapProd(p) && selected[p.id] != null) || catalog.find(isGapProd) || null;
const vscProd = catalog.find(p => isVscProd(p) && selected[p.id] != null) || catalog.find(isVscProd) || null;
const prodPrice = (p) => p ? (selected[p.id] != null ? +selected[p.id] || 0 : +p.retail || 0) : 0;
const prodCost = (p) => p ? +p.cost || 0 : 0;
// The protection products that are PART OF the chosen scenario, as the deal
// record carries them: [{ id, code, name, category, provider, price, cost, termMo }].
// all → every ticked product (GAP + Warranty when nothing is ticked)
// else → [Warranty if vsc, GAP if gap]
const chosenProducts = (i = selSc) => {
const s = (isLease ? lsc : sc)[i] || {};
let list = s.all ? (selectedProducts.length ? selectedProducts : [vscProd, gapProd])
: [s.vsc ? vscProd : null, s.gap ? gapProd : null];
const seen = {};
return list.filter(p => p && !seen[p.id] && (seen[p.id] = true)).map(p => ({
id: p.id, code: p.code || p.id, name: p.name || "", category: p.category || "", provider: p.provider || "",
price: dtRound2(prodPrice(p)), cost: dtRound2(prodCost(p)), termMo: +p.termMo || 0 }));
};
const scAdds = (i) => { const s = sc[i] || {};
if (s.all) { const sel = selectedProducts.reduce((sum, p) => sum + (+selected[p.id] || 0), 0); return sel || prodPrice(gapProd) + prodPrice(vscProd); }
return (s.gap ? prodPrice(gapProd) : 0) + (s.vsc ? prodPrice(vscProd) : 0); };
// amtFin(i) = price + doc + tax + $30 title − trade allowance + trade payoff
// − down(i) − rebate (new units only) + products(i)
const baseFin = (i) => price + docFee + tax + 30 - tradeAllow + tradePayoff - optDown(i, false) - rebate;
// RC27 (dsk): a lender fee PASSED TO THE CUSTOMER is the customer's fee line — in the amount financed (finance desk; never on a lease)
const dkpFeeCust = mode === "lease" ? 0 : CA.feeCustCents / 100;
const dkpDpa = CA.dpaCents / 100; // the store's down payment assistance — the customer pays this much less cash
const amtFin = (i) => baseFin(i) + scAdds(i) + dkpFeeCust;
const fiAdds = scAdds(selSc); // selected scenario's products
const amtFinanced = amtFin(selSc);
const frontGross = price - unit.cost - (tradeAllow - tradeAcv);
const fiGross = (() => { const s = sc[selSc] || {};
if (s.all) return selectedProducts.reduce((sum, p) => sum + Math.max(0, (+selected[p.id] || 0) - (+p.cost || 0)), 0);
let g = 0; if (s.gap) g += Math.max(0, prodPrice(gapProd) - prodCost(gapProd));
if (s.vsc) g += Math.max(0, prodPrice(vscProd) - prodCost(vscProd)); return g; })();
const totalGross = frontGross + fiGross - pack;
// RC27 (dsk): the PROFIT with the cost lines — DealCalc.profitPlan, the same function the server's posting uses for the commission
// (the pay plan the posting books: 25 % used / 20 % new of the front gross after pack and the FRONT cost lines, at least $150)
// RC28 (fx, the reviewer's finding 3): the pay plan is the SERVER's (DMSCore.payPlanConfig — GET /api/pay-plan), the one the posting books
const dkpPlan = window.DMS && window.DMS.payPlanConfig ? window.DMS.payPlanConfig() : undefined;
const dkpPlanWords = dkpPlan ? (Math.round(dkpPlan.usedPct * 10000) / 100) + " % (used) / " + (Math.round(dkpPlan.newPct * 10000) / 100) + " % (new) of the net front, at least " + dt$(dkpPlan.miniFloor) + (dkpPlan.version ? " (pay plan version " + dkpPlan.version + ")" : " (the shipped pay plan)") : "the pay plan";
const dkpProfit = window.DealCalc ? window.DealCalc.profitPlan({ priceCents: Math.round((+price || 0) * 100), costCents: Math.round((+unit.cost || 0) * 100),
overCents: Math.round((+tradeAllow || 0) * 100) - Math.round((+tradeAcv || 0) * 100), packCents: Math.round((+pack || 0) * 100), fiGrossCents: Math.round((+fiGross || 0) * 100),
isNew: unit.newUsed === "N", ca: CA, payPlan: dkpPlan }) : null;
// RC27 (dsk) fix round (audit): a pack typed by hand is a WHAT-IF — the posting books the unit's Inventory pack; show that figure too
const dkpPackWhatIf = dkpInvPack != null && Math.round((+pack || 0) * 100) !== Math.round(dkpInvPack * 100);
const dkpPostProfit = dkpPackWhatIf && window.DealCalc ? window.DealCalc.profitPlan({ priceCents: Math.round((+price || 0) * 100), costCents: Math.round((+unit.cost || 0) * 100),
overCents: Math.round((+tradeAllow || 0) * 100) - Math.round((+tradeAcv || 0) * 100), packCents: Math.round(dkpInvPack * 100), fiGrossCents: Math.round((+fiGross || 0) * 100),
isNew: unit.newUsed === "N", ca: CA, payPlan: dkpPlan }) : null;
const dkpC = (c) => dt$((+c || 0) / 100);
const pmt = (i) => dtPmt(sc[i].apr / 100, sc[i].term, amtFin(i));
// ── Lease side — GMF program glue + per-option engine calls ──────────────
const lsAdds = (i) => { const s = lsc[i] || {};
if (s.all) { const sel = selectedProducts.reduce((sum, p) => sum + (+selected[p.id] || 0), 0); return sel || prodPrice(gapProd) + prodPrice(vscProd); }
return (s.gap ? prodPrice(gapProd) : 0) + (s.vsc ? prodPrice(vscProd) : 0); };
// What the GMF program would auto-fill for a term (MF and/or residual %).
const gmfAutoFor = (term) => {
if (!gmfOn) return {};
const G = dtGmfData();
if (gmfNewUsed === "used") {
const t = (G.usedProgram.tierMf || {})[gmfTier] || null;
return { mf: t && t[String(term)] != null ? +t[String(term)] : null, rv: null };
}
const p = gmfPick;
if (!p) return {};
if (p.source === "rates") {
const row = (G.program.rates || []).find(r => r.model === p.model && r.modelYear === p.modelYear);
const cell = row && row["t" + term];
if (cell) return { mf: +cell.mf, rv: +cell.rv };
}
if (p.source === "guide") {
const g = (G.residualGuide || []).find(r => r.section === p.section && r.model === p.model && r.code === p.code && r.trim === p.trim);
if (g && g.rv && g.rv[String(term)] != null) return { mf: null, rv: +g.rv[String(term)] };
}
return {};
};
const mfOf = (i) => { const s = lsc[i] || {}; const a = gmfAutoFor(+s.term || 36);
return (s.autoMf && a.mf != null) ? +a.mf : (+s.mf || 0); };
const residBaseOf = (i) => { const s = lsc[i] || {}; const a = gmfAutoFor(+s.term || 36);
if (s.autoRv && a.rv != null) return +a.rv;
if (s.resid != null && s.resid !== "") return +s.resid;
return dtRound2(dtResidualPct(+s.term || 36, milesPerYr)); };
const mileageAddOf = (i) => { const s = lsc[i] || {};
return (gmfOn && s.autoRv && window.jbGmfMileageAdd) ? window.jbGmfMileageAdd(+s.term || 36, milesPerYr, dtGmfData().program) : 0; };
const tierAddOf = (i) => { const s = lsc[i] || {};
return (gmfOn && gmfNewUsed === "new" && !s.sp && window.jbGmfTierAdd) ? window.jbGmfTierAdd(gmfTier, dtGmfData().program) : 0; };
const autoMfOn = (i) => { const s = lsc[i] || {}; return !!(s.autoMf && gmfAutoFor(+s.term || 36).mf != null); };
const autoRvOn = (i) => { const s = lsc[i] || {}; return !!(s.autoRv && gmfAutoFor(+s.term || 36).rv != null); };
const setLeaseMf = (i, v) => setLsc(arr => arr.map((s, j) => j === i ? { ...s, mf: v, autoMf: false } : s));
const setLeaseApr = (i, v) => setLeaseMf(i, dtAprToMf(v));
const setLeaseResid = (i, v) => setLsc(arr => arr.map((s, j) => j === i ? { ...s, resid: v, autoRv: false } : s));
const setLeaseTerm = (i, v) => { let t = Math.max(1, Math.round(+v || 0));
if (gmfOn && gmfNewUsed === "used") t = GMF_USED_TERMS.reduce((b, c) => Math.abs(c - t) < Math.abs(b - t) ? c : b, 24);
setLsc(arr => arr.map((s, j) => j === i ? { ...s, term: t, sp: s.sp && !(gmfOn && gmfNewUsed === "used" && t < 36) } : s)); };
const toggleSp = (i) => setLsc(arr => arr.map((s, j) => { if (j !== i) return s;
if (!s.sp && gmfOn && gmfNewUsed === "used" && (+s.term || 0) < 36) {
window.k2bToast && window.k2bToast("GMF used-lease single-pay is 36-48 month terms only.", "warn"); return s; }
return { ...s, sp: !s.sp }; }));
const applyGmfPick = (p) => { setGmfPick(p); setGmfPickerOpen(false);
setLsc(arr => arr.map(s => ({ ...s, autoMf: true, autoRv: true })));
// Program lease cash is a NEW-unit incentive: on a used unit it is only
// held (offered for review if the unit goes back to new).
const pc = p && p.leaseCash != null ? +p.leaseCash || 0 : 0;
if (isUsedUnit) { setGmfLeaseCash(0); setLeaseCashHeld(pc); } else setGmfLeaseCash(pc);
window.k2bToast && window.k2bToast(`GMF program applied — ${p.modelYear || ""} ${p.model}${p.trim ? " " + p.trim : ""}. MF & residuals autofilled per term.`, "ok"); };
const toggleGmf = (on) => { setGmfOn(on);
if (on) { const f = dtGmfData().program.fees || {};
setAcqFee(+f.acquisition || 0);
setDispFee(f.disposition != null ? +f.disposition : 395);
setPurchOptFee(+f.purchaseOption || 0);
setExcessRate(f.excessMileagePerMile != null ? +f.excessMileagePerMile : 0.25);
if (GMF_MILES.indexOf(+milesPerYr) < 0) setMilesPerYr(12000);
setLsc(arr => arr.map(s => ({ ...s, gap: false }))); // GAP included in GMF lease
window.k2bToast && window.k2bToast("GMF program mode ON — acq $0 · disposition $395 · 25¢/mi excess · GAP included.", "ok");
} };
const setNewUsed = (nu) => { setGmfNewUsed(nu);
if (nu === "used") { setMilesPerYr(15000);
setLsc(arr => arr.map((s, j) => { const t = GMF_USED_TERMS[j] || 36; return { ...s, term: t, autoMf: true, autoRv: false, sp: s.sp && t >= 36 }; }));
window.k2bToast && window.k2bToast("GMF used lease — 24/36/48 terms, 15,000 mi/yr, residual entered manually off the used guide.", "ok", 4200);
} else setLsc(arr => arr.map(s => ({ ...s, autoMf: !!gmfPick, autoRv: !!gmfPick }))); };
// Full engine quote for one option (LeaseCalc.jsx.txt). Returns the raw
// engine output plus the legacy field aliases the prints / F&I push use.
const leaseOf = (i) => {
const s = lsc[i] || {};
const term = +s.term || 36;
const sp = !!s.sp;
const G = dtGmfData();
if (!window.jbLeaseCalc) return { term, residPct: 0, residual: 0, grossCap: 0, capCostRed: 0, adjCap: 0, dep: 0, rent: 0, base: 0, tax: 0, payment: 0, apr: 0, dueAtSigning: 0, totalOfPayments: 0, singlePay: sp, singlePayAmount: 0, effectiveMoneyFactor: 0, dueAtSigningItems: { total: 0 }, upsideDown: false, autoDep: 0, secDepUsed: 0 };
const spCfg = (gmfNewUsed === "used" ? G.usedProgram.singlePayMfReduction : G.program.singlePayMfReduction) || undefined;
const args = {
msrp: msrpVal, sellingPrice: price, term, milesPerYr,
residualPct: residBaseOf(i) + mileageAddOf(i),
moneyFactor: mfOf(i),
acqFee, acqFeeCapitalized: acqFeeCapped,
// 4-square Cash Down + any ADDITIONAL cap reduction entered on the rail.
// GMF single-pay rule: NO cash CCR on a single-pay lease (rebates still apply).
capReductionCash: sp ? 0 : optDown(i, true) + (+capRed || 0),
// Rebate + GMF lease cash — both zero on a used unit.
rebates: rebate + leaseCashEff,
tradeAllowance: hasTrade ? tradeAllow : 0, tradePayoff: hasTrade ? tradePayoff : 0,
docFee, docFeeCapitalized: docFeeCapped,
titleLicenseFee: titleLicFee, titleLicenseCapitalized: titleLicCapped,
addOns: lsAdds(i),
// Lease-specific tax rate (customer's combined local rate) — NOT the
// finance-desk taxRate.
taxMethod, taxRate: +leaseTaxRate || 0, securityDeposit: 0,
// Mileage factor group — extra mileage purchased + inception (odometer)
// miles reduce the residual; null inceptionAdjust = engine AUTO window.
extraMilesPurchased: +extraMiles || 0, extraMileageRate: +extraMileageRate || 0,
inceptionMiles: +inceptionMiles || 0, inceptionMileageRate: +inceptionRate || 0,
...(inceptionAdjust != null ? { inceptionMileageAdjust: !!inceptionAdjust } : {}),
dispositionFee: dispFee, excessMileageRate: excessRate, purchaseOptionFee: purchOptFee,
singlePay: sp, tierMfAdd: tierAddOf(i), dealerMarkupMf: +dealerMarkupMf || 0,
singlePayMfReduction: spCfg,
};
let q = window.jbLeaseCalc(args);
// B2/B3 security-deposit rule: payment rounded UP to $25 — overridable.
const autoDep = (gmfOn && (gmfTier === "B2" || gmfTier === "B3") && !sp && window.jbGmfSecurityDeposit)
? window.jbGmfSecurityDeposit(q.totalMonthly) : 0;
const depUsed = (secDep != null && secDep !== "") ? +secDep || 0 : autoDep;
if (depUsed > 0) q = window.jbLeaseCalc({ ...args, securityDeposit: depUsed });
return { ...q,
residPct: q.residualPct, residual: q.residualValue, grossCap: q.grossCapCost,
capCostRed: q.totalCapReduction, adjCap: q.adjustedCapCost,
dep: q.baseDepreciation, rent: q.rentCharge, base: q.baseMonthly,
tax: q.monthlyTax, payment: q.totalMonthly, apr: q.aprEquivalent,
dueAtSigning: q.dueAtSigningItems.total,
autoDep, secDepUsed: depUsed };
};
const isLease = mode === "lease";
// ── GMF titling entity auto-selection ──
// Lender + deal type → the legal titling entity from the Banks registry:
// LEASE → ACAR Leasing Ltd. d/b/a GM Financial Leasing (lease-lessor —
// vehicle titles to the lessor; the lessee is the registrant)
// FINANCE → AmeriCredit Financial Services, Inc. d/b/a GM Financial
// (retail-lienholder — customer owns, entity holds the lien)
// Returns { name, address, city, state, zip, kind } or null (non-GMF lender).
const titlingEntity = (asLease) => {
try {
return (window.jbLenders && window.jbLenders.lienholderFor)
? window.jbLenders.lienholderFor(lender, asLease ? "Lease" : "Retail") : null;
} catch (e) { return null; }
};
const heroPmt = (i) => isLease ? leaseOf(i).payment : dtRound2(pmt(i));
const heroTerm = (i) => isLease ? lsc[i].term : sc[i].term;
const heroRate = (i) => isLease ? leaseOf(i).aprEquivalent : sc[i].apr;
// Autosave the working desk (debounced) so nothing reverts between visits.
React.useEffect(() => {
const t = setTimeout(() => { try { localStorage.setItem("jb_desk_work", JSON.stringify({
price, msrpOv, docFee, taxRate, tradeAllow, tradeAcv, tradePayoff, hasTrade, tradeDesc, tradeLender,
cashDown, rebate: rebateIn, rebateHeld, leaseCashHeld, rebateReview,
lender, pack, packHand, commPct, fico, income, dti, sc, scOn, selSc, selected,
mode, lsc, milesPerYr, acqFee, capRed,
gmfOn, gmfNewUsed, gmfPick, gmfTier, gmfLeaseCash,
acqFeeCapped, docFeeCapped, titleLicFee, titleLicCapped,
taxMethod, secDep, dispFee, excessRate, purchOptFee, dealerMarkupMf,
leaseTaxRate, extraMiles, extraMileageRate, inceptionMiles, inceptionAdjust, inceptionRate, costAdds, dkpFeeOff,
dealId: cust.dealId || null })); } catch (e) {} }, 250);
return () => clearTimeout(t);
});
// ── RC27 (dsk): the cost lines are SAVED WITH THE DEAL as they are typed (debounced) — never onto a posted deal (frozen: the server
// refuses it too). A line the server would refuse is still kept on the desk; the panel shows why.
// A deal still on the desk has no down payment of its own yet (the CRM's Desk It creates it bare): when the deal on file can't carry the
// lines yet (assistance above ITS down payment, a lender fee on a cash deal …) they wait on the desk and Push saves them with the figures.
const dkpSaveSig = JSON.stringify(costAdds);
const [dkpSaveNote, setDkpSaveNote] = React.useState("");
React.useEffect(() => {
if (!cust.dealId || dkpLoadedRef.current !== cust.dealId) { dkpLoadedRef.current = cust.dealId || null; return undefined; }
const t = setTimeout(() => {
const d = dkpDealOf(cust.dealId);
if (!d || dkpPosted(d) || !window.DMS || !window.DMS.updateDeal) return;
const cur = Array.isArray(d.costAdds) ? d.costAdds : [];
if (JSON.stringify(cur) === dkpSaveSig) { setDkpSaveNote(""); return; }
if (dkpCa(costAdds).problems.length) return; // the panel lists the problem; nothing is saved until the line is right
const pre = window.DealCalc ? window.DealCalc.saveProblems(Object.assign({}, d, { costAdds: JSON.parse(dkpSaveSig) })).filter(x => /^(dpa_over|lender_fee_)/.test(x.code)) : [];
if (pre.length) { setDkpSaveNote(pre[0].msg + " — the deal on file does not have the desk's figures yet; Push to Finance / Cash saves the cost lines with them."); return; }
const r = window.DMS.updateDeal(cust.dealId, { costAdds: JSON.parse(dkpSaveSig) });
if (r && !r.ok && r.msg) { setDkpSaveNote(r.msg); window.k2bToast && window.k2bToast(r.msg, "warn", 5200); } else setDkpSaveNote("");
}, 600);
return () => clearTimeout(t);
}, [dkpSaveSig, cust.dealId]);
// ── RC27 (dsk): the LENDER FEE default (Banks / Lenders) is suggested when the lender is picked on a finance / lease deal — an "auto"
// line that follows the lender; a lender fee line typed or changed by hand is never replaced. Not on a posted deal.
React.useEffect(() => { try { if (window.DMS && window.DMS.loadLenderFees && window.DMS.can && (window.DMS.can("desk") || window.DMS.can("fi"))) window.DMS.loadLenderFees(); } catch (e) {} }, []);
const dkpFeesAt = (window.DMS && window.DMS.lenderFeesCached) ? window.DMS.lenderFeesCached().at : 0;
const dkpFeeBase = Math.round((baseFin(selSc) + scAdds(selSc)) * 100);
React.useEffect(() => {
if (dkpIsPosted || !window.DMS || !window.DMS.lenderFeeFor) return;
const fee = dkpFeeOff && dkpFeeOff.toUpperCase() === String(lender || "").toUpperCase() ? null : window.DMS.lenderFeeFor(lender);
setCostAdds(list => {
if (list.some(l => l && l.type === "lenderFee" && !l.auto)) return list;
const rest = list.filter(l => !(l && l.type === "lenderFee" && l.auto));
if (!fee) return rest.length === list.length ? list : rest;
const amtC = fee.kind === "points" ? Math.round(Math.max(0, dkpFeeBase) * (+fee.points || 0) / 100) : (+fee.amountCents || 0);
if (!(amtC > 0)) return rest.length === list.length ? list : rest;
const kinds = (window.DealCalc && window.DealCalc.LENDER_FEE_KINDS) || {};
const line = { id: (list.find(l => l && l.type === "lenderFee" && l.auto) || {}).id || dkpId(), type: "lenderFee", desc: `${lender} — ${kinds[fee.kind] || "fee"}${fee.kind === "points" ? " " + fee.points + " pts" : ""}`.slice(0, 80),
amount: (amtC / 100).toFixed(2), side: "front", weOwe: false, passed: !!fee.passed && mode !== "lease", kind: fee.kind, lender, auto: true };
const old = list.find(l => l && l.type === "lenderFee" && l.auto);
if (old && JSON.stringify(old) === JSON.stringify(line)) return list;
return rest.concat([line]);
});
}, [lender, mode, dkpFeesAt, dkpIsPosted, dkpFeeBase, dkpFeeOff]);
// Apply edited settings to live fields
const applySettings = (next) => {
saveDeskSettings(next); settingsRef.current = next;
setDocFee(next.docFee); setTaxRate(next.taxRate); setPack(next.pack); setCommPct(next.commPct);
// New default terms / rates for A–C (D follows C); each option keeps its
// own down payment + products and the visible set is unchanged.
const tr = [[next.t1, next.a1], [next.t2, next.a2], [next.t3, next.a3], [next.t3, next.a3]];
const lt = [[next.lt1, next.lmf1], [next.lt2, next.lmf2], [next.lt3, next.lmf3], [next.lt3, next.lmf3]];
setSc(arr => arr.map((s, i) => ({ ...s, term: tr[i][0], apr: tr[i][1] })));
setLsc(arr => arr.map((s, i) => ({ ...s, term: lt[i][0], mf: lt[i][1], resid: null, autoMf: false, autoRv: false })));
setAcqFee(next.acqFee); setMilesPerYr(next.milesPerYr);
setSettingsOpen(false);
window.k2bToast && window.k2bToast("Desking defaults saved.", "ok");
};
// Push current desk figures into the shared Buyer's Order draft — including
// the SELECTED scenario's protection products (codes + prices) and the buyer.
const pushToBuyersOrder = () => {
const f2 = (n) => (+n || 0).toFixed(2);
const products = chosenProducts();
const planFields = deskPlanFields(products, f2);
const nm = String(cust.name || "");
const [ln, fn] = nm.includes(",") ? nm.split(",").map(x => x.trim()) : [nm.split(" ").slice(-1)[0], nm.split(" ").slice(0, -1).join(" ")];
// Lease deals use the lease engine's tax (monthly-receipts / upfront per the
// tax method) — NEVER the retail (price − trade allowance) tax credit calc.
const LQ = isLease ? leaseOf(selSc) : null;
const dealTax = isLease ? (taxMethod === "monthly" ? LQ.tax : LQ.upfrontTax) : tax;
window.saveJbForm && window.saveJbForm("buyersOrder", {
...(cust.preset ? {} : { lastName: (ln || "").toUpperCase(), firstName: (fn || "").toUpperCase() }),
// Buyer address / phone / DL come from the CUSTOMER FILE the deal was
// started from in the CRM (the Customers screen no longer fills the BO).
...(() => {
try {
const dd = cust.dealId && window.DMS ? window.DMS.deals().find(x => x.id === cust.dealId) : null;
const cc = dd && dd.custNum && window.DMS.customers ? (window.DMS.customers()[dd.custNum] || null) : null;
const src = cc || dd || null;
if (!src) return {};
return { middle: String(cc && cc.middleName || "").toUpperCase().slice(0, 1),
street: String(src.street || "").toUpperCase(), city: String(src.city || "").toUpperCase(), state: String(src.state || "").toUpperCase(),
zip: src.postal || src.zip || "", phone: src.phone || (cc && (cc.cell || cc.homePhone)) || "", // RC30 (cn): a customer # is not a phone
dlNum: String((cc && (cc.dl || cc.dlNumber)) || "").toUpperCase(), dlState: String((cc && (cc.dlState || cc.state)) || "").toUpperCase() };
} catch (e) { return {}; }
})(),
salePrice: price.toFixed(2), docFee: docFee.toFixed(2), salesTax: dealTax.toFixed(2),
tradeAllow: tradeAllow.toFixed(2), tradePayoff: tradePayoff.toFixed(2),
rebate: rebate.toFixed(2), cashDown: (isLease ? cashDown + capRed : cashDown).toFixed(2),
// RC27 (dsk): the store's down payment assistance (the customer's credit toward the down payment) and a lender fee the customer pays —
// customer-facing lines only; no cost line the store pays ever reaches the Buyer's Order
downAssist: dkpDpa > 0 ? dkpDpa.toFixed(2) : "", customerCash: dkpDpa > 0 ? Math.max(0, (isLease ? cashDown + capRed : cashDown) - dkpDpa).toFixed(2) : "",
lenderFeeCust: dkpFeeCust > 0 ? dkpFeeCust.toFixed(2) : "",
// Vehicle: BLANK when no unit is picked — never a preset VIN/stock (B-05).
stockNum: unit.stock || "", vin: unit.vin || "",
yrMkMdl: unit.stock ? `${unit.yr} ${unit.mk} ${unit.mdl}`.replace(/\s+/g, " ").trim() : "",
newUsed: unit.newUsed || "", odometer: unit.stock && unit.miles ? String(unit.miles) : "",
license: (isLease ? titleLicFee : 30).toFixed(2),
// Trade — description / VIN / miles / payoff lender from the deal record
// (CRM lead → Desk It) and the desk's lender box; neg equity read-out.
...(() => { const d = (cust.dealId && window.DMS && window.DMS.deals) ? (window.DMS.deals().find(x => x.id === cust.dealId) || {}) : {};
return { tradeVin: hasTrade ? String(d.tradeVin || "").toUpperCase() : "", tradeYrMkMdl: hasTrade ? tradeDesc : "",
tradeMiles: hasTrade && d.tradeMiles ? String(d.tradeMiles) : "",
tradeLender: hasTrade ? tradeLender : "", tradeLien: hasTrade ? tradeLender : "", negEquity: f2(negEquity) }; })(),
// Protection products of the chosen option: plan 1 = the legacy
// serviceContract fields, then plan2..plan5 + GAP + the JSON list.
serviceContract: planFields.plan1Price, serviceContractCode: planFields.plan1Code ? `${planFields.plan1Code} — ${planFields.plan1Name}` : "",
...planFields,
gapIns: planFields.gapCode ? planFields.gapIns : "",
gapCode: planFields.gapCode ? `${planFields.gapCode} — ${planFields.gapName}` : "",
scenario: DT_LETTERS[selSc], amtFinanced: (isLease ? LQ.adjCap : amtFinanced).toFixed(2),
dealType: isLease ? "Lease" : "Retail",
// Lease figures for the Buyer's Order balance block (B-08): due at
// signing + monthly incl. tax replace the retail "balance to finance".
...(isLease ? { dueAtSigning: LQ.dueAtSigning.toFixed(2), monthlyPayment: LQ.payment.toFixed(2),
leaseTerm: String(LQ.term), residual: LQ.residual.toFixed(2),
moneyFactor: LQ.effectiveMoneyFactor.toFixed(5), adjCapCost: LQ.adjCap.toFixed(2) }
: { dueAtSigning: "", monthlyPayment: "", leaseTerm: "" }),
lender,
// GMF titling entity: LEASE → ACAR Leasing Ltd. d/b/a GM Financial Leasing
// (lessor/titled owner); FINANCE → AmeriCredit Financial Services, Inc.
// d/b/a GM Financial (lienholder). null for lenders with no entity on file.
lienholder: titlingEntity(isLease) || null,
});
};
// ── Lease + finance print snapshots (saved to the shared form store so the
// Forms library can re-print the last desked figures too) ──────────────
const leaseSnapshot = () => { const G = dtGmfData(); return JSON.parse(JSON.stringify({
customer: cust.preset ? "" : (cust.name || ""), dealId: cust.dealId || "", unit,
printedAt: new Date().toLocaleString(),
// Lessor / titled owner entity (ACAR Leasing Ltd. d/b/a GM Financial
// Leasing when the lender is GM Financial) — prints on the worksheet
// and disclosure; the lessee is the registrant.
lessor: titlingEntity(true) || null,
gmf: gmfOn ? {
mode: gmfNewUsed,
program: gmfNewUsed === "used" ? (G.usedProgram.program || "GMF Used Lease") : (G.program.program || "GMF Lease Program"),
effective: gmfNewUsed === "used" ? (G.usedProgram.effective || "") : (G.program.effective || ""),
pick: gmfPick, tier: gmfTier, tierFico: (G.program.tierFico || {})[gmfTier] || "",
leaseCash: gmfLeaseCash, gapIncluded: G.program.gapIncluded !== false,
} : null,
shared: { msrp: msrpVal, price, docFee, docFeeCapped, acqFee, acqFeeCapped,
titleLicFee, titleLicCapped, cashDown, capRed, rebate, gmfLeaseCash: leaseCashEff, usedUnit: isUsedUnit,
hasTrade, tradeDesc, tradeAllow, tradePayoff, tradeLender, negEquity,
milesPerYr, taxMethod, taxRate: +leaseTaxRate || 0, dispFee, excessRate, purchOptFee, dealerMarkupMf, lender,
extraMiles: +extraMiles || 0, extraMileageRate: +extraMileageRate || 0,
inceptionMiles: +inceptionMiles || 0, inceptionRate: +inceptionRate || 0 },
// VISIBLE options only; selSc = the selected option's POSITION in that list.
selSc: Math.max(0, visIdx.indexOf(selSc)), selLetter: DT_LETTERS[selSc],
options: visIdx.map(i => { const s = lsc[i] || {}; return {
label: DT_LETTERS[i], letter: DT_LETTERS[i], down: optDown(i, true), singlePay: !!s.sp,
products: [s.all ? "All Products" : null, !s.all && s.gap ? "GAP" : null, !s.all && s.vsc ? "Warranty" : null].filter(Boolean).join(" + ") || "—",
addOns: lsAdds(i), q: leaseOf(i) }; }),
})); };
const printLeaseWorksheet = () => { const snap = leaseSnapshot();
window.saveJbForm && window.saveJbForm("leaseDesk", snap);
window.k2bPrintLeaseWorksheet && window.k2bPrintLeaseWorksheet(snap); };
// The disclosure covers the ★ option only — hand it just that option so the
// printer can never land on a hidden or wrong letter.
const printLeaseDisclosure = () => { const snap = leaseSnapshot();
window.saveJbForm && window.saveJbForm("leaseDesk", snap);
const one = { ...snap, options: [snap.options[snap.selSc]].filter(Boolean), selSc: 0 };
window.k2bPrintLeaseDisclosure && window.k2bPrintLeaseDisclosure(one); };
// Contract Prep Sheet — mirrors the 553-MO Truth-in-Lending boxes from the
// FINANCE desk's selected option, so F&I can copy onto the official form.
const financeSnapshot = () => { const s = sc[selSc] || {};
const n = +s.term || 0; const p = dtRound2(pmt(selSc));
const top = dtRound2(p * n);
const netTrade = hasTrade ? dtRound2(tradeAllow - tradePayoff) : 0;
const totalDown = dtRound2(cashDown + rebate + Math.max(0, netTrade));
const inclGap = !!(s.gap || s.all), inclVsc = !!(s.vsc || s.all);
const vscAmt = inclVsc ? dtRound2(prodPrice(vscProd)) : 0;
const gapAmt = inclGap ? dtRound2(prodPrice(gapProd)) : 0;
const otherProducts = Math.max(0, dtRound2(scAdds(selSc) - vscAmt - gapAmt));
return JSON.parse(JSON.stringify({
customer: cust.preset ? "" : (cust.name || ""), dealId: cust.dealId || "", unit, lender,
// Retail lienholder entity (AmeriCredit Financial Services, Inc. d/b/a
// GM Financial when the lender is GM Financial) for the 553-MO prep.
lienholder: titlingEntity(false) || null,
printedAt: new Date().toLocaleString(),
apr: +s.apr || 0, term: n, payment: p,
amtFinanced: dtRound2(amtFinanced), financeCharge: dtRound2(top - amtFinanced),
totalOfPayments: top, totalDown, totalSalePrice: dtRound2(top + totalDown),
firstDueDate: new Date(Date.now() + 45 * 86400000).toLocaleDateString("en-US"),
itemization: { vehiclePrice: dtRound2(price), docFee: dtRound2(docFee), salesTax: dtRound2(tax),
cashPrice: dtRound2(price + docFee + tax),
cashDown: dtRound2(cashDown), rebate: dtRound2(rebate),
tradeAllow: hasTrade ? dtRound2(tradeAllow) : 0, tradePayoff: hasTrade ? dtRound2(tradePayoff) : 0,
netTrade, tradeDesc: hasTrade ? tradeDesc : "", tradeLender: hasTrade ? tradeLender : "", negEquity,
unpaidBalance: dtRound2(price + docFee + tax - (cashDown + rebate + netTrade)),
publicOfficials: 30, insurance: 0,
serviceContract: vscAmt, gap: gapAmt, otherProducts,
downAssist: dkpDpa > 0 ? dtRound2(dkpDpa) : 0, lenderFeeCust: dkpFeeCust > 0 ? dtRound2(dkpFeeCust) : 0 }, // RC27 (dsk)
})); };
const printContractPrep = () => { const snap = financeSnapshot();
window.saveJbForm && window.saveJbForm("contractPrep", snap);
window.k2bPrintContractPrep && window.k2bPrintContractPrep(snap); };
// Product label for an option — identical wording in every print.
const optProdLabel = (s) => [s.all ? "All Products" : null, !s.all && s.gap ? "GAP" : null, !s.all && s.vsc ? "Warranty" : null].filter(Boolean).join(" + ") || "—";
const printCustomerPresentation = () => {
const r = (n) => "$" + Number(n).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const heads = visIdx.map(i => DT_LETTERS[i]);
const selPos = visIdx.indexOf(selSc);
const L1 = DT_LETTERS[selSc];
if (isLease) {
const L = leaseOf(selSc);
const qs = visIdx.map(i => leaseOf(i));
const grid = dtOptGridHtml(heads, selPos, [
["Down payment (cap reduction cash)", visIdx.map((i, k) => qs[k].singlePay ? "n/a (1-pay)" : r(optDown(i, true)))],
["Term", visIdx.map((i, k) => `${lsc[i].term} mo${qs[k].singlePay ? " · 1-PAY" : ""}`)],
["Money factor", qs.map(q => q.effectiveMoneyFactor.toFixed(5))],
["Residual", qs.map(q => `${q.residPct.toFixed(1)}% / ${r(q.residual)}`)],
["Protection products", visIdx.map(i => optProdLabel(lsc[i]))],
["Adjusted cap cost", qs.map((q, k) => dkfBad[visIdx[k]] ? "—" : r(q.adjCap))],
["Due at signing", qs.map((q, k) => dkfBad[visIdx[k]] ? "—" : r(q.dueAtSigning))],
["Payment", qs.map((q, k) => dkfBad[visIdx[k]] ? "—" : q.singlePay ? r(q.singlePayAmount) + " once" : r(q.payment) + "/mo")],
]);
const html = `${dealerHeadHtml()}
LEASE PAYMENT OPTIONS
${_pEsc(cust.name)} · ${_pEsc(unit.yr + " " + unit.mk + " " + unit.mdl)} · Stk ${_pEsc(unit.stock)} · ${(+milesPerYr).toLocaleString()} miles/year
Capitalized Cost — Option ${L1}
MSRP ${r(msrpVal)}
Agreed Upon Value (selling price) ${r(price)}
Doc Fee ${r(docFee)}
Acquisition Fee ${r(acqFee)}
${lsAdds(selSc) ? `Protection Products ${r(lsAdds(selSc))} ` : ""}
${L.negativeEquity > 0 ? `Negative Equity (trade payoff over allowance — capitalized) ${r(L.negativeEquity)} ` : ""}
Gross Capitalized Cost ${r(L.grossCap)}
Capitalized Cost Reduction (cash / trade equity${rebate + leaseCashEff > 0 ? " / rebate" : ""}) − ${r(L.capCostRed)}
Adjusted Capitalized Cost ${r(L.adjCap)}
Residual Value (${L.residPct.toFixed(1)}% of MSRP${L.residualAdjustments > 0 ? ` = ${r(L.residualValueUnadjusted)} − ${r(L.residualAdjustments)} mileage adjustments` : ""}) ${r(L.residual)}
${L.singlePay ? "Single Payment" : "Monthly Payment"} — Option ${L1}
Depreciation & Amortized Amounts ${r(L.dep)}
Rent Charge (money factor ${L.effectiveMoneyFactor.toFixed(5)} ≈ ${L.apr.toFixed(2)}% APR) ${r(L.rent)}
Base Monthly Payment ${r(L.base)}
${taxMethod === "monthly" ? `Monthly Sales Tax (${(+leaseTaxRate).toFixed(3)}%) ${r(L.tax)} ` : `Tax paid up front (${(+leaseTaxRate).toFixed(3)}% ${taxMethod === "upfront_on_capred" ? "on cap reduction" : "on payment stream"}) ${r(L.upfrontTax)} `}
${L.dueAtSigningItems.capReductionTax > 0 ? `Tax on Cap Reduction (${(+leaseTaxRate).toFixed(3)}%, due at signing) ${r(L.dueAtSigningItems.capReductionTax)} ` : ""}
${L.singlePay
? `SINGLE LEASE PAYMENT (${L.term} months prepaid) ${r(L.singlePayAmount)} `
: `TOTAL MONTHLY PAYMENT — ${L.remainingPayments} payments of ${r(L.payment)} (first payment at inception) ${r(L.payment)} `}
Due at Signing ${r(L.dueAtSigning)}
${dkpDpa > 0 ? `Down payment assistance (from the dealer) − ${r(dkpDpa)} Your cash at signing ${r(Math.max(0, L.dueAtSigning - dkpDpa))} ` : ""}
Total of Payments ${r(L.totalOfPayments)}
Lease Options
${grid}
${gmfOn ? `
${_pEsc(gmfNewUsed === "used" ? (dtGmfData().usedProgram.program || "GMF Used Lease") : (dtGmfData().program.program || "GMF Lease Program"))} · Tier ${_pEsc(dtGmfTierLabel(gmfTier))} · Effective ${_pEsc(gmfNewUsed === "used" ? (dtGmfData().usedProgram.effective || "") : (dtGmfData().program.effective || ""))}${dtGmfData().program.gapIncluded !== false ? " · GAP included in GMF lease" : ""}
` : ""}
★ Option ${L1} selected. Mileage allowance ${(+milesPerYr).toLocaleString()}/year; excess mileage ${"$" + (+excessRate).toFixed(2)}/mile and excess wear charges apply per the lease agreement. Disposition fee ${r(dispFee || 0)} at lease end. Estimates only — subject to credit approval and lessor program terms.
`;
window.k2bPrint(html, { title: "Lease Payment Options" });
return;
}
const grid = dtOptGridHtml(heads, selPos, [
["Down payment", visIdx.map(i => r(optDown(i, false)))],
["Term", visIdx.map(i => `${sc[i].term} mo`)],
["APR", visIdx.map(i => `${(+sc[i].apr || 0).toFixed(2)}%`)],
["Protection products", visIdx.map(i => optProdLabel(sc[i]))],
["Amount financed", visIdx.map(i => dkfBad[i] ? "—" : r(amtFin(i)))],
["Monthly payment", visIdx.map(i => dkfBad[i] ? "—" : r(pmt(i))), true],
]);
const prodRows = selectedProducts.map(p => `
${p.name} ${r(selected[p.id])} `).join("");
const html = `
CUSTOMER PRESENTATION
Prepared for ${cust.name} · ${new Date().toLocaleDateString()}
Vehicle
${unit.yr} ${unit.mk} ${unit.mdl}${unit.newUsed ? (unit.newUsed === "U" ? " (Used)" : " (New)") : ""} Stock ${unit.stock}
MSRP ${r(msrpVal)}
Sale Price ${r(price)}
${hasTrade ? `
Trade
${tradeDesc}
Allowance ${r(tradeAllow)}
Payoff${tradeLender ? ` (${_pEsc(tradeLender)})` : ""} ${r(tradePayoff)}
${negEquity > 0
? `Negative Equity (financed) ${r(negEquity)} `
: `Net Equity ${r(tradeAllow - tradePayoff)} `}
` : ``}
${selectedProducts.length ? `
Protection Products ${prodRows}
Total Products — Option ${L1} ${r(fiAdds)}
` : ``}
Your Investment — Option ${L1}
Down Payment ${r(cashDown)}
${dkpDpa > 0 ? `Down payment assistance (from the dealer) − ${r(dkpDpa)} Your cash down ${r(Math.max(0, cashDown - dkpDpa))} ` : ""}
${dkpFeeCust > 0 ? `Lender fee ${r(dkpFeeCust)} ` : ""}
${rebate > 0 ? `Rebate Applied ${r(rebate)} ` : ""}
${posEquity > 0 ? `Trade Equity Applied ${r(posEquity)} ` : ""}
${negEquity > 0 ? `Negative Equity (financed) ${r(negEquity)} ` : ""}
Doc Fee ${r(docFee)}
Sales Tax (${taxRate.toFixed(3)}%) ${r(tax)}
Amount Financed — Option ${L1} ${r(amtFinanced)}
Payment Options — ${lender}
${grid}
★ Option ${L1} selected — each option's Amount Financed reflects that option's own down payment and protection products.
Payments are estimates; final figures are subject to lender approval and verification of stated information. Tax, title, license and dealer fees additional except where noted.
Customer Acknowledgement · Date
Salesperson · Date
`;
window.k2bPrint(`${window.dealerHeadHtml ? window.dealerHeadHtml() : ""}${html}`, { title: "Customer Presentation" });
};
const printFourSquare = () => {
const r = (n) => "$" + Number(n).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const cell = (t, rows) => `
`;
const ln = (l, v) => `
${l} ${v}
`;
const L1 = DT_LETTERS[selSc];
const heads = visIdx.map(i => DT_LETTERS[i]);
const selPos = visIdx.indexOf(selSc);
const qs = isLease ? visIdx.map(i => leaseOf(i)) : null;
const grid = isLease
? dtOptGridHtml(heads, selPos, [
["Down", visIdx.map((i, k) => qs[k].singlePay ? "n/a (1-pay)" : r(optDown(i, true)))],
["Term", visIdx.map(i => `${lsc[i].term} mo`)],
["Money factor", qs.map(q => q.effectiveMoneyFactor.toFixed(5))],
["Products", visIdx.map(i => optProdLabel(lsc[i]))],
["Adj. cap cost", qs.map((q, k) => dkfBad[visIdx[k]] ? "—" : r(q.adjCap))],
["Payment", qs.map((q, k) => dkfBad[visIdx[k]] ? "—" : q.singlePay ? r(q.singlePayAmount) + " once" : r(q.payment)), true],
])
: dtOptGridHtml(heads, selPos, [
["Down", visIdx.map(i => r(optDown(i, false)))],
["Term", visIdx.map(i => `${sc[i].term} mo`)],
["APR", visIdx.map(i => `${(+sc[i].apr || 0).toFixed(2)}%`)],
["Products", visIdx.map(i => optProdLabel(sc[i]))],
["Amt financed", visIdx.map(i => dkfBad[i] ? "—" : r(amtFin(i)))],
["Payment", visIdx.map(i => dkfBad[i] ? "—" : r(pmt(i))), true],
]);
const selAmt = isLease ? leaseOf(selSc).adjCap : amtFinanced;
const html = `${window.dealerHeadHtml ? window.dealerHeadHtml() : ""}
4-SQUARE WORKSHEET
${cust.name} · ${unit.yr} ${unit.mk} ${unit.mdl}${unit.newUsed ? (unit.newUsed === "U" ? " (Used)" : " (New)") : ""} · Stk ${unit.stock} · ${new Date().toLocaleDateString()}
${cell("TRADE", hasTrade ? ln("Allowance", r(tradeAllow)) + ln("Payoff" + (tradeLender ? " · " + _pEsc(tradeLender) : ""), r(tradePayoff)) + ln(negEquity > 0 ? "Negative Equity" : "Net Equity", (negEquity > 0 ? "-" : "") + r(Math.abs(tradeEquity))) : `
No trade-in
`)}
${cell("PRICE", ln("Sale Price", r(price)) + ln("Doc Fee", r(docFee)) + ln("Sales Tax", r(tax)))}
${cell("DOWN", ln(`Down Payment (${L1})`, r(cashDown)) + (dkpDpa > 0 ? ln("Down payment assistance", "-" + r(dkpDpa)) + ln("Customer cash down", r(Math.max(0, cashDown - dkpDpa))) : "") + (rebate > 0 ? ln("Rebate", r(rebate)) : "") + (isLease && leaseCashEff > 0 ? ln("GMF Lease Cash", r(leaseCashEff)) : "") + (posEquity > 0 ? ln("Trade Equity", r(posEquity)) : "") + (negEquity > 0 ? `
Negative Equity (financed) ${r(negEquity)}
` : "") + ln(`${isLease ? "Adj. Cap Cost" : "Amount Financed"} (${L1})`, r(selAmt)))}
${cell("PAYMENT", ln(`Option ${L1} ★ · ${heroTerm(selSc)} mo @ ${isLease ? leaseOf(selSc).effectiveMoneyFactor.toFixed(5) + " MF" : (+sc[selSc].apr || 0).toFixed(2) + "%"}`, r(isLease && lsc[selSc].sp ? leaseOf(selSc).singlePayAmount : heroPmt(selSc))) + `
${visIdx.length} option${visIdx.length !== 1 ? "s" : ""} compared below
`)}
Customer Initials
Sales Manager
`;
window.k2bPrint(html, { title: "4-Square Worksheet" });
};
// ONE PATH — the desk pushes THE deal (created by CRM → Desk It) forward to
// exactly one of two closing screens: kind = "cash" → Cash Deal,
// kind = "finance" → Finance Deal (lease = finance). Both write the deal
// record + the target worksheet form, stamp the stage, set jb_fw_deal, go.
const pushDeal = (kind) => {
if (!cust.dealId) {
window.k2bToast && window.k2bToast("No deal open — deals start in the CRM: pick the lead → Desk It.", "warn", 4200);
return;
}
if (dkfNoFig) { window.k2bToast && window.k2bToast("Enter the sale price first — nothing to push yet.", "warn", 4200); return; } // K2B-013
if (kind === "cash" ? dkfOverSel : dkfBadSel) { window.k2bToast && window.k2bToast(dkfBadMsg(selSc), "warn", 5200); return; } // K2B-013 / 6.3
if (kind === "cash" && !dkpIsPosted && costAdds.some(l => l && l.type === "lenderFee" && !l.auto)) { window.k2bToast && window.k2bToast("A cash deal has no lender — take the lender fee line off the Profit panel first (Show gross › 💲 Profit).", "warn", 5200); return; } // RC27 (dsk)
// RC27 (dsk) fix round (audit): a cost line that is not right yet (no amount, …) stops the push HERE — before any form or the deal
// changes — with the Profit panel's own words (the deal save would refuse the whole figures patch and the deal would sit in F&I bare)
if (!dkpIsPosted) {
const pushLines = kind === "cash" ? costAdds.filter(l => !(l && l.type === "lenderFee")) : costAdds;
const pc = dkpCa(pushLines);
if (pc.problems.length) {
window.k2bToast && window.k2bToast(`Nothing was pushed — a cost line on the Profit panel is not right yet: ${pc.problems[0].msg} Fix it or take the line off (Show gross › 💲 Profit), then push again.`, "warn", 8000);
if (showGross) openDrawer("profit");
return;
}
}
if (kind === "cash" && !dkpIsPosted && costAdds.some(l => l && l.type === "lenderFee")) setCostAdds(arr => arr.filter(l => !(l && l.type === "lenderFee"))); // the suggested lender fee goes with the lender
if (kind === "cash") return pushCash();
return pushFinance();
};
// Shared vehicle / price / trade / tax / fee / product fields both pushes write.
const deskDealCore = () => {
const products = chosenProducts();
const gapList = products.filter(p => /gap/i.test(p.category + " " + p.name));
const vscList = products.filter(p => !/gap/i.test(p.category + " " + p.name));
const sum = (l, k) => dtRound2(l.reduce((t, p) => t + (+p[k] || 0), 0));
return {
vehicle: `${unit.yr} ${unit.mk} ${unit.mdl}`.replace(/\s+/g, " ").trim(), stock: unit.stock, vin: unit.vin || "",
// B-01 / B-02: cost + New/Used ride on the deal from the desk on.
cost: dtRound2(unit.cost || 0), type: unit.type ? (/^n|new/i.test(unit.type) ? "New" : "Used") : undefined,
salePrice: price, tradeAllow: hasTrade ? tradeAllow : 0, tradePayoff: hasTrade ? tradePayoff : 0, rebate,
// B-07: ACV under BOTH spellings so every reader (GL, recap, desk) agrees.
tradeACV: hasTrade ? tradeAcv : 0, tradeAcv: hasTrade ? tradeAcv : 0, tradeDesc: hasTrade ? tradeDesc : "",
tradeLender: hasTrade ? tradeLender : "", negEquity,
docFee,
// The chosen scenario's protection products, itemized with codes — the
// Finance Deal / Cash Deal / Buyer's Order read THIS list.
products,
// B-07: the same products lumped as vscSale (every non-GAP product) /
// gapSale (+ costs) so postDeal's payables and the pay plan see them.
vscSale: sum(vscList, "price"), vscCost: sum(vscList, "cost"),
gapSale: sum(gapList, "price"), gapCost: sum(gapList, "cost"),
fiGross: dtRound2(fiGross),
// Which desk option (A–D) was pushed, and whether the rebate was dropped
// because the unit is used (rebate above is then 0).
deskOption: DT_LETTERS[selSc], rebateNewOnly: isUsedUnit,
// RC27 (dsk): the deal's cost lines (Profit panel) ride with the push (a posted deal keeps its own)
...(dkpIsPosted ? {} : { costAdds: JSON.parse(JSON.stringify(costAdds)) }),
};
};
// Form patches (Finance Deal / Cash Deal / Buyer's Order) carry the plan rows
// as plan1..plan5 {Code, Name, Price, Cost, Term} for the non-GAP products,
// GAP as gapIns/gapCode, and the whole list as a JSON string.
const deskPlanFields = (products, f2) => {
const gap = products.find(p => /gap/i.test(p.category + " " + p.name)) || null;
const plans = products.filter(p => !/gap/i.test(p.category + " " + p.name));
const out = { products: JSON.stringify(products), gapIns: gap ? f2(gap.price) : "0.00", gapCode: gap ? gap.code : "", gapName: gap ? gap.name : "", gapCost: gap ? f2(gap.cost) : "" };
for (let n = 1; n <= 5; n++) {
const p = plans[n - 1];
out[`plan${n}Code`] = p ? p.code : ""; out[`plan${n}Name`] = p ? p.name : "";
out[`plan${n}Price`] = p ? f2(p.price) : ""; out[`plan${n}Cost`] = p ? f2(p.cost) : "";
out[`plan${n}Term`] = p && p.termMo ? String(p.termMo) : ""; out[`plan${n}Id`] = p ? p.id : "";
}
out.warranties = f2(plans.reduce((t, p) => t + (+p.price || 0), 0));
return out;
};
// Push to Cash Deal → the customer pays the balance at delivery. No lender,
// no amount financed, no payment; the Cash Deal screen finalizes + posts.
const pushCash = () => {
pushToBuyersOrder();
const f2 = (n) => (n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const core = deskDealCore();
if (Array.isArray(core.costAdds)) core.costAdds = core.costAdds.filter(l => !(l && l.type === "lenderFee")); // RC27 (dsk): a cash deal has no lender fee
if (!unit.cost) { try { console.warn("[Desking] Push to Cash: unit " + (unit.stock || "(none)") + " has no cost — deal.cost will be 0 until Inventory carries one."); } catch (e) {} }
window.saveJbForm && window.saveJbForm("cashWorksheet", {
dealType: "Cash",
cashPrice: f2(price), tradeValue: f2(core.tradeAllow), tradePayoff: f2(core.tradePayoff), tradeAcv: f2(core.tradeAcv),
tradeDesc: core.tradeDesc, tradeLender: core.tradeLender, negEquity: f2(negEquity),
rebates: f2(rebate), deposit: f2(cashDown), salesTax: f2(tax), docFee: f2(docFee),
license: f2(30),
...deskPlanFields(core.products, f2),
other: "0.00", taxRate: String(taxRate),
});
let rev = null;
if (window.DMS && window.DMS.updateDeal) {
const r = window.DMS.updateDeal(cust.dealId, {
...core,
cashDown, tax: dtRound2(tax), titleFee: 30,
dealType: "Cash", amtFinanced: 0, apr: 0, term: 0, payment: 0, lender: "", lienholder: null, stage: "Cash",
deskView: dkfViewSafe("cash", core), // customer-safe snapshot for the Sales view-only screen
});
// RC27 (dsk) fix round (audit): the deal refused the figures — no stage change, no "pushed", stay on the desk and say why
if (r && r.ok === false) { window.k2bToast && window.k2bToast(`${r.msg || "The deal did not take the figures."} Nothing was pushed — the deal stays where it is; fix it and push again.`, "warn", 8000); return; }
if (r && r.ok) rev = r.rev;
if (window.DMS.setDealStage) { const st = window.DMS.setDealStage(cust.dealId, "Cash"); if (st && st.ok) rev = st.rev; }
// Stamp the form with the deal revision it now mirrors so the Cash Deal
// screen does not re-copy the same figures on mount (B-04).
if (rev != null && window.saveJbForm) window.saveJbForm("cashWorksheet", { _srcDealId: cust.dealId, _srcRev: rev });
}
try { localStorage.setItem("jb_fw_deal", cust.dealId); } catch (e) {}
window.k2bToast && window.k2bToast(`${cust.dealId} pushed to Cash Deal — balance due at delivery ${dt$(Math.max(0, price + tax + docFee + 30 + core.vscSale + core.gapSale + core.tradePayoff - core.tradeAllow - rebate - cashDown - dkpDpa))}${dkpDpa > 0 ? " (after " + dt$(dkpDpa) + " down payment assistance)" : ""}.`, "ok", 4200);
go && go("cashworksheet");
};
// Push to Finance (retail or lease) → the Finance Deal screen (F&I office).
const pushFinance = () => {
pushToBuyersOrder();
const f2 = (n) => (n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const s = (isLease ? lsc : sc)[selSc] || {};
const core = deskDealCore();
const L = isLease ? leaseOf(selSc) : null;
if (!unit.cost) { try { console.warn("[Desking] Push to Finance: unit " + (unit.stock || "(none)") + " has no cost — deal.cost will be 0 until Inventory carries one."); } catch (e) {} }
// Lease inputs the Finance Worksheet needs to RE-RUN the lease engine on
// F5 (B-03) — every field jbLeaseCalc reads, as the desk had them.
const leaseForm = isLease ? {
moneyFactor: L.effectiveMoneyFactor.toFixed(5), residual: f2(L.residual), residualPct: (L.residPct + 0).toFixed(2),
msrp: f2(msrpVal), acqFee: f2(acqFee), acqFeeCapped: acqFeeCapped ? "1" : "", docFeeCapped: docFeeCapped ? "1" : "",
titleLicFee: f2(titleLicFee), titleLicCapped: titleLicCapped ? "1" : "",
capRed: f2(capRed), cashDownOnly: f2(cashDown), leaseRebates: f2(rebate + leaseCashEff),
milesPerYr: String(milesPerYr), taxMethod, leaseTaxRate: String(leaseTaxRate),
extraMiles: String(extraMiles || 0), extraMileageRate: String(extraMileageRate), inceptionMiles: String(inceptionMiles || 0), inceptionRate: String(inceptionRate),
inceptionAdjust: inceptionAdjust == null ? "" : (inceptionAdjust ? "1" : "0"),
securityDeposit: f2(L.secDepUsed), dispFee: f2(dispFee), excessRate: String(excessRate), purchOptFee: f2(purchOptFee),
singlePay: s.sp ? "1" : "",
dueAtSigning: f2(L.dueAtSigning), totalOfPayments: f2(L.totalOfPayments), rentCharge: f2(L.rent),
leaseUpfrontTax: f2(L.taxItemization ? L.taxItemization.estimatedUpfrontTotal : 0),
} : {};
window.saveJbForm && window.saveJbForm("financeWorksheet", {
dealType: isLease ? "Lease" : "Retail",
cashPrice: f2(price), tradeValue: f2(core.tradeAllow), tradePayoff: f2(core.tradePayoff), tradeAcv: f2(core.tradeAcv),
tradeDesc: core.tradeDesc, tradeLender: core.tradeLender, negEquity: f2(negEquity),
rebates: f2(rebate), deposit: f2(isLease ? cashDown + capRed : cashDown), salesTax: f2(isLease ? L.tax : tax), docFee: f2(docFee),
license: f2(isLease ? titleLicFee : 30),
// Protection products of the chosen option — plan rows + GAP + JSON list.
...deskPlanFields(core.products, f2),
amountFinanced: f2(isLease ? L.adjCap : amtFinanced),
apr: (isLease ? L.apr : sc[selSc].apr).toFixed(2), term: String(heroTerm(selSc)),
payment: f2(heroPmt(selSc)), lienName: lender,
// Titling entity for the deal jacket: lease → ACAR (lessor/titled owner),
// finance → AmeriCredit/GMF (lienholder). Editable downstream.
lienholder: titlingEntity(isLease) || null,
...leaseForm,
});
if (cust.dealId && window.DMS && window.DMS.updateDeal) {
const r = window.DMS.updateDeal(cust.dealId, {
// Vehicle / price / trade (+ lender, neg equity) / products (itemized
// with codes + lumped vscSale / gapSale for the GL) — shared with Push to Cash.
...core,
cashDown: isLease ? cashDown + capRed : cashDown,
tax: dtRound2(isLease ? L.tax : tax), titleFee: dtRound2(isLease ? titleLicFee : 30),
amtFinanced: dtRound2(isLease ? L.adjCap : amtFinanced),
apr: isLease ? L.apr : sc[selSc].apr, term: heroTerm(selSc), payment: heroPmt(selSc), lender,
dealType: isLease ? "Lease" : "Retail",
// deal.lienholder = { name, address, city, state, zip, kind } — the
// titling entity (lease-lessor / retail-lienholder) for this deal.
lienholder: titlingEntity(isLease) || null,
stage: "F&I",
deskView: dkfViewSafe("finance"), // customer-safe snapshot for the Sales view-only screen
...(isLease ? { moneyFactor: L.effectiveMoneyFactor, residual: L.residual, residualPct: L.residPct, milesPerYr,
dueAtSigning: dtRound2(L.dueAtSigning), leaseUpfrontTax: dtRound2(L.taxItemization ? L.taxItemization.estimatedUpfrontTotal : 0),
leaseTaxMethod: taxMethod, leaseTaxRate: +leaseTaxRate || 0, totalOfPayments: dtRound2(L.totalOfPayments) } : {}),
});
// RC27 (dsk) fix round (audit): the deal refused the figures — no stage change, no "pushed", stay on the desk and say why
if (r && r.ok === false) { window.k2bToast && window.k2bToast(`${r.msg || "The deal did not take the figures."} Nothing was pushed — the deal stays where it is; fix it and push again.`, "warn", 8000); return; }
let rev = r && r.ok ? r.rev : null;
if (window.DMS.setDealStage) { const st = window.DMS.setDealStage(cust.dealId, "F&I"); if (st && st.ok) rev = st.rev; }
// Stamp the form with the deal revision it now mirrors so the Finance
// Deal screen does not re-copy the same figures on mount (B-04).
if (rev != null && window.saveJbForm) window.saveJbForm("financeWorksheet", { _srcDealId: cust.dealId, _srcRev: rev });
try { localStorage.setItem("jb_fw_deal", cust.dealId); } catch (e) {}
}
window.k2bToast && window.k2bToast(`${cust.dealId} pushed to Finance Deal${isLease ? " (Lease)" : ""} \u2014 F&I office finalizes & posts.`, "ok");
go && go("finance");
};
// No FICO on file → no verdict (never "DIFFICULT" for a blank profile).
const approval = !fico ? "— (no FICO on file)" : fico >= 700 ? "STRONG" : fico >= 640 ? "LIKELY" : fico >= 580 ? "SUBPRIME" : "DIFFICULT";
const approvalColor = !fico ? "var(--jb-text-muted)" : fico >= 700 ? "var(--jb-green)" : fico >= 640 ? "var(--jb-blue)" : "var(--jb-red)";
const ptiEst = income ? (pmt(selSc) / income * 100) : 0;
// Group catalog by category for the product checklist
const cats = (window.jbProducts ? window.jbProducts.CATEGORIES : []).filter(c => catalog.some(p => p.category === c));
// ── One-screen 4-square sheet: VIEW state only (no figures live here) ──
// Box size of the desk window → density (row height / type scale) + how
// much header text fits. Measured, so 1366×768, 125 % scaling and 1080p
// each get a layout that fits without scrolling.
const dkcRootRef = React.useRef(null);
const dkcBox = dkfUseBox(dkcRootRef);
const dkcDens = dkfDensOf(dkcBox);
const dkcWide = dkcBox.w >= 1400, dkcNarrow = dkcBox.w < 1150;
// Customer view (Present): the same 4 squares, no inputs, no dealer numbers.
const [dkcPresent, setDkcPresent] = React.useState(false);
// Dealer numbers ("Show gross"): remembered for THIS customer / deal object
// only — picking another deal or reopening a scenario hands the desk a new
// cust object, so the numbers are hidden again in that same render. Never
// stored: a reopened desk always starts hidden. Present hides them too.
const [dkfGrossCust, setDkfGrossCust] = React.useState(null);
const showGross = !dkcPresent && dkfGrossCust != null && dkfGrossCust === cust;
const toggleGross = () => setDkfGrossCust(g => (g != null && g === cust) ? null : cust);
// Messages that arrive while the customer view is on are held (the status line shows only a count).
const dkfPresAtRef = React.useRef(0);
const togglePresent = () => { const nv = !dkcPresent; setDkcPresent(nv); if (nv) { setDkfGrossCust(null); dkfPresAtRef.current = Date.now(); } setPrintOpen(false); setDkcMenu(null); };
// Details drawer — docked on the right (it shrinks the sheet, never covers
// the payment square). null = automatic: open on wide screens.
const [dkcDrawer, setDkcDrawer] = React.useState(() => { const s = dkcStore("dkc_drawer"); return s === "open" ? true : s === "closed" ? false : null; });
const [dkcTab, setDkcTab] = React.useState(() => { const t = dkcStore("dkc_drawer_tab"); return t && t !== "profit" ? t : "products"; });
const drawerOpen = !dkcPresent && (dkcDrawer == null ? dkcBox.w >= 1500 : dkcDrawer);
const drawerTab = (dkcTab === "lease" && !isLease) ? "products" : (dkcTab === "profit" && !showGross) ? "products" : dkcTab; // RC27 (dsk): the Profit tab only while Show gross is on
const openDrawer = (tab) => { if (tab) { setDkcTab(tab); dkcStore("dkc_drawer_tab", tab); } setDkcDrawer(true); dkcStore("dkc_drawer", "open"); };
const closeDrawer = () => { setDkcDrawer(false); dkcStore("dkc_drawer", "closed"); };
const toggleDrawer = (tab) => { if (drawerOpen && (!tab || tab === drawerTab)) closeDrawer(); else openDrawer(tab); };
const dkcDrawerW = Math.round(Math.max(300, Math.min(440, dkcBox.w * 0.27)));
// Payment-square width (sheet columns 41 / 59; 37 / 63 when the payment
// square would be narrow and there is height to spare) → compact labels.
// Docked beside the sheet when the sheet keeps >= 900 px; on smaller screens
// the drawer takes the Trade / Down half while open (Price + Payment stay).
// A lease payment square carries more rows, so it needs a wider sheet
// (1366 × 768 lease: the drawer takes Trade / Down instead of squeezing it).
const drawerDock = drawerOpen && (dkcBox.w - 20 - dkcDrawerW - 8 >= (isLease ? 1000 : 900));
const drawerLeft = drawerOpen && !drawerDock;
const dkcSheetW = drawerLeft ? (dkcBox.w - 20) : dkcBox.w - 20 - (drawerOpen ? dkcDrawerW + 8 : 0);
const payNarrow = dkcSheetW * 0.59 < 660;
const dkcSplit = (payNarrow && dkcDens !== "tight") ? [37, 63] : [41, 59];
const dkcPayW = Math.round(dkcSheetW * dkcSplit[1] / 100);
// ── Fit: what the sheet gives up, in order, when a square would need scrolling ──
// Measured after every render (before paint); starts again from 0 whenever what
// fills the squares changes (screen size, lease / finance, trade, gross, unit
// alert, details panel, options, customer view …). Nothing is ever lost —
// each step's figures are elsewhere on the sheet or in the details panel.
// 1 · lease: the roomy per-option breakdown rows (Residual $ … Tax / mo — all in Lease detail)
// 2 · unit not clean: the red pill in the Price square instead of the full-width line
// 3 · shorter rows (the compact row height — text size unchanged)
// 4 · the negative-equity sentence under the trade (the Down square keeps "Neg. equity →")
// 5 · the detail lines under the big payment (the option columns carry the same figures)
// 6 · the trade description moves up into the Trade square's header row
const DKF_FIT_MAX = 6;
const dkfFitKey = [dkcBox.w, dkcBox.h, mode, hasTrade ? 1 : 0, negEquity > 0 ? 1 : 0, showGross ? 1 : 0, unitCheck.clean ? 1 : 0,
drawerOpen ? (drawerDock ? 2 : 1) : 0, visIdx.length, dkcPresent ? 1 : 0, rebateReview.length, leaseCashEff > 0 ? 1 : 0, isUsedUnit ? 1 : 0,
gmfOn ? 1 : 0, dkcPresent ? chosenProducts(selSc).length : 0].join(",");
const [dkfFitSt, setDkfFitSt] = React.useState({ key: "", lv: 0 });
const dkfFit = dkfFitSt.key === dkfFitKey ? dkfFitSt.lv : 0;
React.useLayoutEffect(() => {
const root = dkcRootRef.current; if (!root || dkfFit >= DKF_FIT_MAX) return;
const over = Array.from(root.querySelectorAll(".dkc-sheet .dkc-gridwrap, .dkc-sheet .dkc-qbody")).some(el => el.scrollHeight - el.clientHeight > 1);
if (over) setDkfFitSt({ key: dkfFitKey, lv: dkfFit + 1 });
});
// Unit-readiness alert: a full-width line when the screen has height to spare, else a pill in the Price square.
const dkfAlertPill = !(dkcDens === "roomy" || dkcDens === "xl") || dkfFit >= 2;
const [dkcMenu, setDkcMenu] = React.useState(null); // "more" | "log" | "keys" | null (Print uses printOpen)
const [dkcModOpen, setDkcModOpen] = React.useState(false); // module gear (was the title bar ⚙)
const dkcMod = window.jbModSettings && window.jbModSettings.forTitle ? window.jbModSettings.forTitle("Desking — 4-Square Work Tool") : null;
// Sidebar folds to an icon rail while desking; More ▴ › "Keep the full menu open" pins it.
const [dkcPin, setDkcPin] = React.useState(() => dkcStore("dkc_nav_pin") === "1");
React.useEffect(() => { document.body.classList.toggle("dkc-nav-pin", !!dkcPin); }, [dkcPin]);
// Messages: every toast (k2btoast event) is shown in the bottom status line
// while the desk is open — nothing pops up over the numbers (dkfUseMsgs).
const { msgs: dkcMsgs, last: dkcLast, fresh: dkcFresh } = dkfUseMsgs();
React.useEffect(() => {
const B = document.body; B.classList.add("dkc-desk-on");
return () => { B.classList.remove("dkc-desk-on"); B.classList.remove("dkc-nav-pin"); };
}, []);
// Keyboard: Alt+1..4 pick option A–D · Alt+P customer view · Alt+G show /
// hide gross · Alt+M details drawer · Esc closes an open menu / leaves the
// customer view.
React.useEffect(() => {
const onKey = (e) => {
if (document.querySelector("[data-k2b-modal]")) return;
if (e.key === "Escape" && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (printOpen || dkcMenu) { setPrintOpen(false); setDkcMenu(null); e.preventDefault(); }
else if (dkcPresent) { setDkcPresent(false); e.preventDefault(); }
return;
}
if (!e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
const c = e.code || "";
const dg = /^(Digit|Numpad)([1-4])$/.exec(c);
if (dg) { const i = +dg[2] - 1; if (scOn[i]) { setSelSc(i); e.preventDefault(); } return; }
if (c === "KeyP") { e.preventDefault(); togglePresent(); return; }
if (c === "KeyG") { e.preventDefault(); if (!dkcPresent) toggleGross(); return; }
if (c === "KeyM") { e.preventDefault(); toggleDrawer(); }
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
});
const noDeal = !cust.dealId;
// K2B-013: with NO deal open (a unit picked or not), or with no sale price yet, the desk shows NO payment /
// total / amount-financed figure — the defaults alone (down, rebate, title) would compute made-up or negative
// ones — and nothing can be pushed or printed from it. Figures appear once a deal is open (CRM → Desk It).
const dkfNoFig = noDeal || !(+price > 0);
const dkfFig = (s) => dkfNoFig ? "—" : s;
// K2B-013 / spec 6.3 valid-input gate: an option whose amount financed comes out at or below $0 (the down
// payment, rebate and trade equity cover the whole balance), or a lease option whose adjusted cap cost or
// payment comes out below $0, is not a valid loan / lease. It shows "—" instead of a figure; when it is the
// ★ option, Push to Finance, Print and Present are held. A retail ★ option at exactly $0 is paid in full and can
// still go to Cash; one BELOW $0 (down payment more than the whole balance = a refund) cannot go anywhere.
const dkfBadOf = (i) => {
if (dkfNoFig) return false;
if (isLease) { const q = leaseOf(i); return dtRound2(q.adjCap) < 0 || dtRound2(q.singlePay ? q.singlePayAmount : q.payment) < 0; }
return dtRound2(amtFin(i)) <= 0;
};
const dkfBad = [0, 1, 2, 3].map(dkfBadOf);
const dkfBadSel = !!dkfBad[selSc];
const dkfOverSel = dkfBadSel && !isLease && dtRound2(amtFin(selSc)) < 0; // retail ★ balance below $0 → no Cash push either
const dkfOptFig = (i, s) => (dkfNoFig || dkfBad[i]) ? "—" : s;
const dkfSelFig = (s) => (dkfNoFig || dkfBadSel) ? "—" : s;
const dkfBadMsg = (i) => isLease
? `Option ${DT_LETTERS[i]}'s down payment is more than this lease can take — the payment would be below $0. Lower the down payment.`
: dtRound2(amtFin(i)) < 0
? `Option ${DT_LETTERS[i]}'s down payment is ${dt$(-dtRound2(amtFin(i)))} more than the whole balance — lower the down payment.`
: `Option ${DT_LETTERS[i]}'s down payment covers the whole balance — nothing to finance. Lower the down payment, or push it as a cash deal (paid in full).`;
const P = dkcPresent;
const G = dtGmfData();
const usedMode = gmfOn && gmfNewUsed === "used";
const tierList = usedMode ? Object.keys(G.usedProgram.tierMf || {}) : GMF_TIERS;
const gmfReady = !!(window.GMF_LEASE && window.GMF_LEASE.program && Array.isArray(window.GMF_LEASE.program.rates) && window.jbLeaseCalc);
const markupMax = +G.program.dealerMarkupMaxMf || 0.001;
const anySp = isLease && visIdx.some(i => lsc[i] && lsc[i].sp);
const leaseTe = isLease ? titlingEntity(true) : null;
const LQs = isLease ? leaseOf(selSc) : null; // ★ option's lease quote (read-only use)
const heroSp = isLease && lsc[selSc] && lsc[selSc].sp;
const L1 = DT_LETTERS[selSc];
// Figures the footer / hero repeat (read from the existing calculations).
// Total down = cash down + rebate + positive equity. A single-pay lease takes no
// cash as cap reduction (GMF rule — its Down cell reads "n/a (1-pay)"), so its
// total leaves the greyed-out cash out.
const dkfSpOf = (i) => isLease && !!(lsc[i] && lsc[i].sp);
const dkfTotalDownOf = (i) => (dkfSpOf(i) ? 0 : optDown(i)) + rebate + posEquity;
const dkfTotalDown = dkfTotalDownOf(selSc);
const dkfAmt = isLease ? LQs.adjCap : amtFinanced;
const dkfHeroAmt = heroSp ? LQs.singlePayAmount : heroPmt(selSc);
// Title / registration fee as the amount-financed calculation itself carries
// it: base amount financed minus every other itemized line.
const dkfTitleFee = dtRound2(baseFin(selSc) - (price + docFee + tax - tradeAllow + tradePayoff - optDown(selSc, false) - rebate));
// Clicking a total puts the cursor in the box that changes it. On a small
// screen the details drawer may sit over Trade / Down — it is closed first.
const dkfJump = (sel, inTradeDown) => () => {
if (P) return;
const run = () => dkfFocusBox(dkcRootRef.current, sel);
if (inTradeDown && drawerLeft) { closeDrawer(); setTimeout(run, 80); } else run();
};
const jumpPmt = dkfJump(`[data-opt="${L1}"] [data-c="down"]`);
const jumpDown = dkfJump('[data-f="down"]', true);
const jumpAmt = dkfJump('[data-f="salePrice"]');
// ── Customer-safe snapshot of the desk (what a salesperson may show) ──
// Computed figures only — no inputs to recompute from, no invoice, gross,
// pack, commission, ACV, product cost or credit data. Saved with each
// scenario (scenario.view) and with each push (deal.deskView); the Sales
// view-only screen renders nothing else.
// Amounts are stored as the desk holds them (the view formats them with the same
// dt$ the desk uses, so a half-cent amount rounds the same way); payments are
// the rounded figures the desk shows.
const dkfViewOf = (pushedAs, core) => {
const lease = isLease;
const options = visIdx.filter(i => !dkfBad[i]).map(i => { // K2B-013 / 6.3: an option below $0 is not shown to Sales
const s = (lease ? lsc : sc)[i] || {};
const prods = chosenProducts(i).map(p => ({ name: p.name, price: p.price }));
if (lease) {
const q = leaseOf(i);
return { letter: DT_LETTERS[i], term: +s.term || 0, singlePay: !!s.sp, mf: q.effectiveMoneyFactor, aprEq: q.aprEquivalent,
residPct: q.residPct, residual: q.residual, down: s.sp ? null : optDown(i, true), prodLabel: optProdLabel(s), prodTotal: lsAdds(i), products: prods,
amount: q.adjCap, dueAtSigning: q.dueAtSigning, payment: s.sp ? q.singlePayAmount : q.payment, remaining: q.remainingPayments,
totalOfPayments: q.totalOfPayments, totalDown: (s.sp ? 0 : optDown(i, true)) + rebate + posEquity };
}
return { letter: DT_LETTERS[i], term: +s.term || 0, apr: +s.apr || 0, down: optDown(i, false), prodLabel: optProdLabel(s), prodTotal: scAdds(i), products: prods,
amount: amtFin(i), payment: dtRound2(pmt(i)), totalDown: optDown(i, false) + rebate + posEquity };
});
const v = {
v: 1, savedAt: new Date().toISOString(), dealId: cust.dealId || null, customer: cust.preset ? "" : (cust.name || ""),
unit: { yr: unit.yr || "", mk: unit.mk || "", mdl: unit.mdl || "", stock: unit.stock || "", vin: unit.vin || "", newUsed: unit.newUsed || "", miles: +unit.miles || 0 },
mode, lender, titledTo: lease && leaseTe ? dtEntityShort(leaseTe.name) : "",
salePrice: price, docFee, taxRate, salesTax: tax, msrp: msrpVal,
leaseTaxRate: lease ? (+leaseTaxRate || 0) : null, milesPerYr: lease ? (+milesPerYr || 0) : null, taxMethod: lease ? taxMethod : null,
trade: hasTrade ? { desc: tradeDesc || "", allowance: tradeAllow, payoff: tradePayoff, lender: tradeLender || "", equity: tradeEquity, negEquity, posEquity } : null,
rebate, leaseCash: lease ? leaseCashEff : 0, capRed: lease ? (+capRed || 0) : 0, usedUnit: isUsedUnit,
sel: DT_LETTERS[selSc], options,
products: selectedProducts.map(p => ({ name: p.name, price: +selected[p.id] || 0 })),
pushedAs: pushedAs || null,
// RC27 (dsk): customer-facing only — the store's down payment assistance (a credit toward the down) and a lender fee the customer pays
downAssist: dkpDpa > 0 ? dkpDpa : 0, lenderFeeCust: dkpFeeCust > 0 ? dkpFeeCust : 0,
};
if (pushedAs === "cash" && core) v.cashDue = Math.max(0, price + tax + docFee + 30 + core.vscSale + core.gapSale + core.tradePayoff - core.tradeAllow - rebate - cashDown - dkpDpa);
return JSON.parse(JSON.stringify(v));
};
const dkfViewSafe = (pushedAs, core) => { try { return dkfViewOf(pushedAs, core); } catch (e) { return null; } };
// ── A desk opened on a PC that never worked this deal starts from the defaults
// (the working desk lives in each PC's own storage). When the deal has a
// saved scenario, the footer offers it: one click loads the newest one. ──
const dkfFreshRef = React.useRef(!!(W && W.dealId && !Array.isArray(W.sc)));
const [dkfOfferOff, setDkfOfferOff] = React.useState(false);
const dkfOffer = (() => {
if (dkfOfferOff || dkcPresent || !dkfFreshRef.current || !cust.dealId || String(cust.dealId) !== String(W.dealId)) return null;
try {
return ((window.DMS && window.DMS.deskScenarios && window.DMS.deskScenarios()) || [])
.filter(r => r && r.state && r.state.cust && String(r.state.cust.dealId || "") === String(cust.dealId))
.sort((a, b) => String(b.savedAt || "").localeCompare(String(a.savedAt || "")))[0] || null;
} catch (e) { return null; }
})();
const dkfLoadOffer = () => { const r = dkfOffer; if (!r) return; restoreScenario(r.state); setDkfOfferOff(true);
window.k2bToast && window.k2bToast(`Saved desk “${r.name || r.id}” loaded.`, "ok"); };
// ── option grid helpers (layout only) ──
const stopEv = (e) => e.stopPropagation();
const chipFor = (i, k, lab, short) => { const s = (isLease ? lsc : sc)[i] || {}; const L = DT_LETTERS[i];
return (
{ e.stopPropagation(); isLease ? setLscK(i, k, !s[k]) : setScK(i, k, !s[k]); }}>
{short ? <>{lab} {short} > : lab}
); };
const roomyLease = isLease && (dkcDens === "roomy" || dkcDens === "xl") && dkfFit < 1; // fit step 1 drops these rows
// Rows of the options grid (label + one cell per visible option).
const optRows = [
["h",
+ Add option {DT_LETTERS[scOn.findIndex(v => !v)] || ""} ],
["term", isLease ? "Term · 1-pay" : "Term (mo)"],
["rate", isLease ? "Money factor" : "APR %"],
...(isLease ? [["resid", "Residual %"]] : []),
...(roomyLease ? [["residv", "Residual $"]] : []),
["down", "Down $"],
["prod", "Products"],
...(roomyLease ? [["dep", "Depreciation"], ["rent", "Rent charge"], ["base", "Base payment"], ["ltax", taxMethod === "monthly" ? "Tax / mo" : "Tax (upfront)"]] : []),
["amt", isLease ? "Adj. cap cost" : "Amt financed"],
...(isLease ? [["due", "Due at signing"]] : []),
["pmt", "Payment"],
];
const optCell = (row, i) => {
const s = (isLease ? lsc : sc)[i] || {}; const L = DT_LETTERS[i]; const on = i === selSc; const q = isLease ? leaseOf(i) : null;
switch (row) {
case "h": return (
{/* the option letter is a real button: Tab reaches it, Enter / Space picks it */}
{ e.stopPropagation(); setSelSc(i); }}
title={"Present option " + L + " — the ★ one drives the payment, prints and F&I (Alt+" + (i + 1) + ")"}>
{on ? "★ " : ""}{L}{isLease && s.sp ? 1-pay : null}{q && q.upsideDown ? ⚠ : null}
{!P && i >= 2 ? { e.stopPropagation(); removeOption(i); }}>✕ : null}
);
case "term": return (
{P ? {s.term} mo{q && s.sp ? " · 1-PAY" : ""} : (
{isLease && usedMode
? (() => { const t0 = +s.term || 36, off = !GMF_USED_TERMS.includes(t0); // a stored term off the 24 / 36 / 48 program is SHOWN, never replaced by the first option
return setLeaseTerm(i, +e.target.value)} aria-label={"Term option " + L} data-dkc="term"
title={off ? `The payment is figured on ${t0} months — not a GMF used-lease term (24 / 36 / 48). Pick one to put it back on the program.` : "GMF used-lease term"}
style={off ? { borderColor: "var(--jb-red)", color: "var(--jb-red)", fontWeight: 700 } : null}>
{dkfTermList(GMF_USED_TERMS, t0).map(t => {t}{GMF_USED_TERMS.includes(t) ? "" : " ⚠"} )} ; })()
: isLease ? setLeaseTerm(i, v) : setScK(i, "term", v)} width={50} style={dkcIn()} />} )}
{isLease && !P ? (() => { const spDisabled = usedMode && (+s.term || 0) < 36; return (
{ e.stopPropagation(); if (!spDisabled) toggleSp(i); }}>1-Pay ); })() : null}
);
case "rate": return (
{P ? {isLease ? q.effectiveMoneyFactor.toFixed(5) : (+s.apr || 0).toFixed(2) + "%"} : (
{isLease
? (s.aprIn
? <> setLeaseApr(i, v)} width={62} decimals={2} style={dkcIn(autoMfOn(i) ? { border: "1px solid var(--jb-green)", boxShadow: "inset 3px 0 0 var(--jb-green)" } : null)} />% >
: setLeaseMf(i, v)} width={74} decimals={5} style={dkcIn(autoMfOn(i) ? { border: "1px solid var(--jb-green)", boxShadow: "inset 3px 0 0 var(--jb-green)" } : null)} />)
: setScK(i, "apr", v)} width={62} decimals={2} style={dkcIn()} />} )}
);
case "resid": return (
{P ? {q.residPct.toFixed(1)}% : (
{!roomyLease ? {dt$(q.residual)} : null}
{mileageAddOf(i) ? +{mileageAddOf(i)} : null}
setLeaseResid(i, v)} width={56} decimals={1} style={dkcIn(autoRvOn(i) ? { border: "1px solid var(--jb-green)", boxShadow: "inset 3px 0 0 var(--jb-green)" } : null)} />
)}
);
case "residv": return
{dt$(q.residual)}
;
case "down": return (
{P ? {isLease && s.sp ? "n/a (1-pay)" : dt$(+s.down || 0)} : (
isLease ? setLscK(i, "down", +v || 0) : setScK(i, "down", +v || 0)} width={"100%"} style={dkcIn()} />
)}
);
case "prod": return P
?
{optProdLabel(s)}
: (
{chipFor(i, "gap", "GAP")}{chipFor(i, "vsc", "Warranty", "Warr")}{chipFor(i, "all", "All Products", "All")}
);
case "dep": return
{dkfOptFig(i, dt$(q.dep))}
;
case "rent": return
{dkfOptFig(i, dt$(q.rent))}
;
case "base": return
{dkfOptFig(i, dt$(q.base))}
;
case "ltax": return
{dkfOptFig(i, dt$(taxMethod === "monthly" ? q.tax : q.upfrontTax))}
;
case "amt": return (
{isLease ? dkfOptFig(i, dt$(q.adjCap)) : {dkfOptFig(i, dt$(amtFin(i)))} }
);
case "due": return
{dkfOptFig(i, dt$(q.dueAtSigning))}
;
case "pmt": return (
{isLease && s.sp ? once : null}
{dkfOptFig(i, isLease && s.sp ? dt$(q.singlePayAmount) : dt$(heroPmt(i)))}
);
default: return
;
}
};
// Print menu entries (unchanged list + actions).
const printItems = [
// Packet resolver gets "Lease" for a lease (B-03) — the sequencing rules
// then add the lease forms and drop the retail RIC / 553 prep.
["Deal Forms Packet… (sequenced)", () => { pushToBuyersOrder(); const bo = window.loadJbForm ? window.loadJbForm("buyersOrder") : {}; window.k2bPrintSelectDeal && window.k2bPrintSelectDeal({ dealType: isLease ? "Lease" : (/^n|new/i.test(unit.type || bo.newUsed || "") ? "New" : "Used"), newUsed: unit.newUsed || bo.newUsed || "", bank: lender, state: bo.state || "", dealId: cust.dealId || null }); }],
["Due Bill — We Owe", () => { const d = dkpDealOf(cust.dealId); window.k2bPrintDueBill && window.k2bPrintDueBill(Object.assign({}, d || { id: cust.dealId, buyer: cust.name }, { costAdds: dkpLines })); }], // RC27 (dsk): the customer's due bill (no amounts)
["Customer Presentation", () => printCustomerPresentation()],
["4-Square Worksheet", () => printFourSquare()],
["Lease Worksheet (Dealer)", () => printLeaseWorksheet()],
["Lease Disclosure (Customer)", () => printLeaseDisclosure()],
["Contract Prep — 553-MO (F&I)", () => printContractPrep()],
["Buyer's Order", () => { pushToBuyersOrder(); window.k2bPrintBuyersOrder && window.k2bPrintBuyersOrder(); }],
["Sold “As-Is” — No Warranty", () => { pushToBuyersOrder(); window.k2bPrintAsIs && window.k2bPrintAsIs(); }],
["Release of Liability", () => { pushToBuyersOrder(); window.k2bPrintRelease && window.k2bPrintRelease(); }],
];
const goCreditApp = () => { try { const dd = cust.dealId && window.DMS ? window.DMS.deals().find(x => x.id === cust.dealId) : null; if (dd && dd.custNum) localStorage.setItem("jb_credit_cust", dd.custNum); } catch (e) {} go && go("creditapp"); };
const moreItems = [
["🚗 Pick Unit", () => setPicker("unit")],
...(unit.vin && window.K2BApps ? [["📄 Carfax", () => { try { window.K2BApps.open("carfax", { vin: unit.vin, stock: unit.stock }); } catch (e) { window.k2bToast && window.k2bToast("Carfax app not available.", "warn"); } }, "Carfax report for VIN " + unit.vin]] : []),
["🧾 Credit App", goCreditApp],
["📝 Save to Buyer's Order", () => { if (dkfNoFig || dkfBadSel) { window.k2bToast && window.k2bToast(noDeal ? "No deal open — deals start in the CRM: pick the lead → Desk It." : dkfNoFig ? "Enter the sale price first — nothing to save yet." : dkfBadMsg(selSc), "warn", 4200); return; } pushToBuyersOrder(); window.k2bToast && window.k2bToast("Figures saved to Buyer's Order draft.", "ok"); }],
["📋 Buyer's Order", () => go && go("buyersorder")],
null,
["🏦 Banks / lenders", () => go && go("banks"), "Manage lenders / banks"],
["⚙ Desking settings & defaults", () => setSettingsOpen(true)],
["🛡 Manage F&I catalog", () => go && go("fiproducts")],
...(dkcMod && window.K2BModSettingsPanel ? [["⚙ Module settings", () => setDkcModOpen(true)]] : []),
null,
[dkcPin ? "☰ Fold the menu while desking" : "☰ Keep the full menu open", () => { const v = !dkcPin; setDkcPin(v); dkcStore("dkc_nav_pin", v ? "1" : "0"); }],
];
const dkfKeys = [["Alt+1 … 4", "Pick option A – D"], ["Tab", "Price → Trade → Down → Payment"], ["Enter / Space", "On an option letter: pick it"],
["Alt+P", "Customer view on / off"], ["Alt+G", "Show / hide gross"], ["Alt+M", "Details panel"], ["Esc", "Close a menu · leave the customer view"]];
// ── the four squares (numbered in reading order: 1 Trade · 2 Price / 3 Down · 4 Payment) ──
const unitPill = (!unitCheck.clean && !P && dkfAlertPill) ? (
{ setPrintOpen(false); setDkcMenu(m => m === "unit" ? null : "unit"); }}
title={unitCheck.items.map(it => "• " + it.label).join("\n")}>⚠ Unit not clean · {unitCheck.items.length} open item{unitCheck.items.length !== 1 ? "s" : ""} ▾
setDkcMenu(null)} className="dkc-unitpop" style={{ right: 0, top: "calc(100% + 6px)", width: 380 }}>
⚠ UNIT NOT CLEAN — Stk {unit.stock}
{unitCheck.items.map((it, i) => {it.label}
)}
{ setDkcMenu(null); go && go("servicedept"); }}>Service Dept ▸
{ setDkcMenu(null); go && go("partsdept"); }}>Parts Dept ▸
) : null;
const priceSq = (
{unitPill} toggleDrawer("fees")} title="Taxes & fees — itemization, lease fees and tax method">🧾 Taxes & fees >}>
{isLease && }
);
// fit step 6: the trade description moves up into the square's header row
const tradeDescIn = hasTrade && dkfFit >= 6;
const tradeDescEl = P ?
{tradeDesc || "Trade-in"}
:
setTradeDesc(e.target.value)} placeholder="Year / Make / Model" aria-label="Trade description"
style={{ width: "100%", boxSizing: "border-box", fontSize: "var(--dkc-ifs, 13px)", height: "var(--dkc-in-h)", padding: "0 8px" }} />;
const tradeSq = (
{tradeDescIn ? {tradeDescEl}
: null}
{(!P && hasTrade) ? ✕ No trade : null}>}>
{!hasTrade ? (
No trade-in on this deal
{!P &&
⟳ Load Appraisal
+ Enter manually
}
) : (
{!tradeDescIn &&
{tradeDescEl}
}
{P ? {tradeLender || "—"}
: setTradeLender(e.target.value.toUpperCase())} placeholder="Bank holding the payoff" aria-label="Payoff lender" title={tradeLender || ""}
style={{ width: "calc(var(--dkc-mw) + 96px)", maxWidth: "100%", boxSizing: "border-box", fontSize: "var(--dkc-ifs, 13px)", fontWeight: 600, textAlign: "right", height: "var(--dkc-in-h)", padding: "0 6px" }} />}
0 ? "Negative equity" : "Net Equity"} title={negEquity > 0 ? "Negative equity — rolled into the deal" : "Trade equity"} value={dt$(tradeEquity)} color={tradeEquity < 0 ? "var(--jb-red)" : "var(--jb-green)"} strong />
{/* ACV (book) and Trade Hold live in the profit block under Down (Show gross) — they no longer make this row taller */}
{negEquity > 0 && dkcDens !== "tight" && dkfFit < 4 && Payoff is over the allowance — {dt$(negEquity)} is rolled into the {isLease ? "lease" : "loan"} on every option.
}
)}
);
const downSq = (
{isUsedUnit
?
Rebates apply to new units only —
:
}
{!isUsedUnit && !P && rebateReview.map(it => (
{it.label} {dt$(it.amount)} {it.key === "leaseCash" && /program/.test(it.label) ? "available" : "from before"}{it.key === "leaseCash" ? " (lease)" : ""}
reviewApply(it)} style={{ fontWeight: 700 }}>Apply
reviewDiscard(it)}>Discard
))}
{isLease &&
}
{isLease && leaseCashEff > 0 &&
}
{/* RC27 (dsk): customer-facing lines only — the dealer's down payment assistance lowers the customer's cash; a lender fee passed to the customer is their fee */}
{dkpDpa > 0 &&
}
{dkpDpa > 0 &&
}
{dkpFeeCust > 0 &&
}
{negEquity > 0 &&
}
{isLease &&
}
0 ? `includes ${dt$(negEquity)} negative equity` : null} />
{P && chosenProducts(selSc).length ? (
Protection products — option {L1}
{chosenProducts(selSc).map((pp, i) => )}
) : null}
{showGross && !dkfNoFig && (
💲 {dkfLS("Deal profit · managers only", "Deal profit")}
openDrawer("profit")} title="Cost adds — down payment assistance, lender fee, we owe, dealer-paid fees (the Profit panel)">💲 Profit{CA.lines.length ? ` (${CA.lines.length})` : ""}
Hide gross
{/* long labels, or short ones when the Down square is narrow (container query) — never cut off */}
{CA.frontCents ? : null}
{CA.backCents ? : null}
{dkpPackWhatIf && dkpPostProfit ? : null}
{hasTrade && }
{hasTrade && 0 ? "var(--jb-amber)" : "var(--jb-green)"} mgr />}
)}
);
const payGridRows = optRows.length;
const presentBtn = (
🖨 Present to Customer
);
const paySq = (
4 Payment
{P ? {isLease ? "Lease" : "Finance"} · {lender} : (
<>
{[["finance", "💰 Finance"], ["lease", "🔑 Lease"]].map(([m, lab]) => (
setMode(m)}>{lab}
))}
setLender(e.target.value)} aria-label="Lender" data-dkc="lender"
title={"Lender" + (leaseTe ? `\nTitled to: ${leaseTe.name}\n${[leaseTe.address, [leaseTe.city, leaseTe.state].filter(Boolean).join(", "), leaseTe.zip].filter(Boolean).join(", ")}` : "")}
style={{ flex: "0 1 auto" }}>
{lenderList.length ? lenderList.map(Ln => {Ln.name} ) : GM Financial }
>
)}
{!P && toggleDrawer("products")}
title="F&I products & service contracts — the catalog the GAP / Warranty / All chips draw from">🛡 Products{selectedProducts.length ? ` (${selectedProducts.length})` : ""} }
{!P && isLease && toggleDrawer("lease")}
title="Lease detail — program info, mileage factor, MF⇄APR, single-pay, per-option breakdown, line items, worksheet / disclosure">🔑 {payNarrow ? "Lease" : "Lease detail"} }
{isLease ? (heroSp ? "Single-Pay Lease" : "Lease") : "Finance"} Payment — Option {L1} ★
{dkfNoFig || dkfBadSel ?
—
: P ? (heroSp
?
{dt$(LQs.singlePayAmount)} one-pay
:
{dt$(heroPmt(selSc))}/mo
)
:
{heroSp ? <>{dt$(LQs.singlePayAmount)} one-pay > : <>{dt$(heroPmt(selSc))}/mo >}
}
{dkfNoFig && (
{noDeal ? "No deal open" : "No sale price yet"} — {noDeal ? "deals start in the CRM: pick the lead → Desk It." : "enter the sale price to see payments."}
)}
{dkfBadSel && (
{isLease ? "Payment below $0" : dkfOverSel ? "Down payment too high" : "Nothing to finance"} — {dkfBadMsg(selSc)}
)}
{!isLease && !dkfNoFig && !dkfBadSel &&
{heroTerm(selSc)} months @ {sc[selSc].apr.toFixed(2)}% · {lender}
}
{presentBtn}
{isLease && !dkfNoFig && !dkfBadSel && (
{heroTerm(selSc)} months · MF {LQs.effectiveMoneyFactor.toFixed(5)} ({heroRate(selSc).toFixed(2)}% APR equiv) · Residual {dt$(LQs.residual)} ({LQs.residPct.toFixed(1)}%{LQs.residualAdjustments > 0 ? " − mi adj" : ""}) · Due at signing {dt$(LQs.dueAtSigning)}
{heroSp ? "1 payment at inception" : `${LQs.remainingPayments} payments of ${dt$(LQs.payment)} — first paid at inception`} · Total of payments {dt$(LQs.totalOfPayments)} · {(+milesPerYr).toLocaleString()} mi/yr{gmfOn ? ` · ${gmfNewUsed === "used" ? "GMF Used" : "GMF"} Tier ${gmfTier}` : ""}
)}
{isLease && !P && (
{!gmfOn && openDrawer("lease")} title="GMF program mode is OFF — manual MF & residuals. Turn it back on in Lease detail.">Manual program ▸ }
{gmfOn && (
{["new", "used"].map(nu => setNewUsed(nu)}>{nu === "new" ? "New" : "Used (CPO)"} )}
)}
{gmfOn && !usedMode && (
setGmfPickerOpen(true)} title="Pick the GMF model/trim — autofills MF + residual per term">
📋 {gmfPick ? `${gmfPick.modelYear || ""} ${gmfPick.model}${gmfPick.trim ? " · " + gmfPick.trim : ""}` : "Pick GMF model / trim"} ▾
)}
Tier
{ setGmfTier(e.target.value); setSecDep(null); }} data-dkc="tier"
title={tierList.includes(gmfTier) ? "" : `Tier ${gmfTier} is not on the ${usedMode ? "GMF used" : "GMF"} program list — the desk is using ${gmfTier}`}
style={Object.assign({ maxWidth: payNarrow ? 170 : 220 }, tierList.includes(gmfTier) ? {} : { borderColor: "var(--jb-red)", color: "var(--jb-red)", fontWeight: 700 })}>
{/* the stored tier is always one of the choices, so the box shows what the desk is using */}
{(tierList.includes(gmfTier) ? tierList : tierList.concat([gmfTier])).map(t => {dtGmfTierLabel(t)}{tierList.includes(t) ? "" : " ⚠"} )}
{payNarrow ? "Mi/yr" : "Miles/yr"}
{gmfOn ? (
setMilesPerYr(+e.target.value)} disabled={usedMode} data-dkc="miles">
{GMF_MILES.map(m => {m.toLocaleString()} )}
) : }
{leaseTe && dkcPayW >= 900 && (
Titled to: {dtEntityShort(leaseTe.name)}
)}
)}
{optRows.map(([k, lab]) =>
{lab}
)}
{visIdx.map(i => (
setSelSc(i)}>
{optRows.map(([k]) => optCell(k, i))}
))}
);
// ── drawer (secondary detail) ──
const drawerTabs = [["products", "Products"], ["fees", "Fees"], ...(isLease ? [["lease", "Lease"]] : []), ["credit", "Credit"], ...(showGross ? [["profit", "💲 Profit"]] : [])];
const drawerBody = (() => {
// ── RC27 (dsk): the PROFIT panel — managers only (inside Show gross; never on Present / a customer copy) ──
if (drawerTab === "profit" && showGross) {
const pp = dkpProfit || {};
const ro = dkpIsPosted;
const kinds = (window.DealCalc && window.DealCalc.LENDER_FEE_KINDS) || {};
const segBtn = (i, side, on) =>
setCaK(i, "side", side)}>{side === "front" ? "Front" : "Back"} ;
return (
Deal profit — managers only
{ro ?
posted — frozen : null}
{dkpPackWhatIf && dkpPostProfit ?
What-if pack: this desk uses {dt$(+pack || 0)}, but the posting books the unit's pack in Inventory, {dt$(dkpInvPack)} — net front {dkpC(dkpPostProfit.netFrontCents)} , total gross {dkpC(dkpPostProfit.totalGrossCents)} , commission {dkpC(dkpPostProfit.commissionCents)} .
{!ro ? <> setPackHand(false)}>Use the Inventory pack > : null}
: null}
Cost adds — costs the store takes on this deal
{!ro &&
{ if (e.target.value) addCostLine(e.target.value); e.target.value = ""; }}>
+ Add a line…
{DKP_TYPES().map(([k, t]) => {t.label} )}
}
{!dkpLines.length ?
No cost adds on this deal. Add down payment assistance, a lender fee the store pays, a we-owe (it prints a due bill), a dealer-paid fee, an F&I item or another cost.
: null}
{dkpLines.map((l, i) => { const T = (window.DealCalc && window.DealCalc.COST_ADD_TYPES[l.type]) || { label: l.type }; const isFee = l.type === "lenderFee"; const forcedWo = l.type === "weOwe"; const woAllowed = !(l.type === "dpa" || isFee);
return (
setCaK(i, "type", e.target.value)}>
{DKP_TYPES().map(([k, t]) => {t.label} )}
setCaK(i, "desc", e.target.value)} style={{ flex: "1 1 120px", minWidth: 90, height: "var(--dkc-in-h)", fontSize: 13, padding: "0 6px" }} />
{ro ? {dt$(+l.amount || 0)}
: setCaK(i, "amount", (+v || 0) > 0 ? (+v).toFixed(2) : "")} width={96} style={dkcIn()} /> }
{!ro && removeCostLine(i)}>✕ }
{segBtn(i, "front", (l.side || T.side) !== "back")}{segBtn(i, "back", (l.side || T.side) === "back")}
{woAllowed &&
setCaK(i, "weOwe", e.target.checked)} />we owe }
{isFee &&
setCaK(i, "passed", e.target.checked)} />passed to customer }
{isFee && setCaK(i, "kind", e.target.value)}>
{Object.keys(kinds).map(k => {kinds[k]} )} }
{l.auto && !ro ? from {l.lender || "the lender"} : null}
{isFee && l.passed ? customer's fee — not a cost : null}
);
})}
{!ro && dkpSaveNote && !CA.problems.length ?
Not saved to the deal yet: {dkpSaveNote}
: null}
{CA.problems.length ?
{CA.problems.map((x, k) =>
⚠ {x.msg}
)}
Nothing is saved to the deal until the line is right; your entries stay here.
: null}
Front lowers the front gross and the commission the pay plan figures on it; Back lowers the F&I gross. Down payment assistance is the store's money toward the
down payment — the customer pays that much less cash and the deal still balances. A lender fee the store pays comes out of the funding; switched to passed to customer it is the
customer's fee line (in the amount financed) and not a cost. A we owe prints a due bill with the deal documents and goes on the We-Owes list when the deal posts. None of this ever shows on Present or a customer copy.
{ro ?
This deal is posted — its cost adds are on the books. To change them the office unwinds the deal (Deal Backend › Unwind deal), changes it and posts it again.
: null}
);
}
if (drawerTab === "products") return (
<>
F&I Products & Service Contracts
go && go("fiproducts")}>⚙ Manage Catalog
Option chips: GAP / Warranty = the ticked one of that kind · All = every ticked product (GAP + Warranty if none).
{catalog.length === 0 ? (
No active products — add them in the F&I Products catalog.
) : cats.map(cat => (
{cat}
{catalog.filter(p => p.category === cat).map(p => {
const on = selected[p.id] != null;
const sub = `${p.provider}${p.termMo ? " · " + p.termMo + "mo" : ""}`;
// Show gross: the cost is its own line under the customer price (a ticked row's price box left the subtitle no room for it)
return (
toggleProduct(p)} aria-label={p.name} />
toggleProduct(p)} title={`${p.name} — ${sub}${showGross ? " · cost " + dt$(p.cost) : ""}`}>{p.name} {sub}
{on
? setProductPrice(p.id, v)} width={96} style={dkcIn()} />
: {dt$(p.retail)} }
{showGross ? cost {dt$(p.cost)} : null}
);
})}
))}
{showGross && }
>
);
if (drawerTab === "fees") return isLease ? (
<>
Lease fees & tax
>
) : (
<>
How option {L1}'s amount financed is built
{hasTrade && }
{hasTrade && }
{rebate > 0 && }
Taxable = sale price + doc fee − trade allowance; the rebate is applied after tax. Each option uses its own down payment and products.
>
);
if (drawerTab === "lease") { const mq = LQs; return (
<>
{!gmfReady &&
⚠ GMF program data (LeaseRates.js) or the lease engine (LeaseCalc) did not load — lease figures show $0.00. Check the script tags in index.html.
}
{gmfOn ? (usedMode ? (G.usedProgram.program || "GMF Used Lease") : (G.program.program || "GMF Program")) : "Manual program"}
{gmfOn ? `Effective ${usedMode ? (G.usedProgram.effective || "—") : (G.program.effective || "—")}` : "Manual MF & residuals"}
{leaseCashEff > 0 ? <>Lease cash {dt$(leaseCashEff)} applied as rebate > : null}
{gmfOn && G.program.gapIncluded !== false ? <>🛡 GAP included in GMF lease — GAP product defaults off > : null}
{leaseTe ? <> Titled to: {dtEntityShort(leaseTe.name)}> : null}
toggleGmf(!gmfOn)} aria-pressed={gmfOn}
title={gmfOn ? "GMF program mode ON — MF / residuals autofill off the rate sheet. Click for manual MF & residuals." : "GMF program mode OFF — click to turn it on"}>
{gmfOn ? "✓ GMF Program" : "GMF Program: OFF"}
🖨 Worksheet
🖨 Disclosure
Mileage factor · retro adj 1,250–7,500 mi
@
Inception miles {inceptionAdjust == null
? {mq.inceptionMileageAdjust ? "AUTO ADJ ON" : "auto · no adj"}
: {inceptionAdjust ? "ADJ FORCED ON" : "adj forced off"} }>}
title="Odometer at signing — seeds from the desked unit; the retro adjustment auto-applies between 1,250 and 7,500 miles">
@
setInceptionAdjust(e.target.checked)} />
{inceptionAdjust != null && setInceptionAdjust(null)}>↺ Auto }
0 ? "− " : "") + dt$(mq.residualAdjustments)} color={mq.residualAdjustments > 0 ? "var(--jb-amber, #A66B00)" : null} />
Options — breakdown
{dkfNoFig ?
{noDeal ? "No deal open — deals start in the CRM: pick the lead → Desk It." : "Enter the sale price to see the lease figures."}
: visIdx.map(i => { const s = lsc[i] || {}; const q = leaseOf(i); const sel = i === selSc; const spDisabled = usedMode && (+s.term || 0) < 36;
return (
setSelSc(i)} title={"Present option " + DT_LETTERS[i]}>
{sel ? "★ " : ""}{DT_LETTERS[i]}
{s.term} mo · {s.sp ? "single-pay lease" : "monthly lease"}
{dkfBad[i] ?
⚠ BELOW $0 — LOWER THE DOWN
: q.upsideDown &&
⚠ UPSIDE DOWN }
setLscK(i, "aprIn", !s.aprIn)} title="Toggle money-factor vs APR entry (the payment square shows the one picked here)">{s.aprIn ? "APR⇄" : "MF⇄"}
!spDisabled && toggleSp(i)}
title={spDisabled ? "GMF used-lease single-pay is 36-48 months only" : "One up-front payment — GMF band MF reduction, A+/A1 pricing"}>💵 Single-Pay
eff MF {q.effectiveMoneyFactor.toFixed(5)} ≈ {q.aprEquivalent.toFixed(2)}% APR{tierAddOf(i) ? ` (tier +${tierAddOf(i).toFixed(5)})` : ""}{s.sp ? ` (1-pay −${q.singlePayMfReduction.toFixed(5)})` : ""}
0 ? " − mi adj" : ""})`} value={dt$(q.residual)} />
{s.sp
?
: }
{dkfBad[i] ? dkfBadMsg(i) : s.sp ? "1 payment at inception" : `${q.remainingPayments} payments of ${dt$(q.payment)} — first at inception`}
setLeaseExpand(leaseExpand === i ? null : i)}>
{leaseExpand === i ? "▾ Hide line items" : "▸ Line items"}
{leaseExpand === i && (
{lsAdds(i) > 0 &&
}
{acqFeeCapped && acqFee > 0 &&
}
{docFeeCapped && docFee > 0 &&
}
{titleLicCapped && titleLicFee > 0 &&
}
{q.negativeEquity > 0 &&
}
{q.capReductionCash > 0 &&
}
{(rebate + leaseCashEff > 0) &&
}
{q.positiveEquity > 0 &&
}
{q.residualAdjustments > 0 && (
<>
Residual
{q.extraMileageCost > 0 &&
}
{q.inceptionMileageAdjustment > 0 &&
}
>
)}
Due at signing
{s.sp
?
:
}
{q.dueAtSigningItems.capReductionCash > 0 &&
}
{q.dueAtSigningItems.acqFee > 0 &&
}
{q.dueAtSigningItems.docFee > 0 &&
}
{q.dueAtSigningItems.titleLicense > 0 &&
}
{q.dueAtSigningItems.securityDeposit > 0 &&
}
{q.dueAtSigningItems.upfrontTax > 0 &&
}
{q.dueAtSigningItems.capReductionTax > 0 &&
}
{q.dueAtSigningItems.taxOnUpfrontFees > 0 &&
}
)}
); })}
>
); }
return (
<>
Customer profile
setDti(e.target.value)} aria-label="DTI" style={{ width: 70, textAlign: "right", fontSize: 13, height: "var(--dkc-in-h)", padding: "0 6px" }} />
20 ? "var(--jb-amber)" : null} />
= 730 ? "A+ · Tier 1" : fico >= 690 ? "A · Tier 1" : fico >= 640 ? "B · Tier 2" : fico >= 580 ? "C · Tier 3" : "D · Tier 4"} color="var(--jb-blue)" />
🧾 Credit App ▸
>
);
})();
const drawerEl = !drawerOpen ? null : (
{drawerTabs.map(([k, lab]) => (
{ setDkcTab(k); dkcStore("dkc_drawer_tab", k); }}>{lab}
))}
✕ Close
{drawerLeft && Trade & Down are behind this panel — Close brings them back.
}
{drawerBody}
);
const why = noDeal ? "No deal open — deals start in the CRM: pick the lead → Desk It." : dkfNoFig ? "Enter the sale price first — nothing to push yet." : "";
const whyFin = why || (dkfBadSel ? dkfBadMsg(selSc) : "");
// footer read-out: the chosen payment, total down and amount financed (click = jump to the box)
const sumEl = (
Option {L1} ★ {heroSp ? "one-pay" : "payment"} {dkfNoFig || dkfBadSel ? "—" : <>{dt$(dkfHeroAmt)}{heroSp ? "" : "/mo"}>}
{heroSp ? "Total down · 1-pay" : "Total down"} {dkfFig(dt$(dkfTotalDown))}
{isLease ? "Adj. cap cost" : "Amount financed"} {dkfSelFig(dt$(dkfAmt))}
);
return (
= 3 ? " dkf-fit-rows" : "") + (dkfFit >= 5 ? " dkf-fit-hero" : "")} data-fit={dkfFit} data-dens={dkcDens} data-lease={isLease ? "1" : "0"} data-present={P ? "1" : "0"} data-gross={showGross ? "1" : "0"}
style={{ width: "100%", height: 800, margin: "0 auto" }}>
{/* ── header: who · what · save / print / gross / customer view / details ── */}
{P ?
👁 Customer view : null}
{ if (!P) setPicker("cust"); }} title={P ? "" : "Change customer" + (cust.dealId ? " — deal " + cust.dealId : "")}>
👤 {cust.name || "Pick customer…"}{cust.dealId && !P && !dkcNarrow ? ` · ${cust.dealId}` : ""} {P ? null : " ▾"}
{!P && (
FICO
)}
{ if (!P) setPicker("unit"); }} title={P ? "" : "Change unit"} style={{ flexShrink: 3, minWidth: 120 }}>
🚗 {unit.stock ? `${unit.yr} ${dkcWide ? unit.mk + " " : ""}${unit.mdl} · Stk ${unit.stock}` : "Pick unit…"} {P ? null : " ▾"}
{!P && unit.vin && !dkcNarrow &&
}
{!P &&
💾 {dkcWide ? "Save Scenario" : "Save"} }
{!P &&
setScenModal("list")} title="Reopen or delete saved desking scenarios">📂 Scenarios }
{!P && (
{ setDkcMenu(null); setPrintOpen(o => !o); }}>🖨 Print ▾
setPrintOpen(false)} className="dkc-printmenu"
style={{ right: 0, top: "calc(100% + 6px)", gridTemplateColumns: dkcBox.w >= 900 ? "repeat(3, 224px)" : "repeat(2, 224px)" }}>
{printItems.map(([label, fn], i) => (
{ setPrintOpen(false); fn(); }}>🖨 {label}
))}
)}
{!P &&
{showGross ? "💲 Hide gross" : "💲 Show gross"} }
{P ? "✕ Exit customer view" : "👁 Present"}
{!P &&
toggleDrawer()}
title="Details panel — products, fees & tax, lease detail, credit (Alt+M)">▤ Details }
{/* unit-readiness alert — one line on a big screen; on smaller screens a red pill in the Price square (no height taken) */}
{!unitCheck.clean && !P && !dkfAlertPill && (
"• " + it.label).join("\n")}>
⚠ UNIT NOT CLEAN — Stk {unit.stock} has {unitCheck.items.length} open item{unitCheck.items.length !== 1 ? "s" : ""}:
{unitCheck.items.map((it, i) => {i ? " · " : ""}{it.label} )}
go && go("servicedept")}>Service Dept ▸
go && go("partsdept")}>Parts Dept ▸
)}
{/* ── the sheet (+ docked drawer) ── */}
{priceSq}
{drawerLeft ? null : tradeSq}
{drawerLeft ? null : downSq}
{paySq}
{drawerLeft ? drawerEl : null}
{drawerDock ? drawerEl : null}
{/* ── footer: close · more · status line · chosen payment / down / financed · PUSH (always on screen) ── */}
Close
{!P && (
{ setPrintOpen(false); setDkcMenu(m => m === "more" ? null : "more"); }}>⋯ More ▴
setDkcMenu(null)} style={{ left: -72, bottom: "calc(100% + 6px)", minWidth: 250 }}>
{moreItems.map((it, i) => it ? (
{ setDkcMenu(null); it[1](); }}>{it[0]}
) :
)}
)}
{noDeal && (
⚠ No deal open.
Deals start in the CRM — pick the lead → Desk It .{dkcWide ? " (Payments show once a deal is open.)" : ""}
go && go("crm")}>Open CRM ▸
)}
{dkfOffer && !P && (
{/* below ~1700 px of desk the scenario name stays in the tooltip so the status line beside it keeps room for its message */}
📂 Load saved desk{dkcBox.w >= 1700 ? <> “{dkfOffer.name || dkfOffer.id}” > : null}
setDkfOfferOff(true)}>✕
)}
{/* the customer view shows no message text (they can name other customers) — only a count, read after Exit */}
{P ? (() => { const held = dkcMsgs.filter(m => m.at >= dkfPresAtRef.current).length; return (
{held ? ● {held} : }
); })() : (
{ if (dkcMsgs.length) setDkcMenu(m => m === "log" ? null : "log"); }}
title={dkcMsgs.length ? "Messages — click for the last few" : ""}>
{dkcLast ? (() => { const [ic, col] = dkcKind(dkcLast.kind); return (
<>{ic} {dkcLast.msg.replace(/\n/g, " · ")} >); })()
: }
)}
{/* message log — opens over the Trade / Down side, never over the payment square */}
setDkcMenu(null)} className="dkc-log"
style={{ left: 10, bottom: "calc(100% + 6px)", boxSizing: "border-box", width: Math.max(280, Math.round(dkcSheetW * dkcSplit[0] / 100) - 20) }}>
{dkcMsgs.slice().reverse().map(m => { const [ic, col] = dkcKind(m.kind); return (
{ic} {m.msg} {new Date(m.at).toLocaleTimeString()}
); })}
{!P && (
{ setPrintOpen(false); setDkcMenu(m => m === "keys" ? null : "keys"); }}>⌨ Keys
)}
{/* the shortcut list opens over the Trade / Down side like the message log — never over the payment square */}
setDkcMenu(null)} className="dkc-keys"
style={{ left: 10, bottom: "calc(100% + 6px)", boxSizing: "border-box", width: Math.max(300, Math.round(dkcSheetW * dkcSplit[0] / 100) - 20) }}>
{dkfKeys.map(([k, t]) => {k} {t}
)}
{/* the read-out beside Push; a narrow no-deal calculator gives its room to the "No deal open → Open CRM" notice */}
{/* K2B-013: an empty desk has no figures to read out — the notice gets the room at every size */}
{noDeal && (dkcNarrow || dkfNoFig) ? null : sumEl}
{/* ONE PATH: the desk pushes THE deal to exactly one closing screen. */}
{!isLease && !P && (
pushDeal("cash")}>Push to Cash Deal →
)}
{!P &&
pushDeal("finance")}>{isLease ? "Push to Finance (Lease) →" : "Push to Finance →"} }
{dkcModOpen && dkcMod && window.K2BModSettingsPanel ? React.createElement(window.K2BModSettingsPanel, { module: dkcMod, onClose: () => setDkcModOpen(false) }) : null}
{/* ── Settings panel ── */}
{settingsOpen &&
setSettingsOpen(false)} go={go} />}
{/* ── Pickers ── */}
{picker === "unit" && setPicker(null)} go={go} exceptDeal={cust.dealId} />}
{gmfPickerOpen && setGmfPickerOpen(false)} />}
{picker === "cust" && (
setPicker(null)}
buttons={<>
{ setPicker(null); go && go("crm"); }}>Start in CRM ▸
setPicker(null)}>Cancel
>}>
{/* ONE PATH: only OPEN deals are pickable here. Deals (and customers) are created in the CRM (New Lead → Desk It) —
never from the desk. RC19: search box + chips over the list (DsDeskPickList, DealSearch.jsx.txt). */}
pickCust(dtCustProfile(d.buyer, d.id, { ...((custs.find(c => c.custNum === d.custNum)) || {}), ...d }))} />
)}
{/* ── Save Scenario modal ── */}
{scenModal === "save" && (
setScenModal(null)}
buttons={<>
setScenModal(null)}>Cancel
Save
>}>
)}
{/* ── Saved Scenarios list modal ── */}
{scenModal === "list" && (
setScenModal(null)}
buttons={ setScenModal(null)}>Close }>
{(() => {
const list = ((window.DMS && window.DMS.deskScenarios && window.DMS.deskScenarios()) || [])
.slice().sort((a, b) => String(b.savedAt || "").localeCompare(String(a.savedAt || "")));
if (!list.length) return No saved scenarios yet — work a deal, then hit 💾 Save Scenario.
;
return (
Scenario Saved by
When
{list.map((r, i) => (
{r.name || r.id}
{r.savedBy || "—"}
{r.savedAt ? new Date(r.savedAt).toLocaleString() : "—"}
{ restoreScenario(r.state); setScenModal(null); window.k2bToast && window.k2bToast(`Scenario “${r.name || r.id}” reopened.`, "ok"); }}>Open {" "}
{ if (window.confirm(`Delete scenario “${r.name || r.id}”? This removes it for every workstation.`)) { window.DMS && window.DMS.deleteDeskScenario && window.DMS.deleteDeskScenario(r.id); } }}>Delete
))}
);
})()}
)}
);
};
/* ── Desking Settings panel ── */
const DeskSettings = ({ settings, onSave, onClose, go }) => {
const [s, setS] = React.useState({ ...settings });
const set = (k, v) => setS(prev => ({ ...prev, [k]: v }));
return (
{ saveDeskSettings(DESK_DEFAULTS); setS({ ...DESK_DEFAULTS }); }}>Reset Defaults
Cancel
onSave(s)}>Save
>}>
Default Payment Scenarios
{[["A", "t1", "a1"], ["B", "t2", "a2"], ["C", "t3", "a3"]].map(([opt, tk, ak]) => (
{opt}
Term
set(tk, v)} width={50} />
APR
set(ak, v)} width={60} decimals={2} />
))}
Catalogs
{ onClose(); go && go("banks"); }}>🏦 Manage Banks / Lenders ▸
{ onClose(); go && go("fiproducts"); }}>🛡 Manage F&I Products ▸
{/* ── Lease defaults ── */}
🔑 Lease Defaults
set("leaseTaxOnPayment", e.target.checked)} style={{ width: 15, height: 15 }} />
Tax the monthly payment (MO method)
Default lease scenarios — term & money factor
{[["A", "lt1", "lmf1"], ["B", "lt2", "lmf2"], ["C", "lt3", "lmf3"]].map(([opt, tk, mk]) => (
{opt}
Term
set(tk, v)} width={50} />
MF
set(mk, v)} width={70} decimals={5} />
≈{dtMfToApr(s[mk]).toFixed(2)}%
))}
Residuals auto-derive from term + mileage (GM closed-end table) and can be overridden per option on the desk.
);
};
const DkSetFld = ({ label, k, dec = 2, money, s, set }) => (
{label}
{money
? set(k, v)} width={110} style={{ textAlign: "right" }} />
: set(k, v)} width={80} decimals={dec} style={{ textAlign: "right" }} />}
);
/* ── SALES VIEW-ONLY DESK (route "deskview") ──────────────────────────────
A salesperson may LOOK at the latest saved desk of THEIR OWN deal, the way
a customer would see it: vehicle, price, trade allowance / payoff, down,
every option's term / rate / down / payment, the chosen products at their
customer prices, and the lender. Nothing else is ever rendered — no
invoice, gross, cost, ACV, pack or commission, no inputs, no Push, no Save,
no dealer prints (a customer copy may be printed).
Source: the customer-safe snapshot the desk stores with every Save Scenario
(scenario.view) and every Push (deal.deskView) — computed figures only.
It is read from GET /api/desk-view/
when the server has that route
(it returns the snapshot alone); otherwise from what this session already
holds (own deal / saved scenarios). Either way it passes dkfSafeView, a
whitelist copy, before anything is shown. */
const DKF_VIEW_PTR = "dkf_view_deal";
const dkfNum = (n) => (typeof n === "number" && isFinite(n)) ? n : (n != null && n !== "" && isFinite(+n) ? +n : null);
const dkfStr = (s) => (s == null ? "" : String(s)).slice(0, 200);
// Whitelist copy of a stored snapshot — unknown keys are dropped.
function dkfSafeView(v) {
if (!v || typeof v !== "object") return null;
const opt = (o) => ({ letter: dkfStr(o && o.letter).slice(0, 1), term: dkfNum(o.term) || 0, apr: dkfNum(o.apr), mf: dkfNum(o.mf), aprEq: dkfNum(o.aprEq),
singlePay: !!o.singlePay, residPct: dkfNum(o.residPct), residual: dkfNum(o.residual), down: dkfNum(o.down), prodLabel: dkfStr(o.prodLabel), prodTotal: dkfNum(o.prodTotal) || 0,
products: Array.isArray(o.products) ? o.products.slice(0, 12).map(p => ({ name: dkfStr(p && p.name), price: dkfNum(p && p.price) || 0 })) : [],
amount: dkfNum(o.amount), dueAtSigning: dkfNum(o.dueAtSigning), payment: dkfNum(o.payment) || 0, remaining: dkfNum(o.remaining), totalOfPayments: dkfNum(o.totalOfPayments), totalDown: dkfNum(o.totalDown) });
const u = v.unit || {}, t = v.trade;
const options = (Array.isArray(v.options) ? v.options : []).filter(o => o && typeof o === "object").slice(0, 4).map(opt).filter(o => /^[A-D]$/.test(o.letter));
if (!options.length) return null;
return {
savedAt: dkfStr(v.savedAt), dealId: dkfStr(v.dealId), customer: dkfStr(v.customer),
unit: { yr: dkfStr(u.yr), mk: dkfStr(u.mk), mdl: dkfStr(u.mdl), stock: dkfStr(u.stock), vin: dkfStr(u.vin), newUsed: dkfStr(u.newUsed), miles: dkfNum(u.miles) || 0 },
mode: v.mode === "lease" ? "lease" : "finance", lender: dkfStr(v.lender), titledTo: dkfStr(v.titledTo),
salePrice: dkfNum(v.salePrice) || 0, docFee: dkfNum(v.docFee) || 0, taxRate: dkfNum(v.taxRate) || 0, salesTax: dkfNum(v.salesTax) || 0, msrp: dkfNum(v.msrp) || 0,
leaseTaxRate: dkfNum(v.leaseTaxRate), milesPerYr: dkfNum(v.milesPerYr), taxMethod: dkfStr(v.taxMethod),
trade: t && typeof t === "object" ? { desc: dkfStr(t.desc), allowance: dkfNum(t.allowance) || 0, payoff: dkfNum(t.payoff) || 0, lender: dkfStr(t.lender),
equity: dkfNum(t.equity) || 0, negEquity: dkfNum(t.negEquity) || 0, posEquity: dkfNum(t.posEquity) || 0 } : null,
rebate: dkfNum(v.rebate) || 0, leaseCash: dkfNum(v.leaseCash) || 0, capRed: dkfNum(v.capRed) || 0, usedUnit: !!v.usedUnit,
sel: options.some(o => o.letter === v.sel) ? v.sel : options[0].letter, options,
products: Array.isArray(v.products) ? v.products.slice(0, 20).map(p => ({ name: dkfStr(p && p.name), price: dkfNum(p && p.price) || 0 })) : [],
pushedAs: v.pushedAs === "cash" || v.pushedAs === "finance" ? v.pushedAs : null, cashDue: dkfNum(v.cashDue),
downAssist: dkfNum(v.downAssist) || 0, lenderFeeCust: dkfNum(v.lenderFeeCust) || 0, // RC27 (dsk): customer-facing only (twin of routes/desk-view.js)
};
}
const dkfMe = () => { try { return String((window.DMS && window.DMS.userId && window.DMS.userId()) || ""); } catch (e) { return ""; } };
const dkfIsSales = () => { try { return !!(window.DMS && window.DMS.roleIs && window.DMS.roleIs("Sales")); } catch (e) { return false; } };
const dkfCanDesk = () => { try { return !!(window.DMS && window.DMS.can && window.DMS.can("desk")); } catch (e) { return false; } };
// Is this deal the signed-in salesperson's own? (their lead points at it, or the deal names them)
function dkfOwnDeal(dealId) {
try {
const me = dkfMe(); if (!me || !dealId || !window.DMS) return false;
const leads = (window.DMS.crmLeads && window.DMS.crmLeads()) || [];
if (leads.some(l => l && String(l.dealId || "") === String(dealId) && String(l._owner || "") === me)) return true;
const d = ((window.DMS.deals && window.DMS.deals()) || []).find(x => x && x.id === dealId);
if (!d || d._limited) return false;
if (String(d.spId || "") === me) return true;
const mine = ((window.DMS.roster && window.DMS.roster()) || []).find(u => u && String(u.id) === me);
const sp = String(d.salesperson || "").split("/")[0].trim().toLowerCase();
return !!(mine && mine.name && sp && sp === String(mine.name).trim().toLowerCase());
} catch (e) { return false; }
}
// Newest snapshot this session already holds for the deal (own deal's push snapshot, saved scenarios).
function dkfLocalView(dealId) {
const cands = [];
try {
const d = ((window.DMS && window.DMS.deals && window.DMS.deals()) || []).find(x => x && x.id === dealId);
if (d && !d._limited && d.deskView && typeof d.deskView === "object") cands.push(d.deskView);
((window.DMS && window.DMS.deskScenarios && window.DMS.deskScenarios()) || []).forEach(s => {
if (s && s.view && typeof s.view === "object" && String(s.view.dealId || "") === String(dealId)) cands.push(s.view);
});
} catch (e) {}
cands.sort((a, b) => String(b.savedAt || "").localeCompare(String(a.savedAt || "")));
for (const c of cands) { const v = dkfSafeView(c); if (v) return v; }
return null;
}
// Server first (GET /api/desk-view/ returns the snapshot only), then local.
async function dkfLoadView(dealId) {
let tok = null;
try { tok = sessionStorage.getItem("jbrews-auth-token") || localStorage.getItem("jbrews-auth-token"); } catch (e) {}
if (tok) {
try {
const res = await fetch(location.origin + "/api/desk-view/" + encodeURIComponent(dealId), { headers: Object.assign({ Authorization: "Bearer " + tok }, window.K2BStore ? window.K2BStore.stamp({}) : {}), credentials: "omit" }); // RC35 (brd): + the page's store / protocol
let j = null; try { j = await res.json(); } catch (e) {}
if (res.ok && j && j.ok) return { view: dkfSafeView(j.view), source: "server" };
if (j && j.error === "not_yours") return { view: null, source: "server", notYours: true };
if (res.status === 404 && j && j.error === "no_desk") return { view: null, source: "server" };
// any other answer = this server has no desk-view route yet → local copy
} catch (e) {}
}
return { view: dkfLocalView(dealId), source: "local" };
}
// Customer copy print (the customer presentation's content, from the snapshot).
function dkfPrintCustomerCopy(v) {
if (!v || !window.k2bPrint) return;
const r = (n) => "$" + Number(n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const esc = (s) => String(s == null ? "" : s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
const lease = v.mode === "lease";
const heads = v.options.map(o => o.letter), selPos = Math.max(0, heads.indexOf(v.sel));
const sel = v.options[selPos];
const rows = lease ? [
["Down payment (cap reduction cash)", v.options.map(o => o.singlePay ? "n/a (1-pay)" : r(o.down))],
["Term", v.options.map(o => `${o.term} mo${o.singlePay ? " · 1-PAY" : ""}`)],
["Money factor", v.options.map(o => o.mf != null ? o.mf.toFixed(5) : "—")],
["Residual", v.options.map(o => `${(o.residPct || 0).toFixed(1)}% / ${r(o.residual)}`)],
["Protection products", v.options.map(o => esc(o.prodLabel || "—"))],
["Adjusted cap cost", v.options.map(o => r(o.amount))],
["Due at signing", v.options.map(o => r(o.dueAtSigning))],
["Payment", v.options.map(o => o.singlePay ? r(o.payment) + " once" : r(o.payment) + "/mo"), true],
] : [
["Down payment", v.options.map(o => r(o.down))],
["Term", v.options.map(o => `${o.term} mo`)],
["APR", v.options.map(o => `${(o.apr || 0).toFixed(2)}%`)],
["Protection products", v.options.map(o => esc(o.prodLabel || "—"))],
["Amount financed", v.options.map(o => r(o.amount))],
["Monthly payment", v.options.map(o => r(o.payment)), true],
];
const t = v.trade;
const prods = (sel && sel.products) || [];
const html = `${window.dealerHeadHtml ? window.dealerHeadHtml() : ""}
${lease ? "LEASE PAYMENT OPTIONS" : "CUSTOMER PRESENTATION"}
Prepared for ${esc(v.customer)} · ${esc(new Date().toLocaleDateString())} · customer copy
Vehicle
${esc([v.unit.yr, v.unit.mk, v.unit.mdl].filter(Boolean).join(" "))}${v.unit.newUsed ? (v.unit.newUsed === "U" ? " (Used)" : " (New)") : ""} Stock ${esc(v.unit.stock)}
MSRP ${r(v.msrp)}
Sale Price ${r(v.salePrice)}
${t ? `Trade
${esc(t.desc)}
Allowance ${r(t.allowance)}
Payoff${t.lender ? ` (${esc(t.lender)})` : ""} ${r(t.payoff)}
${t.negEquity > 0 ? `Negative Equity (financed) ${r(t.negEquity)} ` : `Net Equity ${r(t.equity)} `}
` : ""}
${prods.length ? `Protection Products — Option ${esc(v.sel)} ${prods.map(p => `${esc(p.name)} ${r(p.price)} `).join("")}
Total Products — Option ${esc(v.sel)} ${r(sel.prodTotal)}
` : ""}
Payment Options — ${esc(v.lender)}
${dtOptGridHtml(heads, selPos, rows)}
★ Option ${esc(v.sel)} selected. Payments are estimates; final figures are subject to ${lease ? "lessor" : "lender"} approval and verification of stated information.
Customer Acknowledgement · Date
Salesperson · Date
`;
window.k2bPrint(html, { title: lease ? "Lease Payment Options (customer copy)" : "Customer Presentation (customer copy)" });
}
// CRM hook: "View desk" for the salesperson's own lead (rendered by CRM next to
// "Ask a manager to desk this lead"). Sales only — managers have Desk It.
const DkfViewDeskButton = ({ lead, go }) => {
if (!lead || !lead.dealId || !dkfIsSales()) return null;
const open = () => {
const ptr = { dealId: String(lead.dealId), leadId: lead.id || null, by: dkfMe() }; // tied to who opened it
window.dkfViewDeal = ptr;
try { sessionStorage.setItem(DKF_VIEW_PTR, JSON.stringify(ptr)); } catch (e) {}
go && go("deskview");
};
return (
👁 View desk
);
};
// Role gate at render time. The shell bounces a screen the role may not open
// only after that screen's first render, so a deep link (?popout=desking) would
// mount — and paint — the full desk for one frame for a Sales user. The desk
// never mounts for a role the shell would not show it to.
// RC19 — the "Pick Customer / Deal" list with the shared deal search (DealSearch.jsx.txt). The list is the same as before
// (open deals: not posted, not dead, in the store's order); the box + chips only narrow it. Chips: My deals / All, stage.
const DS_DESK_STAGE_TONE = { Desked: "var(--jb-amber)", Cash: "var(--jb-green)", "F&I": "var(--jb-blue)", Posted: "var(--jb-navy)", Funded: "var(--jb-green)" };
const dsDeskStageOf = (d) => (window.DMS && window.DMS.dealStage) ? window.DMS.dealStage(d) : (d.stage || "Desked");
const DS_DESK_CHIPS = [
{ key: "mine", group: "who", label: "My deals", title: "Deals you sold (you are the salesperson, or your CRM lead)", test: (d) => dsIsMine(d) },
{ key: "desked", group: "stage", label: "Desked", test: (d) => dsDeskStageOf(d) === "Desked" },
{ key: "cash", group: "stage", label: "Cash", test: (d) => dsDeskStageOf(d) === "Cash" },
{ key: "fi", group: "stage", label: "F&I", test: (d) => dsDeskStageOf(d) === "F&I" },
];
const DsDeskPickList = ({ onPick }) => {
const [, dsForce] = React.useReducer(n => n + 1, 0);
React.useEffect(() => (window.DMS && window.DMS.subscribe) ? window.DMS.subscribe(dsForce) : undefined, []);
const deals = ((window.DMS && window.DMS.deals && window.DMS.deals()) || []).filter(d => !d.postedToGL && d.status !== "Dead");
const custs = Object.values((window.DMS && window.DMS.customers && window.DMS.customers()) || {});
const pick = (d) => onPick && onPick(d, custs);
const ds = dsUseDealSearch({ deals, chips: DS_DESK_CHIPS, onOpen: pick });
if (!deals.length) return (
No open deals. Deals start in the CRM : pick the lead → Desk It .
);
return (
Open deals — pick one to desk
{ds.list.map((d, i) => {
const act = d.id === ds.activeId; // the row Enter picks (none in an empty box until ↑ ↓)
return (
pick(d)}
style={{ background: act ? "var(--jb-blue-tint, #E5EFFA)" : (i % 2 ? "var(--jb-surface-2)" : "#fff"), cursor: "pointer",
outline: act ? "2px solid var(--jb-blue, #1B66A8)" : "none", outlineOffset: -2 }}>
{d.id}
{d.buyer}
{d.vehicle || "—"}
{dsDeskStageOf(d)}
);
})}
);
};
const DeskingTool = (props) => (window.jbNavAllowed && !window.jbNavAllowed("desking")) ? null : ;
const DkfDeskView = ({ onClose, go }) => {
const rootRef = React.useRef(null);
const box = dkfUseBox(rootRef);
const dens = dkfDensOf(box);
const narrow = box.w < 640; // phone-width window: squares stacked, option grid scrolls sideways, Print in the footer (CSS .dkf-narrow)
const ptr = React.useMemo(() => {
let p = null;
if (window.dkfViewDeal && window.dkfViewDeal.dealId) p = window.dkfViewDeal;
else { try { p = JSON.parse(sessionStorage.getItem(DKF_VIEW_PTR) || "null"); } catch (e) {} }
try { delete window.dkfViewDeal; } catch (e) {} // the session copy is enough
return p && p.by && p.by !== dkfMe() ? null : p; // a pointer left by someone else is ignored
}, []);
const dealId = ptr && ptr.dealId ? String(ptr.dealId) : "";
// Desk roles may look at any deal's customer view; everyone else (Sales, and
// any other role the route lets in) only at a deal that is their own.
const allowed = !!dealId && (dkfCanDesk() || dkfOwnDeal(dealId));
const [st, setSt] = React.useState({ loading: !!dealId && allowed, view: null, notYours: false, source: "" });
const [selL, setSelL] = React.useState(null);
// Same quiet screen as the desk (mascot hidden, toasts in the status line —
// see msgs below), but the app menu stays open (dkf-view-on: no fold).
const { msgs, last: msgLast, fresh: msgFresh } = dkfUseMsgs();
const [logOpen, setLogOpen] = React.useState(false);
React.useEffect(() => {
const B = document.body; B.classList.add("dkc-desk-on"); B.classList.add("dkf-view-on");
return () => { B.classList.remove("dkc-desk-on"); B.classList.remove("dkf-view-on"); };
}, []);
React.useEffect(() => {
if (!dealId || !allowed) return undefined;
let live = true;
dkfLoadView(dealId).then((r) => { if (live) setSt({ loading: false, view: r.view, notYours: !!r.notYours, source: r.source }); });
return () => { live = false; };
}, [dealId, allowed]);
const v = st.view;
const sel = v ? (v.options.find(o => o.letter === (selL || v.sel)) || v.options[0]) : null;
const lease = v && v.mode === "lease";
// phone width: keep the ★ option's column in view inside the sideways-scrolling grid (the grid alone scrolls - never the body)
React.useEffect(() => {
if (!narrow || !v) return;
try {
const root = rootRef.current; const gw = root && root.querySelector(".dkc-gridwrap"), c = root && root.querySelector(".dkc-col.opt.on"), lab = root && root.querySelector(".dkc-col.lab");
if (!gw || !c) return;
const gr = gw.getBoundingClientRect(), cr = c.getBoundingClientRect(), labW = lab ? lab.getBoundingClientRect().width : 0;
if (cr.right > gr.right) gw.scrollLeft += Math.ceil(cr.right - gr.right) + 4;
else if (cr.left < gr.left + labW) gw.scrollLeft -= Math.ceil(gr.left + labW - cr.left) + 4;
} catch (e) {}
}, [narrow, v, sel && sel.letter]);
const backCrm = () => { if (onClose) onClose(); else if (go) go("crm"); };
const msg = (title, text) => (
{title} {text}
‹ Back to the CRM
);
let bodyEl = null;
if (!dealId) bodyEl = msg("No deal picked", "Open the CRM, pick your lead and press View desk.");
else if (!allowed || st.notYours) bodyEl = msg("Not your deal", "Only the salesperson on this deal can view its desk. Ask a manager.");
else if (st.loading) bodyEl = Loading the saved desk…
;
else if (!v) bodyEl = msg("No saved desk for this deal yet", "The figures show here after a manager saves the desk (Save Scenario) or pushes the deal. Ask a manager to desk it.");
if (!bodyEl) {
const t = v.trade;
const u = v.unit;
const vehicle = [u.yr, u.mk, u.mdl].filter(Boolean).join(" ");
const F = (k, label, value, opts) => React.createElement(DkcStat, Object.assign({ k, label, value }, opts || {}));
const rows = [
["h", ""], ["term", lease ? "Term" : "Term (mo)"], ["rate", lease ? "Money factor" : "APR %"],
...(lease ? [["resid", "Residual"]] : []), ["down", "Down $"], ["prod", "Products"],
["amt", lease ? "Adj. cap cost" : "Amt financed"], ...(lease ? [["due", "Due at signing"]] : []), ["pmt", "Payment"],
];
const cell = (k, o) => {
const on = o.letter === sel.letter;
switch (k) {
case "h": return { e.stopPropagation(); setSelL(o.letter); }}
title={"Show option " + o.letter + " in the big payment box (nothing is saved)"}>{on ? "★ " : ""}{o.letter}{o.singlePay ? 1-pay : null}
;
case "term": return {o.term} mo{o.singlePay ? " · 1-PAY" : ""}
;
case "rate": return {lease ? (o.mf != null ? o.mf.toFixed(5) : "—") : (o.apr || 0).toFixed(2) + "%"}
;
case "resid": return {(o.residPct || 0).toFixed(1)}% · {dt$(o.residual)}
;
case "down": return {o.singlePay ? "n/a (1-pay)" : dt$(o.down)}
;
case "prod": return {o.prodLabel || "—"}
;
case "amt": return {dt$(o.amount)}
;
case "due": return {dt$(o.dueAtSigning)}
;
case "pmt": return {o.singlePay ? once : null}{dt$(o.payment)}
;
default: return
;
}
};
const tradeSq = (
{!t ? No trade-in on this deal
: (
{t.desc || "Trade-in"}
{F("tradeAllow", "Allowance", dt$(t.allowance))}
{F("tradePayoff", "Pay-off", dt$(t.payoff))}
{F("tradeLender", "Payoff lender", t.lender || "—", { wide: true })}
{F("tradeEquity", t.negEquity > 0 ? "Negative equity" : "Net Equity", dt$(t.equity), { color: t.equity < 0 ? "var(--jb-red)" : "var(--jb-green)", strong: true })}
{t.negEquity > 0 &&
Payoff is over the allowance — {dt$(t.negEquity)} is rolled into the {lease ? "lease" : "loan"} on every option.
}
)}
);
const priceSq = (
{F("salePrice", "Sale Price", dt$(v.salePrice))}
{F("docFee", "Doc Fee", dt$(v.docFee))}
{F("taxRate", "Tax Rate %", (v.taxRate || 0).toFixed(3) + "%")}
{F("salesTax", "Sales Tax", dt$(v.salesTax))}
{lease && v.leaseTaxRate != null && F("leaseTaxRate", "Lease tax %", v.leaseTaxRate.toFixed(3) + "%")}
{F("msrp", "MSRP", dt$(v.msrp))}
);
// Total down: a single-pay lease takes no cash as cap reduction (its Down reads "n/a (1-pay)").
const tdOf = (o) => (lease && o.singlePay) ? (v.rebate || 0) + (t ? t.posEquity || 0 : 0) : o.totalDown;
const downSq = (
{F("down", `Down (option ${sel.letter})`, sel.singlePay ? "n/a (1-pay)" : dt$(sel.down))}
{v.usedUnit ?
Rebates apply to new units only —
: F("rebate", "Rebate", dt$(v.rebate))}
{lease && v.capRed > 0 && F("capRed", "Add'l cap red.", dt$(v.capRed))}
{lease && v.leaseCash > 0 && F("leaseCash", "GMF lease cash (as rebate)", dt$(v.leaseCash), { color: "var(--jb-green)" })}
{t && F("tradeEqDown", "Trade Equity (down)", dt$(t.posEquity))}
{v.downAssist > 0 && F("dpa", "Down payment assistance", "− " + dt$(v.downAssist), { color: "var(--jb-green)" })}
{v.downAssist > 0 && !sel.singlePay && F("custCash", lease ? "Customer cash at signing" : "Customer cash down", dt$(Math.max(0, (sel.down || 0) - v.downAssist)), { strong: true })}
{v.lenderFeeCust > 0 && F("lenderFeeCust", "Lender fee (customer pays)", dt$(v.lenderFeeCust))}
{t && t.negEquity > 0 && F("negEq", "Neg. equity → " + (lease ? "cap cost" : "loan"), dt$(t.negEquity), { color: "var(--jb-red)" })}
{F("totalDown", lease && sel.singlePay ? "Total Down (1-pay: no cash)" : "Total Down", dt$(tdOf(sel)), { color: "var(--jb-blue)", big: true, rule: true, wide: true, strong: true })}
{F("amtFinanced", lease ? "Adj. Cap Cost" : "Amount Financed", dt$(sel.amount), { color: "var(--jb-navy)", big: true, wide: true, strong: true })}
{sel.products.length ? (
Protection products — option {sel.letter}
{sel.products.map((p, i) => )}
) : null}
);
const paySq = (
4 Payment
{lease ? "Lease" : "Finance"} · {v.lender || "—"}
{v.pushedAs === "cash" ? (
Cash deal — balance due at delivery
{dt$(v.cashDue)}
The deal was pushed as a cash deal. The desk's payment options are shown below for reference.
) : (
{lease ? (sel.singlePay ? "Single-Pay Lease" : "Lease") : "Finance"} Payment — Option {sel.letter} ★
{dt$(sel.payment)}{sel.singlePay ? " one-pay" : "/mo"}
{sel.term} months {lease ? <>· MF {sel.mf != null ? sel.mf.toFixed(5) : "—"}{sel.aprEq != null ? ` (${sel.aprEq.toFixed(2)}% APR equiv)` : ""} · Due at signing {dt$(sel.dueAtSigning)}> : <>@ {(sel.apr || 0).toFixed(2)}% · {v.lender}>}
{lease &&
{sel.singlePay ? "1 payment at inception" : `${sel.remaining || 0} payments of ${dt$(sel.payment)} — first paid at inception`} · Total of payments {dt$(sel.totalOfPayments)}{v.milesPerYr ? ` · ${(+v.milesPerYr).toLocaleString()} mi/yr` : ""}{v.titledTo ? ` · Titled to ${v.titledTo}` : ""}
}
)}
{rows.map(([k, lab]) =>
{lab}
)}
{v.options.map(o => (
setSelL(o.letter)}>
{rows.map(([k]) => cell(k, o))}
))}
);
bodyEl = (
{priceSq}{tradeSq}{downSq}{paySq}
);
}
const savedWhen = v && v.savedAt ? (() => { try { return new Date(v.savedAt).toLocaleString(); } catch (e) { return ""; } })() : "";
// the customer copy print: in the header on a desk-size window, beside Close in the footer on a phone (where the header wraps)
const printBtn = v ? (
dkfPrintCustomerCopy(Object.assign({}, v, { sel: sel.letter }))}
title="Print a customer copy of these options">🖨 Print customer copy
) : null;
return (
👁 View only
{v ?
👤 {v.customer || "Customer"} : null}
{v ?
🚗 {[v.unit.yr, v.unit.mk, v.unit.mdl].filter(Boolean).join(" ")}{v.unit.stock ? " · Stk " + v.unit.stock : ""} : null}
{v && v.pushedAs ?
{v.pushedAs === "cash" ? "Pushed to Cash Deal" : "Pushed to F&I"} : null}
{narrow ? null : printBtn}
{bodyEl}
Close
{narrow ? printBtn : null}
{/* status line: messages (bell alerts, sync warnings …) show here for a few seconds, then the view-only note; click for the last few */}
{ if (msgs.length) setLogOpen(o => !o); }} title={msgs.length ? "Messages — click for the last few" : ""}>
{msgFresh ? (() => { const [ic, col] = dkcKind(msgLast.kind); return (
<>{ic} {msgLast.msg.replace(/\n/g, " · ")} >); })()
: {v ? `View only — the desk as a manager saved it${savedWhen ? " on " + savedWhen : ""}${dealId ? " · " + dealId : ""}. Changes are made on the desk by a manager.` : ""} }
0} onClose={() => setLogOpen(false)} className="dkc-log" style={{ left: 10, bottom: "calc(100% + 6px)", boxSizing: "border-box", width: 420, maxWidth: "calc(100% - 20px)" }}>
{msgs.slice().reverse().map(m => { const [ic, col] = dkcKind(m.kind); return (
{ic} {m.msg} {new Date(m.at).toLocaleTimeString()}
); })}
{v && sel ? (
Option {sel.letter} ★ {sel.singlePay ? "one-pay" : "payment"} {dt$(sel.payment)}{sel.singlePay ? "" : "/mo"}
{lease && sel.singlePay ? "Total down · 1-pay" : "Total down"} {dt$((lease && sel.singlePay) ? (v.rebate || 0) + (v.trade ? v.trade.posEquity || 0 : 0) : sel.totalDown)}
{lease ? "Adj. cap cost" : "Amount financed"} {dt$(sel.amount)}
) : null}
);
};
Object.assign(window, { DeskingTool, DkfDeskView, DkfViewDeskButton });