/* 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. */ // K2B DMS — Profit & Loss / GM Operating Statement (report). // Builds a LIVE departmental P&L from posted journal entries, formatted like // the GM Dealer Financial Statement (Page 1): department Sales / Cost / Gross / // GP%, then the income waterfall (Total Gross → Expenses → Operating Profit → // Net Profit Before Tax → Income Tax → Net Income), with This-Month and // Year-to-Date columns side by side. Toggle account detail for the GL behind // each line. Mirrors the first store's real GM P&L (W26.03.04) structure. const pl$ = (n) => { const v = +n || 0; const s = Math.abs(v).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return v < 0 ? "(" + s + ")" : s; }; const plPct = (n) => (n == null || !isFinite(n)) ? "" : n.toFixed(1) + "%"; function plClassify(acct) { const coa = (typeof window !== "undefined" && window.GM_COA) || []; const a = String(acct); let hit = coa.find(e => String(e[0]) === a); if (!hit) { const base = a.replace(/[A-Za-z]+$/, ""); hit = coa.find(e => String(e[0]) === base); } if (!hit) { const b3 = a.replace(/[A-Za-z]+$/, "").slice(0, 3); hit = coa.find(e => String(e[0]).replace(/[A-Za-z]+$/, "") === b3); } if (hit) return { type: hit[2], title: hit[1], group: hit[4] }; return { type: null, title: "", group: "" }; } // Bucket a GM group / account into a GM statement department. function plDept(group, acct) { const g = (group || "").toLowerCase(); const a = String(acct); if (/new vehicle|new car|new truck/.test(g) || /^4[0-3]/.test(a) || /^6[0-3]/.test(a)) return "New Vehicle"; if (/used vehicle|used car|used truck/.test(g) || /^4[4-5]/.test(a) || /^6[4-5]/.test(a)) return "Used Vehicle"; if (/f ?& ?i|finance|insurance|service contract|protection|warranty income/.test(g) || /^80[0-9]/.test(a) || /^44[34]/.test(a)) return "Finance & Insurance"; if (/service|labor/.test(g) || /^46[0-3]/.test(a) || /^66[0-3]/.test(a)) return "Service"; if (/parts/.test(g) || /^46[6-7]/.test(a) || /^48/.test(a) || /^66[6-7]/.test(a) || /^68/.test(a)) return "Parts"; return "Other"; } function plDate(s) { if (!s) return null; if (/^\d{4}-/.test(s)) return new Date(s); const m = String(s).match(/(\d{1,2})\/(\d{1,2})\/(\d{2,4})/); if (!m) { const d = new Date(s); return isNaN(d) ? null : d; } let y = +m[3]; if (y < 100) y += 2000; return new Date(y, +m[1] - 1, +m[2]); } const PL_DEPT_ORDER = ["New Vehicle", "Used Vehicle", "Finance & Insurance", "Service", "Parts", "Other"]; // GM expense four-way split: Variable Selling / Personnel / Semi-Fixed / Fixed. function plExpenseBucket(group, title, acct) { const t = ((group || "") + " " + (title || "")).toLowerCase(); if (/commission|incentive|spiff|policy|delivery|advert|promot|variable|f ?& ?i pay|floor.?plan interest|sales comp/.test(t)) return "Variable Selling"; if (/salar|wage|payroll|benefit|insurance \(emp|pension|personnel|vacation|bonus/.test(t)) return "Personnel"; if (/supplies|laundry|travel|training|dues|data|telephone|freight|small tools|semi/.test(t)) return "Semi-Fixed"; return "Fixed"; // rent, utilities, depreciation, taxes, insurance, interest } const PL_EXP_ORDER = ["Variable Selling", "Personnel", "Semi-Fixed", "Fixed"]; // Aggregate the ledger for a date predicate → departmental sales/cost/gross, // expenses, income-tax, and the net waterfall. function plAggregate(jes, inRange) { const bal = {}; let jeCount = 0; let incomeTax = 0; jes.forEach(je => { if (!inRange(je)) return; jeCount++; (je.lines || []).forEach(l => { const a = String(l.acct); bal[a] = (bal[a] || 0) + (+l.cr || 0) - (+l.dr || 0); // credit-positive }); }); const depts = {}; const acctRows = []; PL_DEPT_ORDER.forEach(d => depts[d] = { sales: 0, cost: 0, gross: 0 }); const expBuckets = {}; PL_EXP_ORDER.forEach(b => expBuckets[b] = 0); let totalExp = 0; Object.keys(bal).forEach(a => { const { type, title, group } = plClassify(a); if (!type) return; if (type === "R") { const dep = plDept(group, a); const v = bal[a]; // revenue credit-positive depts[dep].sales += v; depts[dep].gross += v; acctRows.push({ acct: a, title, dep, kind: "sales", amt: v }); } else if (type === "COGS") { const dep = plDept(group, a); const v = -bal[a]; // cost debit-positive depts[dep].cost += v; depts[dep].gross -= v; acctRows.push({ acct: a, title, dep, kind: "cost", amt: v }); } else if (type === "X") { const v = -bal[a]; if (/income tax/i.test(title)) { incomeTax += v; } else { totalExp += v; const b = plExpenseBucket(group, title, a); expBuckets[b] += v; acctRows.push({ acct: a, title, dep: "Expense", bucket: b, kind: "exp", amt: v }); } } }); const totalSales = PL_DEPT_ORDER.reduce((s, d) => s + depts[d].sales, 0); const totalGross = PL_DEPT_ORDER.reduce((s, d) => s + depts[d].gross, 0); const netBeforeTax = totalGross - totalExp; const netIncome = netBeforeTax - incomeTax; return { depts, expBuckets, totalSales, totalGross, totalExp, incomeTax, netBeforeTax, netIncome, jeCount, acctRows }; } const ProfitLoss = ({ onClose, go }) => { const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => (window.DMS && window.DMS.subscribe) ? window.DMS.subscribe(force) : undefined, []); const [showAccts, setShowAccts] = React.useState(false); const D = window.DMS; const jes = D ? D.journalEntries() : []; // Statement period — defaults to the ledger's OPEN period; any month can be viewed. const [plPer, setPlPer] = React.useState(() => (typeof glOpenPeriod === "function" ? glOpenPeriod() : ((D && D.periodStatus && D.periodStatus().open) || new Date().toISOString().slice(0, 7)))); const plRange = (D && D.periodRange) ? D.periodRange(plPer) : null; // "now" = the last day of the statement period (YTD / prior-YTD run through it). const now = plRange ? plRange.end : (() => { const [y, m] = plPer.split("-").map(Number); return new Date(y, m, 0); })(); const inMTD = (je) => { const d = plDate(je.date); if (!d) return true; return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth(); }; const inYTD = (je) => { const d = plDate(je.date); if (!d) return true; return d.getFullYear() === now.getFullYear() && d <= now; }; const inPY = (je) => { const d = plDate(je.date); if (!d) return false; return d.getFullYear() === now.getFullYear() - 1 && (d.getMonth() < now.getMonth() || (d.getMonth() === now.getMonth() && d.getDate() <= now.getDate())); }; const mtd = plAggregate(jes, inMTD); const ytd = plAggregate(jes, inYTD); const py = plAggregate(jes, inPY); // prior-year-to-date (same calendar point last year) const hasPY = py.jeCount > 0; const monthName = now.toLocaleDateString("en-US", { month: "long", year: "numeric" }); // ── render helpers ── const hd = { padding: "3px 8px", fontSize: 11, color: "var(--k2b-maroon)", textAlign: "right", borderBottom: "1px solid var(--jb-border)", whiteSpace: "nowrap" }; const cell = { padding: "2px 8px", fontFamily: "var(--jb-mono, monospace)", fontSize: 12.5, textAlign: "right" }; // Department table row const DeptRow = ({ name, m, y, bold }) => ( {name} {pl$(m.sales)} {pl$(m.cost)} {pl$(m.gross)} {m.sales ? plPct(m.gross / m.sales * 100) : ""} {pl$(y.sales)} {pl$(y.cost)} {pl$(y.gross)} {y.sales ? plPct(y.gross / y.sales * 100) : ""} ); // Waterfall row (This Month / YTD / Prior YTD when available) const WfRow = ({ label, m, y, p, bold, color, top, indent }) => (
{label} {pl$(m)} {pl$(y)} {hasPY && {pl$(p)}}
); const printStmt = () => { const deptRows = PL_DEPT_ORDER.filter(d => mtd.depts[d].sales || ytd.depts[d].sales || mtd.depts[d].gross || ytd.depts[d].gross) .map(d => `${d}${pl$(ytd.depts[d].sales)}${pl$(ytd.depts[d].cost)}${pl$(ytd.depts[d].gross)}`).join(""); const html = `${window.dealerHeadHtml ? window.dealerHeadHtml() : ""}
PROFIT & LOSS — OPERATING STATEMENT
${monthName} · year-to-date through ${plRange ? plRange.endLabel : now.toLocaleDateString()} · prepared ${new Date().toLocaleDateString()} · ${ytd.jeCount} journal entries

Departmental Gross

${deptRows}
DepartmentSalesCostGross
TOTAL GROSS${pl$(ytd.totalSales)}${pl$(ytd.totalGross)}

Income Waterfall

Total Gross Profit${pl$(ytd.totalGross)}
Total Operating Expenses(${pl$(ytd.totalExp)})
NET PROFIT BEFORE TAX${pl$(ytd.netBeforeTax)}
Income Tax(${pl$(ytd.incomeTax)})
NET INCOME${pl$(ytd.netIncome)}
`; window.k2bPrint(html, { title: "P&L Operating Statement" }); }; const anyData = ytd.jeCount > 0; const shownDepts = PL_DEPT_ORDER.filter(d => mtd.depts[d].sales || ytd.depts[d].sales || mtd.depts[d].gross || ytd.depts[d].gross); return (
{typeof AcctPeriodPicker !== "undefined" ? : e.target.value && setPlPer(e.target.value)} />} {monthName} · YTD {now.getFullYear()} · {ytd.jeCount} JE 🖨 Print {go && go("gmacct")}>GM Statement ▸} {go && go("accounting")}>GL Inquiry ▸}
{!anyData ? (
No journal entries in {now.getFullYear()} through {monthName}. Post a deal, repair order, parts ticket, or payroll — the statement builds itself from the ledger.
) : (
{window.K2BStore ? window.K2BStore.branding().printName : ""}
Operating Statement · This Month ({monthName}) vs. Year-to-Date
{/* Departmental gross */}
{shownDepts.map(d => ( {showAccts && ytd.acctRows.filter(r => r.dep === d).sort((a, b) => b.amt - a.amt).map(r => ( ))} ))}
Department ──────── This Month ──────── ──────── Year-to-Date ────────
SalesCostGrossGP% SalesCostGrossGP%
{r.acct} · {r.title} ({r.kind}) {pl$(r.amt)}
{/* Income waterfall — with GM four-way expense split + prior-year */}
Income WaterfallThis MonthYear-to-Date {hasPY && Prior YTD}
{PL_EXP_ORDER.filter(b => mtd.expBuckets[b] || ytd.expBuckets[b] || py.expBuckets[b]).map(b => ( ))} = 0 ? "var(--jb-green)" : "var(--k2b-maroon)"} /> = 0 ? "var(--jb-green)" : "var(--k2b-maroon)"} />
Built live from posted journal entries, classified by the GM Standard Chart and bucketed into the GM statement departments (New Vehicle, Used Vehicle, F&I, Service, Parts). This mirrors the GM Dealer Financial Statement Page-1 Operating Report. For the full multi-page GM statement (balance sheet, pacing, schedules) use GM Statement ▸; for the line-by-line GL behind any figure use GL Inquiry ▸.
)}
); }; Object.assign(window, { ProfitLoss });