/* Сведение отчётов дочерних компаний — статичный интерфейс, состояние в localStorage */ (function () { "use strict"; var LS = "svod-otchetov-dochernih-v1"; function loadState() { var base = { screen: "upload", recognize: false, manual: false, unitFixed: false, checked: {}, memoText: "", memoSent: false }; try { var raw = localStorage.getItem(LS); if (raw) { var s = JSON.parse(raw); for (var k in base) { if (s && k in s) base[k] = s[k]; } } } catch (e) {} return base; } function save() { try { localStorage.setItem(LS, JSON.stringify(S)); } catch (e) {} } var S = loadState(); /* 15 компаний: [название, выручка план, выручка факт, EBITDA план, EBITDA факт, CAPEX план, CAPEX факт, ДЗ план, ДЗ факт] — млн тг */ var CO = [ ["ООО «Телеком-1»", 12500, 11900, 3100, 2950, 420, 445, 640, 610], ["ООО «Телеком-2»", 8200, 7400, 2100, 1890, 280, 295, 410, 455], ["ООО «Телеком-3»", 15400, 15810, 4100, 4050, 530, 600, 880, 940], ["ООО «Телеком-4»", 9800, 9950, 2500, 2480, 340, 350, 520, 495], ["ООО «Телеком-5»", 11300, 9900, 2850, 2780, 390, 360, 610, 585], ["ООО «Телеком-6»", 7600, 7450, 1900, 1840, 250, 265, 380, 370], ["ООО «Телеком-7»", 13900, 14650, 3600, 3380, 480, 510, 760, 735], ["ООО «Телеком-8»", 6700, 6500, 1680, 1620, 220, 230, 330, 315], ["ООО «Телеком-9»", 10400, 9050, 2640, 2590, 360, 380, 570, 605], ["ООО «Телеком-10»", 8900, 8700, 2280, 2210, 300, 285, 450, 470], ["ООО «Телеком-11»", 12100, 12450, 3200, 3260, 430, 445, 690, 660], ["ООО «Телеком-12»", 7100, 6900, 1780, 1720, 260, 275, 400, 385], ["ООО «Телеком-13»", 14600, 13750, 3850, 3780, 505, 490, 830, 795], ["ООО «Телеком-14»", 9300, 8200, 2380, 2330, 330, 310, 510, 480], ["ООО «Телеком-15»", 11700, 12000, 3000, 2960, 640, 700, 580, 555] ]; /* 15 дочерних компаний: выручка / EBITDA / CAPEX / ДЗ — план и факт, млн тг. Сценарий: 7 отклонений >10% (2/3/5/7/9/13/14) + ошибка единиц у 4 (выручка в тыс. тг). В пределах нормы: 1 (Выр −4.8), 6 (Выр −2), 8 (Выр −3), 11 (Все ≤7.5), 12 (Выр −2.8). */ var _CO = CO; function _dev(f, p) { return p > 0 ? Math.abs(f - p) / p * 100 : 0; } CO = _CO.map(function (r) { var c = { name: r[0], vp: r[1], vf: r[2], ep: r[3], ef: r[4], cp: r[5], cf: r[6], dp: r[7], df: r[8] }; c.dRev = _dev(c.vf, c.vp); c.dEbit = _dev(c.ef, c.ep); c.dCap = _dev(c.cf, c.cp); c.dDz = _dev(c.df, c.dp); c.maxDev = Math.max(c.dRev, c.dEbit, c.dCap, c.dDz); c.revOk = c.dRev > 10; c.ebitOk = c.dEbit > 10; c.capOk = c.dCap > 10; c.dzOk = c.dDz > 10; return c; }); function dev(f, p) { return p > 0 ? Math.abs(f - p) / p * 100 : 0; } function hasDeviations(c) { return c.revOk || c.ebitOk || c.capOk || c.dzOk; } var DEVS = CO.filter(hasDeviations); var REV_ISSUE = 3; // индекс компании с ошибкой единиц (выручка в тыс. тг в файле) (function () { var c = CO[REV_ISSUE]; c.vp *= 1000; c.vf *= 1000; c.dRev = _dev(c.vf, c.vp); c.maxDev = Math.max(c.dRev, c.dEbit, c.dCap, c.dDz); c.revOk = c.dRev > 10; c.vp1000 = true; })(); var FILES = [ ["Отчет_TV-1_агуст.xlsx", "2024", "09:42"], ["Fin_report_TV-2.xlsx", "2025", "09:45"], ["Свод_TV-3_план_факт.xlsx", "2026", "09:47"], ["Отчет_TV-4_август.xlsx", "2024", "09:49"], ["Fin_TV-5_08-2026.xlsx", "2025", "09:51"], ["Финансы_TV-6_август.xlsx", "2024", "09:54"], ["Отчет_TV-7.xlsx", "2026", "09:56"], ["Годовой_отчет_TV-8.xlsx", "2025", "09:58"], ["Данные_TV-9_август.xlsx", "2024", "10:01"], ["Report_TV-10_2026-08.xlsx", "2026", "10:03"], ["Свод_TV-11_август.xlsx", "2025", "10:05"], ["Fin_TV-12_08.xlsx", "2024", "10:07"], ["Отчет_TV-13_август_2026.xlsx", "2026", "10:09"], ["Data_TV-14_2026.08.xlsx", "2025", "10:12"], ["Отчет_TV-15.xlsx", "2024", "10:14"] ]; var TPL = { "2024": "шаблон 2024", "2025": "шаблон 2025", "2026": "шаблон 2026" }; var BASE_LOGS = [ ["10:02:14", "ok", "Получено 15 вложений из общего ящика"], ["10:02:15", "info", "Форматы: 15 Excel · 3 варианта шаблонов (2024 / 2025 / 2026)"] ]; var DONE_LOGS = [ ["10:02:18", "ok", "ООО «Телеком-1» · сопоставлено 8 столбцов (выручка, EBITDA, CAPEX, ДЗ — план и факт)"], ["10:02:21", "warn", "ООО «Телеком-4» · выручка в файле в тысячах тенге → множитель 1/1000 при переносе"], ["10:02:23", "ok", "ООО «Телеком-2»…«Телеком-15» · автоматическое сопоставление колонок 8/8"], ["10:02:29", "ok", "Данные перенесены в единый свод: 15 компаний, 120 показателей план/факт"], ["10:02:30", "warn", "Отклонения от плана более 10% подсвечены: 7 компаний"] ]; var STEPS = [ { id: "upload", n: 1, t: "Загрузка", sub: "распознавание" }, { id: "check", n: 2, t: "Проверка", sub: "данные и отклонения" }, { id: "memo", n: 3, t: "Записка", sub: "для финансового директора" } ]; /* ---------- utils ---------- */ function $(id) { return document.getElementById(id); } function esc(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } var NLC = new Intl.NumberFormat("ru-RU"); function fmt(n) { return n == null ? "—" : NLC.format(Math.round(n)); } function pct(s) { return String(Math.round(s)).replace(".", ","); } function fmtPct(s) { return String(Math.round(s)).replace(".", ","); } function signPct(s) { return (s >= 0 ? "+" : "−") + fmtPct(parseFloat(s.toFixed(1))); } function ic(name) { return ''; } function chip(tone, text) { return '' + text + ''; } function checkedCount() { var n = 0; for (var k in S.checked) { if (S.checked[k]) n++; } return n; } function allChecked() { return CO.every(function (c, i) { return !!S.checked[i]; }); } function kpi(label, value, hint) { return '
' + label + '' + '' + value + '' + '' + (hint || "") + '
'; } function toast(msg, tone) { var d = document.createElement("div"); d.className = "toast"; if (tone) d.setAttribute("data-tone", tone); d.textContent = msg; $("toasts").appendChild(d); setTimeout(function () { d.style.transition = "opacity .3s"; d.style.opacity = "0"; setTimeout(function () { d.remove(); }, 320); }, 3600); } function fileStatus(i) { if (!S.recognize && !S.manual) return ["info", "В очереди"]; if (S.manual) return ["warn", "Вручную"]; var tpl = FILES[i][1]; if (tpl === "2024") return ["ok", "Т 2024 · 8/8"]; if (tpl === "2025") return ["ok", "Т 2025 · 8/8"]; return ["ok", "Т 2026 · 8/8"]; } function logs() { var l = BASE_LOGS.slice(); if (S.recognize || S.manual) l = l.concat(DONE_LOGS); return l; } /* ---------- shell ---------- */ function renderSidebar() { var items = STEPS.map(function (st) { var cur = S.screen === st.id; var badge = ""; if (st.id === "check") badge = allChecked() ? "15/15" : checkedCount() + "/15"; return ''; }); items.push(''); $("sbNav").innerHTML = '
' + items.join("") + '
'; var f = $("sbFootMeta"); if (f) f.textContent = S.memoSent ? "отправлено" : "15 компаний"; } function renderTopbar() { var t = ""; if (S.screen === "upload") t = "Загрузка и распознавание"; else if (S.screen === "check") t = "Проверка данных и отклонений"; else if (S.screen === "memo") t = "Формирование записки"; else t = "О решении"; $("topTitle").innerHTML = '' + t + 'пакет к 15:00 понедельника'; } function showScreen(id) { var ok = canOpen(id); if (!ok) { toast("Сначала завершите предыдущий шаг", "warn"); return; } var need = id !== S.screen; if (need && id === "memo" && !allChecked()) { toast("Сначала отметьте все 15 компаний как проверенные", "warn"); return; } if (need && id === "memo" && !S.unitFixed) { toast("Сначала исправьте расхождения единиц измерения", "warn"); return; } S.screen = id; save(); renderSidebar(); renderTopbar(); var r = { upload: viewUpload, check: viewCheck, memo: viewMemo, about: viewAbout }[id]; var el = $("view"); el.innerHTML = r(); document.querySelector(".kt-ai-main").scrollTop = 0; bind(id); closeMobile(); } function canOpen(id) { if (id === "upload") return true; if (id === "check") return !!S.recognize; if (id === "memo") return !!S.recognize; return true; } function closeMobile() { var sb = $("sidebar"); if (sb) sb.removeAttribute("data-open"); $("scrim").classList.remove("on"); } function stepHtml(curId) { var h = ""; STEPS.forEach(function (st, i) { var open = canOpen(st.id); var done = canOpen({ upload: "check", check: "memo", memo: "done" }[st.id]); var cur = curId === st.id; var cls = []; if (cur) cls.push('data-cur="true"'); if (done) cls.push('data-done="true"'); if (!open && !cur) cls.push('data-disabled="true"'); if (i > 0) h += ''; h += '' + (done ? ic("check") : st.n) + "" + st.t + ""; }); return '
' + h + "
"; } function pageHead(title, sub, right) { return '

' + title + "

" + (sub || "") + "

" + (right ? '
' + right + "
" : "") + "
"; } /* ================= ЭКРАН 1 — загрузка и распознавание ================= */ function viewUpload() { var fileRows = FILES.map(function (f, i) { var st = fileStatus(i); return '
' + '' + ic("spreadsheet") + '' + '
' + esc(f[0]) + '
' + '
' + esc(CO[i].name) + " · получен 21 авг, " + f[2] + "
" + chip(st[0], st[1]) + "
"; }).join(""); var progress = S.recognize || S.manual ? 100 : 0; var banner, tone; if (S.manual) { tone = "warn"; banner = "Распознавание остановлено: данные перенесены в ручной режим. Проверьте столбцы и верните автоматический режим."; } else if (S.recognize) { tone = "ok"; banner = "Рассмотрены все форматы, все 15 файлов сопоставлены, данные перенесены в единый свод. Расхождение единиц отмечено у ООО «Телеком-4»."; } else { tone = "info"; banner = "15 файлов получено из одного ящика. Запустите распознавание — система сопоставит форматы и извлечёт показатели."; } var bannerHtml = '
' + banner + "
"; var logHtml = logs().map(function (l) { var tag = l[1] === "ok" ? "ok" : (l[1] === "warn" ? "warn" : "info"); var label = l[1] === "ok" ? "готово" : (l[1] === "warn" ? "внимание" : "инфо"); return '
' + l[0] + '' + '' + chip(tag, label) + "" + "

" + l[2] + "

"; }).join(""); var extractedHead = CO.map(function (c) { return "" + esc(c.name) + ""; }).join(""); var rowNames = ["Выручка, план", "Выручка, факт", "EBITDA, план", "EBITDA, факт", "CAPEX, план", "CAPEX, факт", "ДЗ, план", "ДЗ, факт"]; var extractedRows = []; extractedRows = rowNames.map(function (label, ri) { var key = ["vp", "vf", "ep", "ef", "cp", "cf", "dp", "df"][ri]; var cells = CO.map(function (c) { var cls = "num cell-num"; if (key === "vf" && c.name === CO[REV_ISSUE].name && !S.unitFixed) cls += " cell-unit-w"; return '' + (key === "vf" && c.name === CO[REV_ISSUE].name && S.unitFixed ? fmt(c.vf / 1000) : fmt(c[key])) + ""; }).join(""); return "" + label + "" + cells + ""; }).join(""); return pageHead("Загрузка и распознавание", "Вложения из общего ящика · отчёт 15 дочерних компаний за август", '' + (S.recognize || S.manual ? '" : '') + (S.recognize ? '' : "")) + bannerHtml + stepHtml("upload") + '
' + kpi("Файлов получено", "15 / 15", "общий ящик · 21 августа") + kpi("Форматов распознано", "3 / 3", "шаблоны 2024, 2025, 2026") + kpi("Показателей извлечено", "120", "8 показателей × 15 компаний") + kpi("Прогресс", progress + "%", S.recognize || S.manual ? "распознавание завершено" : "ожидание запуска") + "
" + '
' + '

Файлы

15 шт. · август 2026
' + '
' + fileRows + "
" + '

Прогресс распознавания

формы → единый шаблон
' + '
' + progress + "%" + (S.recognize || S.manual ? "15/15 файлов" : "0/15 файлов") + 'автоматически
' + '
' + '
Показатели: выручка, EBITDA, CAPEX, дебиторская задолженность — план и факт. Сопоставление по 3 вариантам шаблонов.
' + '

Журнал действий ИИ

' + '
' + logHtml + "
" + "
" + '

Извлечённые показатели

план и факт по 15 компаниям, млн тг
' + '
' + extractedHead + "" + extractedRows + "
Показатель
"; } /* ================= ЭКРАН 2 — проверка данных и отклонений ================= */ var chk = { metric: "all" }; function viewCheck() { var cc = checkedCount(); var devCount = DEVS.length; var unitFixOk = S.unitFixed; var kpis = kpi("Компаний в сводном файле", "15 / 15", "план и факт, август") + kpi("Отклонения от плана более 10%", devCount, "Телеком-2, -3, -5, -7, -9, -13, -14") + kpi("Единицы измерения", unitFixOk ? "исправлены" : "1 требует проверки", "ООО «Телеком-4» · выручка в файле в тыс. тг") + kpi("Статус проверки", cc + " / 15", (unitFixOk && allChecked()) ? "можно переходить к записке" : "отмечайте строки по одной"); return pageHead("Проверка данных и отклонений", "Единый свод, созданный ИИ · проверьте перенос данных и отклонения от плана более 10%", '' + '") + stepHtml("check") + '
' + kpis + "
" + '

Единый свод

' + '
' + '
' + '' + '' + '' + '' + '' + "
" + '' + "
" + '
' + '

Подсветка: «×1 000» — расхождение единиц измерения, «+N%» — отклонение факта от плана. Строки с отклонениями более 10% помечены красным. Клик по строке — отметить данные как проверенные; клик по ячейке с ошибкой — исправить вручную.

' + "
" + unitBanner; } function metricKey(m) { if (m === "rev") return { plan: "vp", fact: "vf", dev: "dRev", ok: "revOk" }; if (m === "ebit") return { plan: "ep", fact: "ef", dev: "dEbit", ok: "ebitOk" }; if (m === "cap") return { plan: "cp", fact: "cf", dev: "dCap", ok: "capOk" }; if (m === "dz") return { plan: "dp", fact: "df", dev: "dDz", ok: "dzOk" }; return null; } function metricLabel(m) { return { rev: "Выручка, млн тг", ebit: "EBITDA, млн тг", cap: "CAPEX, млн тг", dz: "Дебиторская задолженность, млн тг" }[m] || ""; } function rowStatusHtml(c, i) { if (S.checked[i]) return chip("ok", "Проверено"); return '"; } function cellHtml(c, planKey, factKey, devKey, isRevUnit) { if (isRevUnit && c.vp1000 && !S.unitFixed) { return '' + fmt(c[planKey]) + " / " + fmt(c[factKey]) + ' (' + signPct(c[devKey]) + "%) " + chip("red", "×1 000") + ""; } var d = c[devKey]; var cls = "num cell-num" + (d > 10 ? " cell-dev" : ""); return '' + fmt(c[planKey]) + " / " + fmt(c[factKey]) + ' (' + signPct(d) + "%)"; } function coRows() { var m = chk.metric; return CO.map(function (c, i) { var st = rowStatusHtml(c, i); if (m === "all") { return '' + '' + esc(c.name) + "" + cellHtml(c, "vp", "vf", "dRev", true) + cellHtml(c, "ep", "ef", "dEbit") + cellHtml(c, "cp", "cf", "dCap") + cellHtml(c, "dp", "df", "dDz") + '' + st + ""; } var mk = metricKey(m); var rowDev = (m === "rev" && c.revOk) || (m === "ebit" && c.ebitOk) || (m === "cap" && c.capOk) || (m === "dz" && c.dzOk); if (!rowDev) return ""; if (S.checked[i]) return ""; var unit = m === "rev" && c.vp1000 && !S.unitFixed; return '' + '' + esc(c.name) + "" + '' + fmt(c.vp / (c.vp1000 ? 1000 : 1)) + " / " + fmt(c.vf / (c.vp1000 ? 1000 : 1)) + " (" + signPct(c.dRev) + "%)" + '' + fmt(c.ep) + " / " + fmt(c.ef) + " (" + signPct(c.dEbit) + "%)" + cellHtml(c, mk.plan, mk.fact, mk.dev, unit) + '' + st + ""; }).join(""); } function totalRowHtml() { var tv = CO.map(function (c) { return { vp: c.vp / (c.vp1000 ? 1000 : 1), vf: c.vf / (c.vp1000 ? 1000 : 1), ep: c.ep, ef: c.ef, cp: c.cp, cf: c.cf, dp: c.dp, df: c.df }; }); var s = tv.reduce(function (a, b) { a.vp += b.vp; a.vf += b.vf; a.ep += b.ep; a.ef += b.ef; a.cp += b.cp; a.cf += b.cf; a.dp += b.dp; a.df += b.df; return a; }, { vp: 0, vf: 0, ep: 0, ef: 0, cp: 0, cf: 0, dp: 0, df: 0 }); function t(p, f, d) { return '' + fmt(p) + " / " + fmt(f) + ' (' + signPct(d) + "%)"; } return 'Итого, 15 компаний' + t(s.vp, s.vf, dev(s.vf, s.vp)) + t(s.ep, s.ef, dev(s.ef, s.ep)) + t(s.cp, s.cf, dev(s.cf, s.cp)) + t(s.dp, s.df, dev(s.df, s.dp)) + ""; } function emptyRowHtml() { return 'Все отклонения в этом показателе проверены'; } function renderSumTable() { var box = $("sumTable"); if (!box) return; var m = chk.metric; var headExtra = m === "all" ? "Выручка, млн тгEBITDA, млн тгCAPEX, млн тгДЗ, млн тг" : "ВыручкаEBITDA" + metricLabel(m) + ""; var rows = coRows(); var any = rows.indexOf("Статус" + (any ? rows + (m === "all" ? totalRowHtml() : "") : emptyRowHtml()) + ""; var n = $("fNote"); if (n) n.textContent = note; } /* ================= ЭКРАН 3 — формирование записки ================= */ function memoBody() { var s = CO.reduce(function (a, b) { a.vp += b.vp / (b.vp1000 ? 1000 : 1); a.vf += b.vf / (b.vp1000 ? 1000 : 1); a.ep += b.ep; a.ef += b.ef; return a; }, { vp: 0, vf: 0, ep: 0, ef: 0 }); var dV = dev(s.vf, s.vp), dE = dev(s.ef, s.ep); var devLines = DEVS.map(function (c) { var parts = []; if (c.revOk) parts.push("выручка " + signPct(c.dRev) + "%"); if (c.ebitOk) parts.push("EBITDA " + signPct(c.dEbit) + "%"); if (c.capOk) parts.push("CAPEX " + signPct(c.dCap) + "%"); if (c.dzOk) parts.push("ДЗ " + signPct(c.dDz) + "%"); return "• " + c.name + " — " + parts.join(", "); }).join("\n"); var unitLine = S.unitFixed ? "Единицы измерения ООО «Телеком-4» исправлены: выручка переведена из тысяч в миллионы тенге." : "Внимание: у ООО «Телеком-4» в файле выручка в тысячах тенге — требуется ручная проверка единиц."; return "Свод по 15 дочерним компаниям за август 2026 г.\n\n" + "По группе: выручка " + fmt(s.vf) + " млн тг (" + signPct(dV) + "% к плану), EBITDA " + fmt(s.ef) + " млн тг (" + signPct(dE) + "% к плану).\n" + unitLine + "\n" + "Компании с отклонениями от плана более 10% (" + DEVS.length + "):\n" + devLines + "\n\n" + "Рекомендации: разобрать причины отклонений по компаниям из списка выше.\n"; } function viewMemo() { var kpis = kpi("Показателей в записке", "8", "выручка, EBITDA, CAPEX, ДЗ — план/факт") + kpi("Отклонений более 10%", String(DEVS.length), "компаний в списке") + kpi("Статус проверки", checkedCount() + " / 15", allChecked() ? "все компании проверены" : "осталось " + (15 - checkedCount())) + kpi("Готовность", S.memoSent ? "отправлено" : "к отправке", "дедлайн: пн 15:00"); var devLinesHtml = DEVS.map(function (c) { var parts = []; if (c.revOk) parts.push('выручка ' + signPct(c.dRev) + "%"); if (c.ebitOk) parts.push('EBITDA ' + signPct(c.dEbit) + "%"); if (c.capOk) parts.push('CAPEX ' + signPct(c.dCap) + "%"); if (c.dzOk) parts.push('ДЗ ' + signPct(c.dDz) + "%"); return "

• " + esc(c.name) + " — " + parts.join(", ") + "

"; }).join(""); var sentBanner = S.memoSent ? '
Записка отправлена на согласование · финансовый директор · сегодня
' : '
Дедлайн: понедельник, 15:00 · записка уходит финансовому директору после согласования текста
'; return pageHead("Формирование записки", "Итоговый документ для финансового директора · показатели подставлены из проверенного свода", '' + '' + '") + stepHtml("memo") + '
' + kpis + "
" + '
' + '

Предпросмотр документа

' + '
На: финансовый директор · От: сводящий · август 2026
' + "

Свод по 15 дочерним компаниям

" + "

Выручка группы " + fmt(CO.reduce(function (a, b) { return a + b.vf / (b.vp1000 ? 1000 : 1); }, 0)) + " млн тг, EBITDA " + fmt(CO.reduce(function (a, b) { return a + b.ef; }, 0)) + " млн тг. Отклонения от плана более 10% — у " + DEVS.length + " компаний.

" + '
' + devLinesHtml + "
" + "

" + (S.unitFixed ? "Единицы измерения всех компаний согласованы." : "У ООО «Телеком-4» требуют проверки единицы измерения выручки.") + "

" + "
" + '

Компании с отклонениями более 10%

подставлено автоматически
' + chip("warn", "требуют внимания") + '
    ' + DEVS.map(function (c) { return "
  • " + esc(c.name) + "
  • "; }).join("") + "
" + "
" + '

Текст записки

редактируется вручную
' + '' + "
" + sentBanner; } /* ================= ЭКРАН 4 — о решении ================= */ function viewAbout() { return pageHead("О решении", "Сведение отчётов дочерних компаний", "") + '
' + card("Паспорт решения", dl([ ["Наименование", "Сведение отчётов дочерних компаний"], ["Исполнитель", "владелец процесса — сборка на платформе Vibe AI"], ["Пользователи", "сводящий, финансовый директор"], ["Дедлайн", "понедельник, 15:00"] ])) + card("Предпосылки", '') + card("Входные данные", '') + card("Что делает ИИ", '
  1. Собирает файлы и распознаёт форматы
  2. Переносит данные в единый свод
  3. Подсвечивает отклонения от плана более 10%
') + card("Что решает человек", '') + card("Результат и эффект", '') + card("Риски и меры", '') + "
"; } function card(title, body) { return '

' + title + "

" + body + "
"; } function dl(rows) { return '
' + rows.map(function (r) { return "
" + r[0] + "
" + r[1] + "
"; }).join("") + "
"; } /* ================= действия ================= */ function downloadText(text, filename) { try { var b = new Blob([text], { type: "text/plain;charset=utf-8" }); var a = document.createElement("a"); a.href = URL.createObjectURL(b); a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(function () { URL.revokeObjectURL(a.href); }, 2000); toast("Скачивание: " + filename, "ok"); } catch (e) { toast("Не удалось скачать файл", "warn"); } } function excelText() { var head = "Компания;Выручка план;Выручка факт;EBITDA план;EBITDA факт;CAPEX план;CAPEX факт;ДЗ план;ДЗ факт"; var body = CO.map(function (c) { return [c.name, c.vp / (c.vp1000 ? 1000 : 1), c.vf / (c.vp1000 ? 1000 : 1), c.ep, c.ef, c.cp, c.cf, c.dp, c.df].join(";"); }).join("\n"); return "Единый свод, август 2026 (млн тг)\n" + head + "\n" + body + "\n"; } function downloadExcel() { downloadText(excelText(), "svod_otchetov_avgust_2026.csv"); S.excelMade = true; save(); } function markChecked(i, on) { if (on) S.checked[i] = true; else delete S.checked[i]; save(); if (on && checkedCount() === CO.length) { toast("Все 15 компаний проверены — доступно формирование записки", "ok"); } var row = document.querySelector('tr[data-oi="' + i + '"]'); if (row) { var stTd = row.querySelector("td:last-child"); if (stTd) stTd.innerHTML = rowStatusHtml(CO[i], i); } renderSidebar(); var k = document.querySelectorAll(".kt-ai-kpi"); var kk = k.length > 3 ? k[3] : null; if (kk) { var vEl = kk.querySelector(".value"); if (vEl) vEl.textContent = checkedCount() + " / 15"; } var toMemo = document.querySelector("[data-act='to-memo']"); if (toMemo) toMemo.disabled = !(allChecked() && S.unitFixed); toast(CO[i].name + ": " + (on ? "отмечено как проверенное" : "вернуто в работу"), on ? "ok" : "warn"); } function setUnitFix(fixed) { S.unitFixed = fixed; save(); toast(fixed ? "Единицы ООО «Телеком-4» исправлены: выручка переведена в млн тг" : "Единицы ООО «Телеком-4» возвращены в исходные", fixed ? "ok" : "warn"); if (S.screen === "check") showScreen("check"); /* частичное обновление: ячейка, баннер, KPI, кнопка */ var box = $("sumTable"); if (box && S.screen === "check") renderSumTable(); var b = $("unitBanner"); if (b) { b.setAttribute("data-tone", fixed ? "ok" : "risk"); b.innerHTML = fixed ? "Единицы ООО «Телеком-4» исправлены: выручка переведена из тысяч в миллионы тенге." : "Расхождение единиц: ООО «Телеком-4». В файле выручка в тысячах тенге, в сводном — в миллионах. Клик по ячейке выручки — исправить вручную."; } var k = document.querySelectorAll(".kt-ai-kpi"); var kk = k.length > 2 ? k[2] : null; if (kk) { var vEl = kk.querySelector(".value"); if (vEl) vEl.textContent = fixed ? "исправлены" : "1 требует проверки"; } var toMemo = document.querySelector("[data-act='to-memo']"); if (toMemo) toMemo.disabled = !(allChecked() && S.unitFixed); } /* ---------- bind ---------- */ function bind(screen) { var v = $("view"); v.querySelectorAll("[data-goto]").forEach(function (b) { b.addEventListener("click", function () { showScreen(b.getAttribute("data-goto")); }); }); if (screen === "upload") { v.querySelectorAll("[data-act='fetch']").forEach(function (b) { b.addEventListener("click", function () { toast("15 вложений получены из общего ящика", "ok"); }); }); v.querySelectorAll("[data-act='recognize']").forEach(function (b) { b.addEventListener("click", function () { S.recognize = true; save(); toast("Распознавание завершено: 15/15 файлов, 3 варианта шаблонов", "ok"); showScreen("upload"); }); }); v.querySelectorAll("[data-act='manual']").forEach(function (b) { b.addEventListener("click", function () { S.manual = true; save(); toast("Ручной режим включён: проверьте столбцы и верните автоматическое распознавание", "warn"); showScreen("upload"); }); }); v.querySelectorAll("[data-act='to-check']").forEach(function (b) { b.addEventListener("click", function () { showScreen("check"); }); }); v.querySelectorAll("[data-file]").forEach(function (r) { r.addEventListener("click", function () { var i = +r.getAttribute("data-file"); toast(CO[i].name + " · распознан, 8/8 показателей " + (S.recognize || S.manual ? "извлечены" : "в очереди"), "ok"); }); }); } if (screen === "check") { renderSumTable(); var fm = $("fMetric"); if (fm) fm.addEventListener("click", function (e) { var b = e.target.closest(".seg"); if (!b) return; chk.metric = b.getAttribute("data-v"); fm.querySelectorAll(".seg").forEach(function (x) { x.setAttribute("data-active", x === b ? "true" : "false"); }); renderSumTable(); }); var box = $("sumTable"); if (box) box.addEventListener("click", function (e) { var cell = e.target.closest("[data-unit]"); if (cell) { setUnitFix(!S.unitFixed); return; } var cb = e.target.closest("[data-check]"); if (cb) { markChecked(+cb.getAttribute("data-check"), true); return; } var tr = e.target.closest("tr[data-oi]"); if (tr) { markChecked(+tr.getAttribute("data-oi"), true); } }); v.querySelectorAll("[data-act='excel']").forEach(function (b) { b.addEventListener("click", downloadExcel); }); v.querySelectorAll("[data-act='to-memo']").forEach(function (b) { b.addEventListener("click", function () { if (!b.disabled) showScreen("memo"); }); }); } if (screen === "memo") { var ta = $("memoText"); if (ta) { ta.value = S.memoText || memoBody(); ta.addEventListener("input", function () { S.memoText = ta.value; save(); }); } v.querySelectorAll("[data-act='memo-regen']").forEach(function (b) { b.addEventListener("click", function () { var ta2 = $("memoText"); if (ta2 && S.memoText && S.memoText !== memoBody() && !confirm("Текст был отредактирован. Пересобрать записку из данных свода и записать изменения?")) return; S.memoText = memoBody(); save(); if (ta2) ta2.value = S.memoText; toast("Записка пересобрана из данных свода", "ok"); }); }); v.querySelectorAll("[data-act='memo-download']").forEach(function (b) { b.addEventListener("click", function () { downloadText(S.memoText || memoBody(), "zapiska_ugotov_avgust_2026.txt"); }); }); v.querySelectorAll("[data-act='memo-send']").forEach(function (b) { b.addEventListener("click", function () { S.memoSent = true; save(); toast("Записка отправлена на согласование финансовому директору", "ok"); showScreen("memo"); }); }); } } /* ---------- инициализация ---------- */ function init() { $("burger").addEventListener("click", function () { var sb = $("sidebar"); var open = sb.hasAttribute("data-open"); if (open) closeMobile(); else { sb.setAttribute("data-open", "true"); $("scrim").classList.add("on"); } }); $("scrim").addEventListener("click", closeMobile); window.addEventListener("resize", function () { $("burger").hidden = window.innerWidth >= 832; }); document.querySelector(".kt-ai-shell").addEventListener("click", function (e) { var n = e.target.closest(".kt-ai-nav-item[data-screen]"); if (n) showScreen(n.getAttribute("data-screen")); }); renderSidebar(); showScreen(S.screen || "upload"); } document.addEventListener("DOMContentLoaded", init); })();