// fin-report.jsx — Фінансова звітність мікропідприємства (НП(С)БО 25, Дод. 2): // Форма 1-мс «Баланс» (ДКУД 1801006) + Форма 2-мс «Звіт про фінансові результати» (1801007). // Одиниця: тис. грн з 1 знаком. Ф2 автозаповнюється з P&L; у Ф1 — авто-рядки + ручні поля. // Усе редаговане й зберігається (resource fin-reports, id = fin-). (function () { const tis = (uah) => Math.round((uah || 0) / 100) / 10; // ₴ → тис. грн (1 знак) const fmt = (v) => (v == null || v === "" || isNaN(v)) ? "" : Number(v).toLocaleString("uk-UA", { minimumFractionDigits: 1, maximumFractionDigits: 1 }); // ── Структура Форми 1-мс «Баланс» ── const F1_ROWS = [ { sec: "Актив" }, { h: "I. Необоротні активи" }, { code: "1010", label: "Основні засоби:", calc: true, bold: true }, { code: "1011", label: "первісна вартість", indent: true, auto: "ozCost" }, { code: "1012", label: "знос", indent: true, neg: true, auto: "ozWear" }, { code: "1090", label: "Інші необоротні активи" }, { code: "1095", label: "Усього за розділом I", calc: true, bold: true }, { h: "II. Оборотні активи" }, { code: "1100", label: "Запаси" }, { code: "1155", label: "Поточна дебіторська заборгованість", auto: "recv" }, { code: "1165", label: "Гроші та їх еквіваленти", auto: "cash" }, { code: "1190", label: "Інші оборотні активи" }, { code: "1195", label: "Усього за розділом II", calc: true, bold: true }, { code: "1300", label: "Баланс", calc: true, bold: true, total: true }, { sec: "Пасив" }, { h: "I. Власний капітал" }, { code: "1400", label: "Капітал" }, { code: "1420", label: "Нерозподілений прибуток (непокритий збиток)" }, { code: "1425", label: "Неоплачений капітал", neg: true }, { code: "1495", label: "Усього за розділом I", calc: true, bold: true }, { code: "1595", label: "II. Довгострокові зобов'язання, цільове фінансування та забезпечення" }, { h: "III. Поточні зобов'язання" }, { code: "1600", label: "Короткострокові кредити банків" }, { code: "1615", label: "Поточна кредиторська заборгованість за товари, роботи, послуги", auto: "payables" }, { code: "1620", label: "розрахунки з бюджетом" }, { code: "1625", label: "розрахунки зі страхування" }, { code: "1630", label: "розрахунки з оплати праці" }, { code: "1690", label: "Інші поточні зобов'язання" }, { code: "1695", label: "Усього за розділом III", calc: true, bold: true }, { code: "1900", label: "Баланс", calc: true, bold: true, total: true }, ]; // Ф1 колонки: «На початок звітного року» = prev, «На кінець звітного періоду» = cur const F1_CALC = { "1010": (g) => g("1011") - g("1012"), "1095": (g) => g("1010") + g("1090"), "1195": (g) => g("1100") + g("1155") + g("1165") + g("1190"), "1300": (g) => g("1095") + g("1195"), "1495": (g) => g("1400") + g("1420") - g("1425"), "1695": (g) => g("1600") + g("1615") + g("1620") + g("1625") + g("1630") + g("1690"), "1900": (g) => g("1495") + g("1595") + g("1695"), }; // ── Структура Форми 2-мс «Звіт про фінансові результати» ── const F2_ROWS = [ { code: "2000", label: "Чистий дохід від реалізації продукції (товарів, робіт, послуг)", auto: "rev" }, { code: "2160", label: "Інші доходи" }, { code: "2280", label: "Разом доходи (2000 + 2160)", calc: true, bold: true }, { code: "2050", label: "Собівартість реалізованої продукції (товарів, робіт, послуг)", neg: true }, { code: "2165", label: "Інші витрати", neg: true, auto: "exp" }, { code: "2285", label: "Разом витрати (2050 + 2165)", calc: true, bold: true }, { code: "2290", label: "Фінансовий результат до оподаткування (2280 − 2285)", calc: true, bold: true }, { code: "2300", label: "Податок на прибуток", neg: true, auto: "tax" }, { code: "2310", label: "Витрати (доходи), які зменшують (збільшують) фінрезультат після оподаткування" }, { code: "2350", label: "Чистий прибуток (збиток) (2290 − 2300 − (+) 2310)", calc: true, bold: true, total: true }, ]; // Ф2 колонки: «За звітний період» = cur, «За попередній період» = prev const F2_CALC = { "2280": (g) => g("2000") + g("2160"), "2285": (g) => g("2050") + g("2165"), "2290": (g) => g("2280") - g("2285"), "2350": (g) => g("2290") - g("2300") - g("2310"), }; const CALC = Object.assign({}, F1_CALC, F2_CALC); // ── Авто-значення з даних ERP (у тис. грн) ── function autoFor(year) { const out = {}; const plOf = (y) => { try { return window.computeAnnualPL(String(y)) || {}; } catch (e) { return {}; } }; const setF2 = (col, pl) => { const put = (code, v) => { out[code] = out[code] || {}; out[code][col] = v; }; put("2000", tis(pl.revenueNet)); // Собівартість (2050) = усі прямі витрати: ЗП + ЄСВ + субпідряд/експертиза + виробничі ЗВВ + амортизація put("2050", tis((pl.payroll || 0) + (pl.esv || 0) + (pl.subExp || 0) + (pl.zvvOther || 0) + (pl.amort || 0))); // Інші витрати (2165) = адміністративні put("2165", tis(pl.adm || 0)); put("2300", tis(pl.profitTax)); }; setF2("cur", plOf(year)); setF2("prev", plOf(year - 1)); // Ф1 (на кінець = cur) — best-effort зі знімка даних const D = window.DATA || {}; const assets = (D.ASSETS || []).filter(a => a.status !== "written_off"); const ozCost = assets.reduce((s, a) => s + (a.cost || 0), 0); const ozWear = assets.reduce((s, a) => { const dep = window.computeDepreciation ? window.computeDepreciation(a) : null; return s + (dep ? (dep.accumulated || 0) : 0); }, 0); const recv = (D.INVOICES || []).filter(i => i.status !== "paid").reduce((s, i) => s + (i.amount || 0), 0); const cash = (D.CASH_ACCOUNTS || []).reduce((s, a) => s + (a.balance || 0), 0); const payPur = (D.PURCHASE_INVOICES || []).filter(p => p.status !== "paid").reduce((s, p) => s + (p.amount || 0), 0); const paySub = (D.SUB_PAYMENTS || []).filter(p => p.status !== "paid").reduce((s, p) => s + (p.amount || 0), 0); const put = (code, v) => { out[code] = out[code] || {}; out[code].cur = v; }; put("1011", tis(ozCost)); put("1012", tis(ozWear)); put("1155", tis(recv)); put("1165", tis(cash)); put("1615", tis(payPur + paySub)); return out; } const AUTO_KEYS = { rev: "2000", exp: "2165", tax: "2300", ozCost: "1011", ozWear: "1012", recv: "1155", cash: "1165", payables: "1615" }; function FinReportPage({ role }) { const sysYear = parseInt(String(window.SYS_DATE || "2026").slice(0, 4), 10) || 2026; const [year, setYear] = React.useState(sysYear - 1); // звітний рік = минулий const [vals, setVals] = React.useState({}); // {code:{cur,prev}} const [hdr, setHdr] = React.useState({}); // редаговані поля шапки const [recId, setRecId] = React.useState(null); const [saved, setSaved] = React.useState(true); const [busy, setBusy] = React.useState(false); const canEdit = role === "director" || role === "accountant"; const co = window.COMPANY || {}; const load = React.useCallback(() => { const auto = autoFor(year); const apply = (rec) => { const v = (rec && rec.values) ? rec.values : {}; // ефективні значення: збережене перекриває авто const merged = {}; const codes = new Set([...Object.keys(auto), ...Object.keys(v)]); codes.forEach(c => { merged[c] = { cur: pick(v[c], auto[c], "cur"), prev: pick(v[c], auto[c], "prev") }; }); setVals(merged); setHdr((rec && rec.header) || {}); setRecId(rec ? rec.id : null); setSaved(true); }; if (window.API && window.API.list) { window.API.list("fin-reports", { year: String(year) }) .then(rows => apply((rows || []).find(r => String(r.year) === String(year)) || null)) .catch(() => apply(null)); } else apply(null); }, [year]); React.useEffect(() => { load(); }, [load]); function pick(savedCell, autoCell, col) { if (savedCell && savedCell[col] != null && savedCell[col] !== "") return savedCell[col]; if (autoCell && autoCell[col] != null) return autoCell[col]; return ""; } // resolve значення коду в колонці (з урахуванням розрахункових рядків) const resolve = (code, col) => { if (CALC[code]) { const g = (c) => resolve(c, col); return CALC[code](g); } const cell = vals[code]; const x = cell ? cell[col] : ""; return (x === "" || x == null || isNaN(x)) ? 0 : Number(x); }; const setCell = (code, col, raw) => { setVals(prev => ({ ...prev, [code]: { ...(prev[code] || {}), [col]: raw } })); setSaved(false); }; const recalc = () => { const auto = autoFor(year); setVals(prev => { const next = { ...prev }; Object.entries(auto).forEach(([code, cell]) => { next[code] = { ...(next[code] || {}) }; if (cell.cur != null) next[code].cur = cell.cur; if (cell.prev != null) next[code].prev = cell.prev; }); return next; }); setSaved(false); }; const save = async () => { if (!canEdit) return; if (!(window.API && window.API.mode === "live" && window.API.save)) { alert("Збереження доступне лише на сервері."); return; } setBusy(true); const id = recId || ("fin-" + year); const payload = { id, year: String(year), values: vals, header: hdr }; try { await window.API.save("fin-reports", recId ? id : null, payload); setRecId(id); setSaved(true); } catch (e) { console.error("[ERP] save fin-report:", e); alert("Не вдалося зберегти звіт."); } setBusy(false); }; const copyForm = (rows, cols) => { const lines = rows.filter(r => r.code).map(r => { const get = (col) => fmt(r.calc ? resolve(r.code, col) : (vals[r.code] ? vals[r.code][col] : "")); return [r.label, r.code, ...cols.map(c => get(c.key))].join("\t"); }); const text = lines.join("\n"); if (navigator.clipboard) navigator.clipboard.writeText(text).then(() => {}).catch(() => {}); try { window.prompt && false; } catch (e) {} alert("Скопійовано рядки у буфер (можна вставити в кабінет / Excel)."); }; const renderTable = (rows, cols) => ( {cols.map(c => )} {rows.map((r, i) => { if (r.sec) return ; if (r.h) return ; return ( {cols.map(c => { if (r.calc) { const v = resolve(r.code, c.key); return ; } const cur = vals[r.code] ? vals[r.code][c.key] : ""; const isAuto = r.auto && cur !== "" && cur != null; return ( ); })} ); })}
СтаттяКод{c.label}
{r.sec}
{r.h}
{r.label} {r.code}{fmt(v)} {canEdit ? setCell(r.code, c.key, e.target.value)} /> : fmt(cur)} {r.neg && (cur !== "" && cur != null) ? "" : null}
); const F1_COLS = [{ key: "prev", label: "На початок звітного року" }, { key: "cur", label: "На кінець звітного періоду" }]; const F2_COLS = [{ key: "cur", label: "За звітний період" }, { key: "prev", label: "За попередній період" }]; // контроль зведення балансу const b1ok = Math.abs(resolve("1300", "cur") - resolve("1900", "cur")) < 0.05; return (

Фінансова звітність

Мікропідприємство · НП(С)БО 25 Ф1-мс (1801006) + Ф2-мс (1801007) тис. грн
{canEdit && } {canEdit && }
{/* Шапка КОДИ */}
Підприємство{co.name || "ТОВ «УКРБУДПРОЄКТ»"}
ЄДРПОУ{co.edrpou || "44531964"}
КВЕД71.12
Адреса{co.address || co.city || "65030, м. Одеса, вул. Палубна, 9"}
Серед. к-сть прац. {canEdit ? { setHdr(h => ({ ...h, avgEmp: e.target.value })); setSaved(false); }} /> : (hdr.avgEmp || "—")}
Звіт на31.12.{year} р.
{/* ФОРМА 1 */}

1. Баланс · Форма № 1-мс

{b1ok ? "Актив = Пасив ✓" : "Актив ≠ Пасив — перевірте рядки"}
{renderTable(F1_ROWS, F1_COLS)} {!b1ok &&
Актив ({fmt(resolve("1300", "cur"))}) не дорівнює Пасиву ({fmt(resolve("1900", "cur"))}). Заповніть власний капітал / зобов'язання вручну.
}
{/* ФОРМА 2 */}

2. Звіт про фінансові результати · Форма № 2-мс

за {year} рік
{renderTable(F2_ROWS, F2_COLS)}
2050 Собівартість = усі прямі витрати (ЗП + ЄСВ + субпідряд/експертиза + виробничі ЗВВ + амортизація); 2165 Інші витрати = адміністративні (офіс, ПЗ, зв'язок, банк). «Перерахувати з ERP» оновлює авто-рядки.
); } function FinReportStyles() { return ( ); } window.FinReportPage = FinReportPage; })();