/* © 2026 K2B NETWORK SOLUTIONS LLC. All rights reserved. */ /* eslint-disable */ // RoVoid — RC36 (lane rov36): Void RO (an open / held repair order) and Reverse closed RO, the station side. // // Both are SERVER commands (routes/ro-void.js): this file asks the server what will happen (GET /api/ro/void/preview), shows it in plain // words, sends the command with an Idempotency-Key (DMS._command — a dropped answer is looked up by its key on /api/ops/op/, never // sent twice), shows success ONLY from the committed answer and takes the server's records back. Nothing here changes stock, the books // or the repair order by itself. // window.RoVoidDialog({ ro, mode: "void" | "reverse", onClose }) More › Void RO… / More › Reverse closed RO… (ServiceTickets) // window.RoVoidBanner({ ro }) the red VOID / REVERSED line on a voided RO (who / when / why) // DMS.roCanVoid() · DMS.roCanReverse() · DMS.voidRO(roId, opts) · DMS.reverseRO(roId, opts) · DMS.roVoidPreview(roId) // Loaded after ServiceTickets.jsx.txt (index.html). (function () { const D = window.DMS; if (!D || !D._command) return; const OP = "/ops/op/"; const m$ = (c) => (c < 0 ? "-" : "") + "$" + (Math.abs(+c || 0) / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const offline = () => !!(D.isOnline && !D.isOnline()); const refresh = () => { try { D._mutate(() => {}); } catch (e) { /* nothing to redraw */ } }; async function send(path, roId, body, what) { if (offline()) return { ok: false, msg: `${what} needs the server. Connect to the server, then try again. Nothing was changed.` }; const saved = D._fopsSaveFirst ? await D._fopsSaveFirst("repairOrders", roId) : { ok: true, rev: undefined }; if (!saved.ok) return { ok: false, msg: saved.msg }; const key = body.idemKey || D._newIdemKey(path === "/ro/void" ? "rv" : "rr"); const b = Object.assign({}, body, { roId, baseRev: saved.rev }); delete b.idemKey; const r = await D._command(path, b, key, OP); if (!r.ok) { const e = r.err; const eb = (e && e.body) || {}; if (r.unknown) return { ok: false, unknown: true, key, msg: `The server did not answer. Before trying again, look at ${roId} on the board — if it shows VOID it was done. (The office can look up ${key}.)` }; if (eb.record && eb.rev != null) D._applyServerRecords([{ collection: "repairOrders", id: roId, rev: eb.rev, record: eb.record }]); refresh(); if (e && e.status === 426) return { ok: false, key, reload: true, msg: eb.error || "This station is running an older DMS. Reload the DMS (Ctrl+F5)." }; return { ok: false, key, code: eb.code, status: e && e.status, body: eb, msg: eb.error || (e && e.message) || `The server refused ${what}. Nothing was changed.` }; } D._applyServerRecords(r.res.records || []); refresh(); try { D._audit && D._audit(path === "/ro/void" ? "RO Voided" : "RO Reversed", `${roId} — ${r.res.message || "committed"}${r.recovered ? " (answer recovered after a dropped connection)" : ""}`); } catch (err) { /* best effort */ } const w = Array.isArray(r.res.warnings) && r.res.warnings.length ? " ⚠ " + r.res.warnings.join(" ") : ""; return Object.assign({}, r.res, { ok: true, key, recovered: !!r.recovered, msg: (r.res.message || "Done.") + w }); } Object.assign(D, { roCanVoid() { return !!(D.fopsCanPolicy && D.fopsCanPolicy()); }, // the Service Manager (raw role from the token) and the office roCanReverse() { return !!(D.fopsIsOffice && D.fopsIsOffice()); }, async roVoidPreview(roId) { try { return await D._apiGet("/ro/void/preview?roId=" + encodeURIComponent(roId)); } catch (e) { return Object.assign({ ok: false, msg: (e && e.body && e.body.error) || (e && e.message) || "The server could not say what the void would do." }, (e && e.body) || {}); } }, voidRO(roId, opts) { return send("/ro/void", roId, opts || {}, "voiding " + roId); }, reverseRO(roId, opts) { return send("/ro/reverse", roId, opts || {}, "reversing " + roId); }, async roReverseSendCode() { try { return await D._apiPost("/ro/reverse/confirm/send", {}); } catch (e) { return { ok: false, msg: (e && e.body && e.body.error) || "The code could not be sent." }; } }, async roReverseConfirm(method, code) { try { return await D._apiPost("/ro/reverse/confirm", { method, code }); } catch (e) { return { ok: false, code: e && e.body && e.body.code, msg: (e && e.body && e.body.error) || "That code did not work." }; } }, }); const red = "#B23A2E"; const box = { border: "1px solid var(--jb-border)", borderRadius: 8, padding: "8px 10px", marginTop: 8, background: "var(--jb-surface)" }; const h4 = { fontSize: 12, fontWeight: 800, textTransform: "uppercase", letterSpacing: ".04em", color: "var(--jb-text-dim)", margin: "10px 0 4px" }; const inp = { width: "100%", minHeight: 38, border: "1px solid var(--jb-border)", borderRadius: 6, padding: "6px 8px", fontSize: 14, fontFamily: "var(--jb-font)", boxSizing: "border-box" }; const Btn = ({ children, onClick, tone, disabled, hook }) => ( ); const Choice = ({ name, value, set, opts }) => (
{opts.map(o => )}
); function RoVoidDialog({ ro, mode, onClose }) { const reverse = mode === "reverse"; const [pv, setPv] = React.useState(null); const [busy, setBusy] = React.useState(false); const [msg, setMsg] = React.useState(null); const [reasonCode, setReasonCode] = React.useState(""); const [reason, setReason] = React.useState(""); const [techPay, setTechPay] = React.useState("remove"); const [so, setSo] = React.useState(""); const [dep, setDep] = React.useState(""); const [depMethod, setDepMethod] = React.useState("cash"); const [billed, setBilled] = React.useState({}); const [credit, setCredit] = React.useState(false); const [codeMethod, setCodeMethod] = React.useState("sms"); const [code, setCode] = React.useState(""); const [codeOk, setCodeOk] = React.useState(false); const load = async () => { const r = await D.roVoidPreview(ro.id); setPv(r); if (r && r.codeFresh) setCodeOk(true); }; React.useEffect(() => { load(); }, [ro.id]); const blockers = (pv && pv.blockers || []).filter(b => !(reverse && b.code === "credit_needed")); const held = reverse && pv && (pv.payments || []).filter(p => p.live && p.heldCents > 0 && (p.method === "cash" || p.method === "card")); const needCredit = reverse && held && held.length > 0; const asksSo = pv && (pv.specialOrders || []).length > 0, asksDep = pv && (pv.deposits || []).length > 0; const ready = pv && pv.ok !== false && !pv.already && !blockers.length && reasonCode && reason.trim().length >= 3 && (!asksSo || so) && (!asksDep || dep) && (!needCredit || credit) && (!reverse || codeOk); const go = async () => { if (!ready || busy) return; const what = reverse ? `Reverse ${ro.id}? A reversing entry dated today goes on the books${pv.creditCents ? " and " + m$(pv.creditCents) + " becomes a credit for the customer" : ""}.` : `Void ${ro.id}? Its parts go back to stock and nothing is posted to sales. The number ${ro.id} is never used again.`; if (!window.confirm(what)) return; setBusy(true); setMsg(null); const opts = { reasonCode, reason: reason.trim(), techPay }; if (!reverse) { if (asksSo) opts.specialOrders = so; if (asksDep) { opts.deposits = dep; if (dep === "refund") opts.depositMethod = depMethod; } opts.subletsBilled = Object.keys(billed).filter(k => billed[k]); } else opts.keepCredit = needCredit ? true : undefined; const r = reverse ? await D.reverseRO(ro.id, opts) : await D.voidRO(ro.id, opts); setBusy(false); window.k2bToast && window.k2bToast(r.msg, r.ok ? "ok" : "warn", r.ok ? 5200 : 6400); if (r.ok) { onClose && onClose(r); return; } if (r.code === "stepup_required") setCodeOk(false); setMsg(r.msg); load(); }; const sendCode = async () => { const r = await D.roReverseSendCode(); window.k2bToast && window.k2bToast(r.ok ? `Code sent to ${r.sentTo || "your phone"}.` : (r.msg || r.error), r.ok ? "ok" : "warn", 3600); }; const confirmCode = async () => { const r = await D.roReverseConfirm(codeMethod, code.trim()); if (r && r.ok) { setCodeOk(true); setMsg(null); } else setMsg((r && (r.msg || r.error)) || "That code did not work."); }; const title = reverse ? `Reverse closed RO — ${ro.id}` : `Void RO — ${ro.id}`; const K2BModal = window.K2BModal; return ( Cancel{busy ? "Working…" : reverse ? "Reverse RO" : "Void RO"}}>
{!pv &&
Asking the server what this will do…
} {pv && pv.ok === false &&
{pv.error || pv.msg}
} {pv && pv.already &&
{ro.id} is already {pv.roVoid && pv.roVoid.kind === "reversed" ? "REVERSED" : "VOID"}.
} {pv && pv.ok !== false && !pv.already && <>
{reverse ? <>A linked reversing entry dated today ({pv.closeJeId ? "reverses " + pv.closeJeId : "the close entry"}) takes the sale off the books. The parts come back on hand, the customer's A/R statement follows. {ro.id} stays in the list as REVERSED — its number is never used again. : <>Nothing is posted to sales. The parts go back to stock and {ro.id} stays in the list as VOID (with who, when and why) — its number is never used again.}
{blockers.map((b, i) =>
⛔ {b.text}
)}
What happens
    {(pv.parts || []).map(p =>
  • {reverse ? "Back on hand" : "Released back to stock"}: {p.qty} × {p.part}{p.onFile ? "" : " (not in the parts file)"}
  • )} {(pv.bulk || []).map((b, i) =>
  • Bulk oil back into tank {b.fluidId}: {b.qty}
  • )} {!reverse && (pv.sublets || []).map(s =>
  • Sublet cancelled: {s.vendor || s.desc}{s.costCents ? " (cost " + m$(s.costCents) + ")" : ""}
  • )} {(pv.claims || []).map(c =>
  • Warranty claim {c.claim} withdrawn
  • )} {(pv.weOwes || []).map(w =>
  • We-owe "{w.item}" goes back on the we-owe list
  • )} {(pv.units || []).map(u =>
  • Unit {u.stock}: {m$(u.cents)} internal work off the recon
  • )} {(pv.cores || []).filter(c => c.due).map(c =>
  • Core {c.id}: no core due any more
  • )} {pv.wipCents > 0 &&
  • The old system's labor on this RO ({m$(pv.wipCents)}, in work in process 247) is written off to cost of labor
  • } {reverse && pv.creditCents > 0 &&
  • {m$(pv.creditCents)} the customer paid becomes a credit on their account (A/R 220){pv.creditAccount ? " — account " + pv.creditAccount : ""}
  • } {!(pv.parts || []).length && !(pv.bulk || []).length && !(pv.sublets || []).length && !reverse &&
  • No parts or sublet on it.
  • }
{!reverse && (pv.sublets || []).filter(s => s.costCents > 0).length > 0 &&
Did the vendor already bill the sublet? (The bill is then in 246 — it is written off to sublet cost 666.)
{(pv.sublets || []).filter(s => s.costCents > 0).map(s => )}
} {asksSo &&
Special-order parts for this RO ({pv.specialOrders.map(x => x.part).join(", ")})
} {asksDep &&
The customer's deposit{pv.deposits.length === 1 ? "" : "s"}: {pv.deposits.map(d => d.depositId + " " + m$(d.openCents)).join(", ")}
{dep === "refund" && }
} {(pv.techTime || []).length > 0 &&
Technician time on it: {pv.techTime.map(t => `${t.name} ${t.hours}h`).join(" · ")}
The clocked time stays on the time card either way.
} {needCredit &&
The customer paid {m$(held.reduce((s, p) => s + p.heldCents, 0))} that the store still holds ({held.map(p => p.receiptNo).join(", ")}).
Refund it first (Payments › Refund), or leave it as a credit:
}
Why (required)