diff --git a/deploy/app.js b/deploy/app.js index 0fa7c40..82eb8ef 100644 --- a/deploy/app.js +++ b/deploy/app.js @@ -13,12 +13,22 @@ var fileQueue = []; var contractId = null; var batchId = crypto.randomUUID(); // классификация: привязка всех файлов сессии -// Автоочистка старых записей без batch_id при загрузке страницы +// Автоочистка старых записей при загрузке страницы (async function cleanup() { try { - await fetch(VM_API + '/api/cleanup?batch=' + batchId, { method: 'POST' }); + await fetch(VM_API + '/api/cleanup', { method: 'POST' }); } 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) — // вынесены в app_utils.js для облегчения анализа и отладки. @@ -51,19 +61,12 @@ fileInput.addEventListener('change', async function() { // Удалить из очереди файлы, которых нет в новом выборе var newNames = new Set(newFiles.map(function(f) { return f.name; })); - var removed = []; fileQueue = fileQueue.filter(function(f) { - if (!newNames.has(f.name)) { removed.push(f); return false; } - return true; + return newNames.has(f.name); }); - // Удалить с сервера то, что убрали из таблицы - 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(); + // Разблокировать кнопку классификации — состав файлов изменился + showClassifyBtn(); for (var i = 0; i < newFiles.length; i++) { var f = newFiles[i]; @@ -184,15 +187,8 @@ fileInput.addEventListener('change', async function() { await refreshSupps(); - // Удалить с сервера мусор — документы этого батча, которых нет в таблице - if (batchId) { - 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(){}); - } + // Синхронизировать БД с таблицей — удалить всё, чего нет в fileQueue + syncDB(); fileInput.value = ''; fileInput.disabled = false; diff --git a/deploy/app_utils.js b/deploy/app_utils.js index daaef95..1b55509 100644 --- a/deploy/app_utils.js +++ b/deploy/app_utils.js @@ -33,6 +33,10 @@ function removeFile(i) { fileQueue.splice(i, 1); if (fileQueue.length === 0) contractId = null; renderTable(); + // Синхронизировать БД с таблицей + if (typeof syncDB === 'function') syncDB(); + // Разблокировать кнопку классификации при изменении состава файлов + if (typeof showClassifyBtn === 'function') showClassifyBtn(); } window.removeFile = removeFile; diff --git a/deploy/convert_server.py b/deploy/convert_server.py index 70130e3..006fa96 100755 --- a/deploy/convert_server.py +++ b/deploy/convert_server.py @@ -78,8 +78,8 @@ class Handler(BaseHTTPRequestHandler): self._handle_api_prompts_activate() elif self.path == "/api/prompts/delete": self._handle_api_prompts_delete() - elif self.path == "/api/sync-batch": - self._handle_api_sync_batch() + elif self.path == "/api/sync": + self._handle_api_sync() else: self.send_error(404) @@ -160,19 +160,15 @@ class Handler(BaseHTTPRequestHandler): execute("DELETE FROM documents WHERE id = %s", (doc_id,)) self._json({"ok": True}) - # ── POST /api/sync-batch ──────────────────────────────────────────── + # ── POST /api/sync ────────────────────────────────────────────────── - def _handle_api_sync_batch(self): - """Удалить все документы батча, НЕ входящие в keep_ids.""" + def _handle_api_sync(self): + """Удалить ВСЕ документы, НЕ входящие в keep_ids. БД = зеркало таблицы.""" from db.connection import execute, query length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length)) if length > 0 else {} - batch_id = body.get("batch_id", "") keep_ids = set(body.get("keep_ids", [])) - if not batch_id: - self._json({"ok": False, "error": "batch_id required"}, 400) - return - docs = query("SELECT id FROM documents WHERE batch_id = %s", (batch_id,)) + docs = query("SELECT id FROM documents", ()) deleted = 0 for d in (docs or []): 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 documents WHERE id = %s", (d["id"],)) deleted += 1 + # Удалить осиротевшие contracts (без supplements) + execute("DELETE FROM contracts WHERE id NOT IN (SELECT DISTINCT contract_id FROM supplements)") self._json({"ok": True, "deleted": deleted}) # ── /api/groups ?batch=X ─────────────────────────────────────────────