/* © 2026 K2B NETWORK SOLUTIONS LLC. All rights reserved. */ /* eslint-disable */ // ImpGl — RC36 (imp36): every import ties to the general ledger, a template for each, the deal history complete. // window.ImpGlTie the review's "Ties to the general ledger — every import": per destination of the run, each GL account it touches — the // list, the ledger, the difference, the likely causes (most likely first); a balance-sheet line may be ticked (a partial // list also needs "the list is complete") → the approval posts it against the conversion clearing account (390); a profit // & loss line is year-to-date information. The SERVER works every number out (lib/imp-gl.js); this screen only shows and asks. // window.ImpGlLive the same lines read from the live books (GET /api/import/gl-tie) — shown on Accounting › Reconcile Inventory // window.ImpTemplateGuide Upload tool › template: for every import its CSV, its workbook sheet, a made-up sample row and what each column means / // which GL account it ties to (templates/templates.json — the same file the template workbook is built from) // window.DealHistoryWindow Sales history (old deals): by year / new-used / salesperson, read back against the deal file's Summary_by_Year // (GET /api/import/deal-history) (function () { const D = () => window.DMS; const C = () => window.ImpCalc; const money = (c) => (C() ? C().fmt(Math.round(+c || 0)) : String((+c || 0) / 100)); const box = { background: "#FFF", border: "1px solid #C9D6E3", borderRadius: 4 }; const btn = (primary, extra) => ({ fontWeight: 700, padding: "5px 14px", background: primary ? "#FFE9B0" : undefined, ...(extra || {}) }); const errText = (e) => (e && e.body && e.body.error) || (e && e.message) || String(e || "unknown"); const num = { textAlign: "right", fontFamily: "'Courier Prime', monospace", whiteSpace: "nowrap" }; const narrow = () => (typeof window !== "undefined" && window.innerWidth < 760); const useNarrow = () => { const [n, setN] = React.useState(narrow()); React.useEffect(() => { const f = () => setN(narrow()); window.addEventListener("resize", f); return () => window.removeEventListener("resize", f); }, []); return n; }; /* ── one line (an account of one destination) ── */ const GlLine = ({ dest, l, run, may, ticks, setTicks, complete, setComplete, readOnly, n }) => { const k = dest + ":" + l.acct; const p = l.posting; const canTick = !readOnly && p && run && run.status === "waiting" && may && may.approve && (!p.needsConfirm || complete[k]); const col = (w, extra) => ({ flex: n ? "1 1 100%" : "0 0 " + w + "px", minWidth: 0, ...(extra || {}) }); return (
{l.acct} {l.name}{l.kind === "pl" ?
profit & loss — year to date, information
: null}
{l.what}{l.items ? · {l.items} item{l.items === 1 ? "" : "s"} : null}
list {l.noList ? "—" : money(l.listCents)}
ledger {money(l.glCents)}
{l.glWords}
{l.noList ? "" : <>difference {money(l.diffCents)}}
{l.state}{l.noPostWhy && l.diffCents && !l.info ? · no entry: {l.noPostWhy} : null}
{(l.causes || []).length > 0 && (
What this most likely means {l.causes.map((c, i) =>
{c.label}
    {(c.means || []).map((m, j) =>
  1. {m}
  2. )}
Fix: {c.fix}
)}
)} {Array.isArray(l.months) && l.months.length > 0 && (
By month ({l.months.length})
MonthDealsLedger {l.months.map((m) => {m.month}{money(m.listCents)}{money(m.glCents)})}
)} {p && !readOnly && (
{p.needsConfirm && } {ticks[k] && canTick ?
{p.holdWords}
: null}
)}
); }; /* ── the review's section: one card per destination ── */ const ImpGlTie = ({ glTie, run, may, onPostings, readOnly, testid }) => { const n = useNarrow(); const [ticks, setTicks] = React.useState({}); const [complete, setComplete] = React.useState({}); const [open, setOpen] = React.useState({}); React.useEffect(() => { setTicks({}); setComplete({}); }, [glTie]); const dests = (glTie && glTie.dests) || []; const postings = []; dests.forEach((d) => (d.lines || []).forEach((l) => { const k = d.dest + ":" + l.acct; if (l.posting && ticks[k] && (!l.posting.needsConfirm || complete[k])) postings.push({ kind: "gl-tie", dest: d.dest, account: l.acct, expectedDiffCents: l.diffCents, listComplete: l.posting.needsConfirm ? true : undefined, words: l.acct + " " + money(Math.abs(l.diffCents)) }); })); const sig = JSON.stringify(postings); React.useEffect(() => { if (onPostings) onPostings(postings); }, [sig]); // eslint-disable-line if (!glTie) return null; return (
Ties to the general ledger — every import{glTie.live ? " (the books today)" : glTie.staged ? " (as if approved)" : ""} as of {glTie.asOf} · per import, each GL account it touches: the list next to the ledger, the difference and what it most likely means. Balance-sheet differences may be ticked (posted against the conversion clearing account{glTie.conversionAcct ? " " + glTie.conversionAcct : ""}); profit & loss lines are year-to-date information.
{dests.map((d) => (
setOpen({ ...open, [d.dest]: open[d.dest] === false })}> {d.label} {(d.parts || []).join(", ")} · ties to {d.touches} {d.differ ? d.differ + " differ" : "agrees"}{d.offered ? " · " + d.offered + " entr" + (d.offered === 1 ? "y" : "ies") + " offered" : ""}
{open[d.dest] !== false && (
{d.words}
{(d.warnings || []).map((w, i) =>
{w.label}. {(w.means || [])[0]} Fix: {w.fix}
)} {(d.tiedAbove || []).length > 0 &&
Tied on the GL tie-out above: {d.tiedAbove.map((a) => `${a.acct} (${a.diffCents ? "off " + money(a.diffCents) : "agrees"})`).join(" · ")}
} {d.fileCheck && (
{d.fileCheck.agrees ? "✓" : "⚠"} The file's own totals ({d.fileCheck.sheet}): {d.fileCheck.words}
YearDealsNew/UsedSalesFinanced {d.fileCheck.rows.map((r) => {r.year}{r.imported.deals}{r.imported.deals !== r.file.deals ? " / file " + r.file.deals : ""}{r.imported.new}/{r.imported.used}{money(r.imported.salesCents)}{r.file.salesCents !== null && r.file.salesCents !== r.imported.salesCents ? " / file " + money(r.file.salesCents) : ""}{money(r.imported.financedCents)})}
)} {(d.lines || []).map((l) => )} {d.more ?
… {d.more} more account{d.more === 1 ? "" : "s"} differ (the first {d.lines.length} are shown, largest first).
: null}
)}
))} {!dests.length &&
No import part to tie.
}
); }; /* ── Reconcile Inventory: the same lines from the live books ── */ const ImpGlLive = ({ asOf }) => { const [t, setT] = React.useState(null); const [err, setErr] = React.useState(""); React.useEffect(() => { let on = true; (async () => { try { const r = await D()._apiGet("/import/gl-tie" + (asOf ? "?asOf=" + encodeURIComponent(asOf) : "")); if (on) { setT(r.glTie); setErr(""); } } catch (e) { if (on) setErr(errText(e)); } })(); return () => { on = false; }; }, [asOf]); if (err) return
Ties to the GL — every import: {err}
; if (!t) return
Loading the tie-out by import…
; return
; }; /* ── the template page: every import's CSV, sheet, sample row and meaning ── */ let _tpl = null; const loadTemplates = async () => { if (_tpl) return _tpl; const r = await fetch("templates/templates.json", { cache: "no-cache" }); if (!r.ok) throw new Error("templates/templates.json: " + r.status); _tpl = await r.json(); return _tpl; }; const ImpTemplateGuide = ({ onClose, focus }) => { const n = useNarrow(); const [t, setT] = React.useState(null); const [err, setErr] = React.useState(""); const [q, setQ] = React.useState(""); React.useEffect(() => { loadTemplates().then(setT).catch((e) => setErr(errText(e))); }, []); React.useEffect(() => { if (t && focus) { const el = document.querySelector('[data-testid="tg-dest-' + focus + '"]'); if (el) el.scrollIntoView({ block: "start" }); } }, [t, focus]); const list = t ? t.templates.filter((x) => !q || (x.title + " " + x.sheet + " " + x.dest + " " + x.ties).toLowerCase().indexOf(q.toLowerCase()) !== -1) : []; return (
Import templates — what each column means and which GL account it ties to setQ(e.target.value)} style={{ font: "inherit", padding: "3px 6px", width: 170 }} /> Whole workbook (.xlsx)
{err &&
⚠ {err}
} {!t && !err &&
Loading…
} {t &&
{t.intro}
} {list.map((x) => (
{x.order ? x.order + ". " : ""}{x.title} workbook sheet {x.sheet} {x.sheet}.csv sample row (made up)
{x.purpose}
Ties to the general ledger: {x.ties}
{["Column", "Required", "What goes here", "Format", "Sample (made up)", "GL account"].map((h) => )}{x.columns.map((c) => ( ))}
{h}
{c.name} {c.required ? "Yes" : ""} {c.meaning}{c.acs && c.acs.length ?
also read: {c.acs.join(", ")}
: null}
{c.format} {String(c.sample === undefined ? "" : c.sample)} {c.gl || ""}
))}
); }; const ImpTemplateGuideButton = ({ label, focus }) => { const [on, setOn] = React.useState(false); return <>{}{on && setOn(false)} />}; }; /* ── Sales history (old deals) ── */ const DealHistoryWindow = ({ onClose }) => { const n = useNarrow(); const [d, setD] = React.useState(null); const [err, setErr] = React.useState(""); const [tab, setTab] = React.useState("year"); const load = React.useCallback(async () => { try { const r = await D()._apiGet("/import/deal-history"); setD(r); setErr(""); } catch (e) { setErr(errText(e)); } }, []); React.useEffect(() => { load(); }, []); // eslint-disable-line const rows = !d ? [] : tab === "year" ? d.years : tab === "nu" ? d.byYearNu : d.bySalesperson; const ft = d && d.fileTotals; const body = (
Sales history {d ? `${d.deals} deal${d.deals === 1 ? "" : "s"} on file (imported history + K2B's posted deals; unwound left out)` : ""} {[["year", "By year"], ["nu", "By year · new / used"], ["sp", "By salesperson"]].map(([k, w]) => )}
{err &&
⚠ {err}
} {ft && tab === "year" &&
Compared with the deal file's {ft.sheet} (import {ft.runId}): a year marked ⚠ differs from the file.
}
{[tab === "sp" ? "Salesperson" : "Year", "Deals", "New", "Used", "Total sales", "Average", "Financed"].concat(tab === "year" && ft ? ["File"] : []).map((h) => )}{rows.map((g) => ( {tab === "year" && ft ? : null} ))}
{h}
{g.key} {g.deals} {g.new} {g.used} {money(g.salesCents)} {money(g.avgCents)} {money(g.financedCents)}{g.file ? (g.agrees ? "✓ agrees" : `⚠ file ${g.file.deals} · ${money(g.file.salesCents || 0)}`) : "— not in the file"}
The deals' GL tie-out (open items vs 205 / 303 / 324A / 302, sales and cost of sales year to date) is on Accounting › Reconcile Inventory and on each deal import's review.
); return (
{}} onMax={() => {}} /> {body}
); }; Object.assign(window, { ImpGlTie, ImpGlLive, ImpTemplateGuide, ImpTemplateGuideButton, DealHistoryWindow }); })();