113 lines
5.0 KiB
Python
113 lines
5.0 KiB
Python
"""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,)) |