/* 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 — Accounts Receivable (acct 220 open-item sub-ledger). // Customer Accounts (statement + charges/payments), Work Accounts (master // maintenance), Processing (finance charges, letters, statements), and the // A/R Customer landing menu. All actions post to the GL via DMSCore. const arMoney = (n) => { const v = Number(n || 0); const s = Math.abs(v).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return v < 0 ? `($${s})` : `$${s}`; }; const AR_TYPE_NAME = { RTL: "Retail", WHL: "Wholesale", FLT: "Fleet", INT: "Internal", EMP: "Employee" }; const _arToast = (r) => Promise.resolve(r).then(x => { window.k2bToast && window.k2bToast(x.msg, x.ok ? "ok" : "warn", 3200); return x; }); // RC33 (mpx): charges / payments / memos / finance charges / refunds are SERVER commands (promises) // 4-bucket aging strip const ARAgingStrip = ({ buckets, balance }) => { const defs = [["0-30", "Current"], ["31-60", "31–60"], ["61-90", "61–90"], ["90+", "90+"]]; const tones = { "0-30": "var(--jb-green)", "31-60": "var(--jb-amber)", "61-90": "#C0651A", "90+": "var(--jb-red)" }; return (
{defs.map(([k, lbl]) => (
{lbl}
{arMoney(buckets[k])}
))}
Balance
{arMoney(balance)}
); }; const ARKindTag = ({ kind }) => { const map = { INV: { bg: "var(--jb-blue-tint)", fg: "var(--jb-blue)", t: "Charge" }, PAY: { bg: "var(--jb-green-tint)", fg: "var(--jb-green)", t: "Payment" }, FC: { bg: "#FBE4E6", fg: "var(--jb-red)", t: "Fin Chg" }, ADJ: { bg: "var(--jb-amber-tint)", fg: "#9A6700", t: "Adjust" }, }; const m = map[kind] || map.INV; return {m.t}; }; const arWindow = { width: "100%", maxWidth: 1180, height: "calc(100vh - 110px)", margin: "0 auto", display: "flex", flexDirection: "column", background: "var(--jb-surface)" }; /* ════════════════════════════════════════════════════════════════ A/R Customer landing menu (scan 3) ════════════════════════════════════════════════════════════════ */ const ARCustomerMenu = ({ go, onClose }) => ( ); /* ════════════════════════════════════════════════════════════════ Customer Accounts — statement browser + charge/payment/adjust ════════════════════════════════════════════════════════════════ */ const ARCustomerAccounts = ({ onClose, go }) => { const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => DMS.subscribe(force), []); const customers = DMS.arCustomers(); const [selAcct, setSelAcct] = React.useState(customers[0] ? customers[0].acct : null); const [q, setQ] = React.useState(""); const [modal, setModal] = React.useState(null); const filtered = customers.filter(c => !q || c.name.toLowerCase().includes(q.toLowerCase()) || c.acct.includes(q) || (window.DMS && window.DMS.custQueryHit ? window.DMS.custQueryHit(q, c.custNum, [c.phone]) : false)); // RC31 (fx31, F6): the account's customer by an old number / a phone const cust = customers.find(c => c.acct === selAcct) || filtered[0] || customers[0]; const aging = cust ? DMS.arAge(cust.acct) : null; // running-balance statement rows (chronological) const stmt = React.useMemo(() => { if (!cust) return []; const rows = [...(cust.txns || [])].sort((a, b) => new Date(a.date) - new Date(b.date)); let run = 0; return rows.map(t => { run += (t.charge || 0) - (t.credit || 0); return { ...t, balance: +run.toFixed(2) }; }).reverse(); }, [cust, cust && cust.txns.length, customers]); return (
{/* Left rail — customer list */}
setQ(e.target.value)} placeholder="Search name or acct #…" style={{ flex: 1, minWidth: 0, boxSizing: "border-box" }} /> setModal({ t: "new" })}>+ New
{filtered.map(c => { const b = DMS.arAge(c.acct).balance; const active = cust && c.acct === cust.acct; return (
setSelAcct(c.acct)} style={{ padding: "9px 12px", cursor: "pointer", borderBottom: "1px solid var(--jb-border)", background: active ? "var(--jb-blue-tint)" : "transparent", borderLeft: "3px solid " + (active ? "var(--jb-blue)" : "transparent") }}>
{c.name}
{c.acct} · {AR_TYPE_NAME[c.type] || c.type} 0 ? "var(--jb-text)" : "var(--jb-green)" }}>{arMoney(b)}
); })} {filtered.length === 0 &&
No matching accounts.
}
{/* Right — statement */}
{cust ? ( <>
{cust.name}
Acct {cust.acct} · {AR_TYPE_NAME[cust.type] || cust.type} · {cust.terms} {cust.contact ? <> · {cust.contact} : null} · {cust.phone}
{cust.addr}{cust.city ? `, ${cust.city}` : ""} · Credit limit {arMoney(cust.creditLimit)} · Tax {cust.taxCode}
setModal({ t: "pay" })}>+ Payment setModal({ t: "charge" })}>+ Charge setModal({ t: "adj" })}>Adjust setModal({ t: "stmt" })}>Statement setModal({ t: "refund" })} title="Pay a credit balance back to the customer (a check the server writes, or cash)">Refund credit
{stmt.map(t => ( ))} {stmt.length === 0 && }
DateTypeReference DescriptionChargePaymentBalance
{new Date(t.date).toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "2-digit" })} {t.ref} {t.desc} {t.charge ? arMoney(t.charge) : ""} {t.credit ? arMoney(t.credit) : ""} {arMoney(t.balance)}
No activity on this account.
) : (
👤
No A/R accounts yet
Add a charge-account customer to start tracking receivables, or import your existing accounts from the Import / Export tool.
setModal({ t: "new" })}>+ New Account go && go("uploadtool")}>Import Accounts
)}
ESC-Close go && go("arwork")}>F2-Work Accounts go && go("arproc")}>F3-Processing go && go("arreports")}>F5-Reports go && go("accounting")}>F8-GL Inquiry (220)
{modal && modal.t === "charge" && setModal(null)} />} {modal && modal.t === "pay" && setModal(null)} />} {modal && modal.t === "adj" && setModal(null)} />} {modal && modal.t === "stmt" && setModal(null)} />} {modal && modal.t === "refund" && setModal(null)} />} {modal && modal.t === "new" && setModal(null)} onCreated={(acct) => { setSelAcct(acct); setModal(null); }} />}
); }; /* ── dialogs ── */ const ARChargeDialog = ({ cust, onClose }) => { const [f, setF] = React.useState({ amount: "", desc: "", ref: "", taxCode: cust.taxCode, offsetAcct: "482" }); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const taxCodes = DMS.arTaxCodes(); return ( Cancel { const r = await _arToast(DMS.arPostCharge(cust.acct, f)); if (r.ok) onClose(); }}>Post Charge }>
Amount (pre-tax): set("amount", e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: 130 }} /> Reference: set("ref", e.target.value)} placeholder="RO / WP #" /> Description: set("desc", e.target.value)} /> Tax code: Offset acct: set("offsetAcct", e.target.value)} style={{ width: 90 }} />
Debits A/R (220), credits the offset account; tax (if any) credits Sales Tax Payable.
); }; const ARPaymentDialog = ({ cust, onClose }) => { const d = DMS.arDefaults(); const [f, setF] = React.useState({ amount: "", method: "Check", ref: "" }); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const fee = (f.method === "Card" && d.chargeCreditCardFee) ? (Number(f.amount || 0) * (d.creditCardFeePct || 0) / 100) : 0; return ( Cancel { const r = await _arToast(DMS.arTakePayment(cust.acct, f)); if (r.ok) onClose(); }}>Apply Payment }>
Amount: set("amount", e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: 130 }} /> Method: Reference: set("ref", e.target.value)} placeholder="Check # / auth" />
Posts to {f.method === "Card" ? `Credit Card journal ${d.creditCardJournal}` : `Deposit journal ${d.depositJournal} (${d.depositJournalName})`}; credits A/R (220). {fee > 0 ? <>
Card fee {d.creditCardFeePct}% = {arMoney(fee)} expensed to {d.creditCardFeeAcct}. : null}
); }; const ARAdjustDialog = ({ cust, onClose }) => { const [f, setF] = React.useState({ amount: "", desc: "", isCredit: true }); const set = (k, v) => setF(s => ({ ...s, [k]: v })); return ( Cancel { const r = await _arToast(DMS.arAdjust(cust.acct, f)); if (r.ok) onClose(); }}>Post Memo }>
Type: Amount: set("amount", e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: 130 }} /> Reason: set("desc", e.target.value)} />
); }; // RC33 (mpx): a CREDIT balance paid back to the customer — a check the server writes (Check Writing prints it; its void puts the credit back) or cash const ARRefundDialog = ({ cust, onClose }) => { const [f, setF] = React.useState({ amount: "", method: "check", reason: "" }); const set = (k, v) => setF(s => ({ ...s, [k]: v })); return ( Cancel { const r = await _arToast(DMS.arRefundCredit(cust.acct, f)); if (r.ok) onClose(); }}>Pay back }>
Amount: set("amount", e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: 130 }} /> How: Reason: set("reason", e.target.value)} />
Debits A/R (220) for the credit paid back; credits the bank (a check) or cash on hand.
); }; const ARStatementDialog = ({ cust, aging, stmt, onClose }) => { const d = DMS.arDefaults(); const order = ["90+", "61-90", "31-60", "0-30"]; let worst = "0-30"; order.forEach(b => { if (aging.buckets[b] > 0.005) worst = b; }); const dun = (DMS.arDunningMessages().find(m => m.bucket === worst) || {}).message || ""; return ( Close { window.k2bToast && window.k2bToast(`Statement for ${cust.name} queued to printer LP1.`, "ok"); onClose(); }}>Print }>
{window.K2BStore ? window.K2BStore.branding().printName : ""}
{window.K2BStore ? [window.K2BStore.branding().addr1, window.K2BStore.branding().addr2].filter(Boolean).join(" · ") : ""}
STATEMENT
{new Date().toLocaleDateString()}
Acct {cust.acct}
{cust.name}
{cust.addr}
{cust.city}
{[...stmt].reverse().map(t => ( ))}
DateRef DescriptionAmount
{new Date(t.date).toLocaleDateString("en-US", { month: "2-digit", day: "2-digit" })} {t.ref} {t.desc} {t.credit ? "-" + arMoney(t.credit) : arMoney(t.charge)}
{["0-30", "31-60", "61-90", "90+"].map(b => {b}: {arMoney(aging.buckets[b])})} Due: {arMoney(aging.balance)}
{dun ?
{dun}
: null}
); }; /* ════════════════════════════════════════════════════════════════ Work Accounts — master maintenance (add / edit) ════════════════════════════════════════════════════════════════ */ const ARWorkAccounts = ({ onClose, go }) => { const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => DMS.subscribe(force), []); const customers = DMS.arCustomers(); const [edit, setEdit] = React.useState(null); return (
Account master file — {customers.length} accounts
setEdit({ new: true, type: "RTL", terms: "Net 30", taxCode: DMS.arDefaults().defaultTaxCode || "0100", creditLimit: "" })}>+ New Account
{customers.map(c => { const bal = DMS.arAge(c.acct).balance; const over = bal > c.creditLimit && c.creditLimit > 0; return ( ); })}
AcctNameTypeTerms PhoneCredit LimitBalance
{c.acct} {c.name} {AR_TYPE_NAME[c.type] || c.type} {c.terms} {c.phone} {arMoney(c.creditLimit)} {arMoney(bal)}{over ? " ⚠" : ""} setEdit({ ...c })}>Edit
ESC-Close go && go("aracct")}>F2-Customer Accounts go && go("ardefaults")}>F6-A/R Defaults
{edit && setEdit(null)} />}
); }; const ARAccountDialog = ({ row, onClose, onCreated }) => { const [f, setF] = React.useState({ acct: row.acct || "", name: row.name || "", type: row.type || "RTL", contact: row.contact || "", phone: row.phone || "", addr: row.addr || "", city: row.city || "", terms: row.terms || "Net 30", creditLimit: row.creditLimit ?? "", taxCode: row.taxCode || "0100", }); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const types = DMS.arAccountTypes(); const taxCodes = DMS.arTaxCodes(); const save = () => { const r = row.new ? DMS.addARCustomer(f) : DMS.updateARCustomer(row.acct, f); _arToast(r); if (r.ok) { if (onCreated && r.acct) onCreated(r.acct); onClose(); } }; return ( Cancel {row.new ? "Create" : "Save"} }>
{row.new ? <>Acct # (auto): set("acct", e.target.value)} placeholder={DMS.arNextAcct()} style={{ width: 110 }} /> : null} Name: set("name", e.target.value)} /> Type: Contact: set("contact", e.target.value)} /> Phone: set("phone", e.target.value)} /> Address: set("addr", e.target.value)} /> City/ST/ZIP: set("city", e.target.value)} /> Terms: set("terms", e.target.value)} style={{ width: 130 }} /> Credit limit: set("creditLimit", e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: 110 }} /> Tax code:
); }; /* ════════════════════════════════════════════════════════════════ Processing — finance charges, statements, letters, cycle close ════════════════════════════════════════════════════════════════ */ const ARProcessing = ({ onClose, go }) => { const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => DMS.subscribe(force), []); const d = DMS.arDefaults(); const summary = DMS.arSummary(); const pastDueAccts = summary.rows.filter(r => r.pastDue > 0.005); const totalPastDue = pastDueAccts.reduce((s, r) => s + r.pastDue, 0); const lettersSent = DMS.arLetters().length; const Card = ({ icon, title, desc, stat, statTone, button, onClick, btnTone }) => (
{icon}
{title}
{desc}
{stat != null ?
{stat}
: null} {button}
); return (
End-of-cycle A/R processing. All actions post to the GL through the shared ledger and write to the audit trail.
${d.graceDays} days) to finance-charge income (${d.financeChargeAcct}), FC journal ${d.financeChargeJournal}.`} stat={`${pastDueAccts.length} accounts past due · ${arMoney(totalPastDue)}`} statTone={totalPastDue > 0 ? "var(--jb-red)" : "var(--jb-green)"} button="Run Finance Charges" btnTone onClick={() => _arToast(DMS.arAssessFinanceCharges())} /> _arToast(DMS.arSendLetters("31-60"))} /> window.k2bToast && window.k2bToast(`${summary.rows.length} statements queued to LP1.`, "ok")} /> window.k2bToast && window.k2bToast("A/R cycle closed — aging rolled to new period.", "ok")} />
ESC-Close go && go("arreports")}>F5-Reports go && go("ardefaults")}>F6-Defaults
); }; const arTh = { padding: "6px 8px", fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: "0.04em", color: "var(--jb-text-dim)", whiteSpace: "nowrap" }; const arThR = { ...arTh, textAlign: "right" }; const arThC = { ...arTh, textAlign: "center" }; const arTd = { padding: "6px 8px", color: "var(--jb-text)" }; const arTdMono = { ...arTd, fontFamily: "var(--jb-mono)", fontWeight: 700, color: "var(--jb-blue)" }; const arTdR = { ...arTd, textAlign: "right", fontFamily: "var(--jb-mono)" }; const arTdC = { ...arTd, textAlign: "center" }; const arForm = { display: "grid", gridTemplateColumns: "120px 1fr", rowGap: 9, columnGap: 10, alignItems: "center", fontSize: 13, color: "var(--jb-text)" }; const arHint = { marginTop: 12, fontSize: 11, color: "var(--jb-text-muted)", lineHeight: 1.5 }; Object.assign(window, { arMoney, ARAgingStrip, ARKindTag, AR_TYPE_NAME, ARCustomerMenu, ARCustomerAccounts, ARWorkAccounts, ARProcessing, arTh, arThR, arThC, arTd, arTdMono, arTdR, arTdC, arForm, arHint, arWindow, });