feat: add v3 extraction storage and structured parsing
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""CRUD для досье договоров и доказанных связей с документами.
|
||||
|
||||
Досье идентифицируется внутренним UUID, а номер договора и его алиасы
|
||||
сохраняются как изменяемые утверждения. Поэтому опечатки в документах
|
||||
не становятся ключом базы и могут быть пересмотрены без переноса данных.
|
||||
"""
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from db.connection import execute, query, transaction
|
||||
|
||||
|
||||
def _json_value(value, default):
|
||||
"""Кодировать поля-списки, принимая уже сериализованный JSON без повтора."""
|
||||
if value is None:
|
||||
value = default
|
||||
return value if isinstance(value, str) else json.dumps(value, ensure_ascii=False)
|
||||
|
||||
|
||||
def _decode_dossier(row):
|
||||
"""Добавить к строке досье Python-представление списка алиасов."""
|
||||
if not row:
|
||||
return None
|
||||
result = dict(row)
|
||||
try:
|
||||
result["alias_numbers"] = json.loads(result.get("alias_numbers_json") or "[]")
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
result["alias_numbers"] = []
|
||||
return result
|
||||
|
||||
|
||||
def create_dossier(batch_id, canonical_number, alias_numbers=None,
|
||||
counterparty_name="", counterparty_inn="",
|
||||
has_frame_contract=False, status="ACTIVE"):
|
||||
"""Создать досье и вернуть его запись со стабильным внутренним ID."""
|
||||
dossier_id = str(uuid.uuid4())
|
||||
execute(
|
||||
"""INSERT INTO contract_dossiers
|
||||
(id, batch_id, canonical_number, alias_numbers_json,
|
||||
counterparty_name, counterparty_inn, has_frame_contract, status)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
|
||||
(dossier_id, batch_id, canonical_number,
|
||||
_json_value(alias_numbers, []), counterparty_name or "",
|
||||
counterparty_inn or "", int(bool(has_frame_contract)), status),
|
||||
)
|
||||
return get_dossier(dossier_id)
|
||||
|
||||
|
||||
def link_document(dossier_id, document_id, role="supplement",
|
||||
link_status="CONFIRMED", link_reason="", seq_order=0):
|
||||
"""Создать или обновить связь dossier-document идемпотентно.
|
||||
|
||||
Повторная сборка групп не должна создавать дубликаты. При уже имеющейся
|
||||
связи сохраняется её ID, но актуальные роль, статус, основание и порядок
|
||||
заменяются значениями нового решения группировщика.
|
||||
"""
|
||||
existing = query(
|
||||
"SELECT id FROM dossier_documents WHERE dossier_id = %s AND document_id = %s",
|
||||
(dossier_id, document_id),
|
||||
)
|
||||
if existing:
|
||||
link_id = existing[0]["id"]
|
||||
execute(
|
||||
"""UPDATE dossier_documents
|
||||
SET role = %s, link_status = %s, link_reason = %s, seq_order = %s
|
||||
WHERE id = %s""",
|
||||
(role, link_status, link_reason or "", seq_order, link_id),
|
||||
)
|
||||
else:
|
||||
link_id = str(uuid.uuid4())
|
||||
execute(
|
||||
"""INSERT INTO dossier_documents
|
||||
(id, dossier_id, document_id, role, link_status, link_reason, seq_order)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)""",
|
||||
(link_id, dossier_id, document_id, role, link_status,
|
||||
link_reason or "", seq_order),
|
||||
)
|
||||
rows = query("SELECT * FROM dossier_documents WHERE id = %s", (link_id,))
|
||||
return rows[0] if rows else None
|
||||
|
||||
|
||||
def list_dossiers_by_batch(batch_id):
|
||||
"""Получить досье batch вместе с упорядоченным списком документов."""
|
||||
dossiers = query(
|
||||
"SELECT * FROM contract_dossiers WHERE batch_id = %s ORDER BY created_at, id",
|
||||
(batch_id,),
|
||||
)
|
||||
result = []
|
||||
for raw_dossier in dossiers:
|
||||
dossier = _decode_dossier(raw_dossier)
|
||||
dossier["documents"] = query(
|
||||
"""SELECT dd.*, d.filename, d.doc_type, d.own_number, d.parent_number,
|
||||
d.doc_date, d.counterparty, d.zip_source
|
||||
FROM dossier_documents dd
|
||||
JOIN documents d ON d.id = dd.document_id
|
||||
WHERE dd.dossier_id = %s
|
||||
ORDER BY dd.seq_order, d.created_at, d.id""",
|
||||
(dossier["id"],),
|
||||
)
|
||||
result.append(dossier)
|
||||
return result
|
||||
|
||||
|
||||
def get_dossier(dossier_id):
|
||||
"""Получить досье по внутреннему ID, не загружая список документов."""
|
||||
rows = query("SELECT * FROM contract_dossiers WHERE id = %s", (dossier_id,))
|
||||
return _decode_dossier(rows[0]) if rows else None
|
||||
|
||||
|
||||
def clear_batch_dossiers(batch_id):
|
||||
"""Удалить все досье batch; таблицы с ON DELETE CASCADE очистятся вместе с ними."""
|
||||
with transaction():
|
||||
return execute("DELETE FROM contract_dossiers WHERE batch_id = %s", (batch_id,))
|
||||
Reference in New Issue
Block a user