';
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), "млн ₸ в очереди", "сумма новых и проверяемых") +
"
" + (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 '
' +
p("Заявки на закупку поступают из 1С и письмами, около 150 в месяц.") +
p("Процесс включает ручную проверку данных (код ТРУ, план, сумма) и последовательную передачу между участниками.") +
"
" +
'
Входные данные и источники
' +
'
' +
p("Заявки из 1С и электронной почты.") +
p("Реестр в Excel: номер, инициатор, сумма, статус, даты.") +
"
" +
'
Процессы для внедрения ИИ
' +
'
' +
p("Проверка по чек-листу.") +
p("Подсказка кода ТРУ.") +
p("Ведение реестра и напоминания.") +
"
"; }
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();
});