"use strict"; /* Экспертиза документов для советов — статичный прототип, состояние в localStorage. */ var LS_KEY = "edds-v1"; function $(id) { return document.getElementById(id); } function esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]; }); } function escJs(s) { return esc(String(s == null ? "" : s).replace(/\\/g, "\\\\").replace(/'/g, "\\'")); } var ST = { wait: { label: "Ожидает проверки", tone: "warn" }, ready: { label: "Готово к анализу", tone: "ok" }, process: { label: "Процесс отбора", tone: "info" }, qual: { label: "Квалификация", tone: "info" }, contra: { label: "Противоречие", tone: "risk" }, checked: { label: "На проверке", tone: "info" }, conf: { label: "Подтверждено", tone: "ok" }, falsep: { label: "Ложное срабатывание", tone: "muted" }, draft: { label: "Черновик", tone: "warn" }, sent: { label: "Готово к согласованию", tone: "info" }, agree: { label: "Согласовано", tone: "ok" }, decl: { label: "Отклонено", tone: "risk" }, pending: { label: "Ожидает решения", tone: "warn" } }; function pill(key, label) { var st = ST[key] || { label: label || key, tone: "muted" }; return '' + esc(st.label) + ""; } function num(s) { return '' + s + ""; } function icon(name) { return ''; } /* ---------- Демо-данные ---------- */ function seed() { return { view: "upload", req: "Заявка ЭД-2026-118 · Управление сетевых сервисов · 14.09.2026", candidate: "А. Т. Нурмолдаев", sources: [ { n: "Трудовой кодекс Республики Казахстан", s: "Законодательство", d: 207, u: 1 }, { n: "Закон Республики Казахстан «Об акционерных обществах»", s: "Законодательство", d: 148, u: 1 }, { n: "Правила трудового распорядка", s: "Внутренние документы", d: 12, u: 1 }, { n: "Правила поиска и подбора персонала", s: "Внутренние документы", d: 8, u: 1 }, { n: "Корпоративный стандарт по управлению человеческими ресурсами фонда", s: "Внутренние документы", d: 96, u: 1 }, { n: "Должностная инструкция SEO-1 (управление сетевыми сервисами)", s: "Внутренние документы", d: 14, u: 1 }, { n: "Положение о совещаниях и комиссиях фонда", s: "Внутренние документы", d: 22, u: 1 } ], files: [], progress: null, dropActive: false, running: false, contra: false, falseDone: false, log: [], violations: [], checklist: [], verdict: "", status: "draft", rejected: null, stamp: null }; } var D = null; function load() { try { var raw = localStorage.getItem(LS_KEY); D = raw ? JSON.parse(raw) : seed(); } catch (e) { D = seed(); } if (!D.files || !D.violations) D = seed(); } function save() { try { localStorage.setItem(LS_KEY, JSON.stringify(D)); } catch (e) {} } /* ---------- Прогресс процесса ---------- */ function allFilesReady() { for (var i = 0; i < D.files.length; i++) if (D.files[i].s !== "ready") return false; return true; } function doneCount() { var n = 0, i; for (i = 0; i < D.violations.length; i++) if (D.violations[i].v === "conf") n++; return n; } function stepNum() { if (D.view === "upload") return allFilesReady() ? 2 : 1; if (D.view === "results") return 2; if (D.view === "verdict") return 3; if (D.view === "approve") return D.status === "agree" || D.status === "decl" ? 5 : 4; return 1; } var STEPS = ["Загрузка", "Анализ", "Заключение", "Согласование"]; /* ---------- UI: тосты, модалки ---------- */ var S = {}; function toast(msg, tone) { var box = $("toasts"); if (!box) return; var el = document.createElement("div"); el.className = "kt-ai-toast"; if (tone) el.dataset.tone = tone; el.textContent = msg; box.appendChild(el); setTimeout(function () { el.style.transition = "opacity .3s, transform .3s"; el.style.opacity = "0"; el.style.transform = "translateY(6px)"; setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 320); }, 2600); } function openModal(html) { var m = $("modal"); m.innerHTML = html; m.style.display = "flex"; } function closeModal() { var m = $("modal"); m.style.display = "none"; m.innerHTML = ""; } S.closeModal = closeModal; function openDrawer(html) { var d = $("drawer"); d.innerHTML = html; d.style.display = "block"; } function closeDrawer() { var d = $("drawer"); d.style.display = "none"; d.innerHTML = ""; } /* ---------- Навигация и рендер ---------- */ var NAV = [ { id: "upload", label: "Загрузка и обзор", icon: "upload" }, { id: "results", label: "Результаты анализа", icon: "sparkles" }, { id: "verdict", label: "Формирование заключения", icon: "pencil" }, { id: "approve", label: "Согласование", icon: "check-circle" }, { id: "about", label: "О решении", icon: "info", group: "Решение" } ]; var TITLES = { upload: "Загрузка и обзор материалов", results: "Результаты анализа ИИ", verdict: "Формирование заключения", approve: "Согласование руководителем", about: "О решении" }; var SUBS = { upload: "Пакет документов · первичное ознакомление", results: "Сверка процесса · проверка квалификации", verdict: "Верификация выводов · итоговый документ", approve: "Итоговое заключение · решение руководителя", about: "Паспорт ИИ-решения" }; function renderNav() { var nav = $("nav"); if (!nav) return; var group = null, html = ""; for (var i = 0; i < NAV.length; i++) { var n = NAV[i]; if (n.group && n.group !== group) { html += '
' + esc(n.group) + "
"; group = n.group; } if (!n.group) group = null; var active = D.view === n.id ? ' data-active="true"' : ""; html += '
' + '' + esc(n.label) + "
"; (function (id) { nav.lastChild.onclick = function () { S.go(id); }; })(n.id); } var sb = $("sbReq"); if (sb) sb.innerHTML = '
Текущая заявка
' + esc(D.req) + "
"; } function render() { var html; if (D.view === "upload") html = vUpload(); else if (D.view === "results") html = vResults(); else if (D.view === "verdict") html = vVerdict(); else if (D.view === "approve") html = vApprove(); else html = vAbout(); var view = $("viewInner"); if (!view) view = $("view"); if (!view) return; view.innerHTML = html; $("pageTitle").textContent = TITLES[D.view] || ""; $("pageSub").textContent = SUBS[D.view] || ""; $("roleChip").textContent = (D.view === "approve") ? "Руководитель" : "Эксперт"; renderNav(); save(); } function vStepper() { var cur = stepNum(); var html = '
'; for (var i = 0; i < STEPS.length; i++) { var st = i + 1 < cur ? "done" : (i + 1 === cur ? "current" : ""); var n = i + 1 < cur ? "✓" : String(i + 1); html += '
' + n + "" + esc(STEPS[i]) + "
"; if (i < STEPS.length - 1) html += ''; } return html + "
"; } function kpiStrip(items) { return '
' + items.map(function (k) { return '
' + esc(k.l) + '' + k.v + "" + (k.h ? '' + esc(k.h) + "" : "") + "
"; }).join("") + "
"; } /* ---------- Экран 1. Загрузка и обзор материалов ---------- */ function fmtSize(bytes) { if (bytes < 1024) return bytes + " B"; if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + " KB"; var mb = bytes / 1024 / 1024; return (mb < 100 ? mb.toFixed(1) : Math.round(mb)) + " MB"; } function classifyFile(name) { var n = name.toLowerCase(); if (n.indexOf("пояснительная") >= 0 || n.indexOf("записк") >= 0 || n.indexOf("memo") >= 0) return "Пояснительная записка"; var ext = name.slice(name.lastIndexOf(".") + 1).toLowerCase(); if (n.indexOf("договор") >= 0 || n.indexOf("contract") >= 0) return "Договор"; if (n.indexOf("акт") >= 0 || n.indexOf("certificate") >= 0) return "Сертификат / Акт"; if (n.indexOf("счёт") >= 0 || n.indexOf("счет") >= 0 || n.indexOf("invoice") >= 0) return "Счёт"; if (["doc", "docx"].indexOf(ext) >= 0) return "Документ Word"; if (["xls", "xlsx", "csv"].indexOf(ext) >= 0) return "Таблица"; if (ext === "pdf") return "PDF"; if (["jpg", "jpeg", "png"].indexOf(ext) >= 0) return "Изображение"; return "Документ"; } function vUpload() { var progress = D.progress; var zoneBlock; if (progress) { zoneBlock = dropZoneHtml() + '
' + '
' + '' + (progress.step < 3 ? "…" : icon("check")) + "" + '
' + esc(STEPS_UPLOAD[progress.step] || "…") + "" + '' + esc(progress.file) + "
" + '"
' + '
' + '
' + '
' + esc(progress.file) + "" + '' + num(progress.pct) + " %" + "
" + "
"; } else if (progress && progress.file) { zoneBlock = dropZoneHtml() + '
' + '
' + '' + icon("check") + "" + '
Загрузка завершена' + '' + esc(progress.file) + "
" + "
" + '
' + '
' + esc(progress.file) + "" + '' + fmtSize(progress.size || 0) + "
" + "
"; } else { zoneBlock = dropZoneHtml(); } var head = ""; if (!progress && D.files.length) { var all = D.files.every(function (f) { return f.s === "ready"; }); if (all && !D.running) { head = '
' + "Все материалы получены — пакет готов к запуску анализа" + '
'; } else if (!all && !D.running) { head = '
' + "Пакет не собран — не все файлы получены от кандидата" + '
'; } } var table = D.files.length ? '
' + '' + "" + D.files.map(function (f, i) { return "" + '" + '" + '" + '" + '" + ""; }).join("") + "
НазваниеРазмерДатаСтатус
' + esc(f.n) + "' + fmtSize(f.size || 0) + "' + esc(f.d) + "' + pill(f.s) + "
" : ""; return '
Процесс' + '' + 'Заявка ' + num("ЭД-2026-118") + "
" + vStepper() + "
" + (head ? '
' + head + "
" : "") + '
Материалы' + '' + (D.files.length ? '' + num(D.files.length) + " файл(а)" : "") + "
" + zoneBlock + table + "
" + '
Источники для сверки' + '' + '' + num(D.sources.length) + " источника
" + '
' + D.sources.map(function (s) { var pct = s.u ? 100 : 82; return '
' + esc(s.n) + "
" + '
Класс' + esc(s.s) + "" + 'Разделов' + num(s.d) + "" + 'Обновлён01.09.2026
' + '
' + '
' + (s.u ? pill("ok", "Синхронизирован") : pill("checked", "Синхронизация")) + "
"; }).join("") + "
" + '

Что на этом экране

' + "

Загрузите документы: перетащите файлы в зону загрузки или нажмите «выберите на устройстве». Система определит тип каждого файла, " + "отметит готовность и после полной загрузки пакета включит кнопку «Запустить анализ ИИ». Анализ будет идти по фактическим загруженным файлам — " + "сверка с внутренними правилами и законодательством по 20 источникам.

"; } function dropZoneHtml() { var active = D.dropActive ? ' data-active="true"' : ""; return '
' + '
' + '' + icon("upload") + "" + "Перетащите файлы сюда" + "или выберите на устройстве" + "
" + "PDF, DOCX, XLSX, TXT — до 50 МБ" + "
"; } /* ---------- Экран 2. Результаты анализа ИИ ---------- */ function falseCount() { var n = 0; for (var i = 0; i < D.violations.length; i++) if (D.violations[i].v === "falsep") n++; return n; } function vResults() { var confN = doneCount(), falseN = falseCount(); var procN = 0, qualN = 0, contraN = 0; for (var i = 0; i < D.violations.length; i++) { var y = D.violations[i].y; if (y === "process") procN++; else if (y === "qual") qualN++; else if (y === "contra") contraN++; } var kpis = kpiStrip([ { l: "Несогласованностей", v: num(confN + falseN) }, { l: "Процесс отбора", v: num(procN) }, { l: "Квалификация", v: num(qualN) }, { l: "Противоречий", v: num(contraN) } ]); var banner = ""; if (D.running) return runScreen(); if (D.contra) { var contraRow = null; for (var ci = 0; ci < D.violations.length; ci++) if (D.violations[ci].y === "contra") contraRow = ci; banner = '
' + 'Найдено противоречие между документами' + 'Протокол — голосование 4:1, Правила назначений — единогласно. ' + (contraRow != null ? "Проверьте вывод №" + num(contraRow + 1) + " вручную." : "Противоречие разобрано.") + "
"; } var typeTone = { process: "blue", qual: "purple", contra: "red" }; var rows = D.violations.map(function (v, i) { return "" + '' + esc(v.r) + "" + '' + esc(v.d) + "" + '' + esc(ST[v.y].label) + "" + '' + pill(v.v) + "" + '
" + '
" + ""; }).join(""); var logRows = D.log.map(function (l) { return '
' + esc(l.t) + '' + esc(l.b) + "
"; }).join(""); var nextOk = true; for (var k = 0; k < D.violations.length; k++) if (D.violations[k].v === "checked") nextOk = false; var next = ""; if (nextOk) { next = '
' + '' + '
'; } return kpis + banner + '
Несогласованности
' + '
' + '' + "" + rows + "
Ссылка на пунктНарушениеТипСтатусДействие
" + next + "
" + '
Журнал действия ИИ' + '10:42 — 10:46
' + '
' + logRows + "
" + '

Что показывает анализ

' + "

Система сверяет процесс отбора с внутренними документами и законодательством, затем проверяет квалификацию кандидата по должностной инструкции. " + "Каждый вывод ссылается на пункт нормативного акта — эксперт проверяет ссылки и при необходимости отмечает ложные срабатывания.

"; } function runScreen() { var steps = [ "Извлечение текста из пакета документов", "Сверка с законодательством и внутренними документами", "Проверка квалификации кандидата", "Формирование вывода" ]; var rows = steps.map(function (s, i) { return '
' + esc(stepsTime[i] || "10:4" + i) + '' + esc(s) + "
"; }).join(""); return '
' + 'Идёт анализ пакета — ожидайте
' + '
Ход работы' + 'Идёт анализ
' + '
' + rows + "
" + "

Анализ занимает обычно 3–5 минут. Можно закрыть вкладку — прогресс сохранится.

"; } var stepsTime = ["10:46", "10:46", "10:46", "10:46"]; /* ---------- Экран 3. Формирование заключения ---------- */ function vVerdict() { var checks = D.checklist.map(function (idx) { var v = D.violations[idx]; if (!v) return ""; return '"; }).join(""); var sent = D.status === "sent"; var status = sent ? pill("sent") : pill("draft"); var docText = $("docText") && $("docText").textContent ? $("docText").textContent : D.verdict; if (docText !== D.verdict) D.verdict = docText; return vStepper() + '
Итоговое заключение' + '' + status + "
" + '
' + '
ЗАКЛЮЧЕНИЕпо заявке ' + esc("ЭД-2026-118") + " · SEO-1, управление сетевых сервисов
" + '
' + esc(docText) + "
" + '
' + '' + (sent ? '' : '') + "
" + '
Чек-лист подтвержденных нарушений' + '' + num(D.checklist.length + " из " + D.violations.length) + " включены в заключение
" + '
' + checks + "
" + '
' + '' + 'Без системы — вручную по действующим правилам
'; } /* ---------- Экран 4. Согласование руководителем ---------- */ function vApprove() { var st = D.status; var banner = ""; var btns = ""; var kpis = kpiStrip([ { l: "Заявка", v: num("ЭД-2026-118") }, { l: "Подтверждено", v: num(D.checklist.length) }, { l: "Исключено", v: num(D.violations.length - D.checklist.length) }, { l: "Статус", v: ST[st] ? esc(ST[st].label) : "—" } ]); if (st === "agree") { banner = '
Материал согласован' + 'Согласовано ' + num(D.stamp || "15.09.2026, 09:12") + " · руководитель
"; } else if (st === "decl") { banner = '
Материал отклонён' + '' + num(D.stamp || "15.09.2026") + ' · ' + esc(D.rejected || "Без комментария") + "
"; } else { banner = '
' + 'Ожидает решения руководителя' + 'Проверены материалы на назначение руководителя SEO-1 · ' + num("14.09.2026") + "
"; btns = '
' + '' + '
'; } var checks = D.checklist.map(function (idx) { var v = D.violations[idx]; if (!v) return ""; return '
✓' + esc(v.d) + ' ' + esc(v.r) + "
"; }).join(""); return kpis + banner + '
Итоговое заключение
' + '
' + '
ЗАКЛЮЧЕНИЕпо заявке ЭД-2026-118 · SEO-1, управление сетевых сервисов
' + '
' + esc(D.verdict) + "
" + '
Подтвержденные нарушения
' + '
' + checks + "
" + btns; } /* ---------- Экран 5. О решении ---------- */ function vAbout() { return '
О решении
' + '
' + 'НаименованиеЭкспертиза документов для советов
' + 'ИсполнительВладелец процесса — сборка на платформе Vibe AI
' + 'ПользовательЭксперт, руководитель
' + 'Дата внедрения—
' + '
' + passCard("Предпосылки", "") + passCard("Входные данные и источники", "") + passCard("Процессы для внедрения ИИ", "") + passCard("Результат", "") + passCard("Ожидаемый эффект", "") + passCard("Экономический эффект", "
Объём20 экспертиз в месяц во всех экспертах
Высвобождение50 часов в месяц (оценка Төре)
") + passCard("Контроль и риски", "") + passCard("Меры по снижению", "") + passCard("Формат реализации", "") + "
" + '

Путь сотрудника

' + "

Эксперт загружает документы на экране «Загрузка и обзор материалов», переходит на «Результаты анализа ИИ» и проверяет выводы, " + "формировает итоговое заключение на «Формировании заключения», руководитель просматривает и согласует документы на «Согласовании у руководителя».

"; } function passCard(title, body) { return '

' + esc(title) + "

" + body + "
"; } function nowTime() { var d = new Date(); function p(x) { return (x < 10 ? "0" : "") + x; } return p(d.getHours()) + ":" + p(d.getMinutes()); } /* ---------- Действия ---------- */ var STEPS_UPLOAD = [ "Проверка файла", "Сканирование на вирусы", "Извлечение текста" ]; /* Скрытый input для выбора файлов на устройстве */ if (typeof window !== "undefined") { var _fileInput = null; function getFileInput() { if (!_fileInput) { _fileInput = document.createElement("input"); _fileInput.type = "file"; _fileInput.multiple = true; _fileInput.accept = ".pdf,.docx,.doc,.xlsx,.xls,.csv,.txt"; _fileInput.style.display = "none"; _fileInput.addEventListener("change", function () { if (_fileInput.files && _fileInput.files.length) { handleFiles(Array.prototype.slice.call(_fileInput.files)); } _fileInput.value = ""; }); document.body.appendChild(_fileInput); } return _fileInput; } window.__getFileInput = getFileInput; } function handleFiles(arr) { if (!arr || !arr.length) return; var existing = {}; for (var k = 0; k < D.files.length; k++) existing[D.files[k].n] = true; var toAdd = []; for (var i = 0; i < arr.length; i++) { var f = arr[i]; if (existing[f.name]) continue; toAdd.push({ f: f, t: classifyFile(f.name), size: f.size, d: nowTime() + " · " + nowTime() }); } if (!toAdd.length) { toast("Файлы уже загружены", "warn"); return; } var total = 0; for (var j = 0; j < toAdd.length; j++) total += toAdd[j].size; D.progress = { active: true, step: 0, pct: 0, file: toAdd[0].f.name, size: total, done: {} }; D.files = []; save(); render(); simulateUpload(toAdd, 0, total); } function simulateUpload(list, idx, total) { if (idx >= list.length) { if (typeof _uploadTimer !== "undefined" && _uploadTimer) clearInterval(_uploadTimer); D.progress = null; save(); toast(D.files.length + " файл(ов) загружено в пакет", "ok"); return; } var item = list[idx]; var name = item.f.name; var size = item.size; var step = 0; var pct = 0; D.progress = { active: true, step: 0, pct: 0, file: name, size: total }; save(); render(); var iv = setInterval(function () { pct += 8 + Math.floor(Math.random() * 10); if (pct >= 100) pct = 100; step = pct < 34 ? 0 : (pct < 67 ? 1 : 2); D.progress = { active: true, step: step, pct: pct, file: name, size: total }; save(); render(); if (pct >= 100) { clearInterval(iv); setTimeout(function () { D.files.push({ n: name, t: item.t, size: size, d: item.d, s: "ready" }); save(); render(); setTimeout(function () { simulateUpload(list, idx + 1, total); }, 700); }, 400); } }, 140); } S.delFile = function (i) { D.files.splice(i, 1); save(); render(); toast("Файл удалён из пакета", "info"); }; S.fileReady = function (i) { D.files[i].s = "ready"; render(); toast("Файл отмечен как готовый к анализу", "ok"); }; S.go = function (id) { D.view = id; render(); }; function genViolations() { var base = []; for (var i = 0; i < D.files.length; i++) { var f = D.files[i]; base.push( { d: f.n + ": текст извлечён и сверен с внутренними документами", y: "process", r: f.t + ", раздел 1", v: "checked" }, { d: f.n + ": найдено 2 несоответствия с нормативной базой", y: "qual", r: f.t + ", ст. 36", v: "checked" } ); } base.push( { d: "Протокол: решение голосованием 4:1, правила — единогласно", y: "contra", r: "ТК РК, ст. 165", v: "falsep" } ); if (base.length === 1) { base.unshift({ d: "Пакет пуст — нет данных для полного анализа", y: "process", r: "—", v: "falsep" }); } return base; } S.analyze = function () { if (!D.files.length) { toast("Сначала загрузите файлы", "warn"); return; } if (D.files.some(function (f) { return f.s !== "ready"; })) { toast("Дождитесь завершения загрузки всех файлов", "warn"); return; } D.running = true; D.contra = false; D.falseDone = false; var vio = genViolations(); D.checklist = []; for (var k = 0; k < vio.length; k++) { vio[k].v = "checked"; D.checklist.push(k); } D.status = "draft"; D.rejected = null; D.stamp = null; D.violations = vio; D.verdict = "Заявка на назначение руководителя уровня SEO-1, управление сетевых сервисов (кандидат " + esc(D.candidate) + ") прошла проверку соответствия нормативной базе. Итоговое заключение будет сформировано после экспертной проверки выводов."; D.log = []; save(); render(); startLogStream(); setTimeout(function () { if (!D.running) return; for (var m = 0; m < D.violations.length; m++) if (D.violations[m].y === "contra") D.violations[m].v = "falsep"; D.running = false; D.contra = true; var proc = 0, qual = 0, contra = 0; for (var n = 0; n < D.violations.length; n++) { var y = D.violations[n].y; if (y === "process") proc++; else if (y === "qual") qual++; else if (y === "contra") contra++; } D.log.push({ t: nowTime(), b: "Обнаружено несоответствий: " + (proc + qual) + ", противоречий: " + contra }); D.log.push({ t: nowTime(), b: "Журнал завершён — выводы переданы эксперту" }); D.view = "results"; save(); render(); toast("Анализ завершён — переход в результаты", "ok"); }, 6000); }; function genLogPlan() { var plan = []; var files = D.files.concat([]); for (var i = 0; i < files.length; i++) { plan.push({ b: "Извлечение текста: " + files[i].n }); } plan.push({ b: "Сверка с Правилами поиска и подбора персонала, п. 4.2" }); plan.push({ b: "Сверка с Законом «Об АО», ст. 36" }); plan.push({ b: "Проверка квалификации кандидата: стаж, образование, аттестация" }); return plan.map(function (line) { return { t: nowTime(), b: line.b }; }); } function startLogStream() { var plan = genLogPlan(); var delay = 300; for (var i = 0; i < plan.length; i++) (function (line) { setTimeout(function () { if (D.running) { D.log.push(line); save(); render(); } }, delay); })(plan[i]); } S.closeDrawer = closeDrawer; S.viewFile = function (i) { var f = D.files[i]; openDrawer( '
' + "" + esc(f.n) + "" + pill(f.s) + '
" + '
' + '
' + esc(f.t) + '' + esc(f.d) + "
" + '
' + "

Документ загружен во внутреннюю систему. Эксперт просматривает комплектность и при необходимости открывает исходник в корпоративном хранилище.

" + "

Статус: " + (ST[f.s] ? esc(ST[f.s].label) : esc(f.s)) + ".

" + "
" ); }; S.refute = function (i) { var v = D.violations[i]; v.v = v.v === "falsep" ? "checked" : "falsep"; if (v.y === "contra") { D.contra = v.v === "falsep"; D.falseDone = v.v === "falsep"; } D.checklist.forEach(function (idx, pos) { if (D.violations[idx].v === "falsep") { D.checklist.splice(pos, 1); pos--; } }); render(); toast(v.v === "falsep" ? "Вывод отмечен как ложное срабатывание" : "Вывод возвращён в работу", v.v === "falsep" ? "warn" : "ok"); }; S.ck = function (idx) { var pos = D.checklist.indexOf(idx); var el = $("ck" + idx); if (el && el.checked && pos < 0) D.checklist.push(idx); if (el && !el.checked && pos >= 0) D.checklist.splice(pos, 1); D.checklist.sort(function (a, b) { return a - b; }); save(); // обновить счётчик без перерисовки чек-бокса render(); }; function rebuildVerdict() { var parts = D.checklist.map(function (idx) { var v = D.violations[idx]; return "\n" + (parts.indexOf(idx) + 1) + ". " + v.d + " (" + v.r + ")."; }); var base = "Заявка на назначение руководителя уровня SEO-1, управление сетевых сервисов (кандидат " + D.candidate + ") прошла проверку соответствия нормативной базе."; if (D.checklist.length > 0) { base += " По завершённой сверке зафиксированы следующие несоответствия:" + parts.join("") + "\n\nРешение о назначении принимается с учётом перечисленных несоответствий. Материалы переданы на согласование руководителю."; } else { base += " Несоответствий не выявлено."; } return base; } S.sendForApproval = function () { var n = 0; for (var i = 0; i < D.violations.length; i++) if (D.violations[i].v === "checked") n++; if (n > 0) { toast("Проверьте все выводы: " + n + " на проверке", "warn"); return; } D.status = "sent"; D.rejected = null; D.stamp = null; D.verdict = rebuildVerdict(); render(); toast("Заключение отправлено на согласование руководителю", "ok"); }; S.approve = function () { D.status = "agree"; D.stamp = nowTime(); render(); toast("Материал согласован", "ok"); }; S.declare = function () { var m = prompt("Комментарий к отклонению:"); if (m === null) return; D.status = "decl"; D.stamp = nowTime(); D.rejected = m || "Без комментария"; render(); toast("Материал отклонён", "warn"); }; S.manualMode = function () { if (confirm("Вернуться в ручной режим? Текущий черновик заключения сохранится.")) { toast("Ручной режим: работа ведётся без системы", "info"); closeModal(); } }; S.openRef = function (i) { var v = D.violations[i]; openModal( '
' + '
' + icon("link") + "Ссылка на нормативный акт" + '
" + '
Пункт' + esc(v.r) + "
" + "
Вывод системы" + esc(v.d) + "
" + '
' ); }; S.burger = function () { var sb = $("sidebar"); if (!sb) return; if (sb.dataset.open === "true") sb.removeAttribute("data-open"); else sb.setAttribute("data-open", "true"); }; /* ---------- Drag & drop ---------- */ function initDnD() { document.addEventListener("dragover", function (e) { if (e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf("Files") >= 0) { e.preventDefault(); e.dataTransfer.dropEffect = "copy"; D.dropActive = true; var d = $("drop"); if (d) d.setAttribute("data-active", "true"); } }); document.addEventListener("dragleave", function (e) { if (e.relatedTarget === null) { D.dropActive = false; var d = $("drop"); if (d) d.removeAttribute("data-active"); } }); document.addEventListener("drop", function (e) { e.preventDefault(); D.dropActive = false; var d = $("drop"); if (d) d.removeAttribute("data-active"); var files = e.dataTransfer && e.dataTransfer.files; if (files && files.length) handleFiles(Array.prototype.slice.call(files)); }); } /* ---------- Инициализация ---------- */ load(); render(); initDnD(); /* Обновление drop-зоны после dragover/drop без рендера всего экрана */ function refreshDrop() { var d = $("drop"); if (d) { if (D.dropActive) d.setAttribute("data-active", "true"); else d.removeAttribute("data-active"); } } var _dndTimer = null; var _origRender = render; render = function () { _origRender(); if (_dndTimer) clearTimeout(_dndTimer); _dndTimer = setTimeout(refreshDrop, 0); };