Опубликовано через кнопку

This commit is contained in:
MeruertYes 2026-08-24 08:44:57 +00:00
parent be0a8e51f3
commit 790b8751bf
2 changed files with 112 additions and 60 deletions

View File

@ -351,13 +351,17 @@
let db = { checks: [], templates: [], typicalRemarks: [] }; let db = { checks: [], templates: [], typicalRemarks: [] };
async function load() { async function load() {
const res = await fetch("/api/data"); try {
db = await res.json(); const res = await fetch("/api/data");
updateStats(); db = await res.json();
renderRecentChecks(); updateStats();
renderTemplates(); renderRecentChecks();
renderRemarksDb(); renderTemplates();
populateQuestionTypes(); renderRemarksDb();
populateQuestionTypes();
} catch (err) {
console.error("Ошибка загрузки данных:", err);
}
} }
function updateStats() { function updateStats() {
@ -407,33 +411,48 @@
const formData = new FormData(e.target); const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries()); const data = Object.fromEntries(formData.entries());
const res = await fetch("/api/checks", { try {
method: "POST", const res = await fetch("/api/checks", {
headers: { "Content-Type": "application/json" }, method: "POST",
body: JSON.stringify(data) headers: { "Content-Type": "application/json" },
}); body: JSON.stringify(data)
const result = await res.json(); });
if (result.ok) { const result = await res.json();
currentCheckId = result.check.id; if (result.ok) {
showView("check-detail"); currentCheckId = result.check.id;
loadCheckDetail(currentCheckId); showView("check-detail");
loadCheckDetail(currentCheckId);
} else {
alert("Ошибка создания проверки: " + (result.error || "Неизвестная ошибка"));
}
} catch (err) {
alert("Ошибка создания проверки: " + err.message);
} }
}); });
async function loadCheckDetail(id) { async function loadCheckDetail(id) {
const res = await fetch(`/api/checks/${id}`); try {
const check = await res.json(); const res = await fetch(`/api/checks/${id}`);
if (!check.id) return; const check = await res.json();
if (!check || !check.id) {
alert("Проверка не найдена");
showView("checks");
return;
}
document.getElementById("detail-title").textContent = check.question || "Проверка"; document.getElementById("detail-title").textContent = check.question || "Проверка";
const statusEl = document.getElementById("detail-status"); const statusEl = document.getElementById("detail-status");
const statusClass = check.status === "Готово к комиссии" ? "status-green" : check.status === "На доработке" ? "status-red" : "status-yellow"; const statusClass = check.status === "Готово к комиссии" ? "status-green" : check.status === "На доработке" ? "status-red" : "status-yellow";
statusEl.innerHTML = `<span class="status-indicator ${statusClass}">${check.status || "Новая"}</span>`; statusEl.innerHTML = `<span class="status-indicator ${statusClass}">${check.status || "Новая"}</span>`;
renderDocuments(check.documents || []); renderDocuments(check.documents || []);
renderCompleteness(check); renderCompleteness(check);
renderRemarks(check.remarks || []); renderRemarks(check.remarks || []);
renderReport(check); renderReport(check);
} catch (err) {
console.error("Ошибка загрузки проверки:", err);
alert("Ошибка загрузки данных проверки");
}
} }
function renderDocuments(docs) { function renderDocuments(docs) {
@ -474,11 +493,12 @@
} }
function renderRemarks(remarks) { function renderRemarks(remarks) {
const safeText = (text) => String(text || "").replace(/"/g, '&quot;').replace(/'/g, '&#39;');
document.getElementById("remarks-list").innerHTML = remarks.map(r => ` document.getElementById("remarks-list").innerHTML = remarks.map(r => `
<div class="remark-card ${r.category === "КРИТИЧНО" ? "crit" : r.category === "ТРЕБУЕТ ДОРАБОТКИ" ? "warn" : "tech"}"> <div class="remark-card ${r.category === "КРИТИЧНО" ? "crit" : r.category === "ТРЕБУЕТ ДОРАБОТКИ" ? "warn" : "tech"}">
<div class="category">${r.category} • ${r.type}</div> <div class="category">${r.category} • ${r.type}</div>
<div class="text"><strong>${r.document || "Общее"}:</strong> ${r.text}</div> <div class="text"><strong>${r.document || "Общее"}:</strong> ${safeText(r.text)}</div>
<div style="font-size: 13px; color: #666;">${r.recommendation || ""}</div> <div style="font-size: 13px; color: #666;">${safeText(r.recommendation || "")}</div>
<div class="actions"> <div class="actions">
<button class="btn btn-secondary" onclick="toggleRemark(${r.id})">${r.confirmed ? "✓ Подтверждено" : "Подтвердить"}</button> <button class="btn btn-secondary" onclick="toggleRemark(${r.id})">${r.confirmed ? "✓ Подтверждено" : "Подтвердить"}</button>
<button class="btn btn-secondary" onclick="deleteRemark(${r.id})">Отклонить</button> <button class="btn btn-secondary" onclick="deleteRemark(${r.id})">Отклонить</button>
@ -524,7 +544,7 @@
<h4>📝 Ответ исполнителю</h4> <h4>📝 Ответ исполнителю</h4>
<div id="response-text" style="background: #f9f9f9; padding: 16px; border-radius: 6px; margin-top: 10px; font-size: 14px; line-height: 1.6;"> <div id="response-text" style="background: #f9f9f9; padding: 16px; border-radius: 6px; margin-top: 10px; font-size: 14px; line-height: 1.6;">
<strong>По результатам предварительной проверки материалов, представленных для вынесения вопроса на рассмотрение Заседания комиссии, необходимо дополнительно представить/скорректировать следующее:</strong><br><br> <strong>По результатам предварительной проверки материалов, представленных для вынесения вопроса на рассмотрение Заседания комиссии, необходимо дополнительно представить/скорректировать следующее:</strong><br><br>
${(check.remarks || []).filter(r => r.confirmed).map((r, i) => `${i + 1}. ${r.text}`).join("<br>") || "Замечаний нет. Материалы могут быть рассмотрены на комиссии."}<br><br> ${(check.remarks || []).filter(r => r.confirmed).map((r, i) => `${i + 1}. ${String(r.text).replace(/</g, '&lt;').replace(/>/g, '&gt;')}`).join("<br>") || "Замечаний нет. Материалы могут быть рассмотрены на комиссии."}<br><br>
<strong>После устранения указанных замечаний просим повторно направить комплект документов для рассмотрения.</strong> <strong>После устранения указанных замечаний просим повторно направить комплект документов для рассмотрения.</strong>
</div> </div>
</div> </div>
@ -545,14 +565,26 @@
fileInput.addEventListener("change", (e) => handleFiles(e.target.files)); fileInput.addEventListener("change", (e) => handleFiles(e.target.files));
async function handleFiles(files) { async function handleFiles(files) {
if (!currentCheckId) {
alert("Сначала создайте проверку!");
return;
}
for (const file of files) { for (const file of files) {
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
await fetch(`/api/upload?checkId=${currentCheckId}`, { try {
method: "POST", const res = await fetch(`/api/upload?checkId=${currentCheckId}`, {
body: formData method: "POST",
}); body: formData
});
const result = await res.json();
if (!result.ok && result.error) {
alert(`Ошибка загрузки файла ${file.name}: ${result.error}`);
}
} catch (err) {
alert(`Ошибка загрузки файла ${file.name}: ${err.message}`);
}
} }
loadCheckDetail(currentCheckId); loadCheckDetail(currentCheckId);
} }

View File

@ -284,31 +284,40 @@ function compareRequisites(docs) {
function generateRemarks(docs, discrepancies, arithmeticErrors) { function generateRemarks(docs, discrepancies, arithmeticErrors) {
const remarks = []; const remarks = [];
const existingIds = new Set();
for (const disc of discrepancies) { for (const disc of discrepancies) {
remarks.push({ const id = Date.now() + Math.random() * 1000;
id: Date.now() + Math.random(), if (!existingIds.has(id)) {
category: disc.severity, existingIds.add(id);
type: "Расхождение", remarks.push({
document: "Сравнение документов", id,
text: `Выявлено расхождение в поле "${disc.field}": ${disc.values.map(v => `${v.documents.join(", ")} — ${v.value}`).join("; ")}`, category: disc.severity,
recommendation: "Уточнить корректное значение и привести документы к единому значению", type: "Расхождение",
confirmed: true, document: "Сравнение документов",
page: null text: `Выявлено расхождение в поле "${disc.field}": ${disc.values.map(v => `${v.documents.join(", ")} — ${v.value}`).join("; ")}`,
}); recommendation: "Уточнить корректное значение и привести документы к единому значению",
confirmed: true,
page: null
});
}
} }
for (const err of arithmeticErrors) { for (const err of arithmeticErrors) {
remarks.push({ const id = Date.now() + Math.random() * 1000 + remarks.length;
id: Date.now() + Math.random(), if (!existingIds.has(id)) {
category: "КРИТИЧНО", existingIds.add(id);
type: "Арифметика", remarks.push({
document: err.document || "Не определён", id,
text: `Ошибка в расчёте: ${err.formula} = ${err.calculated}, указано ${err.indicated}`, category: "КРИТИЧНО",
recommendation: "Проверить расчёт и исправить значение", type: "Арифметика",
confirmed: true, document: err.document || "Не определён",
page: err.line text: `Ошибка в расчёте: ${err.formula} = ${err.calculated}, указано ${err.indicated}`,
}); recommendation: "Проверить расчёт и исправить значение",
confirmed: true,
page: err.line
});
}
} }
return remarks; return remarks;
@ -450,11 +459,17 @@ http.createServer(async (req, res) => {
check.documents.push(docRecord); check.documents.push(docRecord);
check.updatedAt = new Date().toISOString(); check.updatedAt = new Date().toISOString();
// Сохраняем ручные замечания (у которых нет типа "Расхождение" или "Арифметика")
const manualRemarks = (check.remarks || []).filter(r =>
r.type !== "Расхождение" && r.type !== "Арифметика"
);
const discrepancies = compareRequisites(check.documents); const discrepancies = compareRequisites(check.documents);
const allArithmeticErrors = check.documents.flatMap(d => const allArithmeticErrors = check.documents.flatMap(d =>
(d.arithmeticErrors || []).map(e => ({ ...e, document: d.name })) (d.arithmeticErrors || []).map(e => ({ ...e, document: d.name }))
); );
check.remarks = generateRemarks(check.documents, discrepancies, allArithmeticErrors); const autoRemarks = generateRemarks(check.documents, discrepancies, allArithmeticErrors);
check.remarks = [...manualRemarks, ...autoRemarks];
saveData(db); saveData(db);
} }
@ -482,6 +497,11 @@ http.createServer(async (req, res) => {
(d.arithmeticErrors || []).map(e => ({ ...e, document: d.name })) (d.arithmeticErrors || []).map(e => ({ ...e, document: d.name }))
); );
// Сохраняем только ручные замечания, автоматические перегенерируем
const manualRemarks = (check.remarks || []).filter(r =>
r.type !== "Расхождение" && r.type !== "Арифметика" && r.type !== "Комплектность"
);
const autoRemarks = generateRemarks(check.documents, discrepancies, allArithmeticErrors); const autoRemarks = generateRemarks(check.documents, discrepancies, allArithmeticErrors);
const template = db.templates.find(t => t.name === check.questionType); const template = db.templates.find(t => t.name === check.questionType);
@ -491,7 +511,7 @@ http.createServer(async (req, res) => {
const isProvided = providedTypes.some(t => t.includes(required) || required.includes(t)); const isProvided = providedTypes.some(t => t.includes(required) || required.includes(t));
if (!isProvided) { if (!isProvided) {
autoRemarks.push({ autoRemarks.push({
id: Date.now() + Math.random(), id: Date.now() + Math.random() * 1000,
category: "КРИТИЧНО", category: "КРИТИЧНО",
type: "Комплектность", type: "Комплектность",
document: required, document: required,
@ -504,7 +524,7 @@ http.createServer(async (req, res) => {
} }
} }
check.remarks = [...check.remarks, ...autoRemarks]; check.remarks = [...manualRemarks, ...autoRemarks];
const criticalCount = check.remarks.filter(r => r.category === "КРИТИЧНО" && r.confirmed).length; const criticalCount = check.remarks.filter(r => r.category === "КРИТИЧНО" && r.confirmed).length;
check.status = criticalCount > 0 ? "На доработке" : "Готово к комиссии"; check.status = criticalCount > 0 ? "На доработке" : "Готово к комиссии";