// inventory-page.jsx — Інвентаризація: сесії, звірка факт vs облік, акт function InventoryPage({ role }) { const sessions = window.DATA.INVENTORY_SESSIONS; const [openId, setOpenId] = React.useState(null); if (openId) { return setOpenId(null)} />; } const active = sessions.filter(s => s.status === "active"); const done = sessions.filter(s => s.status === "done"); const openIssues = sessions.reduce((sum, s) => { if (s.status !== "done") return sum; return sum + window.inventoryStats(s).issues; }, 0); const lastDone = done[0]; return (

Інвентаризація

{sessions.length} {window.plural(sessions.length, "сесія", "сесії", "сесій")} {active.length ? {active.length} активна : "немає активних"}
Активних сесій
{active.length}
{active.length ? "звірка триває" : "усі завершені"}
Остання завершена
{lastDone ? window.formatDate(lastDone.dateEnd) : "—"}
{lastDone ? lastDone.title : ""}
Майна в обліку
{window.DATA.ASSETS.filter(a => a.status !== "written_off").length}
підлягає звірці
Виявлено розбіжностей
{openIssues}
за останніми актами

Сесії інвентаризації

{sessions.map(s => { const stats = window.inventoryStats(s); const chair = window.getTeam(s.chair); const pct = Math.round(stats.checked / stats.planned * 100); return ( ); })}
); } // ============ ДЕТАЛІ СЕСІЇ ============ function SessionDetail({ session, onBack }) { // Локальний стан звірки — щоб можна було «сканувати QR» в активній сесії const [recon, setRecon] = React.useState(() => ({ ...session.reconcile })); const [lastScan, setLastScan] = React.useState(null); const isActive = session.status === "active"; const chair = window.getTeam(session.chair); const planned = session.plannedIds; const pending = planned.filter(id => !recon[id]); const stats = (() => { let match = 0, issues = 0, checked = 0; planned.forEach(id => { const r = recon[id]; if (r) { checked++; r.result === "match" ? match++ : issues++; } }); return { planned: planned.length, checked, match, issues, pending: planned.length - checked }; })(); const pct = Math.round(stats.checked / stats.planned * 100); const scanNext = () => { if (!pending.length) return; const id = pending[0]; const asset = window.getAsset(id); // Симуляція: майно знаходиться там, де має бути (збіг) setRecon(prev => ({ ...prev, [id]: { result: "match", scanned: true } })); setLastScan(asset); }; return (

{session.title}

{isActive ? "Триває" : "Завершено"}
{session.order} {window.formatDate(session.dateStart)}{session.dateEnd ? ` – ${window.formatDate(session.dateEnd)}` : ""} {session.scope}
{/* Комісія */}

Інвентаризаційна комісія

голова: {chair ? chair.name : "—"}
{session.commission.map(pid => { const p = window.getTeam(pid); if (!p) return null; return (
{p.initials}
{p.name}{pid === session.chair && " · голова"}
{p.role}
); })}
{/* KPI звірки */}
В обсязі
{stats.planned}
одиниць майна
Звірено
{stats.checked}
{pct}% обсягу
Збіг з обліком
{stats.match}
факт = облік
Розбіжності
{stats.issues}
нестача / надлишок
{/* QR-сканування для активної сесії */} {isActive && (
Обхід зі сканером
{stats.pending > 0 ? `Залишилось перевірити ${stats.pending} ${window.plural(stats.pending, "одиницю", "одиниці", "одиниць")}` : "Усе майно перевірено — можна формувати акт"}
{lastScan &&
✓ Відскановано: {lastScan.invNo} — {lastScan.name}
}
)} {/* Відомість звірки */}

Звіряльна відомість

факт проти облікових даних
{planned.map(id => { const a = window.getAsset(id); if (!a) return null; const r = recon[id] || { result: "pending" }; const rr = window.RECONCILE_RESULT[r.result]; const holder = window.getTeam(a.holder); return ( ); })}
Інв. № Найменування За обліком (МВО / локація) Результат Примітка
{a.invNo}
{a.name}
{holder ? holder.name.split(" ")[0] : "—"} · {window.ASSET_LOCATIONS[a.location]} {rr.label} {r.result === "wrong_location" && r.foundAt ? <>факт: {window.ASSET_LOCATIONS[r.foundAt]}{r.note ? ` — ${r.note}` : ""} : (r.note || (r.result === "pending" ? очікує перевірки : "—"))}
{/* Розбіжності */} {stats.issues > 0 && (

Виявлені розбіжності

{stats.issues} {window.plural(stats.issues, "позиція", "позиції", "позицій")}
{planned.filter(id => recon[id] && recon[id].result !== "match").map(id => { const a = window.getAsset(id); const r = recon[id]; const rr = window.RECONCILE_RESULT[r.result]; return (
{rr.label}
{a.invNo}{a.name}
{r.note &&
{r.note}
}
); })}
)} {/* Дії */}
{isActive ? : <> {session.actSigned && ✓ {session.actNumber} · підписано комісією} }
); } window.InventoryPage = InventoryPage;