/* 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 — Parts Department. // Full parts operations that read/write the shared DMSCore ledger: // Master File · Inventory Control · Special Orders · Purchase Orders · // Cores · Obsolescence · Physical Inventory. // Every sale/receipt/adjustment posts to the GL through the engine. const PartsDept = ({ onClose, go }) => { const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => DMS.subscribe(force), []); const [tab, setTab] = React.useState("master"); const [q, setQ] = React.useState(""); const [modal, setModal] = React.useState(null); const parts = DMS.parts(); const orders = DMS.partsOrders(); const cores = DMS.cores(); const money = (n) => "$" + Number(n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); // RC26 (inv): the parts money actions are server commands (they answer later) — a result or a promise of one const act = (r) => Promise.resolve(r).then((x) => { x = x || { ok: false, msg: "No answer." }; window.k2bToast && window.k2bToast(x.msg, x.ok ? "ok" : "warn", x.ok ? 3500 : 8000); return x; }); const filtered = parts.filter(p => !q || p.part.toLowerCase().includes(q.toLowerCase()) || (p.desc || "").toLowerCase().includes(q.toLowerCase())); const specialOrders = parts.filter(p => p.specialOrder); const obsolete = parts.filter(p => p.age >= 365 || (p.lastSale && (Date.now() - new Date(p.lastSale)) / 86400000 >= 365) || (!p.lastSale && p.age >= 180)); const invValue = parts.reduce((s, p) => s + Math.max(0, p.onHand) * p.cost, 0); const TABS = [ { k: "master", label: "Master File" }, { k: "special", label: `Special Orders (${specialOrders.length})` }, { k: "po", label: `Purchase Orders (${orders.filter(o => o.status === "Open").length})` }, { k: "cores", label: `Core Bank (${cores.filter(c => c.status !== "Credited" && c.status !== "Returned to Mfr" && c.status !== "Part Returned").length})` }, { k: "obsolete",label: `Obsolescence (${obsolete.length})` }, { k: "physical",label: "Physical Inventory" }, ]; return (
{/* KPI strip */}
p.onHand < 0).length} tone={parts.some(p => p.onHand < 0) ? "red" : "ok"} /> o.status === "Open").length} />
setModal({ type: "addPart" })} style={{ background: "var(--jb-yellow, #FFE9B0)", fontWeight: 700 }}>+ Add Part setModal({ type: "newPO" })}>+ New PO
{/* Tabs */}
{TABS.map(t => ( ))}
{/* ── MASTER FILE / INVENTORY CONTROL ── */} {tab === "master" && ( <>
Find: setQ(e.target.value)} placeholder="part # or description" style={{ flex: 1, maxWidth: 320, background: "var(--k2b-yellow, #FFFFE1)", border: "1px solid #808080", padding: "2px 6px", fontFamily: "'Courier Prime', monospace" }} /> {filtered.length} parts · available = on-hand − committed
{filtered.map((p, i) => { const avail = p.onHand - p.committed; return ( ); })}
Part #DescriptionBin On HandCmtdAvailOn Ord CostListTradeSell
{p.part} {p.desc}{p.core ? · core ${p.core} : null} {p.bin || "— none —"} {p.onHand} {p.committed || ""} {avail} {p.onOrder || ""} {money(p.cost)} {money(p.list)} {money(p.trade)}
)} {/* ── SPECIAL ORDERS ── */} {tab === "special" && ( {specialOrders.length === 0 && } {specialOrders.map(p => { const so = p.specialOrder; return ( ); })}
Part #DescriptionCustomer / RO OrderedETAStatusAction
No open special orders.
{p.part} {p.desc} {so.customer}
{so.ro} · {so.advisor}
{so.ordered ? new Date(so.ordered).toLocaleDateString() : "—"} {so.eta ? new Date(so.eta).toLocaleDateString() : "—"} {so.installed ? "Installed" : so.received ? "Received — not installed" : "On order"} {so.received && !so.installed &&
{so.contacted ? "✓ customer contacted" : "⚠ customer not contacted"}
}
{so.received && !so.installed && (
{!so.contacted && }
)}
)} {/* ── PURCHASE ORDERS ── */} {tab === "po" && ( {orders.length === 0 && } {orders.map(o => { const est = o.lines.reduce((s, l) => s + l.qty * l.cost, 0); return ( ); })}
PO #TypeVendor CreatedLinesEst. Cost StatusAction
No purchase orders.
{o.po} {o.type} {o.vendor} {new Date(o.created).toLocaleDateString()} {o.lines.length} {money(est)} {o.status}{Array.isArray(o.receipts) && o.receipts.length ? · {o.receipts.length} receipt{o.receipts.length === 1 ? "" : "s"} : null} {(o.status === "Open" || o.status === "Partial") && }
)} {/* ── CORE BANK (engine / transmission / battery & other reman cores) ── */} {tab === "cores" && (() => { const cb = DMS.coreBankSummary ? DMS.coreBankSummary() : { rows: [], totalOpen: 0, dueFromCustomer: 0, onShelf: 0, shipped: 0, count: 0 }; const TYPE_ICON = { Engine: "🛠", Transmission: "⚙", Battery: "🔋", Alternator: "🔌", Starter: "🔑", Brake: "🛞", Driveline: "🪛", Turbo: "🌀", Other: "📦" }; const openCores = cores.filter(c => c.status !== "Credited" && c.status !== "Part Returned"); // RC26 fix round: "Part Returned" = the sold part came back on a counter return — nothing due const doneCores = cores.filter(c => c.status === "Credited" || c.status === "Part Returned"); const m2 = (v) => "$" + (Number(v) || 0).toFixed(2); const coreAge = (c) => c.created ? Math.max(0, Math.floor((Date.now() - new Date(c.created)) / 86400000)) : (c.age || 0); return (
{/* Pipeline summary */}
setModal({ type: "addCore" })} style={{ background: "var(--jb-yellow, #FFE9B0)", fontWeight: 700 }}>+ Log Core
{/* By-type cards (Engine / Transmission / Battery first) */} {cb.rows.length > 0 && (
{cb.rows.map(r => (
{TYPE_ICON[r.type] || "📦"}{r.type} {r.count}
{money(r.amount)}
{r.dueCust ? `${money(r.dueCust)} due · ` : ""}{r.onShelf ? `${money(r.onShelf)} shelf · ` : ""}{r.shipped ? `${money(r.shipped)} shipped` : ""} {!r.dueCust && !r.onShelf && !r.shipped ? "—" : ""}
))}
)} {/* Outstanding core table with lifecycle actions */} {openCores.length === 0 && } {openCores.map((c) => { const st = c.status; return ( ); })}
TypePart #Description RO / CustomerCore $Age StatusNext step
No outstanding cores. Reman engine, transmission and battery core charges show up here.
{TYPE_ICON[c.coreType] || "📦"} {c.coreType || "Other"} {c.part || "—"} {c.desc} {c.ro}{c.customer ? ` · ${c.customer}` : ""} {money(c.amount)} 30 ? "#C03546" : undefined, fontWeight: coreAge(c) > 30 ? 700 : 400 }}>{coreAge(c)}d {st} {st === "Due from Customer" && } {st === "On Core Shelf" && } {st === "Shipped to Mfr" && } {st === "Shipped to Mfr" && } {c.creditDocId &&
credit doc {c.creditDocId}{c.claimNo ? " · claim " + c.claimNo : ""}
}
{doneCores.length > 0 && (
Credited / closed cores ({doneCores.length}) {doneCores.map((c) => ( ))}
{TYPE_ICON[c.coreType] || "📦"} {c.coreType} {c.part} {c.desc} {money(c.amount)} {c.claimNo || ""}{c.cogJeId ? " · " + c.cogJeId : ""}{c.closedReturnId ? "part came back on " + c.closedReturnId : ""} {c.status === "Part Returned" ? "Part Returned" : "Credited"}
)}
); })()} {/* ── OBSOLESCENCE ── */} {tab === "obsolete" && ( <>
Parts with no sale in 12+ months (or special parts aged 6+ months with no sale). Trapped cash: {money(obsolete.reduce((s, p) => s + Math.max(0, p.onHand) * p.cost, 0))}
{obsolete.length === 0 && } {obsolete.map(p => ( ))}
Part #DescriptionOn Hand Cost EaTrappedLast SaleReturnable
No obsolete parts. ✓
{p.part}{p.desc} {p.onHand}{money(p.cost)} {money(Math.max(0, p.onHand) * p.cost)} {p.lastSale ? new Date(p.lastSale).toLocaleDateString() : "never"} {p.returnable ? Yes : No}
)} {/* ── PHYSICAL INVENTORY ── */} {tab === "physical" && ( <>
Enter the counted quantity and the reason; the server posts the variance at the part's cost (PI-: parts inventory 242 against adjustment 688) and moves the stock — both together or nothing. A count never goes below zero; a large change may need the office (the owner's count limit).
{parts.map(p => )}
Part #DescriptionBin SystemCountedReasonPost
)}
{/* Footer F-keys */}
ESC-Close setModal({ type: "addPart" })}>F2-Add Part setModal({ type: "scan" })} style={{ background: "var(--jb-navy, #1B66A8)", color: "#FFF", fontWeight: 700 }}>📷 Scan Part setModal({ type: "bulkoil" })}>🛢 Bulk Oil setModal({ type: "import" })} style={{ background: "var(--jb-yellow, #FFD24A)", fontWeight: 700 }}>⤓ Import GM Master setModal({ type: "newPO" })}>F3-New PO go && go("parts")}>F4-Parts Ticket go && go("service")}>F5-Service RO go && go("accounting")}>F6-Accounting
{modal && modal.type === "sell" && setModal(null)} act={act} money={money} />} {modal && modal.type === "addPart" && setModal(null)} act={act} prefill={modal.prefill} />} {modal && modal.type === "newPO" && setModal(null)} act={act} money={money} />} {modal && modal.type === "addCore" && setModal(null)} act={act} />} {modal && modal.type === "receivePO" && typeof PartsReceivePODialog === "function" && setModal(null)} act={act} />} {modal && modal.type === "import" && setModal(null)} act={act} />} {modal && modal.type === "bulkoil" && setModal(null)} act={act} />} {modal && modal.type === "scan" && setModal(null)} onFound={(p) => setModal({ type: "sell", part: p })} onUnknown={(code) => setModal({ type: "addPart", prefill: code })} act={act} />}
); }; // KPI pill for the Parts header strip const PKpi = ({ label, value, tone }) => { const border = tone === "red" ? "#C03546" : tone === "amber" ? "#D9920A" : tone === "ok" ? "var(--jb-green, #16A34A)" : "var(--jb-border, #C0C0C0)"; const color = tone === "red" ? "#C03546" : tone === "amber" ? "#9A6700" : "var(--jb-navy, #11337A)"; return (
{label}
{value}
); }; // Physical-inventory row with a counted-qty input — RC26 (inv): Post is the server command POST /api/parts/adjust (reason required; the preview is // PartsCalc.countAdjust, the same calculation the server commits) const PARTS_COUNT_REASONS = ["Physical count", "Damaged", "Found / misplaced", "Lost / missing", "Wrong part in bin", "Other"]; const PhysRow = ({ p, money, act }) => { const [counted, setCounted] = React.useState(""); const [why, setWhy] = React.useState(""); const [other, setOther] = React.useState(""); const [busy, setBusy] = React.useState(false); const PC = window.PartsCalc; const calc = counted === "" || !PC ? null : PC.countAdjust({ onHand: p.onHand, counted, cost: p.cost }); const variance = calc && calc.ok ? calc.varianceMilli / 1000 : null; const reason = why === "Other" ? other.trim() : why; const post = async () => { if (!calc || !calc.ok) return act({ ok: false, msg: calc ? calc.problem : "Enter the counted quantity." }); if (reason.length < 3) return act({ ok: false, msg: "Pick the reason for the count — it goes on the journal entry." }); setBusy(true); const r = await act(DMS.adjustInventory(p.part, counted, reason)); setBusy(false); if (r && r.ok) { setCounted(""); setWhy(""); setOther(""); } }; return ( {p.part}{p.desc} {p.bin || "—"} {p.onHand} setCounted(e.target.value.replace(/[^0-9.]/g, ""))} style={{ width: 56, textAlign: "right", background: "var(--k2b-yellow, #FFFFE1)", border: "1px solid #808080", fontFamily: "'Courier Prime', monospace", padding: "0 4px" }} /> {variance !== null && variance !== 0 && 0 ? "var(--k2b-green)" : "#C03546" }}>{variance > 0 ? "+" : ""}{variance} · {PC.fmt(calc.valueCents)}} {calc && !calc.ok && not a quantity} {why === "Other" && setOther(e.target.value)} placeholder="say why" style={{ width: 110, fontSize: 11.5, marginLeft: 4 }} />} ); }; const SellPartDialog = ({ part, onClose, act, money }) => { const [qty, setQty] = React.useState("1"); const [kind, setKind] = React.useState("retail"); const [override, setOverride] = React.useState(""); const [reason, setReason] = React.useState(""); const avail = part.onHand - part.committed; const baseUnit = kind === "wholesale" ? part.trade : part.list; // round 2: a part with no price on file is never sold for $0.00 — it needs a one-time price and a reason (the server checks the same) const noPrice = !(window.FopsCalc && window.FopsCalc.toCents(baseUnit) > 0); const hasOverride = override !== "" && parseFloat(override) >= 0 && (noPrice || parseFloat(override) !== baseUnit); const unit = hasOverride ? parseFloat(override) : baseUnit; const n = parseInt(qty, 10) || 0; const sale = unit * n; // RC20 (fops): the preview is the server's calculation (integer cents, one tax, the shared rate); Post Sale = the checkout command const pvT = DMS.partsTicketPreview ? DMS.partsTicketPreview(n > 0 ? [{ part: part.part, qty: n, overrideUnit: hasOverride ? override : undefined }] : [], kind) : null; const tax = pvT ? pvT.taxCents / 100 : (kind === "wholesale" ? 0 : sale * (window.jbTaxRate ? window.jbTaxRate("parts") : 0.07225)); const [posting, setPosting] = React.useState(false); return ( Cancel { if (posting) return; if (noPrice && (!hasOverride || !reason.trim())) { act({ ok: false, msg: `${part.part} has no ${kind === "wholesale" ? "trade" : "list"} price in the parts file — enter a one-time price and the reason, or put the price in the parts file first.` }); return; } setPosting(true); const r = await DMS.sellPart(part.part, n, kind, hasOverride ? parseFloat(override) : undefined, hasOverride ? reason.trim() : undefined); setPosting(false); act(r); if (r.ok) onClose(); }}>{posting ? "Posting…" : "Post Sale"} }>
{part.desc}
Available: {avail} · On hand: {part.onHand} · Committed: {part.committed}
Sale type: Quantity: setQty(e.target.value.replace(/[^0-9]/g, ""))} style={{ width: 70, background: "var(--k2b-yellow)", border: "1px solid #808080", padding: "2px 6px", fontFamily: "'Courier Prime', monospace" }} /> List price:{noPrice ? none on file — enter a one-time price and a reason : <>{money(baseUnit)} {kind === "wholesale" ? "(trade)" : "(list)"}} Override price: setOverride(e.target.value.replace(/[^0-9.]/g, ""))} placeholder={"one-time — e.g. " + baseUnit} style={{ width: 100, background: hasOverride ? "#FFFBEA" : "#FFF", border: "1px solid #808080", padding: "2px 6px", fontFamily: "'Courier Prime', monospace" }} /> {hasOverride && this sale only — master price unchanged} {(hasOverride || noPrice) && <>Reason: setReason(e.target.value.slice(0, 200))} placeholder={noPrice ? "required — e.g. price per vendor quote" : "e.g. match competitor quote"} style={{ background: "#FFF", border: "1px solid #808080", padding: "2px 6px" }} />} Unit price:{money(unit)} Sale:{money(pvT ? pvT.subtotalCents / 100 : sale)} Tax:{money(tax)} {pvT && pvT.coreDepositCents ? <>Core deposit:{money(pvT.coreDepositCents / 100)} (refunded when the core comes back) : null} Total:{money(pvT ? pvT.totalCents / 100 : sale + tax)}
{n > avail &&
⚠ Only {avail} available.
}
); }; const AddPartDialog = ({ onClose, act, prefill }) => { const [f, setF] = React.useState({ part: prefill || "", desc: "", bin: "", cost: "", list: "" }); // RC26 fix round (audit F5): no opening on-hand — stock goes in with its books const set = (k, v) => setF(s => ({ ...s, [k]: v })); const suggested = f.cost ? DMS.matrixPrice(f.cost) : 0; return ( Cancel { act(DMS.addPart({ ...f, list: f.list || suggested })); onClose(); }}>Add Part }>
Part #: set("part", e.target.value.toUpperCase())} style={inpStyle} /> Description: set("desc", e.target.value.toUpperCase())} style={inpStyle} /> Bin: set("bin", e.target.value.toUpperCase())} style={inpStyle} /> On Hand:0 — a new part starts empty. Put its stock in with Physical Inventory › Post count or Receive on its purchase order (the stock and its value post together). Cost: set("cost", e.target.value.replace(/[^0-9.]/g, ""))} style={{ ...inpStyle, width: 110 }} /> List: set("list", e.target.value.replace(/[^0-9.]/g, ""))} placeholder={suggested ? String(suggested) : ""} style={{ ...inpStyle, width: 110 }} /> {suggested ? matrix suggests ${suggested} : null}
); }; const NewPODialog = ({ parts, onClose, act, money }) => { const [type, setType] = React.useState("Stock Order"); const [vendor, setVendor] = React.useState("GM Parts"); const [lines, setLines] = React.useState([{ part: "", qty: "1" }]); const setLine = (i, k, v) => setLines(ls => ls.map((l, j) => j === i ? { ...l, [k]: v } : l)); const build = () => { const out = []; lines.forEach(l => { const p = parts.find(x => x.part === l.part); if (p && parseInt(l.qty, 10) > 0) out.push({ part: p.part, desc: p.desc, qty: parseInt(l.qty, 10), cost: p.cost }); }); return out; }; return ( Cancel { const l = build(); if (!l.length) return act({ ok: false, msg: "Add at least one valid part line." }); act(DMS.createPO(type, vendor, l)); onClose(); }}>Create PO }>
Type: Vendor: setVendor(e.target.value)} style={inpStyle} />
Lines
{lines.map((l, i) => (
setLine(i, "part", e.target.value.toUpperCase())} placeholder="part #" style={{ ...inpStyle, flex: 1 }} /> setLine(i, "qty", e.target.value.replace(/[^0-9]/g, ""))} style={{ ...inpStyle, width: 60 }} />
))} {parts.map(p => )} setLines(ls => [...ls, { part: "", qty: "1" }])}>+ Add Line
); }; // styles const inpStyle = { background: "var(--k2b-yellow, #FFFFE1)", border: "1px solid #808080", padding: "3px 6px", fontFamily: "'Courier Prime', monospace", fontSize: 13 }; const partsTable = { width: "100%", borderCollapse: "collapse", fontFamily: "'Courier Prime', monospace", fontSize: 12 }; const partsHeadRow = { background: "#C0C0C0", color: "var(--k2b-maroon, #8a1c1c)", borderBottom: "1px solid #808080", position: "sticky", top: 0 }; const pTh = { padding: "3px 6px", textAlign: "left", whiteSpace: "nowrap" }; const pThL = { ...pTh, width: "auto" }; const pThR = { ...pTh, textAlign: "right" }; const pThC = { ...pTh, textAlign: "center" }; const pTd = { padding: "2px 6px", color: "var(--k2b-navy, #11337A)" }; const pTdMono = { ...pTd, fontFamily: "'Courier Prime', monospace", fontWeight: 700, color: "var(--k2b-blue-data-dk, #11337A)" }; const pTdR = { ...pTd, textAlign: "right" }; const pTdC = { ...pTd, textAlign: "center" }; /* Log a core that must go back to GM (engine / transmission / battery / …) */ const CORE_TYPES = ["Engine", "Transmission", "Battery", "Alternator", "Starter", "Brake", "Driveline", "Turbo", "Other"]; const AddCoreDialog = ({ onClose, act }) => { const [f, setF] = React.useState({ coreType: "Engine", part: "", desc: "", amount: "", ro: "", customer: "" }); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const ip = { width: "100%", boxSizing: "border-box", padding: "5px 8px", border: "1px solid #C8CCD2", fontFamily: "'Courier Prime', monospace", fontSize: 13, color: "var(--k2b-navy)" }; const lab = { fontSize: 11, color: "#666", marginBottom: 2, display: "block" }; const submit = () => { // RC26 (inv): the amount is read strictly (PartsCalc) — "5,00" is not clear and is refused, never read as 500 (it used to strip the comma) const PC = window.PartsCalc; const c = PC ? PC.strictMoney(String(f.amount).trim()) : Math.round((parseFloat(f.amount) || 0) * 100); if (PC && PC.moneyProblem(String(f.amount).trim(), "The core charge")) return act({ ok: false, msg: PC.moneyProblem(String(f.amount).trim(), "The core charge") }); const r = DMS.addCore({ ...f, amount: c / 100 }); act(r); if (r.ok) onClose(); }; return ( Cancel Log Core }>
set("part", e.target.value.toUpperCase())} style={ip} />
set("amount", e.target.value)} placeholder="0.00" style={ip} />
set("desc", e.target.value.toUpperCase())} placeholder="e.g. REMAN 6.2L ENGINE ASSY" style={ip} />
set("ro", e.target.value.toUpperCase())} placeholder="COUNTER" style={ip} />
set("customer", e.target.value.toUpperCase())} style={ip} />
The core deposit is billed to the customer now. When they bring the old unit back you’ll Receive Core (refund the deposit), then Ship to GM and Post Credit when GM pays it.
); }; /* ─── Import the master GM parts list ─────────────────────────────────── Accepts pasted CSV/TSV (part, desc, cost, list, trade, bin, mfr, source, core) or loads a built-in GM sample tape. Imports catalog + pricing only; never touches dealer on-hand quantities. */ const GM_MASTER_SAMPLE = [ ["12378526", "FILTER ASM-OIL", "5.12", "11.49", "8.61", "A-12-04", "ACDelco", "GM", "0"], ["84176191", "PAD KIT-FRT DISC BRK", "41.80", "86.99", "67.20", "B-04-12", "ACDelco", "GM", "0"], ["13508023", "SENSOR-CMSHFT POSN", "28.65", "62.49", "48.00", "C-09-21", "ACDelco", "GM", "0"], ["55509589", "PCV VALVE", "9.40", "21.99", "16.80", "A-15-02", "ACDelco", "GM", "0"], ["19420609", "ROTOR-FRT BRK", "58.10", "119.99", "92.00", "B-05-07", "ACDelco", "GM", "0"], ["84670822", "BATTERY ASM (AGM)", "118.40", "229.99", "188.00", "D-01-01", "ACDelco", "GM", "22.00"], ["12690512", "PLUG ASM-SPARK", "6.95", "14.99", "11.20", "A-18-09", "ACDelco", "GM", "0"], ["23262335", "BELT-SERPENTINE", "31.20", "67.49", "52.00", "C-11-14", "ACDelco", "GM", "0"], ["84383471", "MIRROR ASM-O/S RR VIEW", "184.30", "349.99", "280.00", "E-03-06", "GM", "GM", "0"], ["13537899", "MODULE ASM-BCM", "210.55", "398.99", "320.00", "F-02-02", "GM", "GM", "0"], ]; const ImportMasterDialog = ({ onClose, act }) => { const [text, setText] = React.useState(""); const [updatePrices, setUpdatePrices] = React.useState(true); const [preview, setPreview] = React.useState(null); const parseRows = (raw) => { const lines = raw.split(/\r?\n/).map(l => l.trim()).filter(Boolean); const out = []; lines.forEach(line => { // header row? skip if first cell isn't part-number-ish const cells = line.split(/[\t,]/).map(c => c.trim()); if (/part\s*#?|number|desc/i.test(cells[0]) && out.length === 0 && !/^\w*\d/.test(cells[0])) return; const [part, desc, cost, list, trade, bin, mfr, source, core] = cells; if (!part) return; out.push({ part, desc, cost, list, trade, bin, mfr, source, core }); }); return out; }; const rows = preview || (text ? parseRows(text) : []); const loadSample = () => { setText(GM_MASTER_SAMPLE.map(r => r.join(",")).join("\n")); setPreview(GM_MASTER_SAMPLE.map(r => ({ part: r[0], desc: r[1], cost: r[2], list: r[3], trade: r[4], bin: r[5], mfr: r[6], source: r[7], core: r[8] }))); }; const doImport = () => { const data = parseRows(text); if (!data.length) { act({ ok: false, msg: "Nothing to import — paste rows or load the sample." }); return; } const r = DMS.importPartsMaster(data, { updatePrices }); act(r); if (r.ok) onClose(); }; return ( Load GM Sample Tape Cancel Import {rows.length ? `(${rows.length})` : ""} }>
Paste the GM master price tape as CSV or tab-separated rows, one part per line:
part #, description, cost, list, trade, bin, mfr, source, core