/* 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 — Cash Deal (CashWind).
// The closing screen for a CASH deal (ONE PATH: CRM → Desk It → Push to Cash
// Deal → here → Post to Accounting → Funded). Mirrors FinanceWorksheet: the
// form persists field-by-field under formId "cashWorksheet", the linked deal
// comes from jb_fw_deal, and the deal record (DMS.deals) is the source of
// truth — the desk's push patched it and stamped _srcDealId/_srcRev.
//
// A cash deal has NO lender, NO amount financed and NO payment: the customer
// pays the balance at delivery. K2B-003 (RC18): what the customer OWES (Total
// Due), what was actually RECEIVED (Record Payment — date, amount, how paid)
// and the Balance Due are kept apart. Saving / printing never records money;
// posting books cash only for the recorded payments and is refused while a
// balance is still due (DMSCore cashDealFigures / recordDealReceipt / postDeal).
const CW_FORM = "cashWorksheet";
const CW_HELP = `Cash Deal (CashWind)\n\nESC Close\nF1 Help\nF2 Lookup deal (open Cash / Desked deals)\nF5 Recalculate sales tax\nF6 Print cash bill summary\nF7 Forms\nF10 Save (fields auto-persist as you type)\n\nRecord Payment — records money the customer actually paid (date, amount, how paid).\nPost to Accounting — books the deal once the payments recorded cover the Total Due.`;
// Local field wrappers bound to the cash form (NF/TF in FinanceWorksheet are
// hard-wired to the finance form, so these wrap JBField with CW_FORM).
const CNF = ({ field, value, width = 110, hi, sel, align = "right", readOnly, style, onValueChange }) => (
);
const CTF = ({ field, value, width = 110, hi, sel, uppercase = true }) => (
);
const cwNum = (x) => { const n = parseFloat(String(x ?? "").replace(/[^0-9.\-]/g, "")); return isNaN(n) ? 0 : n; };
const cwFmt = (n) => (+n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const cwCarried = { opacity: 0.62 };
const cwDeskTag = { marginLeft: 6, fontSize: 9, fontWeight: 700, letterSpacing: 0.5, color: "#0E7A37", background: "#E6F7EC", border: "1px solid #BFE3CB", borderRadius: 3, padding: "0 4px", textTransform: "uppercase" };
// Tax rate the desk uses (jb_desk_settings.taxRate), else the MO default.
const cwTaxRate = () => {
try { const s = JSON.parse(localStorage.getItem("jb_desk_settings") || "{}"); if (s && +s.taxRate > 0) return +s.taxRate; } catch (e) {}
return 9.537; // rate collected at the first store (matches the lease desk default)
};
// Protection-product plan rows (plan1..planN + GAP) — the SAME helpers the
// Finance Deal uses (FinanceWorksheet.jsx.txt → window.fwPlans). `warranties`
// is the computed plan total, kept under that name for the GL cascade.
const cwPlans = () => window.fwPlans;
const cwPlanTotal = (f) => { const P = cwPlans(); if (P) return P.total(f); return cwNum(f.warranties); };
// Totals off the form draft. Total Due = everything the customer owes for the
// car. `deposit` on this form is only the desk's PROPOSED down payment (shown for
// information) — money received comes from the deal's recorded receipts (cwMoney).
const cwTotals = (f) => {
const price = cwNum(f.cashPrice), tax = cwNum(f.salesTax), doc = cwNum(f.docFee), lic = cwNum(f.license);
const warranties = cwPlanTotal(f);
const products = warranties + cwNum(f.gapIns) + cwNum(f.other);
const tradeValue = cwNum(f.tradeValue), tradePayoff = cwNum(f.tradePayoff), rebates = cwNum(f.rebates);
const negEquity = Math.max(0, Math.round((tradePayoff - tradeValue) * 100) / 100);
const proposedDown = cwNum(f.deposit);
const totalDue = Math.round((price + tax + doc + lic + products + tradePayoff - tradeValue - rebates) * 100) / 100;
return { price, tax, doc, lic, products, warranties, tradeValue, tradePayoff, negEquity, rebates, proposedDown, totalDue };
};
// T-03 (round 2, T8a): the sales tax Save / Print / Post will use. A rate-based tax (the deal's saved tax, untouched on the
// screen) follows a price / trade change the way F5 does; an explicit $0.00 (tax-exempt) or a hand-typed tax is kept.
// null = the tax on the screen stands. The live Total Due / Balance Due use the same figure, so the screen never shows
// a total that Save / Print / Post would then change.
const cwTaxPlan = (f, deal) => {
if (!deal || deal.postedToGL) return null;
const r2 = (n) => Math.round((+n || 0) * 100) / 100;
const price = cwNum(f.cashPrice), trade = cwNum(f.tradeValue);
const moved = r2(price) !== r2(deal.salePrice) || r2(trade) !== r2(deal.tradeAllow);
if (!moved || !(r2(deal.tax) > 0) || r2(cwNum(f.salesTax)) !== r2(deal.tax)) return null;
const rate = +f.taxRate > 0 ? +f.taxRate : cwTaxRate();
return { tax: r2(Math.max(0, price - trade) * rate / 100), rate };
};
// K2B-003 (RC18): Total Due from the screen + RECEIVED from the deal's recorded payments (never typed here) → Balance Due.
const cwMoney = (f, deal) => {
const plan = cwTaxPlan(f, deal);
const t = cwTotals(plan ? { ...f, salesTax: plan.tax } : f);
const cf = deal && window.DMS && window.DMS.cashDealFigures ? window.DMS.cashDealFigures(deal) : null;
const received = cf ? cf.received : 0;
// RC27 (dsk): the dealer's down payment assistance (desk › Profit panel) is money the customer does not pay
const dpa = deal && window.DealCalc && Array.isArray(deal.costAdds) && deal.costAdds.length ? window.DealCalc.costAddsOf(deal).dpaCents / 100 : 0;
return { ...t, taxPlan: plan, received, dpa, balanceDue: Math.round((t.totalDue - dpa - received) * 100) / 100, receipts: cf ? cf.receipts : [],
legacy: !!(cf && cf.legacy), legacyCashDown: cf ? cf.legacyCashDown : 0, postedLegacy: !!(cf && cf.postedLegacy) };
};
// The figures this screen refuses to save / post / print (spec 6.3 valid-input gate; same wording style as the desk's).
const cwBadMsg = (M, what) => {
const tail = " " + (what || "Nothing was saved") + "; your entries are kept.";
if (M.totalDue < -0.005) return `Trade-in and rebates too high — they are $${cwFmt(-M.totalDue)} more than the price and charges — check the trade-in and rebates.` + tail;
if (M.balanceDue < -0.005) return `Received too much — the payments recorded are $${cwFmt(-M.balanceDue)} more than the total due ($${cwFmt(M.totalDue)}${M.dpa ? " less the dealer's $" + cwFmt(M.dpa) + " down payment assistance" : ""}) — check the price or the payments.` + tail;
return null;
};
const cwEsc = (s) => String(s == null ? "" : s).replace(/[<>&"]/g, (c) => ({ "<": "<", ">": ">", "&": "&", '"': """ }[c]));
const CashWorksheet = ({ 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 [formKey, setFormKey] = React.useState(0); // bump to remount fields after a programmatic save
const [, tick] = React.useReducer(n => n + 1, 0); // live totals as the user types
const D = window.DMS;
const deals = (D && D.deals) ? D.deals() : [];
const deal = deals.find(d => d.id === dealId) || null;
const stageOf = (d) => (D && D.dealStage) ? D.dealStage(d) : (d.stage || "Desked");
// Write the deal's figures into the cash form (single source of truth = the
// DMS deal record the desk pushed). Stamps _srcDealId/_srcRev (B-04).
const writeForm = (d) => {
// Products: the itemized list the desk pushed; a legacy deal that only
// carries the lumped vscSale / gapSale gets synthesized rows.
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 });
}
const P = cwPlans();
window.saveJbForm(CW_FORM, {
_srcDealId: d.id, _srcRev: +d.rev || 0,
dealType: "Cash",
cashPrice: cwFmt(d.salePrice), tradeValue: cwFmt(d.tradeAllow || 0),
tradePayoff: cwFmt(d.tradePayoff || 0), tradeAcv: cwFmt(d.tradeACV != null ? d.tradeACV : (d.tradeAcv != null ? d.tradeAcv : d.tradeAllow || 0)),
tradeDesc: d.tradeDesc || d.tradeYrMkMdl || d.tradeVeh || "", tradeLender: d.tradeLender || "",
negEquity: cwFmt(Math.max(0, (+d.tradePayoff || 0) - (+d.tradeAllow || 0))),
rebates: cwFmt(d.rebate || 0),
// K2B-003 (RC18): `deposit` here = the desk's PROPOSED down payment only (information). Money received is the
// deal's recorded receipts; a pre-RC18 cashDown (= the whole total due) is never shown as received.
deposit: cwFmt(d.proposedDown != null ? d.proposedDown
: (d.postedToGL || Array.isArray(d.receipts) || (window.DMS && window.DMS.cashDealFigures && window.DMS.cashDealFigures(d).legacy) ? 0 : (d.cashDown || 0))),
salesTax: cwFmt(d.tax || 0), license: cwFmt(d.titleFee || 0), docFee: cwFmt(d.docFee || 0),
...(P ? P.fieldsFromProducts(products, cwFmt) : { ...(d.vscSale != null ? { warranties: cwFmt(d.vscSale) } : {}), ...(d.gapSale != null ? { gapIns: cwFmt(d.gapSale) } : {}) }),
...(d.otherSale != null ? { other: cwFmt(d.otherSale) } : {}),
});
};
// Mirror the linked deal ONLY when the form does not already hold this deal
// at this revision (open from the desk push, F2-Lookup, Deal Pipeline). A
// mere remount never overwrites edited figures; a newer push does (B-04).
React.useEffect(() => {
if (!deal) return;
const f = window.loadJbForm(CW_FORM);
const held = f._srcDealId || null, heldRev = f._srcRev != null ? +f._srcRev : null;
if (held === deal.id && heldRev === (+deal.rev || 0)) return;
writeForm(deal);
setFormKey(k => k + 1);
}, [deal && deal.id, deal && deal.rev]);
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 the Cash Deal.`, "ok");
};
const f = window.loadJbForm ? window.loadJbForm(CW_FORM) : {};
const T = cwMoney(f, deal);
// The deal patch every save/post writes. A cash deal: no lender, no amount
// financed, no payment. K2B-003 (RC18): totalDue = what is owed; received /
// cashDown = the RECORDED payments only (never the amount owed, never the
// desk's proposed down — that one is kept apart as proposedDown); receipts
// themselves are written only by Record Payment, never by a save.
const dealPatchFromForm = (fm) => {
const t = cwMoney(fm, deal);
const P = cwPlans();
return {
salePrice: t.price, tradeAllow: t.tradeValue, tradePayoff: t.tradePayoff, rebate: t.rebates,
...(fm.tradeAcv != null && fm.tradeAcv !== "" ? { tradeACV: cwNum(fm.tradeAcv), tradeAcv: cwNum(fm.tradeAcv) } : {}),
...(fm.tradeDesc != null ? { tradeDesc: String(fm.tradeDesc || "") } : {}),
tradeLender: String(fm.tradeLender || ""), negEquity: t.negEquity,
tax: t.tax, titleFee: t.lic, docFee: t.doc,
// Products: itemized `products` + the lumped vscSale / gapSale (+ costs,
// fiGross) the GL cascade reads — rebuilt from the plan rows + GAP.
...(P ? P.productPatch(fm) : { vscSale: cwNum(fm.warranties), gapSale: cwNum(fm.gapIns) }),
otherSale: cwNum(fm.other),
totalDue: t.totalDue, received: t.received, cashDown: t.received, balanceDue: t.balanceDue,
proposedDown: t.legacy ? 0 : t.proposedDown, ...(t.legacy ? { legacyCashDown: t.legacyCashDown } : {}),
dealType: "Cash", amtFinanced: 0, lender: "", apr: 0, term: 0, payment: 0, lienholder: null,
};
};
const noDealMsg = "No deal loaded. Deals start in the CRM → Desk It → Push to Cash Deal.";
// F5 — recompute sales tax from (price − trade) × rate.
const recalcTax = () => {
const fm = window.loadJbForm(CW_FORM);
const rate = cwTaxRate();
const taxable = Math.max(0, cwNum(fm.cashPrice) - cwNum(fm.tradeValue));
const tax = +(taxable * rate / 100).toFixed(2);
window.saveJbForm(CW_FORM, { ...fm, salesTax: cwFmt(tax), taxRate: String(rate) });
setFormKey(k => k + 1);
window.k2bToast && window.k2bToast(`Sales tax recalculated — $${cwFmt(tax)} on $${cwFmt(taxable)} taxable @ ${rate.toFixed(3)}%.`, "ok", 4200);
};
// T-03: Save / Post / Print work from the screen AS IT IS NOW (the totals are live). A rate-based sales tax follows a
// price / trade change the way F5 would; an explicit $0.00 (tax-exempt) or a hand-typed tax is kept. Returns the new tax or null.
const refigureTax = () => {
const plan = cwTaxPlan(window.loadJbForm(CW_FORM), deal); // the same figure the live totals already show
if (!plan) return null;
window.saveJbForm(CW_FORM, { salesTax: cwFmt(plan.tax), taxRate: String(plan.rate) });
return plan.tax;
};
// K2B-003 (RC18): Record Payment — the ONLY way money received gets on the deal (DMS.recordDealReceipt).
const cwToday = () => { const n = new Date(), pad = (x) => String(x).padStart(2, "0"); return n.getFullYear() + "-" + pad(n.getMonth() + 1) + "-" + pad(n.getDate()); };
const [rcpt, setRcpt] = React.useState(null); // { id, date, amount, method, ref, err }
const rcptBusy = React.useRef(false);
const openRecord = () => {
if (!deal) { window.k2bToast && window.k2bToast(noDealMsg, "warn", 4200); return; }
if (deal.postedToGL) { window.k2bToast && window.k2bToast(`${deal.id} is posted — its money is on the books.`, "warn", 4200); return; }
if (D.can && !D.can("glEdit")) { window.k2bToast && window.k2bToast("Recording a customer payment is done by the office (Admin / F&I, Office Manager or Accounting).", "warn", 5200); return; }
// (round 2, T8b) A payment is checked against the SAVED deal. When the screen's total differs from it (a price typed and
// not saved yet), the screen is saved first — the same way Post and Print work from the screen as it is now — so the
// balance shown in the box and the balance the payment is checked against are always the same figure.
const scr = cwMoney(window.loadJbForm(CW_FORM), deal), sv = D.cashDealFigures ? D.cashDealFigures(deal) : null;
if (sv && Math.abs(scr.totalDue - sv.totalDue) > 0.005) {
const s = saveScreen();
if (!s.ok) { window.k2bToast && window.k2bToast(s.msg + " Record the payment after that.", "warn", 6500); return; }
window.k2bToast && window.k2bToast(`${deal.id} saved first${s.tax != null ? ` (sales tax re-figured to $${cwFmt(s.tax)})` : ""} — total due $${cwFmt(s.M.totalDue)}, balance due $${cwFmt(s.M.balanceDue)}. The payment is checked against these figures.`, "ok", 5200);
}
rcptBusy.current = false;
setRcpt({ id: "RCPT-" + deal.id.slice(-5) + "-" + Date.now().toString(36), date: cwToday(), amount: "", method: (D.RECEIPT_METHODS || ["Cash"])[0], ref: "", err: "" });
};
// RC21 (deals): the payment is recorded by the SERVER (POST /api/deals/receipt) — the box stays open (and the button busy) until it answers.
const saveReceipt = async () => {
if (!rcpt || !deal || rcptBusy.current) return;
rcptBusy.current = true;
setRcpt(x => x ? { ...x, busy: true, err: "" } : x);
const r = await D.recordDealReceipt(deal.id, { id: rcpt.id, date: rcpt.date, amount: rcpt.amount, method: rcpt.method, ref: rcpt.ref });
if (r && r.ok) {
if (r.rev != null) window.saveJbForm(CW_FORM, { _srcDealId: deal.id, _srcRev: r.rev }); // keep the screen's edits (B-04)
setRcpt(null);
window.k2bToast && window.k2bToast(r.msg, "ok", 4600);
} else { rcptBusy.current = false; setRcpt(x => x ? { ...x, busy: false, err: (r && r.msg) || "Not recorded." } : x); }
};
// A payment recorded by mistake is VOIDED (kept on the deal with the reason), never deleted.
const voidReceipt = async (rc) => {
if (!deal || !D.voidDealReceipt) return;
const why = window.prompt(`Void the $${cwFmt(rc.amount)} ${rc.method} payment of ${rc.date}? It stays on the deal, marked void.\n\nWhy is it being voided?`, "");
if (why == null) return;
const r = await D.voidDealReceipt(deal.id, rc.id, why);
if (r && r.ok && r.rev != null) window.saveJbForm(CW_FORM, { _srcDealId: deal.id, _srcRev: r.rev });
window.k2bToast && window.k2bToast((r && r.msg) || "Not voided.", r && r.ok ? "ok" : "warn", 4600);
};
// Product picker (shared with the Finance Deal — window.FwProductPicker / fwPlans)
const [picker, setPicker] = React.useState(null); // { slot?, category?, current? }
const openPicker = (opts) => { if (!cwPlans()) { window.k2bToast && window.k2bToast("Product catalog helpers not loaded (FinanceWorksheet).", "warn"); return; } setPicker(opts || {}); };
const onPickProduct = (p) => {
const r = cwPlans().place(CW_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} · $${cwFmt(p.retail)}.`, "ok", 3600);
};
const removePlan = (n) => { cwPlans().remove(CW_FORM, n); setFormKey(k => k + 1); };
const removeGap = () => { cwPlans().removeGap(CW_FORM); setFormKey(k => k + 1); };
const onPlanPriceChange = () => { if (cwPlans()) cwPlans().sync(CW_FORM); tick(); };
// `warranties` is COMPUTED from the plan rows — keep it in step on every (re)mount.
React.useEffect(() => {
const P = cwPlans(); if (!P) return;
const fm = window.loadJbForm(CW_FORM);
if (cwFmt(P.total(fm)) !== String(fm.warranties || "")) P.sync(CW_FORM);
}, [formKey]);
// F10 — patch the linked deal + re-stamp the revision (B-04). saveScreen() is the one save (F10 and Record Payment).
const saveScreen = () => {
if (cwPlans()) cwPlans().sync(CW_FORM);
if (!deal || !D || !D.updateDeal) return { ok: false, msg: noDealMsg, level: deal ? "warn" : "info", ms: 4200 };
// An already POSTED deal is never changed from here (RC18) — corrections go through Deal Backend / Accounting.
if (deal.postedToGL) return { ok: false, msg: `${deal.id} is posted — a posted deal is not changed here (corrections go through Deal Backend / Accounting). Nothing was saved.`, ms: 5200 };
const tax = refigureTax();
if (tax != null) setFormKey(k => k + 1);
const fm = window.loadJbForm(CW_FORM);
const M = cwMoney(fm, deal), bad = cwBadMsg(M);
if (bad) return { ok: false, msg: bad, ms: 6500 };
const r = D.updateDeal(deal.id, dealPatchFromForm(fm));
if (!(r && r.ok)) return { ok: false, msg: (r && r.msg) || "Save failed." };
window.saveJbForm(CW_FORM, { _srcDealId: deal.id, _srcRev: r.rev });
return { ok: true, tax, M, rev: r.rev, other: cwNum(fm.other) };
};
const saveNow = () => {
const s = saveScreen();
if (!s.ok) { window.k2bToast && window.k2bToast(s.msg, s.level || "warn", s.ms || 4200); return; }
const M = s.M;
window.k2bToast && window.k2bToast(`${deal.id} saved${s.tax != null ? ` — sales tax re-figured to $${cwFmt(s.tax)} for the new price` : ""} — total due $${cwFmt(M.totalDue)}, received $${cwFmt(M.received)}, balance due $${cwFmt(M.balanceDue)}${D.isOnline && D.isOnline() ? " (deal and server)" : ""}.`
+ (s.other > 0.005 ? ` Note: the "Other" charge ($${cwFmt(s.other)}) has no account in the GL Posting Map — the deal cannot be posted with it.` : ""), "ok", s.other > 0.005 ? 7000 : 4200);
};
// Post to Accounting — ONE shared gate (window.jbPostDeal), never DMS.postDeal.
const postToAccounting = async () => {
if (!D || !window.jbPostDeal) { window.k2bToast && window.k2bToast("Ledger offline.", "warn"); return; }
if (!deal) { window.k2bToast && window.k2bToast(noDealMsg, "warn", 4200); return; }
if (deal.postedToGL) { window.k2bToast && window.k2bToast(`${deal.id} is already posted.`, "warn", 4200); return; }
if (cwPlans()) cwPlans().sync(CW_FORM);
const tax = refigureTax();
if (tax != null) setFormKey(k => k + 1);
const fm = window.loadJbForm(CW_FORM);
const bad = cwBadMsg(cwMoney(fm, deal), "Nothing was posted");
if (bad) { window.k2bToast && window.k2bToast(bad, "warn", 6500); return; }
const u = D.updateDeal(deal.id, dealPatchFromForm(fm));
if (u && u.ok) window.saveJbForm(CW_FORM, { _srcDealId: deal.id, _srcRev: u.rev });
// ONE gate: a balance still due is a hard block ("Record the customer's payment before posting this cash deal …").
const r = await window.jbPostDeal(deal.id); // RC21: the server posts (or refuses with the reason — nothing changed)
if (r && r.ok) {
const posted = (D.deals() || []).find(d => d.id === deal.id);
window.saveJbForm(CW_FORM, { _srcDealId: deal.id, _srcRev: posted ? (+posted.rev || 0) : (u && u.rev) });
// Straight to the print packet — the Report Selection dialog opens
// pre-checked from the forms-sequencing rules for a cash deal.
setTimeout(() => {
const p = (D.deals() || []).find(d => d.id === deal.id) || deal;
window.k2bPrintSelectDeal && window.k2bPrintSelectDeal({ dealType: "Cash", newUsed: p.type || "", bank: "", state: "" });
}, 700);
}
};
const printBill = () => {
if (deal && !deal.postedToGL && refigureTax() != null) setFormKey(k => k + 1); // T-03: print the screen as it is now
const fm = window.loadJbForm(CW_FORM);
const t = cwMoney(fm, deal);
const bad = cwBadMsg(t, "Nothing was printed");
if (bad) { window.k2bToast && window.k2bToast(bad, "warn", 6500); return; }
const row = (lab, v) => `
${lab} $${cwFmt(v)} `;
const html = `${window.dealerHeadHtml ? window.dealerHeadHtml() : ""}
CASH BILL SUMMARY
${new Date().toLocaleDateString()}${deal ? " · " + deal.id + " · " + deal.buyer + " · " + (deal.vehicle || "") + (deal.stock ? " · Stk " + deal.stock : "") : ""}
Purchase
${row("Cash Price", t.price)}
${row("Sales Tax", t.tax)}
${row("Doc Fee", t.doc)}
${row("License / Title", t.lic)}
${(() => { const P = cwPlans(); if (!P) return cwNum(fm.warranties) ? row("Service Contract", cwNum(fm.warranties)) : "";
let out = ""; for (let n = 1; n <= P.MAX; n++) if (!P.slotEmpty(fm, n)) out += row(`${String(fm[`plan${n}Code`] || "").replace(/[<>&]/g, "")} — ${String(fm[`plan${n}Name`] || "").replace(/[<>&]/g, "")}`, cwNum(fm[`plan${n}Price`])); return out; })()}
${cwNum(fm.gapIns) ? row("GAP" + (fm.gapCode ? " — " + String(fm.gapCode).replace(/[<>&]/g, "") : ""), cwNum(fm.gapIns)) : ""}
${cwNum(fm.other) ? row("Other", cwNum(fm.other)) : ""}
${t.tradePayoff ? row("Trade Payoff" + (fm.tradeLender ? " → " + String(fm.tradeLender).replace(/[<>&]/g, "") : ""), t.tradePayoff) : ""}
${t.negEquity > 0 ? ` Negative equity (payoff over allowance) $${cwFmt(t.negEquity)} ` : ""}
${t.tradeValue ? row("Less: Trade-In Value" + (fm.tradeDesc ? " — " + String(fm.tradeDesc).replace(/[<>&]/g, "") : ""), -t.tradeValue) : ""}
${t.rebates ? row("Less: Rebates", -t.rebates) : ""}
TOTAL DUE $${cwFmt(t.totalDue)}
${t.dpa > 0 ? `Less: Down payment assistance (from the dealer) −$${cwFmt(t.dpa)} DUE FROM CUSTOMER $${cwFmt(t.totalDue - t.dpa)} ` : ""}
Payments received
${t.legacy ? `Received: not recorded (saved before RC18) — ` : ""}
${t.postedLegacy ? row("Received (booked when posted, before RC18)", t.received) : ""}
${t.receipts.map(r => row(`Received ${cwEsc(r.date)} — ${cwEsc(r.method)}${r.ref ? " #" + cwEsc(r.ref) : ""}`, r.amount)).join("")}
${!t.legacy && !t.postedLegacy && !t.receipts.length ? `No payment recorded yet — ` : ""}
RECEIVED $${cwFmt(t.received)}
BALANCE DUE $${cwFmt(t.balanceDue)}
${t.proposedDown > 0 && !t.receipts.length && !t.postedLegacy && !t.legacy ? `Desk down payment — proposed, not received ($${cwFmt(t.proposedDown)}) ` : ""}
Customer Signature · Date
Dealer Representative
`;
window.k2bPrint ? window.k2bPrint(html, { title: "Cash Bill Summary" }) : (window.k2bToast && window.k2bToast("Print engine not loaded.", "warn"));
};
const live = () => tick(); // any field edit → recompute the readouts
// the Record Payment box shows the balance of the SAVED deal — the figure recordDealReceipt checks the amount against
const rcDue = deal && D && D.cashDealFigures ? Math.max(0, D.cashDealFigures(deal).balanceDue) : T.balanceDue;
const posted = !!(deal && deal.postedToGL);
const funded = !!(deal && stageOf(deal) === "Funded");
return (
{/* Purpose banner */}
CASH DEAL
Finalize & post. Confirm price, tax & fees, add any products, Record Payment for each payment the customer actually makes, then Post to Accounting books the deal once nothing is left to pay. Dimmed figures carried from the Sales Desk — edit only to override.
{/* ONE PATH banner when no deal is loaded */}
{!deal && (
No deal open.
Deals start in the CRM — pick the lead → Desk It → Push to Cash Deal .
go && go("crm")}>Open CRM ▸
go && go("desking")}>Desking ▸
)}
{/* Linked-deal strip */}
{deal ? (
<>
{deal.id}
{deal.buyer}
{deal.vehicle}{deal.stock ? ` · Stk ${deal.stock}` : ""}
{funded ? "✓ FUNDED — DONE" : posted ? "✓ POSTED TO ACCOUNTING" : stageOf(deal).toUpperCase()}
>
) : (
{noDealMsg}
)}
{/* Body = [scrolling form | fixed F-key column] — the button column is
outside the scroll area so no field can slide under it; the two form
columns wrap on narrow windows (1366 @125 %, pop-outs). */}
{/* LEFT — pricing */}
{"Cash Price.....: "}
desk
{"Trade in Value.: "}
desk
{"Trade In Payoff: "}
desk
{"to "}
{/* (round 2) the trade's ACV — what it goes into used inventory at. Posting a cash deal needs it; allowance above ACV is
an over-allowance that reduces the sale (never cash). Shown once there is a trade. */}
{(cwNum(f.tradeValue) > 0.005 || cwNum(f.tradeAcv) > 0.005) && (() => {
const tv = cwNum(f.tradeValue), acv = cwNum(f.tradeAcv), missing = tv > 0.005 && !(acv > 0.005), over = Math.round((tv - acv) * 100) / 100;
return (
{"Trade ACV......: "}
{missing ? "enter the trade's ACV — needed to post" : over > 0.005 ? `over-allowance ${cwFmt(over)} (reduces the sale)` : over < -0.005 ? `under-allowance ${cwFmt(-over)} (adds to the sale)` : "allowance = ACV"}
);
})()}
0 ? "var(--jb-red, #B00020)" : undefined, fontWeight: T.negEquity > 0 ? 700 : 400 }}>{"Neg. Equity....: "}
0 ? "var(--jb-red, #B00020)" : "var(--jb-text-muted, #888)", background: T.negEquity > 0 ? "#FDECEC" : "transparent", border: "1px solid " + (T.negEquity > 0 ? "var(--jb-red, #B00020)" : "transparent"), borderRadius: 4, padding: "0 8px", boxSizing: "border-box" }}>{cwFmt(T.negEquity)}
{T.negEquity > 0 && owed on top of the price (in Total Due) }
{"Rebates........: "}
desk
{"Sales Tax......: "}
{T.taxPlan && Math.abs(T.taxPlan.tax - cwNum(f.salesTax)) > 0.005
? {`→ ${cwFmt(T.taxPlan.tax)} for the new price`}
: {f.taxRate ? `@ ${(+f.taxRate).toFixed(3)}%` : `F5 recalcs @ ${cwTaxRate().toFixed(3)}%`} }
{"Doc Fee........: "}
{"License/Title..: "}
{"Service Plans..: "}
{cwFmt(T.warranties)}
= {cwPlans() ? cwPlans().count(f) : 0} plan(s) below
{"GAP............: "}
desk
{f.gapCode ? {f.gapCode}{f.gapName ? " — " + f.gapName : ""} : null}
{"Other..........: "}
{"TOTAL DUE......: "}
{cwFmt(T.totalDue)}
{T.dpa > 0 &&
{"Down pmt assist: "}
−{cwFmt(T.dpa)}
due from the customer {cwFmt(T.totalDue - T.dpa)}
}
{/* K2B-003 (RC18): money RECEIVED = the payments recorded with Record Payment — never typed here, never the amount owed. */}
{"Received.......: "}
{T.legacy ? "not recorded" : cwFmt(T.received)}
0.005)}
title={posted ? "Posted — the deal's money is on the books" : !(T.balanceDue > 0.005) ? "Nothing left to pay" : "Record a payment the customer made"}>+ Record Payment…
{T.legacy && (
Received: not recorded (saved before RC18). The old Cash Deal screen counted the whole ${cwFmt(T.legacyCashDown)} as paid. Record what the customer actually paid with Record Payment .
)}
{T.postedLegacy && (
Posted before RC18 — the ${cwFmt(T.received)} received was booked when the deal posted.
)}
{T.receipts.length > 0 && (
{T.receipts.map(r => (
{r.date} {r.method}{r.ref ? " #" + r.ref : ""} {cwFmt(r.amount)}
{!posted && voidReceipt(r)}
style={{ minWidth: 22, padding: "0 5px", lineHeight: "14px", color: "var(--jb-red, #B00020)", fontWeight: 700 }}>✕ }
))}
)}
{deal && Array.isArray(deal.receipts) && deal.receipts.some(x => x && x.void) && (
{deal.receipts.filter(x => x && x.void).length} voided payment(s) kept on the deal for the record.
)}
{"BALANCE DUE....: "}
0.005 ? "var(--jb-red, #B00020)" : "var(--jb-green, #0E7A37)" }}>{cwFmt(T.balanceDue)}
{T.proposedDown > 0 && !T.receipts.length && !T.postedLegacy && !T.legacy && (
Desk down payment ${cwFmt(T.proposedDown)} — proposed, not received . Record it with Record Payment when the customer pays.
)}
{/* MIDDLE — hero readout */}
{"Deal Type..: "}
CASH
{"Lien Holder: "}
NONE — cash deal
Balance Due at Delivery
{cwFmt(T.balanceDue)}
Total due {cwFmt(T.totalDue)}{T.dpa > 0 ? <> − assistance {cwFmt(T.dpa)}> : null} − received {T.legacy ? "(not recorded)" : cwFmt(T.received)}
Price + tax + doc + license + products
+ trade payoff − trade value − rebates
= Total Due ; less payments recorded = Balance Due .
{/* PROTECTION PRODUCTS — plan rows + GAP, same block as the Finance Deal */}
{window.FwPlanTable && (
)}
{/* RIGHT — action buttons (own column; scrolls on short screens) */}
ESC-Close
alert(CW_HELP)}>F1-Help
setShowLookup(true)}>F2-Lookup
F5-Recalc Tax
🖨 F6-Print
go && go("forms")}>F7-Forms
{posted ? "✓ Posted" : "Post to Accounting"}
F10-Save
{posted && !funded && window.jbFundDeal && (
window.jbFundDeal(deal.id)} title="Cash deal — closes it out (no journal entry)">✓ Mark Funded
)}
{funded &&
DONE ✓
}
{rcpt && deal && (
setRcpt(null)}
buttons={<>{rcpt.busy ? "Recording…" : "Record Payment"}
setRcpt(null)}>Cancel >}>
Balance due ${cwFmt(rcDue)}
Date received
setRcpt({ ...rcpt, date: e.target.value, err: "" })} style={{ fontSize: 13, padding: "4px 6px" }} />
Amount
setRcpt({ ...rcpt, amount: e.target.value, err: "" })} onKeyDown={(e) => { if (e.key === "Enter") saveReceipt(); }}
style={{ fontSize: 13, padding: "4px 6px", textAlign: "right", fontFamily: "'Courier Prime', monospace" }} />
How paid
setRcpt({ ...rcpt, method: e.target.value, err: "" })} style={{ fontSize: 13, padding: "4px 6px" }}>
{(D.RECEIPT_METHODS || ["Cash"]).map(m => {m} )}
Check # / reference
setRcpt({ ...rcpt, ref: e.target.value, err: "" })} style={{ fontSize: 13, padding: "4px 6px" }} />
{rcpt.err && {rcpt.err}
}
Record money the customer has actually paid. Each payment is recorded once, on the server, with your name; it reaches the books when the deal is posted.
)}
{picker && window.FwProductPicker && (
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"} />
)}
{/* Deal lookup modal — open deals on the CASH leg (or still Desked) */}
{showLookup && (() => {
const list = deals.filter(d => !d.postedToGL && d.status !== "Dead" && ["Cash", "Desked"].includes(stageOf(d)));
return (
setShowLookup(false)}
buttons={<> { setShowLookup(false); go && go("crm"); }}>Start in CRM ▸ setShowLookup(false)}>ESC-Cancel >}>
{list.length === 0 ? (
No open cash deals. Deals start in the CRM → Desk It → Push to Cash Deal.
) : (
Deal
Buyer
Vehicle
Price
Stage
{list.map((d, i) => (
loadDeal(d)} style={{ background: i % 2 ? "#F4F4F4" : "#FFFFFF", cursor: "pointer" }}>
{d.id}
{d.buyer}
{d.vehicle}
{cwFmt(d.salePrice)}
{stageOf(d)}
))}
)}
);
})()}
);
};
Object.assign(window, { CashWorksheet });