Опубликовано через кнопку
This commit is contained in:
parent
be0a8e51f3
commit
790b8751bf
106
index.html
106
index.html
@ -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) {
|
||||||
document.getElementById("detail-title").textContent = check.question || "Проверка";
|
alert("Проверка не найдена");
|
||||||
const statusEl = document.getElementById("detail-status");
|
showView("checks");
|
||||||
const statusClass = check.status === "Готово к комиссии" ? "status-green" : check.status === "На доработке" ? "status-red" : "status-yellow";
|
return;
|
||||||
statusEl.innerHTML = `<span class="status-indicator ${statusClass}">${check.status || "Новая"}</span>`;
|
}
|
||||||
|
|
||||||
renderDocuments(check.documents || []);
|
document.getElementById("detail-title").textContent = check.question || "Проверка";
|
||||||
renderCompleteness(check);
|
const statusEl = document.getElementById("detail-status");
|
||||||
renderRemarks(check.remarks || []);
|
const statusClass = check.status === "Готово к комиссии" ? "status-green" : check.status === "На доработке" ? "status-red" : "status-yellow";
|
||||||
renderReport(check);
|
statusEl.innerHTML = `<span class="status-indicator ${statusClass}">${check.status || "Новая"}</span>`;
|
||||||
|
|
||||||
|
renderDocuments(check.documents || []);
|
||||||
|
renderCompleteness(check);
|
||||||
|
renderRemarks(check.remarks || []);
|
||||||
|
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, '"').replace(/'/g, ''');
|
||||||
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, '<').replace(/>/g, '>')}`).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);
|
||||||
}
|
}
|
||||||
|
|||||||
66
server.js
66
server.js
@ -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 ? "На доработке" : "Готово к комиссии";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user