/* 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 (W26) — Payroll Process (PREdit).
// Full payroll workflow: time entry by employee, generate from time cards,
// review/edit gross, print checks (preview), post to employee record,
// post to GL, EFTPS, and the "special" un-post / register / journal queries.
const EMPLOYEES = [
{ id: "E-101", name: "BREWER, JARED", dept: "SAL", type: "Salary", rate: 4_166.66, comm: 8_420.00 },
{ id: "E-102", name: "MORALES, ELENA P.", dept: "SAL", type: "Salary", rate: 3_750.00, comm: 7_185.50,
deductions: [{ label: "Child Support (IWO)", amount: 175.00, payee: "MO Family Support Payment Center" }] },
{ id: "E-103", name: "WALSH, KATHRYN", dept: "SAL", type: "Salary", rate: 3_500.00, comm: 6_280.00 },
{ id: "E-104", name: "HOFFMAN-NGUYEN, A.", dept: "F&I", type: "Salary", rate: 4_500.00, comm: 9_402.45 },
{ id: "E-201", name: "WILSON, RICK", dept: "SVC", type: "FlatRate", rate: 32.50, hrs: 86 },
{ id: "E-202", name: "ALVAREZ, JOEL", dept: "SVC", type: "FlatRate", rate: 36.00, hrs: 82 },
{ id: "E-203", name: "THOMAS, DARRYL", dept: "SVC", type: "FlatRate", rate: 34.00, hrs: 78 },
{ id: "E-204", name: "CASTRO, MIGUEL", dept: "SVC", type: "FlatRate", rate: 30.00, hrs: 88 },
{ id: "E-301", name: "TANNER, BRYCE", dept: "SVC", type: "Hourly", rate: 24.00, hrs: 80, ot: 4 },
{ id: "E-401", name: "RILEY, DELANEY", dept: "PRT", type: "Hourly", rate: 22.50, hrs: 80, ot: 0 },
{ id: "E-402", name: "FITZGERALD, AUDREY", dept: "PRT", type: "Hourly", rate: 21.00, hrs: 78, ot: 2 },
{ id: "E-501", name: "KEYES, SHANNON", dept: "SVC", type: "Hourly", rate: 26.00, hrs: 80, ot: 4 },
{ id: "E-601", name: "BAILEY, MEGAN", dept: "ADM", type: "Hourly", rate: 23.00, hrs: 80, ot: 0 },
];
const pr$ = (n) => (n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
// Post a REAL journal entry to the shared GL — same path the DMS cascades use
// (DMS journal collection + the Accounting JEStore), so payroll postings move
// the chart-of-accounts balances like every other module.
const prPostJE = (entry) => {
if (typeof window === "undefined") return;
if (window.DMS && typeof window.DMS.upsertJournalEntry === "function") window.DMS.upsertJournalEntry(entry);
if (window.__k2bJEStore) window.__k2bJEStore.add(entry, { posted: true });
};
const prToday = () => new Date().toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "2-digit" });
// Compute gross/withholdings/net for one employee record
const computePay = (e) => {
let gross = 0;
if (e.type === "Salary") gross = e.rate + (e.comm || 0);
else if (e.type === "FlatRate") gross = (e.hrs || 0) * e.rate;
else gross = (e.hrs || 0) * e.rate + (e.ot || 0) * e.rate * 1.5;
const fed = gross * 0.16;
const fica = gross * 0.0765;
const state = gross * 0.0425;
const ret = gross * 0.04;
// Custom per-employee deductions (child support / garnishments / insurance
// / union dues) — editable in PREdit, paid out to payees as vendor checks.
const ded = (e.deductions || []).reduce((s, d) => s + (+d.amount || 0), 0);
const net = gross - fed - fica - state - ret - ded;
return { gross, fed, fica, state, ret, ded, net };
};
// Today as MM/DD/YYYY (audit P2-4 — no hard-coded demo dates).
const prTodayMDY = () => new Date().toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "numeric" });
const PayrollProcess = ({ onClose, go }) => {
const [payDate, setPayDate] = React.useState(prTodayMDY);
// Employees live in the shared DMS store (persisted, wiped with the store,
// editable in Settings → Staff & Techs). Demo list is only the lazy seed.
const [employees, _setEmployees] = React.useState(() =>
(window.DMS && DMS.employees) ? DMS.employees().map(e => ({ ...e })) : EMPLOYEES);
const setEmployees = (es) => { _setEmployees(es); if (window.DMS && DMS.setEmployees) DMS.setEmployees(es); };
const [modal, setModal] = React.useState(null); // 'time'|'gen'|'edit'|'checks'|'postEmp'|'postGL'|'postEFTPS'|'unpost'|'stubs'|'register'|'journal'|'setup'|'help'
const [stage, setStage] = React.useState("draft"); // draft | generated | printed | postedEmp | postedGL | done
const [flash, setFlash] = React.useState("");
const toast = (m) => { setFlash(m); setTimeout(() => setFlash(""), 2800); };
// Users → payroll: any login without a payroll record is added on open.
React.useEffect(() => {
// round-2 regress finding: only a role that may change pay plans (the office) auto-adds logins — Parts / Service stations no longer fire 21 refused writes
if (window.DMS && DMS.syncUsersToPayroll && (typeof DMS.can !== 'function' || DMS.can('acct'))) {
const r = DMS.syncUsersToPayroll();
if (r.added) { _setEmployees(DMS.employees().map(e => ({ ...e }))); toast(r.msg); }
}
}, []);
const needSetup = employees.filter(e => e.needsSetup && !(+e.rate) && !(+e.comm));
const totals = employees.reduce((s, e) => {
const p = computePay(e);
return {
gross: s.gross + p.gross,
net: s.net + p.net,
tax: s.tax + (p.fed + p.fica + p.state),
fed: s.fed + p.fed,
fica: s.fica + p.fica,
state: s.state + p.state,
ret: s.ret + (p.ret || 0),
ded: s.ded + (p.ded || 0),
};
}, { gross: 0, net: 0, tax: 0, fed: 0, fica: 0, state: 0, ret: 0, ded: 0 });
const isStage = (...ss) => ss.includes(stage);
const requires = (need) => {
const order = ["draft","generated","printed","postedEmp","postedGL","done"];
if (order.indexOf(stage) < order.indexOf(need)) {
toast(`Must ${need === "generated" ? "Generate Payroll" : need === "printed" ? "Print Checks" : need === "postedEmp" ? "Post to Employee" : "Post Checks to GL"} first.`);
return false;
}
return true;
};
const StageBadge = ({ on, label }) => (
{on ? "✓ " : ""}{label}
);
const stageDone = {
generated: ["generated","printed","postedEmp","postedGL","done"].includes(stage),
printed: ["printed","postedEmp","postedGL","done"].includes(stage),
postedEmp: ["postedEmp","postedGL","done"].includes(stage),
postedGL: ["postedGL","done"].includes(stage),
done: stage === "done",
};
return (
{}} onMax={() => {}} />
{/* Run flow — where you are in the payroll run */}
{["1 · Employees & hours", "2 · Deductions", "3 · Review checks", "4 · Print & post to GL"].map((s, i) => (
{s}
{i < 3 && → }
))}
Staff list: Settings → Staff & Techs · child support & garnishments: Edit → Deductions
{flash && (
✓ {flash}
)}
{needSetup.length > 0 && (
⚠ {needSetup.length} new employee{needSetup.length !== 1 ? "s" : ""} from Users need pay setup:
{needSetup.map(e => e.name).join(", ")}
setModal("edit")}>Set Pay →
)}
{/* Header: pay date + stage pills + KPIs */}
Payroll Date
setPayDate(e.target.value)}
style={{ background: "#FFFEF4", border: "1px solid #C8CCD2", borderRadius: 6,
padding: "4px 8px", width: 110, color: "var(--jb-text, #1C2530)",
fontFamily: "var(--jb-mono, monospace)", fontSize: 14, fontWeight: 700 }} />
setModal("time")} title="Employees & hours">🔍 Hours
setModal("setup")}>🔧 Setup
Esc-Close
{[["Employees", employees.length, "var(--jb-navy, #11337A)"],
["Gross Pay", "$" + pr$(totals.gross), "var(--jb-blue, #1B66A8)"],
["Taxes + Deductions", "$" + pr$(totals.tax + totals.ret + totals.ded), "#9A6700"],
["Net Pay", "$" + pr$(totals.net), "var(--jb-green, #16A34A)"]].map(([lab, val, c]) => (
))}
{/* Standard Functions */}
Run Payroll
setModal("gen")}>1 · Generate Payroll
requires("generated") && setModal("edit")}>2 · Edit Payroll
requires("generated") && setModal("checks")}>3 · Print Checks
requires("printed") && setModal("postEmp")}>4 · Post To Employee
requires("postedEmp") && setModal("postGL")}>5 · Post Checks to GL
requires("postedEmp") && setModal("postEFTPS")}>6 · Post EFTPS to GL
requires("postedGL") && setModal("payded")}>7 · Pay Deductions…
go && go("accounting")}>Go to GL →
Special
stageDone.postedGL ? setModal("unpost") : toast("Nothing posted to un-post yet.")}>
Unpost Payroll
setModal("stubs")}>Check Stubs
setModal("register")}>Check Register
setModal("journal")}>Payroll Journal
{/* Employee payroll preview list */}
Employee Payroll Preview · {employees.length}
{stage === "draft" ? "DRAFT — not yet generated" : stage.toUpperCase()}
Emp #
Name
Dept
Type
Hrs
Gross
Net Pay
{employees.map((e, i) => {
const p = computePay(e);
const ns = e.needsSetup && !(+e.rate) && !(+e.comm);
return (
{e.id}
{e.name}{ns && · needs pay setup }
{e.dept}
{e.type}
{e.type === "Salary" ? "—" : (e.hrs || 0) + (e.ot ? "+" + e.ot : "")}
{pr$(p.gross)}
{pr$(p.net)}
);
})}
—
{employees.length} employees
{pr$(totals.gross)}
{pr$(totals.net)}
{/* Quick menu (right rail) */}
Quick Menu
{[
["👥 Employees & hours", () => setModal("time")],
["▶ Generate payroll", () => setModal("gen")],
["📄 Reports", () => setModal("journal")],
["🏦 Post to GL", () => setModal("postGL")],
["🔧 Setup", () => setModal("setup")],
["← Back", () => onClose()],
].map(([it, fn]) => (
e.currentTarget.style.background = "var(--jb-surface-2, #F5F7FA)"}
onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
{it}
))}
New logins appear here automatically. Pay setup, child support & garnishments: Edit Payroll → Deductions .
{modal === "help" && setModal(null)} />}
{modal === "time" && setModal(null)}
onSave={(es) => { setEmployees(es); setModal(null); toast("Time entries saved."); }} />}
{modal === "gen" && setModal(null)}
onRun={() => { setStage("generated"); setModal(null); toast(`Payroll generated for ${employees.length} employees.`); }} />}
{modal === "edit" && setModal(null)}
onSave={(es) => { setEmployees(es); setModal(null); toast("Edits saved."); }} />}
{modal === "checks" && setModal(null)}
onPrint={(start) => { setStage("printed"); setModal(null); toast(`${employees.length} checks printed starting at #${start}.`); }} />}
{modal === "postEmp" && setModal(null)}
onPost={() => { setStage("postedEmp"); setModal(null); toast("Posted to employee YTD records."); }} />}
{/* RC33 (mpx): Post Checks to GL / EFTPS / Un-post are SERVER commands — the payroll run (PRL-) marks the commissions it pays PAID and the
chargebacks it takes off DEDUCTED in the same step; the tax deposit carries the real EFTPS / state confirmation (typed); Un-post reverses
THIS run (a linked entry) and puts its commissions back to owed. Nothing here writes a journal entry itself any more. */}
{modal === "postGL" && setModal(null)}
onPost={async () => {
const D = window.DMS;
if (!D || !D.postPayroll) { toast("Reload the DMS (Ctrl+F5) — payroll posts on the server."); return; }
const all = (D.commissions && D.commissions()) || [];
const comm = all.filter(c => c && c.kind !== "chargeback" && !c.paid && !c.reversed);
const cbs = all.filter(c => c && c.kind === "chargeback" && c.dueNextPayroll && !c.deducted);
const r = await D.postPayroll({ payDate, period: payDate, commissionIds: comm.map(c => c.id), chargebackIds: cbs.map(c => c.id),
employees: employees.map(e => { const p = computePay(e); return { id: e.id, name: e.name, gross: p.gross.toFixed(2), fed: p.fed.toFixed(2), fica: p.fica.toFixed(2), state: p.state.toFixed(2), ret: (p.ret || 0).toFixed(2), ded: (p.ded || 0).toFixed(2) }; }) });
if (r.ok) { setStage("postedGL"); setModal(null); try { window.__k2bLastPayrollRun = r.runId; } catch (e) {} }
toast(r.msg);
}} />}
{modal === "postEFTPS" && setModal(null)}
onPost={async (conf, stateConf) => {
const D = window.DMS;
const fedAmt = +(totals.fed + totals.fica).toFixed(2), stAmt = +(+totals.state || 0).toFixed(2);
const r1 = await D.payTaxDeposit({ kind: "federal", amount: fedAmt.toFixed(2), period: payDate, method: conf ? "eft" : "check", ref: conf || undefined, breakdown: { fit: totals.fed.toFixed(2), fica: totals.fica.toFixed(2) } });
if (!r1.ok) { toast(r1.msg); return; }
const r2 = stAmt > 0.004 ? await D.payTaxDeposit({ kind: "state", amount: stAmt.toFixed(2), period: payDate, method: stateConf ? "eft" : "check", ref: stateConf || undefined }) : { ok: true, msg: "" };
if (r2.ok) { setStage("done"); setModal(null); }
toast([r1.msg, r2.msg].filter(Boolean).join(" "));
}} />}
{modal === "payded" && setModal(null)}
onDone={(n, amt) => { setModal(null); toast(`${n} deduction check(s) written — $${pr$(amt)} — Dr 300A · Cr 202. See Check Writing register.`); }} />}
{modal === "unpost" && setModal(null)} onUnpost={async () => {
const D = window.DMS;
const runs = D.payrollRuns ? await D.payrollRuns() : [];
const last = runs.find(x => !x.reversed);
if (!last) { setModal(null); toast("No payroll run on the server to un-post."); return; }
const why = window.prompt(`Un-post payroll ${last.runId}? A reversing entry is posted and its commissions are owed again.\n\nReason:`, "");
if (!why || !why.trim()) return;
const r = await D.reversePayroll(last.runId, why.trim());
if (r.ok) { setStage("postedEmp"); setModal(null); }
toast(r.msg);
}} />}
{modal === "stubs" && setModal(null)} />}
{modal === "register" && setModal(null)} />}
{modal === "journal" && setModal(null)} />}
{modal === "setup" && setModal(null)} onSave={() => { setModal(null); toast("Payroll setup saved."); }} />}
);
};
// ─── Modal shell ───────────────────────────────────────────────────────────
const PrModal = ({ title, width = 620, children, onClose }) => (
{ 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, background: "#C0C0C0" }}>
{}} onMax={() => {}} />
{children}
);
// ─── Time entry ───────────────────────────────────────────────────────────
const PrTimeEntry = ({ employees, onClose, onSave }) => {
const [es, setEs] = React.useState(employees);
const updHrs = (id, field, v) => setEs(prev => prev.map(e => e.id === id ? { ...e, [field]: parseFloat(v) || 0 } : e));
return (
Hourly & Flat-Rate Employee Time — period ending current pay date
Emp #
Name
Dept
Type
Rate
Hrs
O.T. Hrs
{es.map(e => (
{e.id}
{e.name}
{e.dept}
{e.type}
{pr$(e.rate)}
{e.type === "Salary" ? — :
updHrs(e.id, "hrs", ev.target.value)}
style={{ width: "100%", textAlign: "right", background: "var(--k2b-yellow)",
border: "1px solid #808080", padding: "0 2px",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />}
{e.type === "Hourly" ? (
updHrs(e.id, "ot", ev.target.value)}
style={{ width: "100%", textAlign: "right", background: "var(--k2b-yellow)",
border: "1px solid #808080", padding: "0 2px",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
) : — }
))}
ESC-Cancel
onSave(es)}>F10-Save Time
);
};
// ─── Generate ──────────────────────────────────────────────────────────────
const PrGenerate = ({ payDate, totals, count, onClose, onRun }) => {
const [step, setStep] = React.useState(0);
const steps = [
"Load employee master file......",
"Apply time cards & flat-rate hrs.",
"Calculate gross / commissions...",
"Apply Fed / FICA / State / Ret..",
"Build pre-post payroll register..",
];
return (
Generating payroll for {payDate} — {count} employees
{steps.map((s, i) => (
{i < step ? "✓" : i === step ? "▶" : "·"}
{s}
))}
Gross total: ${pr$(totals.gross)} · Withholdings: ${pr$(totals.tax)} · Deductions: ${pr$(totals.ded)} · Net to checks: ${pr$(totals.net)}
ESC-Cancel
{step < steps.length
? setStep(step + 1)}>F8-Run Next Step
: F10-Generate }
);
};
// ─── Edit ─────────────────────────────────────────────────────────────────
const PrEditList = ({ employees, onClose, onSave }) => {
const [es, setEs] = React.useState(employees);
const [sel, setSel] = React.useState(0);
const e = es[sel];
const upd = (k, v) => setEs(prev => prev.map((x, i) => i === sel ? { ...x, [k]: v } : x));
const addEmp = () => {
const n = { id: "E-" + String(101 + es.length), name: "NEW, EMPLOYEE", dept: "SAL", type: "Hourly", rate: 0, hrs: 0, comm: 0, deductions: [] };
setEs(prev => [...prev, n]); setSel(es.length);
};
const delEmp = () => {
if (!e || !window.confirm(`Remove ${e.name} from payroll?`)) return;
setEs(prev => prev.filter((_, i) => i !== sel)); setSel(0);
};
return (
{es.map((x, i) => {
const p = computePay(x);
return (
setSel(i)}
style={{ display: "grid", gridTemplateColumns: "70px 1fr 90px",
background: i === sel ? "var(--k2b-navy)" : (i % 2 ? "#F4F4F4" : "#FFFFFF"),
color: i === sel ? "#FFFFFF" : "var(--k2b-blue-data-dk)" }}>
{x.id}
{x.name}
{pr$(p.net)}
);
})}
{e && (
{e.name} · {e.id}
Type:
{e.type}
Rate:
upd("rate", parseFloat(ev.target.value) || 0)}
style={{ width: 100, textAlign: "right", background: "var(--k2b-yellow)",
border: "1px solid #808080", padding: "0 4px",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
{e.type !== "Salary" && (
Hours:
upd("hrs", parseFloat(ev.target.value) || 0)}
style={{ width: 80, textAlign: "right", background: "var(--k2b-yellow)",
border: "1px solid #808080", padding: "0 4px",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
)}
{e.type === "Hourly" && (
O.T.:
upd("ot", parseFloat(ev.target.value) || 0)}
style={{ width: 80, textAlign: "right", background: "var(--k2b-yellow)",
border: "1px solid #808080", padding: "0 4px",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
)}
{e.type === "Salary" && (
Comm $:
upd("comm", parseFloat(ev.target.value) || 0)}
style={{ width: 100, textAlign: "right", background: "var(--k2b-yellow)",
border: "1px solid #808080", padding: "0 4px",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }} />
)}
{/* Editable deductions — child support / garnishments / insurance */}
DEDUCTIONS (per pay period)
{(e.deductions || []).map((d, di) => (
upd("deductions", e.deductions.map((x, j) => j === di ? { ...x, label: ev.target.value } : x))}
style={{ flex: 1, minWidth: 0, background: "var(--k2b-yellow)", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)", fontSize: 11 }} />
upd("deductions", e.deductions.map((x, j) => j === di ? { ...x, amount: parseFloat(ev.target.value) || 0 } : x))}
style={{ width: 56, textAlign: "right", background: "var(--k2b-yellow)", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)", fontSize: 11 }} />
upd("deductions", e.deductions.filter((x, j) => j !== di))}
style={{ border: 0, background: "transparent", color: "#FFB4B4", cursor: "pointer", fontWeight: 700 }}>×
))}
{(e.deductions || []).map((d, di) => (
pay to
upd("deductions", e.deductions.map((x, j) => j === di ? { ...x, payee: ev.target.value } : x))}
style={{ flex: 1, minWidth: 0, background: "#FFFEF4", border: "1px solid #808080", padding: "0 4px", fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)", fontSize: 11 }} />
))}
upd("deductions", [...(e.deductions || []), { label: "", amount: 0, payee: "" }])}>+ Add deduction
{(() => {
const p = computePay(e);
return (
<>
Gross: {pr$(p.gross)}
Federal: ({pr$(p.fed)})
FICA: ({pr$(p.fica)})
State: ({pr$(p.state)})
401k: ({pr$(p.ret)})
{p.ded > 0 &&
Deductions: ({pr$(p.ded)})
}
Net: {pr$(p.net)}
>
);
})()}
)}
+ Add Employee
Delete
ESC-Cancel
onSave(es)}>F10-Save Edits
);
};
// ─── Print Checks ──────────────────────────────────────────────────────────
const PrPrintChecks = ({ employees, payDate, onClose, onPrint }) => {
const [start, setStart] = React.useState(48211);
const [printer, setPrinter] = React.useState("LP1 — MICR Check Printer");
return (
{employees.length} checks · pay date {payDate}
Starting Check #:
{/* Audit A-06: draft while typing, parse on blur (JBLiveInt) — the
field can be cleared / retyped without snapping back. A blank or
zero entry keeps the previous starting number. */}
{ if (n > 0) setStart(n); }} width={110}
style={{ textAlign: "left", color: "var(--k2b-navy)" }} />
Printer.........:
setPrinter(e.target.value)}
style={{ flex: 1, background: "var(--k2b-yellow)", border: "1px solid #808080",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }}>
{["LP1 — MICR Check Printer","LP2 — Office HP LaserJet","LP3 — Service Lane"].map(p => {p} )}
Check sequence preview
{employees.map((e, i) => {
const p = computePay(e);
return (
#{start + i}
{e.name}
${pr$(p.net)}
);
})}
ESC-Cancel
onPrint(start)}>F9-Print {employees.length} Checks
);
};
// ─── Post to Employee record ──────────────────────────────────────────────
const PrPostEmployee = ({ count, onClose, onPost }) => (
Update YTD records for {count} employees
✓ Increment YTD gross, deductions, and net.
✓ Update YTD 401k, vacation accrual, PTO used.
✓ Stamp last-pay date and check number on each record.
✓ Roll period quarter-to-date totals for 941 reporting.
ESC-Cancel
F10-Post to Employees
);
// ─── Post to GL ───────────────────────────────────────────────────────────
const PrPostGL = ({ totals, onClose, onPost }) => (
Journal entry preview — payroll posting
Acct
Description
Debit
Credit
{[ // RC33 (mpx): the server's payroll map (routes/money-office.js — the accountant confirms the accounts)
["321", "Salaries / wages / commissions accrual relieved", totals.gross, 0],
["323", "Federal withholding + FICA — payable", 0, totals.fed + totals.fica],
["323B", "State withholding — payable", 0, totals.state],
["300B", "401(k) employee deferrals payable", 0, totals.ret],
["300A", "Other deductions payable", 0, totals.ded],
["202", "Operating cash — net checks issued", 0, totals.gross - totals.tax - totals.ret - totals.ded],
].filter(r => (r[2] || 0) > 0.004 || (r[3] || 0) > 0.004).map(([acct, desc, dr, cr], i) => (
{acct}
{desc}
{dr ? pr$(dr) : ""}
{cr ? pr$(cr) : ""}
))}
—
Totals · BALANCED
{pr$(totals.gross)}
{pr$(totals.gross)}
ESC-Cancel
F10-Post to GL
);
// ─── EFTPS ────────────────────────────────────────────────────────────────
const PrPayDeductions = ({ employees, onClose, onDone }) => {
// Group employee deductions by payee — one check per payee.
const byPayee = {};
employees.forEach(e => (e.deductions || []).forEach(d => {
if (!(+d.amount)) return;
const key = (d.payee || d.label || "Deduction payee").trim() || "Deduction payee";
(byPayee[key] = byPayee[key] || []).push({ emp: e.name, label: d.label, amount: +d.amount });
}));
const payees = Object.keys(byPayee);
const total = payees.reduce((s, p) => s + byPayee[p].reduce((x, d) => x + d.amount, 0), 0);
const payAll = async () => {
if (!window.DMS || !(window.DMS.postCheck || window.DMS.writeCheck)) return;
let n = 0, amt = 0;
for (const p of payees) { // one at a time: each check number is reserved on the server (shared check numbers)
const sum = +byPayee[p].reduce((x, d) => x + d.amount, 0).toFixed(2);
const memo = byPayee[p].map(d => `${d.label || "Deduction"} — ${d.emp}`).join("; ").slice(0, 120);
const r = window.DMS.postCheck ? await window.DMS.postCheck({ payee: p, amount: sum, acct: "300A", memo }) : window.DMS.writeCheck({ payee: p, amount: sum, acct: "300A", memo }); // RC33 (mpx): the payroll run credits deductions to 300A (one server map)
if (r.ok) { n++; amt += sum; } else if (window.k2bToast) window.k2bToast(r.msg, "warn", 5000);
}
onDone(n, amt);
};
return (
Remit withheld deductions — one check per payee (Dr 300A · Cr 202)
{payees.length === 0 ? (
No employee deductions set up. Add them in Edit Payroll → Deductions.
) : payees.map(p => (
{p} ${pr$(byPayee[p].reduce((x, d) => x + d.amount, 0))}
{byPayee[p].map((d, i) => (
{d.label || "Deduction"} — {d.emp} ${pr$(d.amount)}
))}
))}
{payees.length} check(s) · total ${pr$(total)} — checks land in the Check Writing register with the next sequence numbers.
ESC-Cancel
F10-Write Checks
);
};
const PrPostEFTPS = ({ totals, onClose, onPost }) => {
const [conf, setConf] = React.useState(""); // RC33 (mpx): the REAL confirmation numbers (never made up)
const [stateConf, setStateConf] = React.useState("");
const taxes = {
fed: totals.fed,
fica: totals.fica,
state: totals.state,
};
const liability = taxes.fed + taxes.fica + taxes.state;
return (
);
};
// ─── Unpost ───────────────────────────────────────────────────────────────
const PrUnpost = ({ onClose, onUnpost }) => (
⚠ This will create a reversing journal entry. Posting status drops back to "Posted to Emp".
ESC-Cancel
F10-Reverse Post
);
// ─── Check Stubs ──────────────────────────────────────────────────────────
const PrCheckStubs = ({ employees, payDate, onClose }) => {
const [sel, setSel] = React.useState(0);
const e = employees[sel];
const p = e && computePay(e);
return (
{employees.map((x, i) => (
setSel(i)}
style={{ padding: "2px 6px",
background: i === sel ? "var(--k2b-navy)" : "transparent",
color: i === sel ? "var(--k2b-yellow)" : "var(--k2b-blue-data-dk)" }}>
{x.id} {x.name}
))}
{e && (
{window.K2BStore ? window.K2BStore.branding().printName : ""} — PAYROLL EARNINGS STATEMENT
Pay Date: {payDate} · {e.name} · {e.id}
Earnings Current
Deductions Current
Regular / Salary {pr$(e.type === "Salary" ? e.rate : (e.hrs || 0) * e.rate)}
Federal W-H ({pr$(p.fed)})
O.T. / Commission {pr$(e.type === "Hourly" ? (e.ot || 0) * e.rate * 1.5 : (e.comm || 0))}
FICA ({pr$(p.fica)})
State W-H ({pr$(p.state)})
401(k) ({pr$(p.ret)})
Net Pay
${pr$(p.net)}
)}
ESC-Close
{ window.k2bPrint && window.k2bPrint(window.dealerHeadHtml() + 'PAY STUB
Bi-Weekly Pay Stub · Direct Deposit
Pay stub detail for the selected employee. Full earnings, taxes, deductions and YTD totals print here.
Employee Acknowledgement
Payroll Officer
', { title: "Pay Stub" }); window.k2bToast && window.k2bToast("Pay stub sent to printer.", "ok"); }}>🖨 F9-Print Stub
);
};
// ─── Check Register ───────────────────────────────────────────────────────
const PrCheckRegister = ({ employees, payDate, onClose }) => (
Check Register — pay date {payDate}
Check #
Emp #
Name
Gross
Tax
Net
{employees.map((e, i) => {
const p = computePay(e);
return (
{48211 + i}
{e.id}
{e.name}
{pr$(p.gross)}
{pr$(p.fed + p.fica + p.state)}
{pr$(p.net)}
);
})}
ESC-Close
{ window.k2bPrint && window.k2bPrint(window.dealerHeadHtml() + 'PAYROLL REGISTER
Bi-Weekly · ' + new Date().toLocaleDateString() + '
Employee-by-employee earnings, taxes, and net pay for the current pay period.
', { title: "Payroll Register" }); window.k2bToast && window.k2bToast("Payroll register sent to printer.", "ok"); }}>🖨 F9-Print Register
);
// ─── Payroll Journal ──────────────────────────────────────────────────────
const PrPayrollJournal = ({ totals, onClose }) => (
Period summary — distribution by department
{[
["SAL — Sales", totals.gross * 0.45, totals.tax * 0.45, totals.net * 0.45],
["F&I", totals.gross * 0.15, totals.tax * 0.15, totals.net * 0.15],
["SVC — Service", totals.gross * 0.28, totals.tax * 0.28, totals.net * 0.28],
["PRT — Parts", totals.gross * 0.08, totals.tax * 0.08, totals.net * 0.08],
["ADM — Admin", totals.gross * 0.04, totals.tax * 0.04, totals.net * 0.04],
].map(([d, g, t, n]) => (
{d}
{pr$(g)}
{pr$(t)}
{pr$(n)}
))}
ESC-Close
{ window.k2bPrint && window.k2bPrint(window.dealerHeadHtml() + 'PAYROLL JOURNAL ENTRY
Posting summary to GL accounts 011/021/022/023/025/321/323
', { title: "Payroll Journal" }); window.k2bToast && window.k2bToast("Payroll journal sent to printer.", "ok"); }}>🖨 F9-Print Journal
);
// ─── Setup ────────────────────────────────────────────────────────────────
const PrSetup = ({ onClose, onSave }) => {
const [pf, setPf] = React.useState({
period: "Semi-monthly",
fed: 16.00,
fica: 7.65,
state: 4.25,
ret: 4.00,
cashAcct: "41 — Operating Cash",
});
const upd = (k, v) => setPf(prev => ({ ...prev, [k]: v }));
return (
Payroll Defaults
Pay Period.....:
upd("period", e.target.value)}
style={{ background: "var(--k2b-yellow)", border: "1px solid #808080",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }}>
{["Weekly","Bi-weekly","Semi-monthly","Monthly"].map(p => {p} )}
Cash account...:
upd("cashAcct", e.target.value)}
style={{ background: "var(--k2b-yellow)", border: "1px solid #808080",
fontFamily: "'Courier Prime', monospace", color: "var(--k2b-navy)" }}>
{["41 — Operating Cash","40 — Payroll Checking","42 — Money Market"].map(p => {p} )}
Default withholding rates
{[["Fed","fed"],["FICA","fica"],["State","state"],["401(k)","ret"]].map(([lbl,k]) => (
{lbl} %........:
{/* Audit A-06: JBLiveDec keeps a text draft while focused so a
decimal point can be typed and the box can be cleared. */}
upd(k, n)} width={80} decimals={2}
style={{ color: "var(--k2b-navy)" }} />
))}
ESC-Cancel
F10-Save Setup
);
};
// ─── Help ─────────────────────────────────────────────────────────────────
const PrHelp = ({ onClose }) => (
Workflow
Enter time cards (🔍 icon next to date).
Generate Payroll — calculates gross/W-H/net.
Edit Payroll — manual adjustments by employee.
Print Checks — MICR printer to LP1.
Post to Employee — updates YTD on the master file.
Post Checks to GL — releases the journal entry.
Post EFTPS — files the 941 deposit.
ESC-Close
);
Object.assign(window, { PayrollProcess, EMPLOYEES });
// Lazy-seed source for DMS.employees() on non-blank stores.
window.__PR_EMPLOYEES_DEFAULT = EMPLOYEES;