v1.0.174: /api/sync — БД = зеркало таблицы, удаление ВСЕГО кроме keep_ids
This commit is contained in:
+17
-21
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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 ─────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user