/* Свод отчётов дочерних компаний — статичный прототип.
Состояние и правки держим в localStorage; данные — правдоподобные примеры. */
"use strict";
/* ── Справочник компаний (нейтральные названия) ───────────── */
var COMPANIES = [
"ООО «Альфа»", "ООО «Бета»", "ООО «Вега»", "ООО «Гамма»", "ООО «Дельта»",
"ООО «Эпсилон»", "ООО «Зета»", "ООО «Эта»", "ООО «Тета»", "ООО «Йота»",
"ООО «Каппа»", "ООО «Лямбда»", "ООО «Мю»", "ООО «Ню»", "ООО «Омикрон»"
];
var METRIC_TYPES = ["Выручка", "EBITDA", "CAPEX", "Дебиторка"];
var UNIT_NOTE = {
"Вега_CAPEX": "CAPEX: значение в файле в тыс. тг — пересчитано ИИ",
"Зета_Дебиторка": "Дебиторка: пустая ячейка в файле — значение не извлечено",
"Каппа_Выручка": "Выручка: в файле без разбивки по периодам — взято поле «Итого»"
};
/* План и факт по каждому показателю каждой компании: { plan, fact | null } */
var BASE = {
0: { rev: [145200, 152800], ebitda: [41200, 39600], capex: [28400, 30100], deb: [98600, 104300] },
1: { rev: [88300, 86100], ebitda: [21700, 19840], capex: [14200, 15900], deb: [52300, 58900] },
2: { rev: [210400, 206700], ebitda: [58900, 45100], capex: [33600, 34800], deb: [122400, 99800] },
3: { rev: [64800, 71200], ebitda: [15300, 10800], capex: [9800, 9200], deb: [40100, 37600] },
4: { rev: [305600, 312400], ebitda: [82400, 84900], capex: [41200, 39800], deb: [158700, 163200] },
5: { rev: [47900, 46300], ebitda: [12600, 11700], capex: [6400, 7100], deb: [28900, 31200] },
6: { rev: [127500, 124100], ebitda: [33800, 32100], capex: [18700, 17900], deb: [76400, 81900] },
7: { rev: [193200, 190800], ebitda: [51200, 12500], capex: [21400, 22300], deb: [108500, 112700] },
8: { rev: [72600, 75900], ebitda: [18900, 19400], capex: [11300, 12800], deb: [44800, 47100] },
9: { rev: [158700, 155200], ebitda: [42100, 40300], capex: [25600, 24900], deb: [89300, 92600] },
10: { rev: [112400, 119800], ebitda: [29700, 31800], capex: [16800, 17400], deb: [67200, 63400] },
11: { rev: [88400, 82400], ebitda: [22800, 12100], capex: [13100, 12600], deb: [51700, 55900] },
12: { rev: [171300, 176500], ebitda: [47600, 48900], capex: [27200, 28800], deb: [94100, 98700] },
13: { rev: [96800, 93500], ebitda: [25400, 24200], capex: [15300, 14900], deb: [58600, 61200] },
14: { rev: [132900, 138600], ebitda: [36800, 38200], capex: [22100, 23400], deb: [81500, 84300] }
};
/* Журнал действий ИИ (время — минуты от начала сессии) */
var LOG = [
{ t: "08:51", m: "Подключение к общему ящику — получено 9 писем с вложениями" },
{ t: "08:52", m: "Скачаны 9 файлов Excel, дубликаты не обнаружены", tag: "09/09" },
{ t: "08:53", m: "Файл «Альфа»: шаблон А1, показатели найдены, перенесены в свод" },
{ t: "08:53", m: "Файл «Бета»: шаблон А1, показатели найдены, перенесены в свод" },
{ t: "08:54", m: "Файл «Гамма»: шаблон А1, показатели найдены, перенесены в свод" },
{ t: "08:54", m: "Файл «Дельта»: шаблон А1, показатели найдены, перенесены в свод" },
{ t: "08:55", m: "Файл «Эпсилон»: шаблон А1, показатели найдены, перенесены в свод" },
{ t: "08:55", m: "Файл «Зета»: шаблон А1, 1 ячейка не распознана — требуется ручной режим" },
{ t: "08:56", m: "Файл «Эта»: шаблон Б2, нестандартная структура — требуется ручной режим" },
{ t: "08:56", m: "Файл «Каппа»: шаблон Б2, поле «Итого» взято как выручка" },
{ t: "08:59", m: "Ожидаются файлы: «Вега», «Тета», «Йота», «Лямбда», «Мю», «Ню», «Омикрон»" }
];
var FILE_TPL = {
ok: { status: "ok", label: "Успешно", date: "06.05.2026, 08:53" },
error: { status: "risk", label: "Ошибка формата", date: "06.05.2026, 08:55" },
pending: { status: "muted", label: "Ожидается", date: "" }
};
var FILE_SIZES = [
"1.2 МБ", "0.8 МБ", "1.6 МБ", "0.6 МБ", "2.1 МБ",
"0.9 МБ", "1.1 МБ", "1.7 МБ", "0.7 МБ", "1.4 МБ",
"1.3 МБ", "0.9 МБ", "1.5 МБ", "0.8 МБ", "1.9 МБ"
];
var DEFAULT_NOTE = "Уважаемый финансовый директор!\n\nГотов свод отчётов 15 дочерних компаний за апрель 2026 года. Файл «Свод отчётов — апрель 2026.xlsx» прилагается.\n\nКлючевые отклонения:\n" +
"1. ООО «Зета» — выручка выше плана на 18,6%.\n" +
"2. ООО «Вега» — EBITDA ниже плана на 23,4%.\n" +
"3. ООО «Эта» — EBITDA ниже плана на 75,6%.\n\n" +
"Просьба ознакомиться. По вопросам — на связи.\n\nС уважением,\nСводящий";
/* ── Состояние ───────────────────────────────────────────── */
var LS_KEY = "svod-otchetov-v1";
function pad2(n) { return String(n).length < 2 ? "0" + String(n) : String(n); }
function fmtTime(minutes) {
var h = Math.floor(minutes / 60);
var m = minutes % 60;
return pad2(h) + ":" + pad2(m);
}
/* 15 компаний × 4 показателя: план и факт. Факт null — ячейка не извлечена. */
function buildMetrics() {
var out = [];
var keys = ["rev", "ebitda", "capex", "deb"];
for (var i = 0; i < 15; i++) {
for (var j = 0; j < 4; j++) {
var row = BASE[i][keys[j]];
var plan = row[0];
var fact = row[1];
if (i === 6 && j === 3) fact = null; // Зета, дебиторка — пустая ячейка
out.push({
id: "C" + i + "_M" + j,
c: i,
m: j,
company: COMPANIES[i],
type: METRIC_TYPES[j],
plan: plan,
fact: fact
});
}
}
return out;
}
function defaultState() {
var files = [];
for (var i = 0; i < 15; i++) {
var tpl = FILE_TPL.pending;
if (i === 2 || i === 7) tpl = FILE_TPL.error;
else if (i < 9) tpl = FILE_TPL.ok;
var short = COMPANIES[i].replace("ООО «", "").replace("»", "");
files.push({
name: "Отчёт " + short + " — апрель 2026.xlsx",
company: COMPANIES[i],
status: tpl.status,
statusLabel: tpl.label,
date: tpl.date
});
}
return {
screen: "upload",
files: files,
log: LOG,
processed: 7,
metrics: buildMetrics(),
notesResolved: {},
manual: {},
approved: false,
noteText: DEFAULT_NOTE,
sent: false,
sentAt: null,
period: "апрель 2026"
};
}
function load() {
try {
var raw = localStorage.getItem(LS_KEY);
if (raw) {
var s = JSON.parse(raw);
if (s && s.metrics && s.metrics.length) return s;
}
} catch (e) { /* ignore */ }
return defaultState();
}
function save() {
try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ }
}
var state = load();
/* ── Утилиты разметки ────────────────────────────────────── */
function esc(s) {
var str = String(s == null ? "" : s);
return str
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function fmtNum(v) {
if (v === null || v === undefined) return "—";
return Number(v).toLocaleString("ru-RU");
}
function fmtDev(dev) {
if (dev === null || dev === undefined) return "—";
var sign = dev > 0 ? "+" : "";
return sign + dev.toFixed(1).replace(".", ",") + "%";
}
function icon(name) {
return '';
}
function pill(status, label) {
return '' + esc(label) + "";
}
function metricOf(id) {
for (var i = 0; i < state.metrics.length; i++) {
if (state.metrics[i].id === id) return state.metrics[i];
}
return null;
}
function companyShort(name) {
return name.replace("ООО «", "").replace("»", "");
}
/* ── Журнал: добавление записи ───────────────────────────── */
var clock = 9 * 60 + 19;
function addLog(msg) {
clock += 1;
state.log.unshift({ t: fmtTime(clock), m: msg });
}
/* ── Тосты ───────────────────────────────────────────────── */
function toast(text, tone) {
var stack = document.getElementById("toasts");
if (!stack) return;
var el = document.createElement("div");
el.className = "kt-ai-toast";
el.setAttribute("data-tone", tone || "info");
el.innerHTML = "" + esc(text) + "";
stack.appendChild(el);
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 4200);
}
/* ── Навигация ───────────────────────────────────────────── */
function show(id) {
state.screen = id;
save();
renderAll();
window.scrollTo(0, 0);
}
(function initNav() {
var nav = document.getElementById("app-nav");
if (!nav) return;
nav.addEventListener("click", function (e) {
var item = e.target.closest(".kt-ai-nav-item");
if (!item) return;
var scr = item.getAttribute("data-nav");
if (scr) show(scr);
});
})();
/* ── Мини-KPI ────────────────────────────────────────────── */
function kpis(cards) {
var h = '
';
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
h += '
';
h += '
' + esc(c.label) + "
";
h += '
' + esc(c.val) + "";
if (c.status && c.badge) {
h += '' + esc(c.badge) + "";
}
h += "
";
if (c.hint) h += '
' + esc(c.hint) + "
";
h += "
";
}
return h + "
";
}
function logHtml(entries) {
var h = '
';
for (var i = 0; i < entries.length; i++) {
var e = entries[i];
h += "
";
h += '' + esc(e.t) + "";
h += "" + esc(e.m) + "";
if (e.tag) h += '' + esc(e.tag) + "";
h += "
";
}
return h + "
";
}
function warningsHtml() {
var items = [];
var w1 = { key: "Вега_CAPEX", text: "«Вега»: CAPEX в файле в тыс. тг. ИИ пересчитал в млн, сверьте ячейку CAPEX." };
var w2 = { key: "Зета_Дебиторка", text: "«Зета»: дебиторка не извлечена — пустая ячейка в файле. Укажите значение вручную." };
var w3 = { key: "Каппа_Выручка", text: "«Каппа»: в файле нет разбивки выручки — взято поле «Итого». Проверьте период." };
items.push(w1); items.push(w2); items.push(w3);
var h = '
';
for (var i = 0; i < items.length; i++) {
var it = items[i];
var resolved = !!(state.notesResolved[it.key]);
h += '
';
h += icon(resolved ? "check-circle" : "warning");
h += "" + esc(it.text) + "";
if (!resolved) {
h += '";
}
h += "
";
}
return h + "
";
}
function renderUpload() {
var files = state.files;
var okCount = 0, errCount = 0, pendCount = 0;
for (var i = 0; i < files.length; i++) {
if (files[i].status === "ok") okCount++;
else if (files[i].status === "risk") errCount++;
else pendCount++;
}
var html = "";
html += '
';
html += '
';
html += '
';
html += '
Загрузка и распознавание
';
html += 'апрель 2026 · 15 дочерних';
html += "
";
html += '
Файлы с общего ящика, обработка разными шаблонами и контроль качества извлечения показателей.
";
}
tb.innerHTML = trHtml;
var fc = document.getElementById("f-count");
if (fc) fc.textContent = "Показано компаний: " + n + " из " + COMPANIES.length;
}
/* ── Экран 3: Формирование результата ────────────────────── */
function topDeviations() {
var list = [];
for (var i = 0; i < state.metrics.length; i++) {
var mt = state.metrics[i];
if (mt.plan === null || mt.fact === null || mt.plan === 0) continue;
var dev = Math.round(((mt.fact - mt.plan) / mt.plan) * 1000) / 10;
if (Math.abs(dev) > 10) {
list.push({ company: mt.company, type: mt.type, dev: dev });
}
}
list.sort(function (a, b) { return Math.abs(b.dev) - Math.abs(a.dev); });
return list;
}
function renderResult() {
var devs = topDeviations();
var approvedCount = 0;
for (var i = 0; i < state.metrics.length; i++) {
if (state.metrics[i].fact !== null) approvedCount++;
}
var ready = state.approved && !state.sent;
var html = "";
html += '
';
html += '
';
html += '
';
html += '
Формирование результата
';
html += 'шаг 3 из 3';
html += "
";
html += '
Сводный файл и записка финансовому директору. Отправьте, когда всё проверите.
';
html += "
";
html += '
';
if (!state.approved) {
html += '
Для формирования результата сначала утвердите данные на шаге «Проверка данных и отклонений».
';
} else if (state.sent) {
html += '
Результат отправлен финансовому директору ' + esc(state.sentAt || "") + ". Файл и записка доступны в архиве переписки.
";
} else {
html += '
Готово к отправке: ' + approvedCount + " показателей, ключевые отклонения " + devs.length + " шт.
";
}
/* Предпросмотр сводного файла */
html += '';
html += '
Предпросмотр «Свод отчётов — апрель 2026.xlsx»
';
html += '
';
html += '
';
html += "
Компания
Выручка
EBITDA
CAPEX
Дебиторка
";
html += "";
for (var c = 0; c < 15; c++) {
html += '
' + esc(COMPANIES[c]) + "
";
for (var m = 0; m < 4; m++) {
var mt = metricOf("C" + c + "_M" + m);
var val = (mt && mt.fact !== null) ? fmtNum(mt.fact) : "—";
html += '
' + esc(val) + "
";
}
html += "
";
}
html += "
";
html += "";
html += '
';
/* Записка */
html += '';
html += '
Записка финансовому директору
';
html += '
';
html += '";
html += '
';
html += '";
html += 'Смена текста не влияет на файл свода.';
html += "
";
html += "
";
html += "";
/* Ключевые отклонения */
html += '';
html += '
Ключевые отклонения для записки
';
if (devs.length === 0) {
html += '
Отклонений больше 10% нет
';
} else {
html += '
';
for (var d = 0; d < devs.length; d++) {
var it = devs[d];
var up = it.dev > 0;
html += "
';
html += '";
if (state.sent) {
html += '";
}
html += "
";
html += "
";
html += "
";
return html;
}
/* ── Экран 4: О решении ──────────────────────────────────── */
function renderAbout() {
var items = [
{
title: "Предпосылки",
icon: "info",
items: [
"Свод отчётов 15 дочерних компаний выполняется вручную раз в месяц",
"Шаблон у каждой компании разный",
"Процесс долгий и содержит ошибки"
]
},
{
title: "Входные данные",
icon: "file",
kv: [
["Источники", "Файлы Excel с выручкой, EBITDA, CAPEX, дебиторкой, планом и фактом"],
["Канал передачи", "Письма на общий ящик"],
["Периодичность", "Ежемесячный"]
]
},
{
title: "Роль ИИ",
icon: "sparkles",
items: [
"Скачивает файлы с общего ящика",
"Распознаёт разные форматы",
"Переносит показатели в единый свод",
"Подсвечивает отклонения от плана больше 10%"
]
},
{
title: "Роль человека",
icon: "user",
items: [
"Проверяет корректность извлечения данных из нестандартных файлов",
"Контролирует единицы измерения и ошибки ИИ",
"Утверждает итоговый свод перед запиской",
"Решает, когда и как отправить результат финансовому директору"
]
},
{
title: "Результат",
icon: "check-circle",
items: [
"Сводный Excel",
"Записка финансовому директору к 15:00 понедельника"
]
},
{
title: "Ожидаемый эффект",
icon: "trophy",
items: [
"Ускорение процесса",
"Снижение количества ошибок",
"Ускорение принятия решений"
]
},
{
title: "Экономический эффект",
icon: "chart",
kv: [
["Расход времени сейчас", "3–4 часа в неделю"],
["Итого", "около 12–16 часов в месяц"]
]
},
{
title: "Риски и меры",
icon: "alert-circle",
items: [
"Ошибка ИИ на нестандартном файле — контроль осуществляет сводящий",
"Ошибка ИИ в единицах измерения — предупреждения в каждом файле",
"Журнал действий ИИ и возможность включить ручной режим"
]
}
];
var html = "";
html += '
';
html += '
';
html += '
';
html += '
О решении
';
html += "
";
html += '
Свод отчётов дочерних компаний: автоматизация сбора и сверки 15 файлов с контролем отклонений от плана.
';
html += "
";
html += '
';
html += '';
html += '
Паспорт решения
';
html += '
';
html += '
НаименованиеСвод отчётов дочерних компаний
';
html += '
ИсполнительВладелец процесса, сборка на платформе Vibe AI
';
html += '
ПользовательСводящий, финансовый директор
';
html += "
";
html += "";
html += '
';
for (var i = 0; i < items.length; i++) {
var it = items[i];
html += '
';
html += '
' + icon(it.icon) + "" + esc(it.title) + "
";
if (it.items) {
html += '
';
for (var j = 0; j < it.items.length; j++) html += "
" + esc(it.items[j]) + "
";
html += "
";
}
if (it.kv) {
html += '
';
for (var k2 = 0; k2 < it.kv.length; k2++) {
html += '