/* 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 Network Solutions — Document Entry (DocEntry). // Faithful recreation of the Doc Entry screen + working journal-entry // workflow: // • Post-Ahead Y/N toggles // • Document Date is editable // • "Process Docs" opens a real journal-entry form for the selected // document type (pre-loaded with the canonical distribution) // • "Setup Journal" opens the per-journal distribution-rule editor const DOC_TYPES = [ ["CAR SALES", 10], ["SERVICE TICKETS", 30], ["SERVICE TICKETS", 31], ["BODY SHOP", 32], ["PARTS TICKETS", 35], ["Old Checking", 40], ["Current Checking", 41], ["CASH RECEIPTS", 50], ["DEPOSITS", 55], ["EFT DEPOSIT", 56], ["NEW VEHICLE PURCHASE", 70], ["USED VEHICLE PURCHASE",71], ["GENERAL PURCHASE", 75], ["GENERAL JOURNAL", 80], ["FC JOURNAL", 81], ]; // Canonical chart-of-accounts distribution per document type const JOURNAL_RULES = { 10: { name: "CAR SALES", ref: "CS", lines: [ { acct: "231", side: "CR", note: "New car inventory relief (cost)" }, { acct: "205", side: "DR", note: "Contracts in Transit" }, { acct: "400A",side: "CR", note: "Vehicle sales revenue" }, { acct: "310", side: "DR", note: "Floorplan payoff" }, ]}, 30: { name: "SERVICE TICKETS", ref: "ST", lines: [ { acct: "460A",side: "CR", note: "Customer labor revenue" }, { acct: "660A",side: "DR", note: "Cost — customer labor" }, { acct: "220", side: "DR", note: "A/R or cash collected" }, ]}, 31: { name: "SERVICE TICKETS", ref: "ST", lines: [ { acct: "462", side: "CR", note: "Warranty labor revenue" }, { acct: "263", side: "DR", note: "Warranty claims receivable" }, ]}, 32: { name: "BODY SHOP", ref: "BS", lines: [ { acct: "470", side: "CR", note: "Body shop labor" }, { acct: "220", side: "DR", note: "Insurance / customer A/R" }, ]}, 35: { name: "PARTS TICKETS", ref: "PT", lines: [ { acct: "242", side: "CR", note: "Parts inventory" }, { acct: "220", side: "DR", note: "Parts A/R or cash" }, ]}, 40: { name: "OLD CHECKING", ref: "CK", lines: [ { acct: "202B",side: "CR", note: "Checking — BNM Used Cars" }, ]}, 41: { name: "CURRENT CHECKING", ref: "CK", lines: [ { acct: "202", side: "DR", note: "Operating cash" }, ]}, 50: { name: "CASH RECEIPTS", ref: "CR", lines: [ { acct: "202", side: "DR", note: "Operating cash" }, { acct: "220", side: "CR", note: "A/R relieved" }, ]}, 55: { name: "DEPOSITS", ref: "DP", lines: [ { acct: "202", side: "DR", note: "Operating cash" }, { acct: "331", side: "CR", note: "Customer deposits" }, ]}, 56: { name: "EFT DEPOSIT", ref: "EFT",lines: [ { acct: "202", side: "DR", note: "Operating cash (EFT)" }, { acct: "205", side: "CR", note: "Contracts funded" }, ]}, 70: { name: "NEW VEHICLE PURCHASE", ref: "NP", lines: [ { acct: "231", side: "DR", note: "New vehicle inventory" }, { acct: "310", side: "CR", note: "Floorplan payable" }, ]}, 71: { name: "USED VEHICLE PURCHASE",ref: "UP", lines: [ { acct: "240", side: "DR", note: "Used vehicle inventory" }, { acct: "314", side: "CR", note: "Used floorplan — Bank of NM" }, ]}, 75: { name: "GENERAL PURCHASE", ref: "AP", lines: [ { acct: "242", side: "DR", note: "Parts / supplies" }, { acct: "300", side: "CR", note: "A/P trade" }, ]}, 80: { name: "GENERAL JOURNAL", ref: "JE", lines: [ { acct: "", side: "DR", note: "" }, { acct: "", side: "CR", note: "" }, ]}, 81: { name: "FC JOURNAL", ref: "FC", lines: [ { acct: "310", side: "DR", note: "Floorplan curtailment" }, { acct: "202", side: "CR", note: "Operating cash" }, ]}, }; // Common recurring General-Journal templates. Shown only when journal = 80. // Templates carry ACCOUNTS only — amounts are always keyed by the user. const GJ_TEMPLATES = [ { id: "depr", label: "Monthly depreciation accrual", desc: "Monthly depreciation — fixed assets", lines: [ { acct: "0831",side: "DR", note: "Depr — buildings & improvements", amt: 0 }, { acct: "351", side: "CR", note: "Accum depr — buildings", amt: 0 }, ]}, { id: "comm", label: "Sales commission accrual", desc: "Sales commission accrual", lines: [ { acct: "855", side: "DR", note: "F&I commission expense", amt: 0 }, { acct: "321", side: "CR", note: "Wages payable", amt: 0 }, ]}, { id: "vsc", label: "VSC / GAP income recognition", desc: "VSC income recognition", lines: [ { acct: "220", side: "DR", note: "VSC receivable", amt: 0 }, { acct: "806", side: "CR", note: "Finance income — new", amt: 0 }, ]}, { id: "recon", label: "Used recon — parts to inventory", desc: "Reclass — used recon (parts to inventory)", lines: [ { acct: "240", side: "DR", note: "Used vehicle inventory", amt: 0 }, { acct: "242", side: "CR", note: "Parts inventory", amt: 0 }, ]}, { id: "accrue", label: "Accrued expense (manual)", desc: "Accrued expense", lines: [ { acct: "", side: "DR", note: "Expense account", amt: 0 }, { acct: "332", side: "CR", note: "Accrued liabilities", amt: 0 }, ]}, { id: "blank", label: "Blank (2 lines)", desc: "", lines: [ { acct: "", side: "DR", note: "", amt: 0 }, { acct: "", side: "CR", note: "", amt: 0 }, ]}, ]; // ─── Persistence ───────────────────────────────────────────────────────── // Distribution rules are editable per-journal and persist to localStorage. const DOC_RULES_KEY = "jbrews-doc-rules-v1"; const _loadDocRules = () => { try { const r = localStorage.getItem(DOC_RULES_KEY); return r ? JSON.parse(r) : null; } catch (e) { return null; } }; const _saveDocRules = (rules) => { try { localStorage.setItem(DOC_RULES_KEY, JSON.stringify(rules)); } catch (e) {} }; // Hydrate JOURNAL_RULES from localStorage at load time if user has saved customizations (function _hydrateRules() { const saved = _loadDocRules(); if (saved && typeof saved === "object") { Object.keys(saved).forEach(k => { if (JOURNAL_RULES[k]) Object.assign(JOURNAL_RULES[k], saved[k]); }); } })(); // Next free document number for a journal prefix (CS-0001, JE-0002 …) based on // what is actually in the ledger — never a canned number. const docNextRef = (prefix) => { const all = (window.DMS && window.DMS.journalEntries ? window.DMS.journalEntries() : []); const re = new RegExp("^" + prefix + "-(\\d+)$"); const max = all.reduce((m, j) => { const x = String(j.id).match(re); return x ? Math.max(m, +x[1]) : m; }, 0); return prefix + "-" + String(max + 1).padStart(4, "0"); }; const DocEntry = ({ onClose, go }) => { const D = window.DMS; const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => (D && D.subscribe) ? D.subscribe(force) : undefined, []); const [postAhead, setPostAhead] = React.useState("N"); // Document date defaults to TODAY (or the last day of the open period when // today falls in a closed month) — never a hard-coded demo date. const [docDate, setDocDate] = React.useState(() => glDefaultEntryIso()); const [docType, setDocType] = React.useState(10); const [modal, setModal] = React.useState(null); // 'process' | 'setup' const [stack, setStack] = React.useState([]); // shared GL modal stack (JE detail / ledger / edit) const [flash, setFlash] = React.useState(""); const [viewPer, setViewPer] = React.useState(() => glOpenPeriod()); const toast = (msg) => { setFlash(msg); setTimeout(() => setFlash(""), 3200); }; const openPer = glOpenPeriod(); const docPer = String(docDate || "").slice(0, 7); const docSt = /^\d{4}-\d{2}$/.test(docPer) ? glPerState(docPer) : "bad"; // Every document posts through DMS.postJournalEntry — balanced, period // checked, audited, synced to every station. No drafts, no raw upserts. const handlePost = (entry) => { if (docSt === "ahead" && postAhead !== "Y") return { ok: false, msg: `${glPerLabel(docPer)} is after the open period (${glPerLabel(openPer)}). Set "Post Ahead" to Y to post into it.` }; const r = D.postJournalEntry({ id: entry.id, date: glUsFromIso(docDate), desc: entry.desc, ref: entry.ref || entry.id, jrn: entry.jrn, lines: entry.lines, src: "DocEntry" }); if (!r.ok) return r; setModal(null); toast(r.msg); setStack([{ t: "je", id: r.id }]); return r; }; const drafts = (typeof JEStore !== "undefined") ? JEStore.getSession().length : 0; const releaseDrafts = () => { const n = JEStore.releaseAll(); toast(n ? `${n} legacy draft(s) posted to the GL.` : "No draft could be posted (unbalanced or closed period)."); }; const docJEs = glJEsIn(viewPer).filter(j => j.src === "DocEntry"); return (
{flash && (
✓ {flash}
)}
{/* LEFT — fields + this period's Doc Entry postings */}
Open period: {glPerLabel(openPer)} ({glRange(openPer).startLabel} – {glRange(openPer).endLabel})
Do you wish to Post Ahead: Y allows dates after the open period
Default Document Date: setDocDate(e.target.value)} style={{ background: "#FFFFE1", border: "1px solid #808080", padding: "0 4px", color: "var(--k2b-blue-data-dk)", fontFamily: "'Courier Prime', monospace", fontSize: 15 }} /> {docSt === "bad" ? "pick a date" : docSt === "closed" ? `${glPerLabel(docPer)} is CLOSED` : docSt === "ahead" ? (postAhead === "Y" ? `POST-AHEAD into ${glPerLabel(docPer)}` : `${glPerLabel(docPer)} is ahead — Post Ahead = N`) : `→ ${glPerLabel(docPer)}`}
Document Type (Journal): {docType} {JOURNAL_RULES[docType]?.name}
{/* Real Doc Entry postings for the viewed period — each opens the JE */}
Doc Entry postings ({docJEs.length}) {" "}{glPerLabel(viewPer)}{" "}
{docJEs.length === 0 ? (
No journal entries in {glPerLabel(viewPer)} from Doc Entry yet. Pick a journal at right, then Process Docs.
) : ( {docJEs.map(p => ( setStack([{ t: "je", id: p.id }])} style={{ cursor: "pointer" }} title="Open this journal entry"> ))}
Ref Date Jrn Description Amount Status
{p.id} {p.date} {p.jrn} {p.desc} {glMoney(glJeTotal(p))} {glJeChips(p).map(([t, c]) => )}
)}
{/* MIDDLE — document-type list */}
{DOC_TYPES.map(([name, code]) => ( ))}
{/* RIGHT — action buttons */}
◀ Back To Menu setModal("process")}>▶ Process Docs setModal("setup")}>🔧 Setup Journal go && go("accounting")}>📒 Open Ledger {drafts > 0 && {`✓ Post ${drafts} Draft${drafts === 1 ? "" : "s"}`}}
{modal === "process" && ( setModal(null)} onPost={handlePost} /> )} {modal === "setup" && ( setModal(null)} onSave={(updatedLines) => { // Persist the new distribution rules JOURNAL_RULES[docType].lines = updatedLines; const all = {}; Object.keys(JOURNAL_RULES).forEach(k => { all[k] = { lines: JOURNAL_RULES[k].lines }; }); _saveDocRules(all); setModal(null); toast(`Journal ${docType} distribution rules saved. (Persisted.)`); }} /> )} {typeof GlModals !== "undefined" && }
); }; // ─── Process Docs dialog ───────────────────────────────────────────────── const ProcessDocsDialog = ({ journal, rule, date, postAhead, refNo, onClose, onPost }) => { // Pre-load lines from the rule const [lines, setLines] = React.useState(() => (rule?.lines || []).map(l => ({ acct: l.acct, dr: 0, cr: 0, note: l.note, hint: l.side })) ); const [err, setErr] = React.useState(""); const [desc, setDesc] = React.useState(rule?.name + " — " + date); const [vendor, setVendor] = React.useState(""); const [tmpl, setTmpl] = React.useState(""); const isGJ = journal === 80; const applyTemplate = (id) => { setTmpl(id); if (!id) return; const t = GJ_TEMPLATES.find(t => t.id === id); if (!t) return; if (t.desc) setDesc(t.desc); setLines(t.lines.map(l => ({ acct: l.acct, dr: l.side === "DR" && l.amt ? l.amt.toFixed(2) : 0, cr: l.side === "CR" && l.amt ? l.amt.toFixed(2) : 0, note: l.note, hint: l.side, }))); }; const setLine = (i, k, v) => setLines(arr => arr.map((l, j) => j === i ? { ...l, [k]: v } : l)); const drTotal = lines.reduce((s, l) => s + (parseFloat(l.dr) || 0), 0); const crTotal = lines.reduce((s, l) => s + (parseFloat(l.cr) || 0), 0); const unknown = lines.filter(l => l.acct && ((parseFloat(l.dr) || 0) || (parseFloat(l.cr) || 0)) && !glAcctKnown(l.acct)).map(l => l.acct); const noAcct = lines.some(l => !l.acct && ((parseFloat(l.dr) || 0) || (parseFloat(l.cr) || 0))); const balanced = Math.abs(drTotal - crTotal) < 0.005 && drTotal > 0; const canPost = balanced && !unknown.length && !noAcct; const submit = () => { if (!canPost) return; const r = onPost({ id: refNo, ref: vendor ? refNo + " · " + vendor : refNo, jrn: journal, desc: vendor ? `${desc} — ${vendor}` : desc, lines: lines.map(l => ({ acct: String(l.acct).trim(), dr: parseFloat(l.dr) || 0, cr: parseFloat(l.cr) || 0, desc: l.note || "" })), }); if (r && !r.ok) setErr(r.msg); }; const acctOptions = (typeof glChart !== "undefined" ? glChart() : (typeof GL_ACCOUNTS !== "undefined" ? GL_ACCOUNTS : [])); return (
{ e.currentTarget.__k2bDownOnBackdrop = (e.target === e.currentTarget); }} onClick={(e) => { const d = e.currentTarget.__k2bDownOnBackdrop; e.currentTarget.__k2bDownOnBackdrop = false; if (d && e.target === e.currentTarget) onClose && onClose(); }}>
e.stopPropagation()} className="k2b-window" style={{ width: 820, background: "#C0C0C0" }}> {}} onMax={() => {}} />
Reference..: {refNo}
Date.....: {date} {postAhead && POST-AHEAD}
Jrn.....: {journal}
Description: setDesc(e.target.value)} style={{ flex: 1, background: "var(--k2b-yellow)", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
{(journal === 70 || journal === 71 || journal === 75 || journal === 81) && (
Vendor/Stk: setVendor(e.target.value)} placeholder={journal === 75 ? "Vendor name" : "Stock number"} style={{ width: 240, background: "var(--k2b-yellow)", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
)} {isGJ && (
Template..: templates pre-fill the distribution; you can still edit before posting
)} {/* Distribution */}
Acct
Description / Note
Debit
Credit
{lines.map((l, i) => (
setLine(i, "acct", e.target.value)} list="acct-list" style={{ width: "100%", background: "var(--k2b-yellow)", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)", fontWeight: 700 }} />
setLine(i, "note", e.target.value)} placeholder={l.hint === "DR" ? "Debit line note…" : "Credit line note…"} style={{ width: "100%", background: "#FFFFFF", border: "1px solid #C0C0C0", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
setLine(i, "dr", e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: "100%", textAlign: "right", background: l.hint === "DR" ? "var(--k2b-yellow)" : "#FFFFFF", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
setLine(i, "cr", e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: "100%", textAlign: "right", background: l.hint === "CR" ? "var(--k2b-yellow)" : "#FFFFFF", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
))} {/* Totals row */}
—
Totals · {balanced ? BALANCED : {drTotal || crTotal ? `Out of balance by $${Math.abs(drTotal - crTotal).toFixed(2)}` : "enter amounts"}}
{drTotal.toFixed(2)}
{crTotal.toFixed(2)}
{(unknown.length > 0 || noAcct || err) && (
{noAcct &&
⚠ Every amount needs an account.
} {unknown.length > 0 &&
⚠ Not in the chart of accounts: {unknown.join(", ")}
} {err &&
⚠ {err}
}
)} {acctOptions.map(a => )}
ESC-Cancel setLines(arr => [...arr, { acct: "", dr: 0, cr: 0, note: "" }])}>F3-Add Line { // Auto-balance: copy DR total into the first empty CR (or vice versa) setLines(arr => { const dr = arr.reduce((s, l) => s + (parseFloat(l.dr) || 0), 0); const cr = arr.reduce((s, l) => s + (parseFloat(l.cr) || 0), 0); const diff = dr - cr; if (Math.abs(diff) < 0.005) return arr; const idx = diff > 0 ? arr.findIndex(l => !(parseFloat(l.cr) > 0)) : arr.findIndex(l => !(parseFloat(l.dr) > 0)); if (idx < 0) return arr; const next = [...arr]; next[idx] = { ...next[idx], [diff > 0 ? "cr" : "dr"]: Math.abs(diff).toFixed(2) }; return next; }); }}>F4-Balance
); }; // ─── Setup Journal dialog ───────────────────────────────────────────────── const SetupJournalDialog = ({ journal, rule, onClose, onSave }) => { const [lines, setLines] = React.useState(() => (rule?.lines || []).map(l => ({ ...l }))); const setLine = (i, k, v) => setLines(arr => arr.map((l, j) => j === i ? { ...l, [k]: v } : l)); return (
{ e.currentTarget.__k2bDownOnBackdrop = (e.target === e.currentTarget); }} onClick={(e) => { const d = e.currentTarget.__k2bDownOnBackdrop; e.currentTarget.__k2bDownOnBackdrop = false; if (d && e.target === e.currentTarget) onClose && onClose(); }}>
e.stopPropagation()} className="k2b-window" style={{ width: 680, background: "#C0C0C0" }}> {}} onMax={() => {}} />
{rule?.name} — default GL distribution
Account
Side
Default Note
{lines.map((l, i) => (
setLine(i, "acct", e.target.value)} style={{ width: "100%", background: "var(--k2b-yellow)", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)", fontWeight: 700 }} />
setLine(i, "note", e.target.value)} style={{ width: "100%", background: "#FFFFFF", border: "1px solid #C0C0C0", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
))}
setLines(arr => [...arr, { acct: "", side: "DR", note: "" }])}>F3-Add Line ESC-Cancel onSave(lines)} style={{ background: "var(--k2b-yellow)", fontWeight: 700 }}>F10-Save Rules
); }; Object.assign(window, { DocEntry, DOC_TYPES, JOURNAL_RULES, GJ_TEMPLATES });