/* Согласование заявок на закупку — статический интерфейс. Состояние (статусы заявок, выбор экрана, роль) — в localStorage. */ "use strict"; var LS_KEY = "sgl-zakupki-v1"; // ---------- справочники ---------- var SOURCES = ["1С", "Почта"]; var STATUS = { new: { label: "Новая", tone: "blue" }, checking: { label: "На проверке", tone: "orange" }, toLegal: { label: "На согласовании", tone: "orange" }, toSign: { label: "На подписании", tone: "purple" }, sent: { label: "Отправлена в 1С", tone: "green" }, rejected: { label: "Возвращена", tone: "red" }, disputed: { label: "Спорный случай", tone: "red" } }; function statusChip(st) { var s = STATUS[st] || STATUS.checking; return '' + s.label + ""; } // ИИ-подсказки по кодам ТРУ: для каждой заявки — 2 кандидата var TRU_HINTS = { "ЗК-0918": [ { c: "ТРУ-442.01", n: "Оптические модули, передача данных", conf: 96 }, { c: "ТРУ-442.05", n: "Кабель оптический, внешняя прокладка", conf: 54 } ], "ЗК-0919": [ { c: "ТРУ-101.22", n: "Оборудование для обслуживания АТС", conf: 91 }, { c: "ТРУ-101.27", n: "Монтажный инструмент для линий", conf: 47 } ], "ЗК-0920": [ { c: "ТРУ-310.14", n: "Провод медный, ВВГнг 3х25", conf: 98 }, { c: "ТРУ-310.11", n: "Провод медный, ВВГнг 3х16", conf: 61 } ], "ЗК-0912": [ { c: "ТРУ-501.03", n: "Сухие строительные смеси, оптом", conf: 88 }, { c: "ТРУ-501.07", n: "Краски и лаки, фасовка 10 кг", conf: 40 } ], "ЗК-0913": [ { c: "ТРУ-071.19", n: "Услуги связи, прокат линии", conf: 94 }, { c: "ТРУ-071.11", n: "Аренда коммутаторов", conf: 38 } ], "ЗК-0914": [ { c: "ТРУ-442.01", n: "Оптические модули, передача данных", conf: 52 }, { c: "ТРУ-440.02", n: "Сетевые коммутаторы", conf: 49 } ], "ЗК-0915": [ { c: "ТРУ-610.05", n: "Хостинг вычислительных мощностей", conf: 93 }, { c: "ТРУ-610.09", n: "Облачные услуги, хранилище", conf: 58 } ] }; // ---------- заявки ---------- var BASE_REQUESTS = [ { id: "ЗК-0918", init: "Ахметов С. Т.", sum: 2480000, src: "1С", in: "22.09.2026", done: "", st: "checking", trr: "ТРУ-440.12", plan: "Да", codeTru: false, dispute: false, subj: "Оптические модули для узла связи", justif: "Замена изношенных модулей на магистральном узле, срок годности запаса истёк." }, { id: "ЗК-0919", init: "Байжанова Ш. К.", sum: 1120000, src: "Почта", in: "22.09.2026", done: "", st: "checking", trr: "ТРУ-101.20", plan: "Да", codeTru: false, dispute: false, subj: "Монтажный инструмент", justif: "Дополнение оснастки бригады по заявке на обслуживание линий." }, { id: "ЗК-0920", init: "Дюсембаев Н. Б.", sum: 560000, src: "1С", in: "21.09.2026", done: "", st: "new", trr: "ТРУ-310.09", plan: "Да", codeTru: false, dispute: false, subj: "Провод ВВГнг 3х25, 1 200 м", justif: "Стройка внутреннего контура в техническом помещении." }, { id: "ЗК-0915", init: "Ермек А. Д.", sum: 1490000, src: "1С", in: "20.09.2026", done: "21.09.2026", st: "toSign", trr: "ТРУ-610.05", plan: "Да", codeTru: true, dispute: false, subj: "Хостинг дополнительных мощностей", justif: "Временное расширение хранилища до завершения строительства стойки." }, { id: "ЗК-0916", init: "Исаев Г. М.", sum: 3840000, src: "Почта", in: "20.09.2026", done: "21.09.2026", st: "toLegal", trr: "ТРУ-220.31", plan: "Нет", codeTru: true, dispute: false, subj: "Панели управления для ЩУ-2", justif: "Панели не включены в план закупок: потребность подтверждена актом осмотра." }, { id: "ЗК-0917", init: "Кадырова Ж. С.", sum: 2960000, src: "1С", in: "19.09.2026", done: "22.09.2026", st: "toSign", trr: "ТРУ-442.01", plan: "Да", codeTru: true, dispute: false, subj: "Оборудование передачи данных", justif: "Поставка по утверждённому плану на вторую половину года." }, { id: "ЗК-0914", init: "Мухитов Р. О.", sum: 1730000, src: "1С", in: "18.09.2026", done: "", st: "disputed", trr: "ТРУ-440.12", plan: "Да", codeTru: true, dispute: true, subj: "Коммутаторы сетевые, 8 шт", justif: "В заявке указано оборудование, не совпадающее с артикулом плана закупки." }, { id: "ЗК-0913", init: "Нурлан С. А.", sum: 640000, src: "Почта", in: "17.09.2026", done: "17.09.2026", st: "rejected", trr: "ТРУ-071.19", plan: "Нет", codeTru: true, dispute: false, subj: "Прокат телефонной линии", justif: "Возвращена: услуга уже приобретена по договору № 4542/2026." }, { id: "ЗК-0912", init: "Оспанова Л. Т.", sum: 1250000, src: "1С", in: "16.09.2026", done: "18.09.2026", st: "sent", trr: "ТРУ-501.03", plan: "Да", codeTru: true, dispute: false, subj: "Стройматериалы для ремонта серверной", justif: "Ремонт по смете, согласованной в плановом периоде." }, { id: "ЗК-0911", init: "Павлов К. И.", sum: 480000, src: "1С", in: "15.09.2026", done: "16.09.2026", st: "sent", trr: "ТРУ-118.40", plan: "Да", codeTru: true, dispute: true, subj: "Комплект ЗИП для линии связи", justif: "Подтверждён вручную: ИИ предложил неверный код (ТРУ-118.44), закупщик исправил." }, { id: "ЗК-0910", init: "Сафин Д. Р.", sum: 910000, src: "Почта", in: "14.09.2026", done: "15.09.2026", st: "sent", trr: "ТРУ-330.08", plan: "Да", codeTru: true, dispute: false, subj: "Эксплуатационные материалы", justif: "Запас на третий квартал, остатки ниже минимального уровня." }, { id: "ЗК-0909", init: "Тлеуберди А. М.", sum: 3200000, src: "1С", in: "12.09.2026", done: "16.09.2026", st: "sent", trr: "ТРУ-443.02", plan: "Да", codeTru: true, dispute: false, subj: "Кабель оптический, 48 волокон", justif: "Резервирующая линия между узлами, включена в план." } ]; // ---------- состояние ---------- function today() { return new Date().toISOString().slice(0, 10); } function loadState() { try { var raw = localStorage.getItem(LS_KEY); if (raw) { var st = JSON.parse(raw); if (st && st.v === 1) return st; } } catch (e) { /* повреждённые данные — начинаем заново */ } return { v: 1, screen: "work", approverRole: "legal", selected: "ЗК-0918", regStatus: "all", regSrc: "all", regQuery: "", regRole: "legal", modified: {}, daySent: "ЗК-0912", dayNew: "ЗК-0920", created: today() }; } var S; function saveState() { try { localStorage.setItem(LS_KEY, JSON.stringify(S)); } catch (e) {} } function daysIn(id) { var d = { "ЗК-0918": 2, "ЗК-0919": 1, "ЗК-0920": 1, "ЗК-0916": 2, "ЗК-0915": 2, "ЗК-0917": 4, "ЗК-0914": 3, "ЗК-0909": 8 }; return d[id] || 1; } function byId(id) { for (var i = 0; i < BASE_REQUESTS.length; i++) { if (BASE_REQUESTS[i].id === id) return BASE_REQUESTS[i]; } return null; } function live(id) { var b = byId(id); var r = {}; for (var k in b) { if (Object.prototype.hasOwnProperty.call(b, k)) r[k] = b[k]; } var m = S.modified[id]; if (m) { if (m.st) r.st = m.st; if (m.codeTru) r.codeTru = true; if (m.dispute) r.dispute = true; if (m.disputeReset) r.dispute = false; if (m.trr) r.trr = m.trr; if (m.done) r.done = m.done; if (m.doneReset) r.done = ""; } return r; } function allLive() { var out = []; for (var i = 0; i < BASE_REQUESTS.length; i++) out.push(live(BASE_REQUESTS[i].id)); return out; } // ---------- утилиты ---------- function fmtSum(x) { return x.toLocaleString("ru-RU") + " ₸"; } function esc(s) { return String(s == null ? "" : s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function toast(msg, tone) { var stack = document.getElementById("toasts"); if (!stack) return; var el = document.createElement("div"); el.className = "kt-ai-toast"; el.setAttribute("data-tone", tone || ""); el.textContent = msg; stack.appendChild(el); setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 4600); } function modalOpen(title, body) { var root = document.getElementById("modalRoot"); if (!root) return; root.innerHTML = '
' + '" + esc(body) + '
'; var btn = document.getElementById("mClose"); if (btn) btn.addEventListener("click", modalClose); var ov = root.querySelector(".kt-ai-modal-overlay"); if (ov) ov.addEventListener("click", function (e) { if (e.target === ov) modalClose(); }); } function modalClose() { var root = document.getElementById("modalRoot"); if (root) root.innerHTML = ""; } // ================= РАБОЧИЙ СТОЛ ЗАКУПЩИКА ================= function renderWork() { var list = allLive(); var queue = list.filter(function (r) { return r.st === "new" || r.st === "checking"; }); // свежие первыми, затем по дате поступления queue.sort(function (a, b) { if ((a.st === "new") !== (b.st === "new")) return a.st === "new" ? -1 : 1; return a.in < b.in ? 1 : -1; }); var sel = null; for (var i = 0; i < queue.length; i++) { if (queue[i].id === S.selected) { sel = queue[i]; break; } } if (!sel && queue.length) { sel = queue[0]; S.selected = sel.id; } var rows = queue.map(function (r) { var selAttr = r.id === S.selected ? ' data-selected="true"' : ""; var srcIc = r.src === "1С" ? "spreadsheet" : "mail"; return '" + "" + r.id + "" + "" + esc(r.init) + "" + '' + fmtSum(r.sum) + "" + "" + statusChip(r.st) + "" + '' + r.src + "" + ""; }).join(""); var k1 = queue.length; var k2 = queue.filter(function (r) { return r.st === "new"; }).length; var k3 = queue.filter(function (r) { return r.st === "checking" && !r.codeTru; }).length; var k4 = list.filter(function (r) { return r.st === "toSign" || r.st === "toLegal"; }).length; var k5 = list.filter(function (r) { return r.st === "sent"; }).length; var html = "

Рабочий стол

" + '

Проверка поступивших заявок на закупку: код ТРУ, план, сумма.

' + '
' + kpi(k1, "В очереди проверки", "новых заявок: " + k2) + kpi(k3, "Без подтверждённого кода", "из ожидает проверки") + kpi(k4, "На согласовании", "у юриста и руководителя") + kpi(k5, "Отправлено в 1С", "с начала месяца") + "
" + '
' + '
' + 'Очередьобновлено ИИ: 22.09, 09:14' + '
' + "" + (rows || emptyRow("Очередь пуста", "Все поступившие заявки проверены.")) + "
НомерИнициаторСуммаСтатусИсточник
" + "
" + workDetail(sel) + "
"; document.getElementById("view").innerHTML = html; bindWork(); } function kpi(v, label, hint) { return '
' + v + '' + esc(label) + "" + (hint ? '' + esc(hint) + "" : "") + "
"; } function emptyRow(title, sub) { return '
' + esc(title) + "
" + '
' + esc(sub) + "
"; } function workDetail(r) { if (!r) { return ''; } var hint = TRU_HINTS[r.id]; var codeErr = !r.codeTru || (r.id === "ЗК-0919"); var sumMismatch = r.id === "ЗК-0919"; var planIssue = r.plan !== "Да"; var isErr = codeErr || sumMismatch || planIssue; var errReason = codeErr ? (r.id === "ЗК-0919" ? "Сумма не сходится с ценой в плане" : (r.dispute ? "Код несовпадает с оборудованием в заявке" : "ИИ не подтвердил код")) : (sumMismatch ? "Сумма не сходится с ценой в плане" : "Нет обоснования сверх плана"); var checks = checkItem(planIssue ? "warn" : "ok", "В плане закупок", planIssue ? "В плане отсутствует, нужно обоснование" : "Строка плана найдена") + checkItem(codeErr ? "err" : "ok", "Код ТРУ", codeErr ? errReason : "Подтверждён по справочнику") + checkItem(sumMismatch ? "err" : "ok", "Сумма в пределах лимита", sumMismatch ? "Не сходится с ценой в плане" : (r.src === "1С" ? "Сверено со строкой 1С" : "В письмо от инициатора")); var okCount = isErr ? 2 : 3; return '"; } function kpiCell(k, v) { return '
' + v + '' + k + "
"; } function checkItem(tone, title, why) { var ic = tone === "ok" ? "check" : (tone === "err" ? "x" : "warning"); return '
' + "
" + esc(title) + (why ? '
' + esc(why) + "
" : "") + "
"; } function aiPanel(r) { var hint = TRU_HINTS[r.id]; var head = '
Подсказка ИИ · код ТРУ
'; if (r.codeTru) { return '
' + head + '
Код ' + r.trr + " подтверждён " + (S.modified[r.id] && S.modified[r.id].at ? S.modified[r.id].at : "(ранее)") + ". Реестр обновлён автоматически.
"; } if (!hint) return '
Подсказка не найдена
'; var best = hint[0]; var cands = hint.map(function (h, idx) { return '
' + h.c + "
" + '
' + esc(h.n) + "
" + '' + h.conf + "%
"; }).join(""); return '
' + head + '
Уверенность по первой подсказке
' + best.conf + "%
" + '
' + cands + "
" + '
' + '
'; } function bindWork() { var trs = document.querySelectorAll("#view .kt-ai-table tbody tr[data-id]"); for (var i = 0; i < trs.length; i++) { trs[i].addEventListener("click", function () { S.selected = this.getAttribute("data-id"); saveState(); renderWork(); }); } var btns = document.querySelectorAll('#view [data-act]'); for (var j = 0; j < btns.length; j++) { btns[j].addEventListener("click", onWorkAction); } var cands = document.querySelectorAll("#view .cand-row[data-cand]"); for (var c = 0; c < cands.length; c++) { cands[c].addEventListener("click", function () { var rows = document.querySelectorAll("#view .cand-row[data-cand]"); for (var k = 0; k < rows.length; k++) rows[k].classList.remove("sel"); this.classList.add("sel"); }); } } function onWorkAction(ev) { var act = ev.currentTarget.getAttribute("data-act"); var r = live(S.selected); if (!r) return; var now = "22.09.2026, " + new Date().toTimeString().slice(0, 5); var m = S.modified[r.id] || {}; if (act === "confirm") { var code = r.trr; var hintArr = TRU_HINTS[r.id]; if (hintArr && hintArr[0]) { var selCand = document.querySelector("#view .cand-row.sel"); var ci = 0; if (selCand) { var ciRaw = parseInt(selCand.getAttribute("data-cand"), 10); ci = !isNaN(ciRaw) ? ciRaw : 0; if (ci >= hintArr.length) ci = 0; } var picked = hintArr[ci]; if (picked) code = picked.c; } m.codeTru = true; m.at = now; m.trr = code; S.modified[r.id] = m; saveState(); toast("Код " + code + " подтверждён, реестр обновлён", "ok"); renderWork(); } else if (act === "return") { m.st = "rejected"; m.done = "22.09.2026"; S.modified[r.id] = m; saveState(); S.dayNew = r.id === S.dayNew ? "" : S.dayNew; S.selected = nextOf(S.selected, ["new", "checking"]); saveState(); toast(r.id + " возвращена инициатору с комментарием", "warn"); renderWork(); } else if (act === "dispute") { m.dispute = true; S.disputeTarget = r.id; saveState(); modalOpen("Спорный случай", "
Заявка " + r.id + " будет передана руководителю для разбора: ИИ предложил код, который не совпадает с версией в заявке. Решение по ней примет руководитель.
"); var ok = document.getElementById("dOk"); if (ok) ok.addEventListener("click", function () { modalClose(); S.modified[r.id] = S.modified[r.id] || {}; S.modified[r.id].st = "disputed"; delete S.modified[r.id].disputeReset; S.selected = nextOf(S.selected, ["new", "checking"]); saveState(); toast(r.id + " передана руководителю на разбор", "warn"); renderWork(); }); return; } else if (act === "other") { toast("Подскажи код — выберите из списка или верните заявку инициатору", "warn"); return; } else if (act === "legal") { m.st = "toLegal"; m.done = "22.09.2026"; S.modified[r.id] = m; saveState(); S.selected = nextOf(S.selected, ["new", "checking"]); saveState(); toast(r.id + " передана юристу на согласование", "ok"); renderWork(); } } function nextOf(skip, stList) { var q = allLive().filter(function (r) { return stList.indexOf(r.st) >= 0 && r.id !== skip; }); q.sort(function (a, b) { return a.in < b.in ? 1 : -1; }); return q.length ? q[0].id : ""; } // ================= РЕЕСТР ЗАЯВОК ================= var MONTHS = [["04.09", 7, "ok"], ["05.09", 9, "ok"], ["08.09", 8, "ok"], ["09.09", 11, "ok"], ["10.09", 8, "ok"], ["11.09", 13, "orange"], ["14.09", 7, "ok"], ["15.09", 10, "ok"], ["16.09", 9, "ok"], ["17.09", 12, "orange"], ["18.09", 8, "ok"], ["19.09", 11, "ok"]]; function renderRegistry() { var list = allLive(); var st = S.regStatus, src = S.regSrc, q = (S.regQuery || "").toLowerCase(); var filtered = list.filter(function (r) { if (st !== "all" && r.st !== st) return false; if (src !== "all" && r.src !== src) return false; if (q && (r.id.toLowerCase().indexOf(q) < 0) && (r.init.toLowerCase().indexOf(q) < 0) && (r.subj.toLowerCase().indexOf(q) < 0)) return false; return true; }); // сортировка: сначала новые, затем по дате filtered.sort(function (a, b) { return a.in < b.in ? 1 : -1; }); var inWork = list.filter(function (r) { return ["new", "checking", "toLegal", "toSign", "disputed"].indexOf(r.st) >= 0; }).length; var done = list.filter(function (r) { return r.st === "sent"; }).length; var rej = list.filter(function (r) { return r.st === "rejected"; }).length; var sumWork = 0; list.forEach(function (r) { if (["new", "checking"].indexOf(r.st) >= 0) sumWork += r.sum; }); var rows = filtered.map(function (r) { var srcIc = r.src === "1С" ? "spreadsheet" : "mail"; var disp = r.done ? r.done : "—"; var di = daysIn(r.id); return '' + r.id + "" + "" + esc(r.init) + "" + '' + fmtSum(r.sum) + "" + "" + statusChip(r.st) + "" + '' + di + "" + "" + r.in + "" + '' + disp + "" + '' + r.src + ""; }).join(""); var bars = MONTHS.map(function (m) { var max = 13, h = Math.round((m[1] / max) * 100); return '
'; }).join(""); var html = "

Реестр заявок

" + '

Все заявки филиала с текущим статусом; реестр ведёт ИИ, статусы меняет человек.

' + '
' + kpi(inWork, "В работе", "новые + проверка + согласование") + kpi(done, "Обработано", "отправлено в 1С в сентябре") + kpi(rej, "Возвращено", "инициатору на доработку") + kpi(Math.round(sumWork / 1000000), "млн ₸ в очереди", "сумма новых и проверяемых") + "
" + '
' + '
' + inputSearch() + '' + selStatus() + selSrc() + '' + "
" + '
' + "" + "" + (rows || emptyRow("Ничего не найдено", "Снимите фильтры — реестр за месяц заполнен.")) + "
НомерИнициаторСуммаСтатусСрок, днейПоступилаОбработанаИсточник
" + '
Заявки по неделям сентября
' + '
' + bars + "
" + '
' + 'входящие' + 'высокая нагрузка
' + "
Что показывает реестр" + "Пик нагрузки — 2–7 сентября: 10–13 заявок в день совпали с закрытием планового цикла. Возвратов в сентябре немного, большинство возвратов — дубли уже приобретённых услуг.
" + "
"; document.getElementById("view").innerHTML = html; bindRegistry(); } function inputSearch() { return ''; } function selStatus() { var opts = ""; for (var k in STATUS) { if (!Object.prototype.hasOwnProperty.call(STATUS, k)) continue; opts += '"; } return '
"; } function selSrc() { return '
"; } function bindRegistry() { var trs = document.querySelectorAll("#view .kt-ai-table tbody tr[data-id]"); for (var i = 0; i < trs.length; i++) { trs[i].addEventListener("click", function () { S.selected = this.getAttribute("data-id"); S.screen = "approve"; S.regRole = "legal"; saveState(); render(); }); } var q = document.querySelector(".reg-q"); if (q) q.addEventListener("input", function () { S.regQuery = this.value; saveState(); renderRegistry(); }); var status = document.querySelector(".reg-status"); if (status) status.addEventListener("change", function () { S.regStatus = this.value; saveState(); renderRegistry(); }); var src = document.querySelector(".reg-src"); if (src) src.addEventListener("change", function () { S.regSrc = this.value; saveState(); renderRegistry(); }); var exp = document.querySelector('#view [data-act="csv"]'); if (exp) exp.addEventListener("click", exportCsv); } function exportCsv() { var list = allLive(); var head = "Номер;Инициатор;Сумма;Статус;Срок (дней);Поступила;Обработана;Источник;Предмет\n"; var body = list.map(function (r) { var parts = [r.id, r.init, r.sum, STATUS[r.st].label, daysIn(r.id), r.in, r.done || "", r.src, '"' + esc(r.subj) + '"']; return parts.join(";"); }).join("\n"); var blob = new Blob(["\uFEFF" + head + body], { type: "text/csv;charset=utf-8" }); var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "reestr-zayavok-sentyabr.csv"; document.body.appendChild(a); a.click(); document.body.removeChild(a); toast("Реестр выгружен в CSV", "ok"); } // ================= СОГЛАСОВАНИЕ ================= function renderApprove() { var role = S.regRole || "legal"; var list = allLive(); var pool = list.filter(function (r) { return role === "legal" ? r.st === "toLegal" : r.st === "toSign"; }); pool.sort(function (a, b) { return a.in < b.in ? 1 : -1; }); var sel = null; for (var i = 0; i < pool.length; i++) { if (pool[i].id === S.selected) { sel = pool[i]; break; } } var isDisputed = role === "boss" && sel && sel.st === "disputed"; if (!sel && pool.length) { sel = pool[0]; } var rows = pool.map(function (r) { var sAttr = r.id === S.selected ? ' data-selected="true"' : ""; return '' + r.id + "" + "" + esc(r.init) + "" + '' + fmtSum(r.sum) + "" + "" + r.in + "" + "" + statusChip(role === "legal" ? "toLegal" : "toSign") + ""; }).join(""); // disputed-строки для руководителя var dis = list.filter(function (r) { return r.st === "disputed"; }); var disRows = dis.map(function (r) { var sAttr = isDisputed && r.id === S.selected ? ' data-selected="true"' : ""; return '' + r.id + "" + "" + esc(r.init) + "" + '' + fmtSum(r.sum) + "" + "" + r.in + "" + "" + statusChip("disputed") + ""; }).join(""); var roleTitle = role === "legal" ? "Согласование юриста" : "Подписание руководителя"; var poolLabel = role === "legal" ? "ждут согласования" : "ждут подписания"; var nToAct = pool.length + dis.length; var k1 = pool.length, k2 = dis.length, k3 = list.filter(function (r) { return r.st === "sent"; }).length, k4 = list.filter(function (r) { return r.st === "rejected"; }).length; var html = "

" + roleTitle + "

" + '

Заявки, ' + poolLabel + " после проверки закупщиком.

" + '
' + kpi(k1, "Ждут моего действия", "в очереди сейчас") + kpi(k2, "Спорные случаи", "ошибки ИИ по кодам ТРУ") + kpi(k3, "Отправлено в 1С", "сентябрь") + kpi(k4, "Возвращено", "на доработку") + "
" + '
' + 'Роль' + '
" + 'на рассмотрении: ' + nToAct + "
" + '
' + '
' + "" + "" + (rows || emptyRow("Очередь пуста", "Все заявки переданы на следующий этап.")) + disRows + "
НомерИнициаторСуммаПоступилаСтатус
" + "
" + apprDetail(sel, role, isDisputed) + "
"; document.getElementById("view").innerHTML = html; bindApprove(role); } function apprDetail(r, role, isDisputed) { if (!r) { return ''; } var hint = TRU_HINTS[r.id]; var trrNote = r.dispute ? 'ИИ предложил «' + r.trr + '», но версия оборудования в заявке не сходится — на разборе.' : 'Подтверждён закупщиком' + (hint ? ' (уверенность ИИ: ' + hint[0].conf + "%)" : "") + "."; var subj = isDisputed ? "Разбор спорного случая" : "Детали заявки"; var tone = isDisputed ? "risk" : "ok"; return '"; } function bindApprove(role) { var trs = document.querySelectorAll("#view .kt-ai-table tbody tr[data-id]"); for (var i = 0; i < trs.length; i++) { trs[i].addEventListener("click", function () { S.selected = this.getAttribute("data-id"); saveState(); renderApprove(); }); } var roleSel = document.querySelector("#view .sel-role"); if (roleSel) roleSel.addEventListener("change", function () { S.regRole = this.value; S.selected = ""; saveState(); renderApprove(); }); var acts = document.querySelectorAll("#view [data-act]"); for (var a = 0; a < acts.length; a++) acts[a].addEventListener("click", onApproveAction); } function onApproveAction(ev) { var act = ev.currentTarget.getAttribute("data-act"); var r = live(S.selected); if (!r) return; var m = S.modified[r.id] || {}; var isDisputed = r.st === "disputed"; if (act === "approve") { var role = S.regRole; if (role === "legal") { m.st = "toSign"; m.done = "22.09.2026"; } else { m.st = "sent"; m.disputeReset = true; m.at = nowStr(); m.done = "22.09.2026"; } S.modified[r.id] = m; saveState(); var msg = isDisputed ? r.id + ": спорный случай разобран, отправлена в 1С" : (role === "legal" ? r.id + " согласована и передана руководителю" : r.id + " подписана и отправлена в 1С"); toast(msg, "ok"); S.selected = ""; saveState(); renderApprove(); } else if (act === "reject" || act === "reject-draft") { m.st = "rejected"; m.done = "22.09.2026"; if (act === "reject") m.dispute = false; S.modified[r.id] = m; saveState(); toast(r.id + " возвращена инициатору", "warn"); S.selected = ""; saveState(); renderApprove(); } } function nowStr() { return "22.09.2026, " + new Date().toTimeString().slice(0, 5); } // ================= О РЕШЕНИИ ================= function renderAbout() { var html = "

О решении

" + '

Согласование заявок на закупку филиала — паспорт ИИ-решения.

' + '
' + '
' + "" + tr("Наименование", "Согласование заявок на закупку филиала") + tr("Исполнитель", "Владелец процесса — сборка на платформе Vibe AI") + tr("Пользователи", "Закупщик · Юрист · Руководитель") + tr("Дата внедрения", "—") + "
" + '
Предпосылки
' + '
' + p("Заявки на закупку поступают из 1С и письмами, около 150 в месяц.") + p("Процесс включает ручную проверку данных (код ТРУ, план, сумма) и последовательную передачу между участниками.") + "
" + '
Входные данные и источники
' + '
' + p("Заявки из 1С и электронной почты.") + p("Реестр в Excel: номер, инициатор, сумма, статус, даты.") + "
" + '
Процессы для внедрения ИИ
' + '
' + p("Проверка по чек-листу.") + p("Подсказка кода ТРУ.") + p("Ведение реестра и напоминания.") + "
" + '
Шаги процесса
' + steps() + "
" + passSteps() + risks() + "
"; document.getElementById("view").innerHTML = html; } function tr(k, v) { return "" + esc(k) + "" + esc(v) + ""; } function p(t) { return '

' + esc(t) + "

"; } function steps() { var steps = [ "Поступление заявки из 1С или по электронной почте", "Закупщик проверяет код ТРУ, наличие в плане и сумму", "ИИ подсказывает код ТРУ и ведёт реестр", "Закупщик возвращает заявку инициатору при ошибке", "Передача заявки юристу для согласования", "Руководитель подписывает заявку", "Закупщик отправляет заявку в 1С и уведомляет инициатора" ]; return '
' + steps.map(function (s, i) { return '
' + (i + 1) + '
' + esc(s) + "
"; }).join("") + "
"; } function passSteps() { var ai = ["ИИ подсказывает код ТРУ", "ИИ ведёт реестр", "ИИ обновляет статусы и даты"]; var hum = ["Закупщик проверяет код ТРУ, план и сумму", "Закупщик решает, вернуть ли заявку", "Юрист согласовывает", "Руководитель подписывает", "Руководитель разбирает спорные случаи"]; return '
Кто делает что
' + '
' + '
' + '
ИИ
' + ai.map(function (s) { return '
·
' + esc(s) + "
"; }).join("") + "
" + '
' + '
Человек
' + hum.map(function (s) { return '
·
' + esc(s) + "
"; }).join("") + "
"; } function pass() {} function risks() { return '
Контроль и риски
' + '
' + riskItem("Ошибка ИИ в коде ТРУ", "ИИ может ошибаться в коде ТРУ — все коды подтверждает закупщик.") + riskItem("Дубли", "ИИ может пропустить дубли — закупщик проверяет дубли перед отправкой в 1С.") + riskItem("Спорные случаи", "Спорные случаи разбирает руководитель, решение фиксируется в реестре.") + "
" + '
' + '
' + '
~3 000 чэкономия в месяц150 заявок × 20 ч — оценка владельца процесса
' + '
~150заявок в месяциз 1С и по почте
' + '
2 ролиюрист и руководительпроверяют после закупщика
' + "
" + '
' + 'Результат' + p("Реестр и статус заявки для закупщика и руководителя: меньше времени на обработку и меньше ошибок в кодах ТРУ.") + 'Ожидаемый эффект' + p("Быстрая обработка заявок, меньше ошибок в кодах ТРУ.") + 'Экономический эффект' + p("Около 3 000 ч в месяц (150 заявок × 20 ч) — оценка владельца процесса.") + 'Формат реализации' + p("Веб-интерфейс на Vibe AI: сначала статичный макет, после согласования — рабочее решение.") + "
"; } function riskItem(t, d) { return '
' + esc(t) + "

" + esc(d) + "

"; } // ================= НАВИГАЦИЯ + СЕССИЯ ================= var CRUMBS = { work: "Филиал · Закупки · Рабочий стол", registry: "Филиал · Закупки · Реестр", approve: "Филиал · Закупки · Согласование", about: "Филиал · Закупки · О решении" }; function render() { // сбросить выбранную заявку при смене экрана S.selected = ""; saveState(); if (S.screen === "work") renderWork(); else if (S.screen === "registry") renderRegistry(); else if (S.screen === "approve") renderApprove(); else renderAbout(); // навигация var items = document.querySelectorAll(".kt-ai-nav-item"); for (var i = 0; i < items.length; i++) { items[i].setAttribute("data-active", items[i].getAttribute("data-screen") === S.screen ? "true" : "false"); } // счётчики var list = allLive(); setCount("work", list.filter(function (r) { return r.st === "new" || r.st === "checking"; }).length); setCount("approve", list.filter(function (r) { return r.st === "toLegal" || r.st === "toSign" || r.st === "disputed"; }).length); var crumb = document.getElementById("crumb"); if (crumb) crumb.textContent = CRUMBS[S.screen] || CRUMBS.work; } function setCount(id, n) { document.querySelectorAll('[data-count="' + id + '"]').forEach(function (el) { el.textContent = n; }); } document.addEventListener("DOMContentLoaded", function () { S = loadState(); var items = document.querySelectorAll(".kt-ai-nav-item"); for (var i = 0; i < items.length; i++) { (function (a) { a.addEventListener("click", function (e) { e.preventDefault(); S.screen = a.getAttribute("data-screen"); S.selected = ""; S.selectedIsDisputed = false; saveState(); render(); }); })(items[i]); } render(); });