/* 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. */ // JBREWS DMS — Finance Deal (FW). // Slate-blue dense monospace form. Fully wired to the DMS ledger: // F2 loads a deal from DMS.deals(), F5 recalculates amount financed + // payment, F9 patches the deal with the worksheet figures and posts it to // accounting through window.jbPostDeal (ONE PATH). Fields persist as you type. // This screen never creates a deal: CRM → Desk It → Push to Finance → here. // // Protection products: the desk pushes the chosen option's products as plan // rows (plan1..plan5 {Code, Name, Price, Cost, Term, Id}) + GAP (gapIns / // gapCode / gapName / gapCost / gapId) + `products` (JSON). `warranties` is a // COMPUTED total of the plan prices (kept under that name so the GL cascade — // vscSale / gapSale — is unchanged). "Details" / "+ Add product" open a real // catalog picker (window.jbProducts). Save / Post write `products` (rebuilt // from the rows) + vscSale / vscCost / gapSale / gapCost / fiGross to the deal. const FW_HELP = `Finance Deal (FW)\n\nESC Finished\nF1 Help\nF2 Lookup deal (open deals first)\nF3 CRM (deals start there: + New Lead → Desk It)\nF4 Insurance products\nF5 Recalculate amount financed & payment\nF6 Print\nF7 Forms\nF8 Credit App\nF9 Post to Accounting\nF10 Save (fields auto-persist as you type)`; // ONE PATH: this screen never creates a deal — it finalizes the one the desk pushed. const FW_NO_DEAL = "No deal loaded. Deals start in the CRM → Desk It → Push to Finance."; // Real editable input fields (replaces old span-based readouts). // Persists to formId="financeWorksheet" for full save/restore. const FW_FORM = "financeWorksheet"; const FW_MAX_PLANS = 5; const NF = ({ field, value, width = 110, hi, sel, align = "right", readOnly, style, onValueChange, placeholder }) => ( ); const TF = ({ field, value, width = 100, hi, sel, uppercase = true, mono, style, placeholder, onValueChange }) => ( ); const fwNum = (x) => { const n = parseFloat(String(x ?? "").replace(/[^0-9.\-]/g, "")); return isNaN(n) ? 0 : n; }; // K2B-005: an explicit 0 (0% APR) is a value — only a MISSING figure (blank / no digits) takes a default. const fwHasNum = (x) => x != null && /\d/.test(String(x)); // A deal's APR is set when it holds a number and terms exist: a deal born at Desk It carries apr 0 / term 0 // ("no terms yet" → default rate); a pushed / saved 0% deal carries apr 0 with its term (kept as 0%). const fwDealAprSet = (d) => fwHasNum(d.apr) && isFinite(+d.apr) && (+d.apr !== 0 || +d.term > 0); // ONE blank-APR default for the Finance Deal AND the F&I Menu (RC18): the dealership's "Default APR %" (Desking ⚙ settings) // when it set one, else 6.49 (the Finance Deal's default since RC16). Used ONLY when the APR is blank — an entered 0.00 stays 0%. const fwDefaultApr = () => { try { const v = window.jbSetting ? window.jbSetting("desking", "defaultApr") : null; if (fwHasNum(v) && isFinite(+v) && +v >= 0 && +v < 100) return +v; } catch (e) {} return 6.49; }; const fwFmt = (n) => (+n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const fwR2 = (n) => Math.round((+n || 0) * 100) / 100; // Visual treatment for figures carried in from the Sales Desk — dimmed so the // worksheet reads as a finalization screen, not a second desk (still editable). const fwCarried = { opacity: 0.62 }; const fwDeskTag = { marginLeft: 6, fontSize: 9, fontWeight: 700, letterSpacing: 0.5, color: "#7C3AED", background: "#F3ECFB", border: "1px solid #D8C8F0", borderRadius: 3, padding: "0 4px", textTransform: "uppercase" }; const fwRed = "var(--jb-red, #B00020)"; // ── Protection-product plan rows — shared with the Cash Deal (window.fwPlans) ── const fwIsGap = (p) => /gap/i.test(String(p ? (p.category || "") + " " + (p.name || "") : "")); const fwCatalog = () => (window.jbProducts && window.jbProducts.list) ? window.jbProducts.list() : []; const fwCatalogFind = (id, code) => fwCatalog().find(p => (id && p.id === id) || (code && String(p.code || "").toUpperCase() === String(code || "").toUpperCase())) || null; const fwSlotEmpty = (f, n) => !String(f[`plan${n}Code`] || "").trim() && !String(f[`plan${n}Name`] || "").trim() && !fwNum(f[`plan${n}Price`]); const fwPlanTotal = (f) => { let t = 0; for (let n = 1; n <= FW_MAX_PLANS; n++) t += fwNum(f[`plan${n}Price`]); return fwR2(t); }; // deal.products → the plan-row form fields (every slot written, so a new deal never inherits stale rows). const fwPlanFieldsFromProducts = (products, fmt) => { const f2 = fmt || fwFmt; const list = Array.isArray(products) ? products : []; const gap = list.find(fwIsGap) || null; const plans = list.filter(p => !fwIsGap(p)); const out = { products: JSON.stringify(list), gapIns: gap ? f2(gap.price) : "0.00", gapCode: gap ? (gap.code || gap.id || "") : "", gapName: gap ? (gap.name || "") : "", gapCost: gap ? f2(gap.cost) : "", gapId: gap ? (gap.id || "") : "" }; for (let n = 1; n <= FW_MAX_PLANS; n++) { const p = plans[n - 1]; out[`plan${n}Code`] = p ? (p.code || p.id || "") : ""; 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; }; // form → products list [{ id, code, name, category, provider, price, cost, termMo }] (catalog fills category / provider). const fwProductsFromForm = (f) => { const list = []; for (let n = 1; n <= FW_MAX_PLANS; n++) { if (fwSlotEmpty(f, n)) continue; const code = String(f[`plan${n}Code`] || "").trim().toUpperCase(); const cat = fwCatalogFind(f[`plan${n}Id`], code); const costRaw = f[`plan${n}Cost`]; list.push({ id: f[`plan${n}Id`] || (cat ? cat.id : "P-" + (code || "PLAN" + n)), code: code || (cat ? cat.code : ""), name: String(f[`plan${n}Name`] || (cat ? cat.name : "")), category: cat ? cat.category : "Service Contract", provider: cat ? cat.provider : "", price: fwNum(f[`plan${n}Price`]), cost: costRaw != null && String(costRaw).trim() !== "" ? fwNum(costRaw) : (cat ? +cat.cost || 0 : 0), termMo: Math.round(fwNum(f[`plan${n}Term`])) || (cat ? +cat.termMo || 0 : 0) }); } const gapPrice = fwNum(f.gapIns); if (gapPrice > 0 || String(f.gapCode || "").trim()) { const cat = fwCatalogFind(f.gapId, f.gapCode) || fwCatalog().find(p => p.active && fwIsGap(p)) || null; list.push({ id: f.gapId || (cat ? cat.id : "P-GAP"), code: String(f.gapCode || (cat ? cat.code : "GAP")).toUpperCase(), name: f.gapName || (cat ? cat.name : "GAP"), category: "GAP", provider: cat ? cat.provider : "", price: gapPrice, cost: f.gapCost != null && String(f.gapCost).trim() !== "" ? fwNum(f.gapCost) : (cat ? +cat.cost || 0 : 0), termMo: 0 }); } return list; }; // What Save / Post write to the deal for the products: itemized list + the lumped // vscSale / gapSale (+ costs, fiGross) postDeal's payables and the pay plan read. const fwProductPatch = (f) => { const products = fwProductsFromForm(f); const gap = products.filter(fwIsGap), vsc = products.filter(p => !fwIsGap(p)); const sum = (l, k) => fwR2(l.reduce((t, p) => t + (+p[k] || 0), 0)); return { products, vscSale: sum(vsc, "price"), vscCost: sum(vsc, "cost"), gapSale: sum(gap, "price"), gapCost: sum(gap, "cost"), fiGross: fwR2(products.reduce((t, p) => t + Math.max(0, (+p.price || 0) - (+p.cost || 0)), 0)) }; }; // Recompute the plan total into `warranties` (+ refresh the JSON list). Returns the total string. const fwSyncWarranties = (formId) => { const f = window.loadJbForm(formId); const w = fwFmt(fwPlanTotal(f)); window.saveJbForm(formId, { warranties: w, products: JSON.stringify(fwProductsFromForm(f)) }); return w; }; // Put a catalog product on the form: GAP → the GAP row; else `slot` or the first empty plan slot. const fwPlacePlan = (formId, p, slot) => { const f = window.loadJbForm(formId); if (fwIsGap(p)) { const nf = { ...f, gapIns: fwFmt(p.retail), gapCode: p.code || p.id, gapName: p.name || "", gapCost: fwFmt(p.cost), gapId: p.id }; window.saveJbForm(formId, { gapIns: nf.gapIns, gapCode: nf.gapCode, gapName: nf.gapName, gapCost: nf.gapCost, gapId: nf.gapId, products: JSON.stringify(fwProductsFromForm(nf)) }); return { ok: true, slot: "gap" }; } let n = slot || 0; if (!n) for (let i = 1; i <= FW_MAX_PLANS; i++) { if (fwSlotEmpty(f, i)) { n = i; break; } } if (!n) return { ok: false, msg: `All ${FW_MAX_PLANS} plan slots are full — remove one first.` }; const patch = { [`plan${n}Code`]: p.code || p.id, [`plan${n}Name`]: p.name || "", [`plan${n}Price`]: fwFmt(p.retail), [`plan${n}Cost`]: fwFmt(p.cost), [`plan${n}Term`]: p.termMo ? String(p.termMo) : "", [`plan${n}Id`]: p.id }; const nf = { ...f, ...patch }; patch.warranties = fwFmt(fwPlanTotal(nf)); patch.products = JSON.stringify(fwProductsFromForm(nf)); window.saveJbForm(formId, patch); return { ok: true, slot: n }; }; // Remove plan slot n (later rows move up) / clear the GAP row. const fwRemovePlan = (formId, n) => { const f = window.loadJbForm(formId), patch = {}; for (let i = n; i <= FW_MAX_PLANS; i++) { const src = i + 1; for (const k of ["Code", "Name", "Price", "Cost", "Term", "Id"]) patch[`plan${i}${k}`] = src <= FW_MAX_PLANS ? (f[`plan${src}${k}`] || "") : ""; } const nf = { ...f, ...patch }; patch.warranties = fwFmt(fwPlanTotal(nf)); patch.products = JSON.stringify(fwProductsFromForm(nf)); window.saveJbForm(formId, patch); }; const fwRemoveGap = (formId) => { const f = window.loadJbForm(formId); const nf = { ...f, gapIns: "0.00", gapCode: "", gapName: "", gapCost: "", gapId: "" }; window.saveJbForm(formId, { gapIns: "0.00", gapCode: "", gapName: "", gapCost: "", gapId: "", products: JSON.stringify(fwProductsFromForm(nf)) }); }; const fwPlanCount = (f) => { let c = 0; for (let n = 1; n <= FW_MAX_PLANS; n++) if (!fwSlotEmpty(f, n)) c++; return c; }; /* ── Product picker — the REAL "Details": the F&I catalog (window.jbProducts) grouped by category with retail / cost / term; click a row to add it (GAP → the GAP row, else the first empty plan slot) or, when opened from a plan row's own Details, to swap that row. `focusCategory` pre-scrolls. ── */ const FwProductPicker = ({ onPick, onClose, focusCategory, title, current }) => { const [q, setQ] = React.useState(""); const list = fwCatalog().filter(p => p.active); const catsAll = (window.jbProducts && window.jbProducts.CATEGORIES) || []; const cats = [...catsAll, ...list.map(p => p.category)].filter((c, i, a) => c && a.indexOf(c) === i).filter(c => list.some(p => p.category === c)); const qq = q.trim().toLowerCase(); const refs = React.useRef({}); React.useEffect(() => { const el = focusCategory && refs.current[focusCategory]; if (el && el.scrollIntoView) { try { el.scrollIntoView({ block: "start" }); } catch (e) {} } }, []); const money = (n) => "$" + fwFmt(n); return ( { onClose(); window.k2bToast && window.k2bToast("Manage the catalog under F&I Products.", "info", 3000); }}>Manage Catalog…ESC-Cancel}>
setQ(e.target.value)} placeholder="🔍 Search code, name, provider…" style={{ flex: 1, fontSize: 13.5, padding: "8px 10px", background: "#FFFEF4", border: "2px solid var(--jb-navy, #1B66A8)", borderRadius: 4 }} /> {list.length} active products
{list.length === 0 ? (
No active products in the catalog — add them under F&I Products.
) : (
{cats.map(cat => { const rows = list.filter(p => p.category === cat && (!qq || [p.code, p.name, p.provider, p.category].join(" ").toLowerCase().includes(qq))); if (!rows.length) return null; return ( { refs.current[cat] = el; }} style={{ background: cat === focusCategory ? "#FFF3C4" : "#E9EEF6" }}> {rows.map((p, i) => ( onPick(p)} title="Click to add this product to the deal" style={{ background: current && current === p.id ? "#FFF7DF" : (i % 2 ? "var(--jb-surface-2, #F7F8FA)" : "#fff"), cursor: "pointer" }}> ))} ); })}
CodeProductProvider TermRetailCost
{cat}
{p.code || p.id} {p.name}{current && current === p.id ? ON DEAL : null} {p.provider} {p.termMo ? p.termMo + " mo" : "—"} {money(p.retail)} {money(p.cost)}
)}
GAP products fill the GAP row; everything else lands in the first empty plan slot (up to {FW_MAX_PLANS}). Prices are editable on the row after adding.
); }; /* ── Plan table — Plan 1..N rows (code · product · term · price · cost) + the GAP row + the computed Warranties total. formId-driven so the Finance Deal and the Cash Deal render the same block over their own forms. Boxes are real widths: code 92, product grows (min 230), price 114, cost 96. ── */ const FwPlanTable = ({ formId, live, tick, openPicker, removePlan, removeGap, totalLabel, onPriceChange, accent = "#7C3AED" }) => { const planTotal = fwPlanTotal(live); const planCount = fwPlanCount(live); const slots = []; for (let n = 1; n <= FW_MAX_PLANS; n++) if (n <= 3 || !fwSlotEmpty(live, n)) slots.push(n); const planCat = (n) => { const c = fwCatalogFind(live[`plan${n}Id`], live[`plan${n}Code`]); return c ? c.category : "Service Contract"; }; let costTotal = 0; for (let n = 1; n <= FW_MAX_PLANS; n++) costTotal += fwNum(live[`plan${n}Cost`]); const F = (p) => ; return (
PROTECTION PRODUCTS {planCount ? `${planCount} plan${planCount === 1 ? "" : "s"} · $${fwFmt(planTotal)}` : "no service plans yet"}{live.gapCode ? ` · GAP${/^gap$/i.test(live.gapCode) ? "" : " " + live.gapCode} $${fwFmt(fwNum(live.gapIns))}` : ""} — from the desked option; edit or add here.
openPicker({})}>Details openPicker({})}>+ Add product
{["", "Code", "Product", "Term", "Price", "Cost", ""].map((h, i) => ( = 3 && i <= 5 ? "right" : "left", paddingRight: i >= 3 && i <= 5 ? 6 : 0 }}>{h} ))} {slots.map(n => { const empty = fwSlotEmpty(live, n); return ( {`Plan ${n}`} {F({ field: `plan${n}Code`, type: "text", width: 92, mono: true, hi: !empty, uppercase: true, placeholder: "code", onValueChange: tick })} {F({ field: `plan${n}Name`, type: "text", width: "100%", style: { minWidth: 150, width: "100%" }, placeholder: empty ? "— empty — click Details or + Add product" : "", onValueChange: tick })} {F({ field: `plan${n}Term`, type: "number", width: 60, placeholder: "mo" })} {F({ field: `plan${n}Price`, type: "money", width: 114, placeholder: "0.00", onValueChange: onPriceChange })} {F({ field: `plan${n}Cost`, type: "money", width: 96, style: { opacity: 0.62 }, placeholder: "cost", onValueChange: tick })} openPicker({ slot: n, category: empty ? "Service Contract" : planCat(n), current: live[`plan${n}Id`] })}>Details removePlan(n)} style={{ color: fwRed, fontWeight: 700, minWidth: 28, padding: "0 6px", visibility: empty ? "hidden" : "visible" }}>✕ ); })} GAP {F({ field: "gapCode", type: "text", width: 92, mono: true, hi: !!live.gapCode, uppercase: true, placeholder: "code", onValueChange: tick })} {F({ field: "gapName", type: "text", width: "100%", style: { minWidth: 150, width: "100%" }, placeholder: "— no GAP — click Details to add", onValueChange: tick })} {F({ field: "gapIns", type: "money", width: 114, placeholder: "0.00", onValueChange: onPriceChange })} {F({ field: "gapCost", type: "money", width: 96, style: { opacity: 0.62 }, placeholder: "cost", onValueChange: tick })} openPicker({ category: "GAP", current: live.gapId })}>Details ✕ Warranties (plan total → deal vscSale) {fwFmt(planTotal)} {fwFmt(costTotal)} + GAP {fwFmt(fwNum(live.gapIns))} = {fwFmt(planTotal + fwNum(live.gapIns))} in the {totalLabel || "amount financed"}
); }; /* ── Deal breakdown — the Cash Price / Deposit "Details": how the amount financed (or adjusted cap cost) is built from the worksheet's live figures. ── */ const FwBreakdownModal = ({ form, formId, deal, isLease, onClose, title }) => { const f = form || window.loadJbForm(formId || FW_FORM); const n = (k) => fwNum(f[k]); const negEq = Math.max(0, n("tradePayoff") - n("tradeValue")); const posEq = Math.max(0, n("tradeValue") - n("tradePayoff")); const plans = []; for (let i = 1; i <= FW_MAX_PLANS; i++) if (!fwSlotEmpty(f, i)) plans.push([`${f[`plan${i}Code`] || "PLAN " + i} — ${f[`plan${i}Name`] || ""}`, n(`plan${i}Price`)]); const products = fwPlanTotal(f) + n("gapIns") + n("otherIns"); // RC27 (dsk): the deal's cost lines — a lender fee passed to the customer is in the amount financed; the dealer's down payment assistance is part of the down const ca = deal && window.DealCalc ? window.DealCalc.costAddsOf(deal) : null; const feeCust = ca && !isLease ? ca.feeCustCents / 100 : 0, dpa = ca ? ca.dpaCents / 100 : 0; const amt = n("cashPrice") + n("salesTax") + n("license") + n("docFee") + products + feeCust + n("tradePayoff") - n("tradeValue") - n("rebates") - n("deposit"); const rows = [ ["Cash Price", n("cashPrice")], ["+ Sales Tax", n("salesTax")], ["+ License / Title", n("license")], ["+ Doc Fee", n("docFee")], ...plans.map(([l, v]) => ["+ " + l, v]), ...(n("gapIns") ? [["+ GAP" + (f.gapCode ? " — " + f.gapCode : ""), n("gapIns")]] : []), ...(n("otherIns") ? [["+ Other insurance", n("otherIns")]] : []), ...(feeCust ? [["+ Lender fee (the customer pays it)", feeCust]] : []), ["− Trade-in allowance" + (f.tradeDesc ? " (" + f.tradeDesc + ")" : ""), -n("tradeValue")], ["+ Trade payoff" + (f.tradeLender ? " → " + f.tradeLender : ""), n("tradePayoff")], ...(negEq > 0 ? [[" = NEGATIVE EQUITY rolled into the loan", negEq, "neg"]] : posEq > 0 ? [[" = trade equity applied as down", -0, "pos", posEq]] : []), ["− Rebates", -n("rebates")], ["− Deposit / cash down", -n("deposit")], ...(dpa ? [[" of which down payment assistance from the dealer", -0, "pos", dpa], [" = customer cash down", -0, "pos", Math.max(0, n("deposit") - dpa)]] : []), ]; const money = (v) => (v < 0 ? "−$" : "$") + fwFmt(Math.abs(v)); return ( Close}> {rows.map(([l, v, kind, alt], i) => ( ))}
{l} {kind === "pos" ? money(alt) + (/^ (of which|=)/.test(l) && !/trade/.test(l) ? "" : " (in trade line)") : money(v)}
{isLease ? "= ADJ. CAP COST (worksheet)" : "= AMOUNT FINANCED"}{negEq > 0 ? ` (incl. $${fwFmt(negEq)} negative equity)` : ""} {money(isLease ? n("amountFinanced") : amt)}
{isLease ?
Lease: the engine capitalizes negative equity into the gross cap cost and applies positive equity as cap reduction — see F5-Recalculate.
: Math.abs(amt - n("amountFinanced")) > 0.01 ?
Worksheet shows ${fwFmt(n("amountFinanced"))} — press F5-Recalculate to refresh it from these figures.
: null}
); }; const FinanceWorksheet = ({ onClose, go }) => { const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => (window.DMS && window.DMS.subscribe) ? window.DMS.subscribe(force) : undefined, []); const [dealId, setDealId] = React.useState(() => { try { return localStorage.getItem("jb_fw_deal") || null; } catch (e) { return null; } }); const [showLookup, setShowLookup] = React.useState(false); const [esignOpen, setEsignOpen] = React.useState(false); const [formKey, setFormKey] = React.useState(0); // bump to remount fields after programmatic save const [, tick] = React.useReducer(n => n + 1, 0); // live read-outs (neg equity, plan total) as the user types const [picker, setPicker] = React.useState(null); // { slot?, category?, current? } → product picker const [breakdown, setBreakdown] = React.useState(null); // "Cash Price" | "Deposit" → breakdown modal const deals = (window.DMS && window.DMS.deals) ? window.DMS.deals() : []; const deal = deals.find(d => d.id === dealId) || null; // Lease vs retail — the deal record wins, then the form draft (the desk's // F9-Send writes dealType:"Lease" into both). const formDraft = window.loadJbForm ? window.loadJbForm(FW_FORM) : {}; const isLease = /lease/i.test(String((deal && (deal.dealType || deal.mode)) || formDraft.dealType || "")); // RC27 (dsk): the deal's cost lines (desk › Profit panel): a lender fee PASSED TO THE CUSTOMER is the customer's fee line (in the amount // financed); the dealer's DOWN PAYMENT ASSISTANCE lowers the customer's cash due (the deposit stays the whole down payment) const fwCa = deal && window.DealCalc ? window.DealCalc.costAddsOf(deal) : null; const fwFeeCust = fwCa && !isLease ? fwCa.feeCustCents / 100 : 0; const fwDpa = fwCa ? fwCa.dpaCents / 100 : 0; // Write a deal's figures into the worksheet form (single source of truth = the // DMS deal record, which Desking's push patched with trade/rebate/down/lender/ // products). Stamps _srcDealId/_srcRev so a remount can tell "already holding // this revision" from "a newer revision arrived" (B-04). const writeForm = (d) => { const lease = /lease/i.test(String(d.dealType || d.mode || "")); // Products: the itemized list when the desk sent one; a legacy deal that only // carries the lumped vscSale gets a single "SERVICE CONTRACT" plan row. let products = Array.isArray(d.products) ? d.products : []; if (!products.length && (+d.vscSale > 0 || +d.gapSale > 0)) { products = []; if (+d.vscSale > 0) products.push({ id: "P-VSC-LEGACY", code: "VSC", name: "SERVICE CONTRACT (desk)", category: "Service Contract", provider: "", price: +d.vscSale, cost: +d.vscCost || 0, termMo: 0 }); if (+d.gapSale > 0) products.push({ id: "", code: "GAP", name: "GAP", category: "GAP", provider: "", price: +d.gapSale, cost: +d.gapCost || 0, termMo: 0 }); } window.saveJbForm(FW_FORM, { _srcDealId: d.id, _srcRev: +d.rev || 0, dealType: lease ? "Lease" : (d.dealType || "Retail"), cashPrice: fwFmt(d.salePrice), tradeValue: fwFmt(d.tradeAllow || 0), tradePayoff: fwFmt(d.tradePayoff || 0), tradeAcv: fwFmt(d.tradeACV != null ? d.tradeACV : (d.tradeAcv != null ? d.tradeAcv : d.tradeAllow || 0)), tradeDesc: d.tradeDesc || d.tradeYrMkMdl || d.tradeVeh || "", tradeLender: d.tradeLender || "", negEquity: fwFmt(Math.max(0, (+d.tradePayoff || 0) - (+d.tradeAllow || 0))), rebates: fwFmt(d.rebate || 0), // Down = the deal's down payment only. K2B-003 (RC18): never a cash deal's old "deposit" figure; on a cash deal // only the payments actually recorded count as money down. deposit: fwFmt(window.DMS && window.DMS.isCashDeal && window.DMS.isCashDeal(d) ? window.DMS.cashDealFigures(d).received : (d.cashDown || 0)), salesTax: fwFmt(d.tax || 0), license: fwFmt(d.titleFee || 0), docFee: fwFmt(d.docFee || 0), ...fwPlanFieldsFromProducts(products), otherIns: d.otherSale != null ? fwFmt(d.otherSale) : "0.00", amountFinanced: fwFmt(d.amtFinanced || 0), apr: (fwDealAprSet(d) ? +d.apr : (lease ? 0 : fwDefaultApr())).toFixed(2), term: String(d.term || (lease ? 36 : 72)), payment: fwFmt(d.payment || 0), lienName: d.lender || "GM FINANCIAL", ...(lease ? { moneyFactor: d.moneyFactor != null ? (+d.moneyFactor).toFixed(5) : undefined, residual: d.residual != null ? fwFmt(d.residual) : undefined, residualPct: d.residualPct != null ? (+d.residualPct).toFixed(2) : undefined, milesPerYr: d.milesPerYr != null ? String(d.milesPerYr) : undefined, dueAtSigning: d.dueAtSigning != null ? fwFmt(d.dueAtSigning) : undefined, leaseTaxRate: d.leaseTaxRate != null ? String(d.leaseTaxRate) : undefined, taxMethod: d.leaseTaxMethod || undefined, totalOfPayments: d.totalOfPayments != null ? fwFmt(d.totalOfPayments) : undefined, } : {}), }); // T-03: the figures just mirrored are the deal's own — current for these inputs only when the deal carries real terms. fwStampCalc(fwDealHasTerms(d)); }; // Mirror the linked deal ONLY when the form does not already hold this deal // at this revision (open from Desking F9, F2-Lookup, Deal Review). A mere // remount of the route never overwrites F&I's edited APR/term/deposit/lender // (B-04); a newer desk push (rev bump) does refresh the figures. React.useEffect(() => { if (!deal) return; const f = window.loadJbForm(FW_FORM); const held = f._srcDealId || null, heldRev = f._srcRev != null ? +f._srcRev : null; if (held === deal.id && heldRev === (+deal.rev || 0)) { // T-03: figures that arrived from outside for this revision (a desk push) count as calculated only when they are // exactly the deal's; otherwise Save / Post / Print recalculate first. if (f._calcFor !== deal.id + "@" + heldRev) fwStampCalc(fwDealHasTerms(deal) && fwFormMatchesDeal(f, deal)); return; } writeForm(deal); setFormKey(k => k + 1); }, [deal && deal.id, deal && deal.rev]); // `warranties` is COMPUTED from the plan rows — keep it in step on every mount // / remount (a desk push, an F2 load, a picker add / remove all remount). React.useEffect(() => { const f = window.loadJbForm(FW_FORM); const w = fwFmt(fwPlanTotal(f)); if (w !== String(f.warranties || "")) fwSyncWarranties(FW_FORM); }, [formKey]); // Load a deal's figures into the worksheet form (F2-Lookup picker — an // explicit user action, so it always overwrites). const loadDeal = (d) => { writeForm(d); setDealId(d.id); try { localStorage.setItem("jb_fw_deal", d.id); } catch (e) {} setFormKey(k => k + 1); setShowLookup(false); window.k2bToast && window.k2bToast(`${d.id} — ${d.buyer} loaded into worksheet.`, "ok"); }; // Build the lease-engine input from the form draft (the desk saved every // jbLeaseCalc input on F9-Send; F&I can edit any of them here). const leaseArgsFromForm = (f) => ({ msrp: fwNum(f.msrp) || fwNum(f.cashPrice), sellingPrice: fwNum(f.cashPrice), term: Math.max(1, Math.round(fwNum(f.term)) || 36), milesPerYr: fwNum(f.milesPerYr) || 12000, residualPct: fwNum(f.residualPct) || (fwNum(f.residual) && (fwNum(f.msrp) || fwNum(f.cashPrice)) ? fwNum(f.residual) / (fwNum(f.msrp) || fwNum(f.cashPrice)) * 100 : 0), moneyFactor: f.moneyFactor != null && f.moneyFactor !== "" ? fwNum(f.moneyFactor) : undefined, apr: f.moneyFactor != null && f.moneyFactor !== "" ? undefined : fwNum(f.apr), acqFee: fwNum(f.acqFee), acqFeeCapitalized: f.acqFeeCapped != null ? !!f.acqFeeCapped : true, // Deposit on a lease = cash cap reduction (cash down + additional CCR). capReductionCash: f.singlePay ? 0 : fwNum(f.deposit), rebates: f.leaseRebates != null && f.leaseRebates !== "" ? fwNum(f.leaseRebates) : fwNum(f.rebates), tradeAllowance: fwNum(f.tradeValue), tradePayoff: fwNum(f.tradePayoff), docFee: fwNum(f.docFee), docFeeCapitalized: f.docFeeCapped != null ? !!f.docFeeCapped : true, titleLicenseFee: fwNum(f.titleLicFee) || fwNum(f.license), titleLicenseCapitalized: f.titleLicCapped != null ? !!f.titleLicCapped : true, addOns: fwPlanTotal(f) + fwNum(f.gapIns) + fwNum(f.otherIns), taxMethod: f.taxMethod || "monthly", taxRate: fwNum(f.leaseTaxRate), securityDeposit: fwNum(f.securityDeposit), dispositionFee: fwNum(f.dispFee), excessMileageRate: fwNum(f.excessRate), purchaseOptionFee: fwNum(f.purchOptFee), extraMilesPurchased: fwNum(f.extraMiles), extraMileageRate: f.extraMileageRate != null && f.extraMileageRate !== "" ? fwNum(f.extraMileageRate) : undefined, inceptionMiles: fwNum(f.inceptionMiles), inceptionMileageRate: f.inceptionRate != null && f.inceptionRate !== "" ? fwNum(f.inceptionRate) : undefined, ...(f.inceptionAdjust === "1" || f.inceptionAdjust === "0" ? { inceptionMileageAdjust: f.inceptionAdjust === "1" } : {}), singlePay: !!f.singlePay, }); // ── T-03 (RC18): Save / Post / Print / RouteOne always work from the inputs on screen NOW. _calcSig is the fingerprint // of the inputs AND the amount financed / payment as last calculated (F5, the deal mirror or a matching desk push); any // difference — a price, down, trade, product or rate typed without F5, or a figure typed straight into the amount // financed / payment boxes — recalculates first, so an old payment or amount financed is never stored or printed. const fwCalcSig = (f) => JSON.stringify({ lease: isLease, i: isLease ? leaseArgsFromForm(f) : [fwNum(f.cashPrice), fwNum(f.tradeValue), fwNum(f.tradePayoff), fwNum(f.rebates), fwNum(f.deposit), fwNum(f.salesTax), fwNum(f.license), fwNum(f.docFee), fwPlanTotal(f), fwNum(f.gapIns), fwNum(f.otherIns), fwHasNum(f.apr) ? fwNum(f.apr) : null, Math.round(fwNum(f.term)), fwFeeCust], o: [fwR2(fwNum(f.amountFinanced)), fwR2(fwNum(f.payment))] }); // current = true → the form's figures are calculated for its inputs; false → the next Save / Post / Print recalculates. function fwStampCalc(current) { const f = window.loadJbForm(FW_FORM); window.saveJbForm(FW_FORM, { _calcSig: current ? fwCalcSig(f) : "", _calcFor: (f._srcDealId || "") + "@" + (f._srcRev != null ? +f._srcRev : "") }); } function fwDealHasTerms(d) { return /lease/i.test(String(d.dealType || d.mode || "")) ? (+d.payment || 0) > 0 : (+d.term || 0) > 0 && ((+d.payment || 0) !== 0 || (+d.amtFinanced || 0) !== 0); } function fwFormMatchesDeal(f, d) { const eq = (a, b) => Math.abs(fwR2(a) - fwR2(+b || 0)) < 0.006; return eq(fwNum(f.cashPrice), d.salePrice) && eq(fwNum(f.tradeValue), d.tradeAllow) && eq(fwNum(f.tradePayoff), d.tradePayoff) && eq(fwNum(f.rebates), d.rebate) && eq(fwNum(f.deposit), d.cashDown) && eq(fwNum(f.salesTax), d.tax) && eq(fwNum(f.license), d.titleFee) && eq(fwNum(f.docFee), d.docFee) && eq(fwPlanTotal(f), d.vscSale) && eq(fwNum(f.gapIns), d.gapSale) && eq(fwNum(f.amountFinanced), d.amtFinanced) && eq(fwNum(f.payment), d.payment) && Math.round(fwNum(f.term)) === Math.round(+d.term || 0) && eq(fwNum(f.apr), d.apr); } // spec 6.3 valid-input gate (same wording style as the desk's): a negative amount financed / cap cost is refused, inputs kept. const fwNegMsg = (over, what) => `Down payment too high — the down payment, trade equity and rebates are $${fwFmt(over)} more than the whole ${isLease ? "cap cost" : "balance"} — lower the down payment.${what ? " " + what + "; your entries are kept." : " Your entries are kept."}`; // Compute amount financed, payment and finance charge from the form AS IT IS (pure: returns the form patch, writes nothing). // LEASE deals go through the closed-end lease engine (LeaseCalc.jsx.txt — window.jbLeaseCalc), never retail amortization (B-03). const fwCompute = (f, what) => { const negEq = Math.max(0, fwNum(f.tradePayoff) - fwNum(f.tradeValue)); if (isLease) { if (!window.jbLeaseCalc) return { ok: false, msg: "Lease engine not loaded (LeaseCalc)." }; const q = window.jbLeaseCalc(leaseArgsFromForm(f)); const pay = q.singlePay ? q.singlePayAmount : q.totalMonthly; if (q.adjustedCapCost < -0.005) return { ok: false, neg: true, msg: fwNegMsg(-q.adjustedCapCost, what) }; if (pay < -0.005) return { ok: false, neg: true, msg: `Lease payment would be below $0.00 ($${fwFmt(pay)}) — lower the cap cost reduction.${what ? " " + what + "; your entries are kept." : " Your entries are kept."}` }; return { ok: true, warn: q.upsideDown, patch: { dealType: "Lease", negEquity: fwFmt(negEq), amountFinanced: fwFmt(q.adjustedCapCost), // adjusted cap cost payment: fwFmt(q.singlePay ? q.singlePayAmount : q.totalMonthly), // monthly incl. tax basePayment: fwFmt(q.baseMonthly), monthlyTax: fwFmt(q.monthlyTax), financeCharge: fwFmt(q.rentCharge * q.term), // total rent charge rentCharge: fwFmt(q.rentCharge), residual: fwFmt(q.residualValue), residualPct: q.residualPct.toFixed(2), moneyFactor: q.effectiveMoneyFactor.toFixed(5), apr: q.aprEquivalent.toFixed(2), term: String(q.term), salesTax: fwFmt(q.taxMethod === "monthly" || !q.upfrontTax ? q.monthlyTax : q.upfrontTax), dueAtSigning: fwFmt(q.dueAtSigningItems.total), totalOfPayments: fwFmt(q.totalOfPayments), leaseUpfrontTax: fwFmt(q.taxItemization.estimatedUpfrontTotal), grossCapCost: fwFmt(q.grossCapCost), }, toast: `Lease recalculated — $${fwFmt(q.totalMonthly)}/mo incl. tax · ${q.term}mo · MF ${q.effectiveMoneyFactor.toFixed(5)} · residual $${fwFmt(q.residualValue)}${q.upsideDown ? " · ⚠ adj. cap below residual" : ""}.` }; } const amt = fwNum(f.cashPrice) - fwNum(f.tradeValue) + fwNum(f.tradePayoff) - fwNum(f.rebates) - fwNum(f.deposit) + fwNum(f.salesTax) + fwNum(f.license) + fwNum(f.docFee) + fwPlanTotal(f) + fwNum(f.gapIns) + fwNum(f.otherIns) + fwFeeCust; // RC27 (dsk): + a lender fee passed to the customer if (fwR2(amt) < 0) return { ok: false, neg: true, msg: fwNegMsg(-fwR2(amt), what) }; const aprPct = fwHasNum(f.apr) ? fwNum(f.apr) : fwDefaultApr(); // blank → the ONE default; an entered 0.00 stays 0% (K2B-005) const n = Math.max(1, Math.round(fwNum(f.term)) || 72); const r = aprPct / 100 / 12; const pmt = r === 0 ? amt / n : (amt * r) / (1 - Math.pow(1 + r, -n)); return { ok: true, patch: { amountFinanced: fwFmt(amt), payment: fwFmt(pmt), negEquity: fwFmt(negEq), financeCharge: fwFmt(pmt * n - amt), apr: aprPct.toFixed(2), term: String(n) }, toast: `Recalculated — $${fwFmt(pmt)} / ${n}mo @ ${aprPct.toFixed(2)}%${negEq > 0 ? " · incl. $" + fwFmt(negEq) + " negative equity" : ""}.` }; }; // F5-Recalculate. const recalc = () => { fwSyncWarranties(FW_FORM); const c = fwCompute(window.loadJbForm(FW_FORM)); if (!c.ok) { window.k2bToast && window.k2bToast(c.msg, "warn", 6500); return; } // nothing written — the entries stay as typed window.saveJbForm(FW_FORM, c.patch); fwStampCalc(true); setFormKey(k => k + 1); window.k2bToast && window.k2bToast(c.toast, c.warn ? "warn" : "ok", isLease ? 5000 : undefined); }; // Save / Post / Print / RouteOne: recalculate first when anything changed since the figures were calculated. const ensureCalc = (what) => { fwSyncWarranties(FW_FORM); const f = window.loadJbForm(FW_FORM); if (f._calcSig && f._calcSig === fwCalcSig(f)) return { ok: true, recalculated: false }; const c = fwCompute(f, what); if (!c.ok) return { ok: false, msg: c.msg }; window.saveJbForm(FW_FORM, c.patch); fwStampCalc(true); setFormKey(k => k + 1); return { ok: true, recalculated: true, toast: c.toast }; }; // The deal patch every save/post writes: the worksheet's edited figures, // the protection products (itemized `products` + vscSale/gapSale for the GL, // B-07), the trade lender / neg equity, and on a lease the lease structure // postDeal books (B-03). const dealPatchFromForm = (f) => ({ salePrice: fwNum(f.cashPrice), tradeAllow: fwNum(f.tradeValue), tradePayoff: fwNum(f.tradePayoff), rebate: fwNum(f.rebates), ...(f.tradeAcv != null && f.tradeAcv !== "" ? { tradeACV: fwNum(f.tradeAcv), tradeAcv: fwNum(f.tradeAcv) } : {}), ...(f.tradeDesc != null ? { tradeDesc: String(f.tradeDesc || "") } : {}), tradeLender: String(f.tradeLender || ""), negEquity: Math.max(0, fwR2(fwNum(f.tradePayoff) - fwNum(f.tradeValue))), cashDown: fwNum(f.deposit), tax: fwNum(f.salesTax), titleFee: fwNum(f.license), docFee: fwNum(f.docFee), ...fwProductPatch(f), otherSale: fwNum(f.otherIns), amtFinanced: fwNum(f.amountFinanced), apr: fwNum(f.apr), term: Math.round(fwNum(f.term)), payment: fwNum(f.payment), lender: f.lienName || (deal && deal.lender) || "", dealType: isLease ? "Lease" : (f.dealType || (deal && deal.dealType) || "Retail"), ...(isLease ? { moneyFactor: fwNum(f.moneyFactor), residual: fwNum(f.residual), residualPct: fwNum(f.residualPct), milesPerYr: fwNum(f.milesPerYr), dueAtSigning: fwNum(f.dueAtSigning), leaseUpfrontTax: fwNum(f.leaseUpfrontTax), leaseTaxMethod: f.taxMethod || "monthly", leaseTaxRate: fwNum(f.leaseTaxRate), totalOfPayments: fwNum(f.totalOfPayments), } : {}), }); // Write the worksheet to the linked deal + re-stamp the revision (B-04). // Shared by F10-Save, F9-Post and Send to RouteOne. Returns the update result. const writeDeal = (what) => { const c = ensureCalc(what || "Nothing was saved"); // T-03: never store the old payment / amount financed if (!c.ok) return { ok: false, refused: true, msg: c.msg }; const f = window.loadJbForm(FW_FORM); const r = window.DMS.updateDeal(deal.id, dealPatchFromForm(f)); if (r && r.ok) { window.saveJbForm(FW_FORM, { _srcDealId: deal.id, _srcRev: r.rev }); fwStampCalc(true); r.recalculated = c.recalculated; } return r; }; // F10-Save — actually persists: the form draft is already written field-by- // field; this patches the linked deal with the edited figures and re-stamps // the form with the new revision so the next mount does not re-mirror (B-04). const saveNow = () => { if (!deal || !window.DMS || !window.DMS.updateDeal) { window.k2bToast && window.k2bToast(FW_NO_DEAL, deal ? "warn" : "info", 4200); return; } // RC21 (deals): a POSTED deal's figures are on the books — F10 never changes them (the server refuses it too) if (deal.postedToGL && !deal.imported) { window.k2bToast && window.k2bToast(`${deal.id} is posted — its figures are on the books and can't be changed here. Correcting a posted deal (reverse and re-post) is not part of this release; ask the office. Nothing was saved; your entries are kept on the screen.`, "warn", 7000); return; } const r = writeDeal(); if (r && r.ok) { const f = window.loadJbForm(FW_FORM); window.k2bToast && window.k2bToast(`${deal.id} saved${r.recalculated ? ` — recalculated from the figures on screen first: $${f.payment || "0.00"} / ${f.term}mo @ ${f.apr}% on $${f.amountFinanced || "0.00"}` : ""} — worksheet figures written to the deal${window.DMS.isOnline && window.DMS.isOnline() ? " and the server" : ""}.`, "ok", r.recalculated ? 5200 : 3600); } else window.k2bToast && window.k2bToast((r && r.msg) || "Save failed.", "warn", r && r.refused ? 6500 : undefined); }; // Patch the linked deal with the worksheet figures and post to accounting // through the ONE shared gate (window.jbPostDeal: compliance, truth box, // open items, stage checks — one confirm, logged override). Never // DMS.postDeal directly. const postToGL = async () => { if (!window.DMS || !window.jbPostDeal) { window.k2bToast && window.k2bToast("Ledger offline.", "warn"); return; } if (!deal) { window.k2bToast && window.k2bToast(FW_NO_DEAL, "warn", 4200); return; } const u = writeDeal("Nothing was posted"); if (!(u && u.ok)) { window.k2bToast && window.k2bToast((u && u.msg) || "Could not write the worksheet to the deal — nothing was posted.", "warn", 6500); return; } const f = window.loadJbForm(FW_FORM); const r = await window.jbPostDeal(deal.id); // RC21: the server posts (or refuses with the reason — nothing changed) if (r && r.ok) { // Posting bumps the deal revision — stamp the form so the mount effect // does not re-mirror over the worksheet (B-04). const posted = (window.DMS.deals() || []).find(d => d.id === deal.id); window.saveJbForm(FW_FORM, { _srcDealId: deal.id, _srcRev: posted ? (+posted.rev || 0) : (u && u.rev) }); fwStampCalc(true); } // Ease of flow: a booked deal goes straight to the print packet — the // Report Selection dialog opens pre-checked from the forms-sequencing rules. // A lease resolves as "Lease" (lease forms in, retail RIC / 553 prep out) — B-03. if (r && r.ok) setTimeout(() => { const posted = (window.DMS.deals() || []).find(d => d.id === deal.id) || deal; window.k2bPrintSelectDeal && window.k2bPrintSelectDeal({ dealType: isLease ? "Lease" : (/^n|new/i.test(String(posted.type || posted.newUsed || "")) ? "New" : "Used"), newUsed: posted.type || "", bank: f.lienName || deal.lender || "", state: "" }); }, 700); }; // Send to RouteOne — the package is built from the DEAL record, so the // worksheet (products, trade lender, neg equity, lender/APR/term) is written // to it first, exactly like F10-Save. const sendRouteOne = () => { if (!deal) { window.k2bToast && window.k2bToast("Load a deal first (F2-Lookup).", "warn"); return; } if (window.DMS && window.DMS.updateDeal) { const u = writeDeal("Nothing was sent"); if (!(u && u.ok)) { window.k2bToast && window.k2bToast((u && u.msg) || "Could not write the worksheet to the deal.", "warn"); return; } } const r = window.DMS.sendToRouteOne(deal.id); window.k2bToast && window.k2bToast(r.msg, r.ok ? "ok" : "warn", 4600); }; // Product picker actions const openPicker = (opts) => setPicker(opts || {}); const onPickProduct = (p) => { const r = fwPlacePlan(FW_FORM, p, picker && picker.slot); setPicker(null); if (!r.ok) { window.k2bToast && window.k2bToast(r.msg, "warn", 3600); return; } setFormKey(k => k + 1); window.k2bToast && window.k2bToast(`${p.code || p.id} — ${p.name} ${r.slot === "gap" ? "set as GAP" : "added as plan " + r.slot} · $${fwFmt(p.retail)}. Press F5 to refresh the payment.`, "ok", 3600); }; const removePlan = (n) => { fwRemovePlan(FW_FORM, n); setFormKey(k => k + 1); }; const removeGap = () => { fwRemoveGap(FW_FORM); setFormKey(k => k + 1); }; const onPlanPriceChange = () => { fwSyncWarranties(FW_FORM); tick(); }; // Live read-outs off the persisted draft (re-read on every render — the // JBFields persist per keystroke and call tick()). const live = window.loadJbForm ? window.loadJbForm(FW_FORM) : {}; const negEquityLive = Math.max(0, fwR2(fwNum(live.tradePayoff) - fwNum(live.tradeValue))); const planTotalLive = fwPlanTotal(live); const planCount = fwPlanCount(live); return ( // R-4: never a fixed 490px box — the window grows to its content and the // form body scrolls inside the viewport, so every field and F10-Save are // reachable at 1366×768 as well as 1600×1000.
{/* Purpose banner — distinguishes this from the Sales Desk */}
F&I OFFICE Finalize & post. Confirm lender, APR & term, add the insurance & service-plan products, then F9-Post to GL books the deal. Dimmed figures carried from the Sales Desk — edit only to override.
{/* Carried-from-desk strip — every figure the desk sent, no re-entry */} {(() => { const f = live; const lease = /lease/i.test(String(f.dealType || "")); const plans = []; for (let n = 1; n <= FW_MAX_PLANS; n++) if (!fwSlotEmpty(f, n)) plans.push([`Plan ${n} ${f[`plan${n}Code`] || ""}`, f[`plan${n}Price`]]); const chips = [lease ? ["LEASE", "✓"] : null, ["Cash Price", f.cashPrice], ["Trade", f.tradeValue], ["Payoff", f.tradePayoff], f.tradeLender ? ["Payoff to", f.tradeLender] : null, [lease ? "Cap Red (cash)" : "Down", f.deposit], ["Rebates", f.rebates], [lease ? "Tax/mo" : "Tax", f.salesTax], ["Doc", f.docFee], ...plans, ["GAP", f.gapIns], lease ? ["MF", f.moneyFactor] : ["APR", f.apr && f.apr + "%"], lease ? ["Residual", f.residual] : null, ["Term", f.term && f.term + " mo"], [lease ? "Monthly incl. tax" : "Payment", f.payment], lease ? ["Due at signing", f.dueAtSigning] : null].filter(Boolean).filter(([, v]) => v && v !== "0.00"); if (negEquityLive > 0) chips.push(["NEG. EQUITY", fwFmt(negEquityLive), "neg"]); if (!chips.length) return null; return (
CARRIED FROM DESK {chips.map(([l, v, kind]) => {l} {v})}
); })()} {/* Linked-deal strip */}
{deal ? ( <> {deal.id} {deal.buyer} {deal.vehicle}{deal.stock ? ` · Stk ${deal.stock}` : ""} {live.tradeDesc ? Trade: {live.tradeDesc} : null} {(() => { const st = window.DMS && window.DMS.dealStage ? window.DMS.dealStage(deal) : (deal.stage || "F&I"); return st === "Funded" ? "✓ FUNDED — DONE" : st === "Posted" ? "✓ POSTED TO ACCOUNTING" : st.toUpperCase(); })()} ) : ( {FW_NO_DEAL} )}
{/* Body = [scrolling form | fixed F-key column]. Overlap fix: the F-key column is OUTSIDE the scroll area and never shrinks, so no field can slide under it; the form's two columns wrap (Lender/Payment drops below Pricing) when the window is narrow (1366 @125 %, pop-outs). */}
{/* LEFT COLUMN — Pricing */}
{"Cash Price.....: "} desk setBreakdown("Cash Price")} title="How the amount financed is built from these figures">Details
{"Trade in Value.: "} desk
{"Trade In Payoff: "} desk {"to "}
0 ? fwRed : undefined, fontWeight: negEquityLive > 0 ? 700 : 400 }}>{"Neg. Equity....: "} 0 ? fwRed : "var(--jb-text-muted, #888)", background: negEquityLive > 0 ? "#FDECEC" : "transparent", border: "1px solid " + (negEquityLive > 0 ? fwRed : "transparent"), borderRadius: 4, padding: "0 8px", boxSizing: "border-box" }}>{fwFmt(negEquityLive)} {negEquityLive > 0 && rolled into the {isLease ? "cap cost" : "loan"}}
{"Rebates........: "} desk
{"Deposit........: "} setBreakdown("Deposit")} title="Down, rebates, trade equity and what is financed">Details
{fwDpa > 0 &&
{"Down pmt assist: "} −{fwFmt(fwDpa)} customer cash down {fwFmt(Math.max(0, fwNum(window.loadJbForm(FW_FORM).deposit) - fwDpa))}
} {fwFeeCust > 0 &&
{"Lender fee.....: "} {fwFmt(fwFeeCust)} the customer pays it — in the amount financed
}
{"Other Ins......: "}
{"Sales Tax......: "} window.k2bToast && window.k2bToast("Sales tax: manually entered. Rate override active.", "info")}>Entered
{"License........: "}
{"Warranties.....: "} {fwFmt(planTotalLive)} = {planCount} plan{planCount === 1 ? "" : "s"} below
{"Gap Ins........: "} desk {live.gapCode ? {live.gapCode}{live.gapName ? " — " + live.gapName : ""} : null}
{"Doc Fee........: "}
{isLease ? "ADJ. CAP COST..: " : "AMOUNT FINANCED: "} {negEquityLive > 0 && (incl. ${fwFmt(negEquityLive)} negative equity)}
{isLease ? "Rent Charge tot: " : "Finance Charge.: "}
{isLease && ( <>
{"Residual.......: "} {" %"}
{"Due at Signing.: "}
)}
{/* MIDDLE COLUMN — Lender + Payment */}
{"Lien Holder: "}
{isLease ? "LEASE — Monthly incl. tax" : "Payment of"}
for months
{isLease ? (
{"Money Fctr: "} {" APR eq: "}
) : null}
{isLease ? "Tax Rate %: " : "APR%......: "} {isLease ? : } {" First Pay: "}
{"Final Pay.: "}
{/* PROTECTION PRODUCTS — full-width plan table (was three 20-px "Svc Plan" boxes). Rows come from the desk's chosen option; Details / + Add product open the real catalog picker; prices edit in place and roll into `warranties`. */}
{/* RIGHT COLUMN — Action buttons stacked (own column; scrolls on short screens) */}
ESC-Finished alert(FW_HELP)}>F1-Help setShowLookup(true)}>F2-Lookup { go && go("crm"); window.k2bToast && window.k2bToast("Deals start in the CRM (+ New Lead → Desk It)", "info", 3500); }}>F3-CRM openPicker({})} title="Add a protection product from the F&I catalog">F4-Insurance F5-Recalculate { const pc = ensureCalc("Nothing was printed"); // T-03: print the figures for the inputs on screen now if (!pc.ok) { window.k2bToast && window.k2bToast(pc.msg, "warn", 6500); return; } if (pc.recalculated) window.k2bToast && window.k2bToast("Recalculated before printing — " + pc.toast, "ok", 4200); const d = window.loadJbForm(FW_FORM); const fmt = (k, lab) => `${lab}$${d[k] || "0.00"}`; const esc = (s) => String(s == null ? "" : s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); const negEq = Math.max(0, fwNum(d.tradePayoff) - fwNum(d.tradeValue)); let planRows = ""; for (let n = 1; n <= FW_MAX_PLANS; n++) if (!fwSlotEmpty(d, n)) planRows += `${esc(d[`plan${n}Code`])} — ${esc(d[`plan${n}Name`])}${d[`plan${n}Term`] ? ` (${esc(d[`plan${n}Term`])} mo)` : ""}$${d[`plan${n}Price`] || "0.00"}`; const html = `${window.dealerHeadHtml()}
${isLease ? "LEASE WORKSHEET (F&I)" : "FINANCE WORKSHEET"}
${new Date().toLocaleDateString()} · ${deal ? deal.id + " · " + deal.buyer + " · " : ""}Lender ${esc(d.lienName || "")}

Pricing

${fmt("cashPrice", "Cash Price")} ${fmt("tradeValue", "Trade-In Value" + (d.tradeDesc ? " — " + esc(d.tradeDesc) : ""))} ${fmt("tradePayoff", "Trade-In Payoff" + (d.tradeLender ? " → " + esc(d.tradeLender) : ""))} ${negEq > 0 ? `` : ""} ${fmt("rebates", "Rebates")} ${fmt("deposit", "Deposit")} ${fwDpa > 0 ? `` : ""} ${fwFeeCust > 0 ? `` : ""} ${fmt("salesTax", "Sales Tax")} ${fmt("license", "License")} ${fmt("docFee", "Doc Fee")} ${planRows} ${fwNum(d.gapIns) ? fmt("gapIns", "GAP" + (d.gapCode ? " — " + esc(d.gapCode) : "")) : ""} ${fwNum(d.otherIns) ? fmt("otherIns", "Other Insurance" + (d.otherInsDesc ? " — " + esc(d.otherInsDesc) : "")) : ""}
Negative Equity (financed)$${fwFmt(negEq)}
Down payment assistance (from the dealer)−$${fwFmt(fwDpa)}
Customer cash down$${fwFmt(Math.max(0, fwNum(d.deposit) - fwDpa))}
Lender fee$${fwFmt(fwFeeCust)}
${isLease ? "ADJUSTED CAP COST" : "AMOUNT FINANCED"}$${d.amountFinanced || "0.00"}

${isLease ? "Lease Terms" : "Payment Terms"}

${isLease ? `` : ``}
Money Factor (APR equiv.)${d.moneyFactor || ""} (${d.apr || "0.00"}%)
Residual Value$${d.residual || "0.00"} (${d.residualPct || ""}%)
Due at Signing$${d.dueAtSigning || "0.00"}
APR${d.apr || "0.00"}%
Term (months)${d.term || ""}
First Payment Date${d.firstPay || ""}
Final Payment Date${d.finalPay || ""}
${isLease ? "MONTHLY PAYMENT (incl. tax)" : "MONTHLY PAYMENT"}$${d.payment || "0.00"}
Customer Signature · Date
F&I Manager Signature
`; window.k2bPrint(html, { title: "Finance Worksheet" }); }}>🖨 F6-Print
go && go("forms")}>F7-Forms go && go("creditapp")}>F8-Credit App deal ? setEsignOpen(true) : (window.k2bToast && window.k2bToast("Load a deal first (F2-Lookup).", "warn"))} title="Text + email the customer a link to e-sign the purchase documents"> ✎ E-Sign Docs{deal && deal.esign ? " ✓" : ""} ⇪ Send to RouteOne{deal && deal.routeOne ? " · " + deal.routeOne.status : ""} {deal && deal.postedToGL ? "✓ Posted" : "F9-Post to Accounting"} F10-Save
{/* Modals */} {esignOpen && deal && window.k2bToast && window.k2bToast(r.msg, r.ok ? "ok" : "warn", 3600)} onClose={() => setEsignOpen(false)} />} {picker && setPicker(null)} focusCategory={picker.category} current={picker.current} title={picker.slot ? `Plan ${picker.slot} — pick a product (replaces the row)` : "Protection Products — pick from the F&I catalog"} />} {breakdown && setBreakdown(null)} title={`${breakdown} — deal breakdown${deal ? " · " + deal.id : ""}`} />} {showLookup && ( setShowLookup(false)} buttons={<> { setShowLookup(false); go && go("crm"); }}>Start in CRM ▸ setShowLookup(false)}>ESC-Cancel}> {(() => { const stageOf = (d) => window.DMS && window.DMS.dealStage ? window.DMS.dealStage(d) : (d.stage || "Desked"); // Unposted (open) deals first, then posted / funded history. const list = deals.filter(d => d.status !== "Dead").slice().sort((a, b) => (a.postedToGL ? 1 : 0) - (b.postedToGL ? 1 : 0)); const tone = { Desked: "#B26A00", Cash: "var(--k2b-green)", "F&I": "var(--k2b-navy)", Posted: "var(--k2b-green)", Funded: "var(--k2b-green)" }; return list.length === 0 ? (
No deals on file. Deals start in the CRM: + New Lead → Desk It → Push to Finance.
) : ( {list.map((d, i) => ( loadDeal(d)} style={{ background: i % 2 ? "#F4F4F4" : "#FFFFFF", cursor: "pointer", opacity: d.postedToGL ? 0.7 : 1 }}> ))}
Deal Buyer Vehicle Price Stage
{d.id} {d.buyer} {d.vehicle} {fwFmt(d.salePrice)} {stageOf(d)}
); })()}
)}
); }; // Shared with the Cash Deal (CashWorksheet.jsx.txt) — same plan rows, same picker. window.fwPlans = { MAX: FW_MAX_PLANS, isGap: fwIsGap, catalog: fwCatalog, catalogFind: fwCatalogFind, slotEmpty: fwSlotEmpty, total: fwPlanTotal, count: fwPlanCount, fieldsFromProducts: fwPlanFieldsFromProducts, productsFromForm: fwProductsFromForm, productPatch: fwProductPatch, sync: fwSyncWarranties, place: fwPlacePlan, remove: fwRemovePlan, removeGap: fwRemoveGap }; Object.assign(window, { FinanceWorksheet, NF, TF, FwProductPicker, FwBreakdownModal, FwPlanTable, fwDefaultApr });