- Исправлен N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js: использовать uploads_field_labels[0] вместо [grp] - Создан SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql с дедупликацией documents_meta - Создан SQL_CLEANUP_DOCUMENTS_META_DUPLICATES.sql для очистки существующих дубликатов - Создан полный уникальный индекс idx_document_texts_hash_unique на document_texts(file_hash) - Добавлен SESSION_LOG_2025-11-28_documents_dedup.md с описанием всех изменений Fixes: - field_label теперь корректно отображает 'Переписка' вместо 'group-2' - documents_meta не накапливает дубликаты при повторных сохранениях - ON CONFLICT (file_hash) теперь работает для document_texts
155 lines
5.7 KiB
Python
155 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Исправление field_name в таблице clpr_claim_documents
|
||
Пересоздаёт записи с правильными field_name на основе documents_uploaded и documents_required
|
||
"""
|
||
import asyncio
|
||
import asyncpg
|
||
import json
|
||
from datetime import datetime
|
||
|
||
POSTGRES_HOST = "147.45.189.234"
|
||
POSTGRES_PORT = 5432
|
||
POSTGRES_DB = "default_db"
|
||
POSTGRES_USER = "gen_user"
|
||
POSTGRES_PASSWORD = "2~~9_^kVsU?2\\S"
|
||
|
||
CLAIM_ID = "bddb6815-8e17-4d54-a721-5e94382942c7"
|
||
|
||
async def fix_field_names():
|
||
conn = await asyncpg.connect(
|
||
host=POSTGRES_HOST,
|
||
port=POSTGRES_PORT,
|
||
database=POSTGRES_DB,
|
||
user=POSTGRES_USER,
|
||
password=POSTGRES_PASSWORD
|
||
)
|
||
|
||
try:
|
||
# Получаем данные черновика
|
||
row = await conn.fetchrow("""
|
||
SELECT id, payload
|
||
FROM clpr_claims
|
||
WHERE id::text = $1 OR payload->>'claim_id' = $1
|
||
ORDER BY updated_at DESC
|
||
LIMIT 1
|
||
""", CLAIM_ID)
|
||
|
||
if not row:
|
||
print(f"❌ Черновик {CLAIM_ID} не найден!")
|
||
return
|
||
|
||
claim_uuid = row['id']
|
||
payload = row['payload'] if isinstance(row['payload'], dict) else json.loads(row['payload'])
|
||
|
||
documents_required = payload.get('documents_required', [])
|
||
documents_uploaded = payload.get('documents_uploaded', [])
|
||
|
||
print(f"📋 documents_required: {len(documents_required)} документов")
|
||
print(f"📋 documents_uploaded: {len(documents_uploaded)} документов")
|
||
|
||
# Создаём мапу: doc_id -> group_index
|
||
doc_id_to_index = {}
|
||
for idx, doc_req in enumerate(documents_required):
|
||
doc_id = doc_req.get('id')
|
||
if doc_id:
|
||
doc_id_to_index[doc_id] = idx
|
||
|
||
print(f"\n📋 Маппинг документов:")
|
||
for doc_id, idx in doc_id_to_index.items():
|
||
print(f" {doc_id} -> group_index {idx}")
|
||
|
||
# Удаляем старые записи
|
||
deleted_count = await conn.execute("""
|
||
DELETE FROM clpr_claim_documents
|
||
WHERE claim_id = $1
|
||
""", str(claim_uuid))
|
||
|
||
print(f"\n🗑️ Удалено старых записей: {deleted_count.split()[-1]}")
|
||
|
||
# Вставляем новые записи с правильными field_name
|
||
inserted_count = 0
|
||
for doc_up in documents_uploaded:
|
||
doc_type = doc_up.get('type') or doc_up.get('id')
|
||
file_id = doc_up.get('file_id')
|
||
|
||
if not doc_type or not file_id:
|
||
print(f" ⚠️ Пропущен документ без type/id или file_id: {doc_up}")
|
||
continue
|
||
|
||
group_index = doc_id_to_index.get(doc_type)
|
||
if group_index is None:
|
||
print(f" ⚠️ Не найден group_index для типа {doc_type}")
|
||
continue
|
||
|
||
field_name = f"uploads[{group_index}][0]"
|
||
|
||
# Парсим uploaded_at
|
||
uploaded_at_str = doc_up.get('uploaded_at')
|
||
uploaded_at = None
|
||
if uploaded_at_str:
|
||
try:
|
||
# Пробуем разные форматы даты
|
||
if isinstance(uploaded_at_str, str):
|
||
if 'T' in uploaded_at_str:
|
||
uploaded_at = datetime.fromisoformat(uploaded_at_str.replace('Z', '+00:00'))
|
||
else:
|
||
uploaded_at = datetime.fromisoformat(uploaded_at_str)
|
||
elif isinstance(uploaded_at_str, datetime):
|
||
uploaded_at = uploaded_at_str
|
||
except Exception as e:
|
||
print(f" ⚠️ Ошибка парсинга даты {uploaded_at_str}: {e}")
|
||
uploaded_at = None
|
||
|
||
await conn.execute("""
|
||
INSERT INTO clpr_claim_documents (
|
||
claim_id,
|
||
field_name,
|
||
file_id,
|
||
file_name,
|
||
original_file_name,
|
||
uploaded_at
|
||
)
|
||
VALUES ($1, $2, $3, $4, $5, $6)
|
||
ON CONFLICT (claim_id, field_name) DO UPDATE SET
|
||
file_id = EXCLUDED.file_id,
|
||
file_name = EXCLUDED.file_name,
|
||
original_file_name = EXCLUDED.original_file_name,
|
||
uploaded_at = EXCLUDED.uploaded_at
|
||
""",
|
||
str(claim_uuid),
|
||
field_name,
|
||
file_id,
|
||
doc_up.get('file_name', ''),
|
||
doc_up.get('original_file_name', ''),
|
||
uploaded_at
|
||
)
|
||
|
||
inserted_count += 1
|
||
print(f" ✅ Вставлен: {field_name} -> {doc_type} ({file_id[:50]}...)")
|
||
|
||
print(f"\n✅ Вставлено новых записей: {inserted_count}")
|
||
|
||
# Проверяем результат
|
||
result_rows = await conn.fetch("""
|
||
SELECT
|
||
field_name,
|
||
file_id,
|
||
file_name,
|
||
original_file_name
|
||
FROM clpr_claim_documents
|
||
WHERE claim_id = $1
|
||
ORDER BY field_name
|
||
""", str(claim_uuid))
|
||
|
||
print(f"\n📊 Результат в таблице ({len(result_rows)} записей):")
|
||
for row in result_rows:
|
||
print(f" {row['field_name']}: {row['file_name']} ({row['file_id'][:50]}...)")
|
||
|
||
finally:
|
||
await conn.close()
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(fix_field_names())
|
||
|