v1.0.174: /api/sync — БД = зеркало таблицы, удаление ВСЕГО кроме keep_ids

This commit is contained in:
2026-06-24 15:11:57 +04:00
parent b19e6ba06e
commit 70a7c8bfb4
3 changed files with 29 additions and 31 deletions
+17 -21
View File
@@ -13,12 +13,22 @@ var fileQueue = [];
var contractId = null; var contractId = null;
var batchId = crypto.randomUUID(); // классификация: привязка всех файлов сессии var batchId = crypto.randomUUID(); // классификация: привязка всех файлов сессии
// Автоочистка старых записей без batch_id при загрузке страницы // Автоочистка старых записей при загрузке страницы
(async function cleanup() { (async function cleanup() {
try { try {
await fetch(VM_API + '/api/cleanup?batch=' + batchId, { method: 'POST' }); await fetch(VM_API + '/api/cleanup', { method: 'POST' });
} catch(e) { /* ignore */ } } catch(e) { /* ignore */ }
})(); })();
// Синхронизация БД с таблицей — удалить всё, чего нет в fileQueue
function syncDB() {
var keepIds = fileQueue.map(function(f) { return f.doc_id; }).filter(Boolean);
fetch(VM_API + '/api/sync', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({keep_ids: keepIds})
}).catch(function(){});
}
// Утилиты (formatSize, formatDate, moveUp, moveDown, escHtml, fmtDate) — // Утилиты (formatSize, formatDate, moveUp, moveDown, escHtml, fmtDate) —
// вынесены в app_utils.js для облегчения анализа и отладки. // вынесены в app_utils.js для облегчения анализа и отладки.
@@ -51,19 +61,12 @@ fileInput.addEventListener('change', async function() {
// Удалить из очереди файлы, которых нет в новом выборе // Удалить из очереди файлы, которых нет в новом выборе
var newNames = new Set(newFiles.map(function(f) { return f.name; })); var newNames = new Set(newFiles.map(function(f) { return f.name; }));
var removed = [];
fileQueue = fileQueue.filter(function(f) { fileQueue = fileQueue.filter(function(f) {
if (!newNames.has(f.name)) { removed.push(f); return false; } return newNames.has(f.name);
return true;
}); });
// Удалить с сервера то, что убрали из таблицы
for (var ri = 0; ri < removed.length; ri++) {
var rf = removed[ri];
if (rf.doc_id) {
fetch(VM_API + '/api/documents/' + rf.doc_id + '?contract_id=' + (contractId||''), { method: 'DELETE' }).catch(function(){});
}
}
renderTable(); renderTable();
// Разблокировать кнопку классификации — состав файлов изменился
showClassifyBtn();
for (var i = 0; i < newFiles.length; i++) { for (var i = 0; i < newFiles.length; i++) {
var f = newFiles[i]; var f = newFiles[i];
@@ -184,15 +187,8 @@ fileInput.addEventListener('change', async function() {
await refreshSupps(); await refreshSupps();
// Удалить с сервера мусор — документы этого батча, которых нет в таблице // Синхронизировать БД с таблицей — удалить всё, чего нет в fileQueue
if (batchId) { syncDB();
var keepIds = fileQueue.map(function(f) { return f.doc_id; }).filter(Boolean);
fetch(VM_API + '/api/sync-batch', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({batch_id: batchId, keep_ids: keepIds})
}).catch(function(){});
}
fileInput.value = ''; fileInput.value = '';
fileInput.disabled = false; fileInput.disabled = false;
+4
View File
@@ -33,6 +33,10 @@ function removeFile(i) {
fileQueue.splice(i, 1); fileQueue.splice(i, 1);
if (fileQueue.length === 0) contractId = null; if (fileQueue.length === 0) contractId = null;
renderTable(); renderTable();
// Синхронизировать БД с таблицей
if (typeof syncDB === 'function') syncDB();
// Разблокировать кнопку классификации при изменении состава файлов
if (typeof showClassifyBtn === 'function') showClassifyBtn();
} }
window.removeFile = removeFile; window.removeFile = removeFile;
+8 -10
View File
@@ -78,8 +78,8 @@ class Handler(BaseHTTPRequestHandler):
self._handle_api_prompts_activate() self._handle_api_prompts_activate()
elif self.path == "/api/prompts/delete": elif self.path == "/api/prompts/delete":
self._handle_api_prompts_delete() self._handle_api_prompts_delete()
elif self.path == "/api/sync-batch": elif self.path == "/api/sync":
self._handle_api_sync_batch() self._handle_api_sync()
else: else:
self.send_error(404) self.send_error(404)
@@ -160,19 +160,15 @@ class Handler(BaseHTTPRequestHandler):
execute("DELETE FROM documents WHERE id = %s", (doc_id,)) execute("DELETE FROM documents WHERE id = %s", (doc_id,))
self._json({"ok": True}) self._json({"ok": True})
# ── POST /api/sync-batch ──────────────────────────────────────────── # ── POST /api/sync ──────────────────────────────────────────────────
def _handle_api_sync_batch(self): def _handle_api_sync(self):
"""Удалить все документы батча, НЕ входящие в keep_ids.""" """Удалить ВСЕ документы, НЕ входящие в keep_ids. БД = зеркало таблицы."""
from db.connection import execute, query from db.connection import execute, query
length = int(self.headers.get("Content-Length", 0)) length = int(self.headers.get("Content-Length", 0))
body = json.loads(self.rfile.read(length)) if length > 0 else {} body = json.loads(self.rfile.read(length)) if length > 0 else {}
batch_id = body.get("batch_id", "")
keep_ids = set(body.get("keep_ids", [])) keep_ids = set(body.get("keep_ids", []))
if not batch_id: docs = query("SELECT id FROM documents", ())
self._json({"ok": False, "error": "batch_id required"}, 400)
return
docs = query("SELECT id FROM documents WHERE batch_id = %s", (batch_id,))
deleted = 0 deleted = 0
for d in (docs or []): for d in (docs or []):
if d["id"] in keep_ids: if d["id"] in keep_ids:
@@ -184,6 +180,8 @@ class Handler(BaseHTTPRequestHandler):
execute("DELETE FROM supplements WHERE id = %s", (s["id"],)) execute("DELETE FROM supplements WHERE id = %s", (s["id"],))
execute("DELETE FROM documents WHERE id = %s", (d["id"],)) execute("DELETE FROM documents WHERE id = %s", (d["id"],))
deleted += 1 deleted += 1
# Удалить осиротевшие contracts (без supplements)
execute("DELETE FROM contracts WHERE id NOT IN (SELECT DISTINCT contract_id FROM supplements)")
self._json({"ok": True, "deleted": deleted}) self._json({"ok": True, "deleted": deleted})
# ── /api/groups ?batch=X ───────────────────────────────────────────── # ── /api/groups ?batch=X ─────────────────────────────────────────────