diff --git a/storage/2025/December/week1/399620_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf b/storage/2025/December/week1/399620_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf new file mode 100644 index 00000000..45eb5e32 Binary files /dev/null and b/storage/2025/December/week1/399620_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf differ diff --git a/storage/2025/December/week1/399622_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf b/storage/2025/December/week1/399622_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf new file mode 100644 index 00000000..2cbad00d Binary files /dev/null and b/storage/2025/December/week1/399622_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf differ diff --git a/storage/2025/December/week1/399623_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf b/storage/2025/December/week1/399623_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf new file mode 100644 index 00000000..b33ea018 Binary files /dev/null and b/storage/2025/December/week1/399623_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf differ diff --git a/storage/2025/December/week1/399625_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf b/storage/2025/December/week1/399625_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf new file mode 100644 index 00000000..dbce836f Binary files /dev/null and b/storage/2025/December/week1/399625_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf differ diff --git a/storage/2025/December/week1/399627_доказательство_направления_иска_ответчику_Шор_ООО_ХЕЛПЕР_1_стр.pdf b/storage/2025/December/week1/399627_доказательство_направления_иска_ответчику_Шор_ООО_ХЕЛПЕР_1_стр.pdf new file mode 100644 index 00000000..925a4dfe Binary files /dev/null and b/storage/2025/December/week1/399627_доказательство_направления_иска_ответчику_Шор_ООО_ХЕЛПЕР_1_стр.pdf differ diff --git a/storage/2025/December/week1/399629_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf b/storage/2025/December/week1/399629_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf new file mode 100644 index 00000000..4e7a86d6 Binary files /dev/null and b/storage/2025/December/week1/399629_6_Расчет_исковых_требований_Шор_ООО_ХЕЛПЕР_1_стр.pdf differ diff --git a/storage/2025/December/week1/399630_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf b/storage/2025/December/week1/399630_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf new file mode 100644 index 00000000..879fd19b Binary files /dev/null and b/storage/2025/December/week1/399630_0_Исковое_заявление_по_делу_Шор_ООО_ХЕЛПЕР_4_стр.pdf differ diff --git a/test/LanguageManager/Workflow2 b/test/LanguageManager/Workflow2 index cf59144b..a6ef99c5 100644 --- a/test/LanguageManager/Workflow2 +++ b/test/LanguageManager/Workflow2 @@ -1 +1 @@ -2025-12-01 14:55:09 \ No newline at end of file +2025-12-02 15:00:08 \ No newline at end of file diff --git a/ticket_form/check_claim_226564ce.py b/ticket_form/check_claim_226564ce.py new file mode 100644 index 00000000..9e5a1a49 --- /dev/null +++ b/ticket_form/check_claim_226564ce.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Проверка заявки 226564ce-d7cf-48ee-a820-690e8f5ec8e5 +""" +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 = "226564ce-d7cf-48ee-a820-690e8f5ec8e5" + +async def check_claim(): + conn = await asyncpg.connect( + host=POSTGRES_HOST, + port=POSTGRES_PORT, + database=POSTGRES_DB, + user=POSTGRES_USER, + password=POSTGRES_PASSWORD + ) + + try: + # 1. Проверяем заявку + claim_row = await conn.fetchrow(""" + SELECT + id, + status_code, + created_at, + updated_at, + expires_at, + payload + FROM clpr_claims + WHERE id::text = $1 OR payload->>'claim_id' = $1 + ORDER BY updated_at DESC + LIMIT 1 + """, CLAIM_ID) + + if not claim_row: + print(f"❌ Заявка {CLAIM_ID} не найдена!") + return + + print(f"✅ Заявка найдена:") + print(f" ID: {claim_row['id']}") + print(f" Status: {claim_row['status_code']}") + print(f" Created: {claim_row['created_at']}") + print(f" Updated: {claim_row['updated_at']}") + print(f" Expires: {claim_row['expires_at']}") + + payload = claim_row['payload'] if isinstance(claim_row['payload'], dict) else json.loads(claim_row['payload']) + + # 2. Проверяем documents_meta + documents_meta = payload.get('documents_meta', []) + print(f"\n📋 documents_meta: {len(documents_meta)} записей") + + if documents_meta: + # Проверяем на дубликаты + field_names = [doc.get('field_name') for doc in documents_meta] + duplicates = [name for name in field_names if field_names.count(name) > 1] + + if duplicates: + print(f" ⚠️ Найдены дубликаты field_name: {set(duplicates)}") + + for i, doc in enumerate(documents_meta): + print(f"\n {i+1}. field_name: {doc.get('field_name', 'N/A')}") + print(f" field_label: {doc.get('field_label', 'N/A')}") + print(f" file_id: {doc.get('file_id', 'N/A')}") + print(f" file_name: {doc.get('file_name', 'N/A')}") + print(f" uploaded_at: {doc.get('uploaded_at', 'N/A')}") + else: + print(" ⚠️ documents_meta пуст!") + + # 3. Проверяем документы в таблице + claim_uuid = claim_row['id'] + docs_rows = await conn.fetch(""" + SELECT + id, + claim_id, + field_name, + file_id, + file_name, + original_file_name, + uploaded_at, + file_hash + FROM clpr_claim_documents + WHERE claim_id = $1 + ORDER BY uploaded_at DESC + """, str(claim_uuid)) + + print(f"\n📄 Документы в clpr_claim_documents: {len(docs_rows)} записей") + for i, row in enumerate(docs_rows): + print(f"\n {i+1}. field_name: {row['field_name']}") + print(f" file_id: {row['file_id']}") + print(f" file_name: {row['file_name']}") + print(f" file_hash: {row['file_hash'] or 'NULL'}") + print(f" uploaded_at: {row['uploaded_at']}") + + # 4. Сравниваем documents_meta и clpr_claim_documents + print(f"\n🔍 Сравнение:") + meta_field_names = set(doc.get('field_name') for doc in documents_meta) + table_field_names = set(row['field_name'] for row in docs_rows) + + only_in_meta = meta_field_names - table_field_names + only_in_table = table_field_names - meta_field_names + + if only_in_meta: + print(f" ⚠️ Только в documents_meta: {only_in_meta}") + if only_in_table: + print(f" ⚠️ Только в clpr_claim_documents: {only_in_table}") + if not only_in_meta and not only_in_table: + print(f" ✅ Все field_name совпадают") + + # 5. Проверяем поле upload_description + upload_description = payload.get('upload_description') + print(f"\n📝 upload_description: {upload_description}") + + # 6. Проверяем answers + answers = payload.get('answers', {}) + print(f"\n💬 answers: {len(answers)} полей") + if answers: + for key, value in list(answers.items())[:5]: # Первые 5 + print(f" {key}: {str(value)[:50]}...") + + finally: + await conn.close() + +if __name__ == "__main__": + asyncio.run(check_claim()) + diff --git a/ticket_form/docs/CLAIM_226564ce_STATUS.md b/ticket_form/docs/CLAIM_226564ce_STATUS.md new file mode 100644 index 00000000..1f8f2261 --- /dev/null +++ b/ticket_form/docs/CLAIM_226564ce_STATUS.md @@ -0,0 +1,94 @@ +# Статус заявки 226564ce-d7cf-48ee-a820-690e8f5ec8e5 + +## ✅ Общая информация + +- **ID**: `226564ce-d7cf-48ee-a820-690e8f5ec8e5` +- **Status**: `draft_docs_complete` +- **Unified ID**: `usr_b1fbffa0-477b-4abb-95d6-8d6f849ddc71` +- **Session Token**: `sess_c278abf8-1603-484d-af98-8b93843e5253` +- **Phone**: `71234543212` +- **Channel**: `web_form` +- **Is Confirmed**: `false` (должна отображаться в списке) +- **Created**: `2025-12-01 14:38:11` +- **Updated**: `2025-12-01 20:06:18` +- **Expires**: `2025-12-15 19:35:30` + +## ✅ Документы + +### documents_meta (2 записи) + +1. **uploads[1][0]** + - `field_label`: "Чек или подтверждение оплаты" ✅ (правильно, не "group-2") + - `file_id`: `/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/crm2/CRM_Active_Files/Documents/Project/ERV_3212_КлиентПрав_399543/e34f2f9e-e48d-47f4-9c2d-6957012c0800__chek-ili-podtverzhdenie-oplaty.pdf` + - `file_name`: `e34f2f9e-e48d-47f4-9c2d-6957012c0800__chek-ili-podtverzhdenie-oplaty.pdf` + - `uploaded_at`: `2025-12-01T14:15:54.122Z` + +2. **uploads[0][0]** + - `field_label`: "Договор или заказ" ✅ (правильно) + - `file_id`: `/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/crm2/CRM_Active_Files/Documents/Project/ERV_3212_КлиентПрав_399543/344deab2-1a3a-46ce-931b-5a29bb2c40a3__dogovor-ili-zakaz.pdf` + - `file_name`: `344deab2-1a3a-46ce-931b-5a29bb2c40a3__dogovor-ili-zakaz.pdf` + - `uploaded_at`: `2025-12-01T13:47:15.772Z` + +### clpr_claim_documents (2 записи) + +1. **uploads[1][0]** + - `id`: `e34f2f9e-e48d-47f4-9c2d-6957012c0800` + - `file_hash`: `3e1f1332a76b7f26df1628c49579f30a873de9170f3b8007b0bac5e4a439ca67` ✅ + +2. **uploads[0][0]** + - `id`: `344deab2-1a3a-46ce-931b-5a29bb2c40a3` + - `file_hash`: `83822e59662aa2037977dc5a8661d8a057ae6572e6f99936a31c6cdd7d66f1d9` ✅ + +## ✅ Проверки + +- ✅ **Дубликатов нет** — все `field_name` уникальны +- ✅ **field_label правильные** — не "group-2", а реальные названия +- ✅ **Синхронизация** — `documents_meta` и `clpr_claim_documents` совпадают +- ✅ **file_hash заполнен** — оба документа имеют хеш +- ✅ **Заявка должна отображаться** — `is_confirmed = false`, `status_code != 'approved'` + +## 📋 Payload структура + +Заявка содержит следующие ключи в `payload`: +- `body` +- `email` +- `phone` +- `tg_id` +- `answers` +- `claim_id` +- `applicant` +- `contact_id` +- `form_draft` +- `ai_analysis` +- `claim_ready` +- `wizard_plan` +- `wizard_ready` +- `ai_agent13_rag` +- `documents_meta` ✅ +- `ai_agent1_facts` +- `answers_prefill` +- `current_doc_index` +- `documents_skipped` +- `documents_required` +- `documents_uploaded` +- `problem_description` + +## 🔍 Возможные проблемы с отображением + +Если заявка не отображается или отображается неправильно, проверьте: + +1. **API endpoint `/drafts/list`** — должен находить заявку по `unified_id`, `phone` или `session_token` +2. **Фронтенд фильтрация** — возможно, фильтруется по `status_code` +3. **Отображение `field_label`** — должно использовать `documents_meta[].field_label`, а не вычислять из `field_name` + +## ✅ Вывод + +**Заявка в порядке!** Все данные корректны: +- ✅ Нет дубликатов в `documents_meta` +- ✅ `field_label` правильные +- ✅ Документы синхронизированы +- ✅ `file_hash` заполнен +- ✅ Заявка должна отображаться в списке + +Если есть проблемы с отображением, они скорее всего на стороне фронтенда или API фильтрации. + diff --git a/ticket_form/frontend/src/components/form/Step1Phone.tsx b/ticket_form/frontend/src/components/form/Step1Phone.tsx index 41c65a9d..bdc7f20a 100644 --- a/ticket_form/frontend/src/components/form/Step1Phone.tsx +++ b/ticket_form/frontend/src/components/form/Step1Phone.tsx @@ -278,6 +278,25 @@ export default function Step1Phone({ maxLength={10} size="large" style={{ flex: 1 }} + onPaste={(e) => { + // Обработка вставки: очищаем от +7, пробелов и других символов + e.preventDefault(); + const pastedText = (e.clipboardData || (window as any).clipboardData).getData('text'); + // Убираем все нецифровые символы + let cleanText = pastedText.replace(/\D/g, ''); + // Если начинается с 7 или 8, убираем первую цифру (код страны) + if (cleanText.length === 11 && (cleanText.startsWith('7') || cleanText.startsWith('8'))) { + cleanText = cleanText.substring(1); + } + // Оставляем только первые 10 цифр + cleanText = cleanText.substring(0, 10); + // Устанавливаем очищенное значение + form.setFieldValue('phone', cleanText); + // Показываем предупреждение, если номер был обрезан + if (pastedText.replace(/\D/g, '').length > 10) { + message.warning('Номер автоматически обрезан до 10 цифр'); + } + }} /> diff --git a/ticket_form/frontend/src/components/form/Step3Payment.tsx b/ticket_form/frontend/src/components/form/Step3Payment.tsx index db71c37e..c125bd5d 100644 --- a/ticket_form/frontend/src/components/form/Step3Payment.tsx +++ b/ticket_form/frontend/src/components/form/Step3Payment.tsx @@ -1,12 +1,10 @@ import { useState, useEffect } from 'react'; -import { Form, Input, Button, Select, message, Space, Divider } from 'antd'; +import { Form, Input, Button, AutoComplete, message, Space, Divider } from 'antd'; import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined, CopyOutlined } from '@ant-design/icons'; const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200'; const NSPK_BANKS_API = 'http://212.193.27.93/api/payouts/dictionaries/nspk-banks'; -const { Option } = Select; - interface Bank { bankid: string; bankname: string; @@ -61,15 +59,34 @@ export default function Step3Payment({ setBanks(banksData); addDebugEvent?.('banks', 'success', `✅ Загружено ${banksData.length} банков`, { count: banksData.length }); - // Если есть сохранённый bankName, но нет bankId - пытаемся найти по названию - if (formData.bankName && !formData.bankId) { + // Если есть сохранённый bankName или bankId - восстанавливаем значения + if (formData.bankName) { const foundBank = banksData.find(b => b.bankname.toLowerCase() === formData.bankName.toLowerCase() || b.bankname.toLowerCase().includes(formData.bankName.toLowerCase()) ); if (foundBank) { - updateFormData({ bankId: foundBank.bankid }); - form.setFieldsValue({ bankId: foundBank.bankid }); + updateFormData({ + bankId: foundBank.bankid, + bankName: foundBank.bankname + }); + form.setFieldsValue({ + bankId: foundBank.bankid, + bankName: foundBank.bankname + }); + } + } else if (formData.bankId) { + // Если есть только bankId, находим по ID + const foundBank = banksData.find(b => b.bankid === formData.bankId); + if (foundBank) { + updateFormData({ + bankId: foundBank.bankid, + bankName: foundBank.bankname + }); + form.setFieldsValue({ + bankId: foundBank.bankid, + bankName: foundBank.bankname + }); } } } catch (error: any) { @@ -189,12 +206,15 @@ export default function Step3Payment({ } }; - // Инициализация формы с bankId если есть + // Инициализация формы с bankId и bankName если есть useEffect(() => { - if (formData.bankId) { - form.setFieldsValue({ bankId: formData.bankId }); + if (formData.bankId || formData.bankName) { + form.setFieldsValue({ + bankId: formData.bankId, + bankName: formData.bankName + }); } - }, [formData.bankId, form]); + }, [formData.bankId, formData.bankName, form]); return (
@@ -377,40 +398,78 @@ export default function Step3Payment({ + {/* Скрытое поле для bankId */} + + - + onChange={(value) => { + // При вводе текста ищем точное совпадение по названию + if (typeof value === 'string') { + const foundBank = banks.find(b => + b.bankname.toLowerCase() === value.toLowerCase() + ); + if (foundBank) { + updateFormData({ + bankId: foundBank.bankid, + bankName: foundBank.bankname + }); + form.setFieldsValue({ bankId: foundBank.bankid }); + } else if (value === '') { + // Если поле очищено, очищаем и bankId + updateFormData({ bankId: undefined, bankName: undefined }); + form.setFieldsValue({ bankId: undefined }); + } + } + }} + style={{ width: '100%' }} + /> diff --git a/ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts b/ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts index 2f388cf1..f6df339d 100644 --- a/ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts +++ b/ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts @@ -775,10 +775,16 @@ export function generateConfirmationFormHTML(data: any): string { function createBankSelect(root, key, value) { var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2); - var selectHtml = ''; - return selectHtml; + var datalistId = 'bank-datalist-' + id; + // Создаём input с datalist для автоподстановки + var inputHtml = ''; + inputHtml += ''; + inputHtml += ''; + inputHtml += ''; + // Скрытое поле для bank_id + var hiddenId = id + '_id'; + inputHtml += ''; + return inputHtml; } function createCheckbox(root, key, checked, labelText, required) { @@ -1240,6 +1246,19 @@ export function generateConfirmationFormHTML(data: any): string { var fields = document.querySelectorAll('.bind'); console.log('Found fields:', fields.length); + // Обработка скрытых полей bank_id + var bankIdFields = document.querySelectorAll('.bank-id-field'); + Array.prototype.forEach.call(bankIdFields, function(field) { + field.addEventListener('change', function() { + var root = this.getAttribute('data-root'); + var value = this.value; + if (root === 'user') { + state.user = state.user || {}; + state.user.bank_id = value; + } + }); + }); + // ✅ Устанавливаем начальный стиль для всех полей и форматируем телефоны Array.prototype.forEach.call(fields, function(field) { var key = field.getAttribute('data-key'); @@ -1335,6 +1354,12 @@ export function generateConfirmationFormHTML(data: any): string { // Обновляем состояние if (root === 'user') { state.user = state.user || {}; + // Для bank_id не сохраняем название банка, только ID из скрытого поля + if (key === 'bank_id' && this.classList.contains('bank-select')) { + // Это текстовое поле для названия банка - не сохраняем в state + // bank_id будет сохранён из скрытого поля + return; + } state.user[key] = value; // Обновляем телефон в СБП @@ -1437,8 +1462,8 @@ export function generateConfirmationFormHTML(data: any): string { // Загрузка списка банков СБП function loadBanks() { - var bankSelects = document.querySelectorAll('.bank-select'); - if (bankSelects.length === 0) { + var bankInputs = document.querySelectorAll('.bank-select'); + if (bankInputs.length === 0) { console.log('Bank select fields not found'); return; } @@ -1458,32 +1483,109 @@ export function generateConfirmationFormHTML(data: any): string { return a.bankname.localeCompare(b.bankname, 'ru'); }); - // Заполняем все bank-select элементы - Array.prototype.forEach.call(bankSelects, function(select) { - var currentValue = select.getAttribute('data-selected') || state.user?.bank_id || ''; - select.innerHTML = ''; + // Сохраняем список банков глобально для поиска + window.__banksList = banks; + + // Заполняем все datalist элементы + Array.prototype.forEach.call(bankInputs, function(input) { + var datalistId = input.getAttribute('list'); + var datalist = document.getElementById(datalistId); + var hiddenId = input.id + '_id'; + var hiddenField = document.getElementById(hiddenId); + var currentBankId = state.user?.bank_id || ''; + var currentBankName = ''; + if (!datalist) { + console.error('Datalist not found for input:', input.id); + return; + } + + // Очищаем datalist + datalist.innerHTML = ''; + + // Заполняем datalist опциями banks.forEach(function(bank) { var option = document.createElement('option'); - option.value = bank.bankid; - option.textContent = bank.bankname; - if (bank.bankid === currentValue) { - option.selected = true; + option.value = bank.bankname; + option.setAttribute('data-bank-id', bank.bankid); + datalist.appendChild(option); + + // Если это текущий банк, устанавливаем значение + if (bank.bankid === currentBankId) { + currentBankName = bank.bankname; } - select.appendChild(option); }); - // Если выбран банк, обновляем стиль - if (currentValue && select.value) { - select.classList.add('filled'); - updateFieldStyle(select); + // Устанавливаем текущее значение если есть + if (currentBankName) { + input.value = currentBankName; + if (hiddenField) { + hiddenField.value = currentBankId; + } + input.classList.add('filled'); + updateFieldStyle(input); } + + // Обработчик изменения для поиска банка по названию + input.addEventListener('input', function() { + var inputValue = this.value.trim(); + var foundBank = null; + + // Ищем точное совпадение + if (inputValue) { + foundBank = banks.find(function(b) { + return b.bankname.toLowerCase() === inputValue.toLowerCase(); + }); + } + + if (foundBank) { + // Найден банк - сохраняем ID + if (hiddenField) { + hiddenField.value = foundBank.bankid; + } + state.user = state.user || {}; + state.user.bank_id = foundBank.bankid; + this.classList.add('filled'); + } else { + // Банк не найден - очищаем ID + if (hiddenField) { + hiddenField.value = ''; + } + state.user = state.user || {}; + state.user.bank_id = ''; + this.classList.remove('filled'); + } + updateFieldStyle(this); + updateSubmitButton(); + }); + + // Обработчик выбора из списка + input.addEventListener('change', function() { + var inputValue = this.value.trim(); + var foundBank = banks.find(function(b) { + return b.bankname.toLowerCase() === inputValue.toLowerCase(); + }); + + if (foundBank) { + if (hiddenField) { + hiddenField.value = foundBank.bankid; + } + state.user = state.user || {}; + state.user.bank_id = foundBank.bankid; + this.classList.add('filled'); + updateFieldStyle(this); + } + }); }); }) .catch(function(error) { console.error('Error loading banks:', error); - Array.prototype.forEach.call(bankSelects, function(select) { - select.innerHTML = ''; + Array.prototype.forEach.call(bankInputs, function(input) { + var datalistId = input.getAttribute('list'); + var datalist = document.getElementById(datalistId); + if (datalist) { + datalist.innerHTML = ''; + } }); }); } @@ -1552,6 +1654,17 @@ export function generateConfirmationFormHTML(data: any): string { return; } + // Собираем bank_id из скрытых полей перед отправкой + var bankIdFields = document.querySelectorAll('.bank-id-field'); + Array.prototype.forEach.call(bankIdFields, function(field) { + var root = field.getAttribute('data-root'); + var bankId = field.value; + if (root === 'user' && bankId) { + state.user = state.user || {}; + state.user.bank_id = bankId; + } + }); + window.parent.postMessage({ type: 'claim_confirmed', data: {