);
// RC28 (fx, the reviewer's finding 3): the plan is the SERVER's — one stored configuration, VERSIONED with an effective date and audited
// (GET / POST /api/pay-plan). Saving makes a NEW version (the owner, Admin / F&I or the Office Manager); every other login reads it.
// The desk, the posting, the chargebacks and this screen all use it; a posted deal keeps the version it was posted with.
const PayPlanConfig = ({ cfg, act, onClose }) => {
const D = window.DMS;
const pp = (D && D.payPlanCached) ? D.payPlanCached() : {};
const may = !!pp.mayEdit;
const today = (() => { const t = new Date(); return t.getFullYear() + "-" + String(t.getMonth() + 1).padStart(2, "0") + "-" + String(t.getDate()).padStart(2, "0"); })();
const pctTxt = (x) => String(Math.round((+x || 0) * 10000) / 100);
const [f, setF] = React.useState({
usedPct: pctTxt(cfg.usedPct), newPct: pctTxt(cfg.newPct),
miniFloor: (+cfg.miniFloor || 0).toFixed(2), vscBonus: (+cfg.vscBonus || 0).toFixed(2), gapBonus: (+cfg.gapBonus || 0).toFixed(2),
appearanceBonus: (+cfg.appearanceBonus || 0).toFixed(2), fundedOnly: cfg.fundedOnly !== false, packCounts: cfg.packCounts !== false,
effectiveOn: today, reason: "",
});
const [busy, setBusy] = React.useState(false);
const [msg, setMsg] = React.useState(null);
const set = (k, v) => setF(s => ({ ...s, [k]: v }));
const save = async () => {
setBusy(true); setMsg(null);
const r = await D.setPayPlanConfig({ usedPctText: f.usedPct, newPctText: f.newPct, miniFloor: f.miniFloor, vscBonus: f.vscBonus, gapBonus: f.gapBonus, appearanceBonus: f.appearanceBonus,
fundedOnly: !!f.fundedOnly, packCounts: !!f.packCounts }, { effectiveOn: f.effectiveOn, reason: f.reason });
setBusy(false);
if (r.ok) { if (window.k2bToast) window.k2bToast(r.msg, /still scheduled/.test(r.msg || "") ? "warn" : "ok", /still scheduled/.test(r.msg || "") ? 12000 : 3000); onClose(); } else setMsg(r.msg);
};
const versions = (pp.versions || []).slice().reverse();
// RC28 fix round (audit FU4): each version says when it is in effect — the one in effect today, the ones still to come (each on its own day)
const vState = (v) => (v.version === (cfg.version || 0) ? "IN EFFECT" : v.effectiveOn > (pp.today || "") ? "takes effect " + v.effectiveOn : "");
return (
{may ? "Cancel" : "Close"}{may && {busy ? "Saving…" : "Save as a new version"}}>}>
In effect today: version {cfg.version || 0}{cfg.version ? " (from " + cfg.effectiveOn + ")" : " — the shipped default plan; the store's own plan has not been saved yet. Check it matches your real pay plan before the next deal posts."}
The desk, the posting, the product chargebacks and this screen all use this plan. A deal already posted keeps the version it was posted with.
{!may &&
Only the owner, Admin / F&I or the Office Manager can save a new version of the pay plan — your login can read it.