feat: Получение cf_2624 из MySQL при загрузке черновика
- Добавлен сервис CrmMySQLService для подключения к MySQL БД vtiger CRM - Обновлён get_draft() для прямого SQL запроса к MySQL вместо webservice API - Получение cf_2624 и всех данных контакта из MySQL - Обновлена документация и SQL файлы для n8n - Добавлено логирование для отладки Преимущества: - Проще: один SQL запрос вместо цепочки HTTP запросов - Быстрее: прямой запрос к БД - Надёжнее: не зависит от webservice API - Актуальнее: всегда свежие данные из БД
This commit is contained in:
@@ -22,7 +22,7 @@ vimport ('includes.runtime.LanguageHandler');
|
|||||||
* @param string $firstname - имя (опционально)
|
* @param string $firstname - имя (опционально)
|
||||||
* @param string $lastname - фамилия (опционально)
|
* @param string $lastname - фамилия (опционально)
|
||||||
* @param string $email - email (опционально)
|
* @param string $email - email (опционально)
|
||||||
* @return int - ID контакта
|
* @return string - JSON строка с contact_id, is_new и cf_2624 (Данные подтверждены)
|
||||||
*/
|
*/
|
||||||
function vtws_createwebcontact($mobile, $firstname = '', $lastname = '', $email = '', $user = false) {
|
function vtws_createwebcontact($mobile, $firstname = '', $lastname = '', $email = '', $user = false) {
|
||||||
|
|
||||||
@@ -56,18 +56,29 @@ function vtws_createwebcontact($mobile, $firstname = '', $lastname = '', $email
|
|||||||
$isNew = false; // Флаг: создан ли контакт сейчас
|
$isNew = false; // Флаг: создан ли контакт сейчас
|
||||||
|
|
||||||
// Проверяем существование контакта по номеру телефона
|
// Проверяем существование контакта по номеру телефона
|
||||||
$query = "select c.contactid
|
// ✅ Добавляем выборку поля cf_2624 (Данные подтверждены)
|
||||||
|
$query = "select c.contactid, cf.cf_2624
|
||||||
from vtiger_contactdetails c
|
from vtiger_contactdetails c
|
||||||
left join vtiger_crmentity e on e.crmid = c.contactid
|
left join vtiger_crmentity e on e.crmid = c.contactid
|
||||||
|
left join vtiger_contactscf cf on cf.contactid = c.contactid
|
||||||
where e.deleted = 0 and c.mobile = ?
|
where e.deleted = 0 and c.mobile = ?
|
||||||
limit 1";
|
limit 1";
|
||||||
$result = $adb->pquery($query, array($mobile));
|
$result = $adb->pquery($query, array($mobile));
|
||||||
|
|
||||||
|
$cf_2624_value = "0"; // По умолчанию "Нет" (данные не подтверждены)
|
||||||
|
|
||||||
if ($adb->num_rows($result) > 0) {
|
if ($adb->num_rows($result) > 0) {
|
||||||
// Контакт существует - ПРОСТО ВОЗВРАЩАЕМ ID (НЕ обновляем!)
|
// Контакт существует - ПРОСТО ВОЗВРАЩАЕМ ID (НЕ обновляем!)
|
||||||
$output = $adb->query_result($result, 0, 'contactid');
|
$output = $adb->query_result($result, 0, 'contactid');
|
||||||
$isNew = false;
|
$isNew = false;
|
||||||
$logstring = date('Y-m-d H:i:s').' ✅ Контакт найден с id '.$output.' (БЕЗ обновления)'.PHP_EOL;
|
|
||||||
|
// ✅ Получаем значение поля cf_2624 (Данные подтверждены)
|
||||||
|
$cf_2624_value = $adb->query_result($result, 0, 'cf_2624');
|
||||||
|
if (empty($cf_2624_value)) {
|
||||||
|
$cf_2624_value = "0"; // По умолчанию "Нет"
|
||||||
|
}
|
||||||
|
|
||||||
|
$logstring = date('Y-m-d H:i:s').' ✅ Контакт найден с id '.$output.', cf_2624='.$cf_2624_value.' (БЕЗ обновления)'.PHP_EOL;
|
||||||
file_put_contents('logs/CreateWebContact.log', $logstring, FILE_APPEND);
|
file_put_contents('logs/CreateWebContact.log', $logstring, FILE_APPEND);
|
||||||
} else {
|
} else {
|
||||||
// Контакт НЕ существует - создаём новый
|
// Контакт НЕ существует - создаём новый
|
||||||
@@ -92,6 +103,7 @@ function vtws_createwebcontact($mobile, $firstname = '', $lastname = '', $email
|
|||||||
'mailingstreet' => '', // Адрес пустой
|
'mailingstreet' => '', // Адрес пустой
|
||||||
'cf_1849' => '', // Реквизиты пустые
|
'cf_1849' => '', // Реквизиты пустые
|
||||||
'cf_1580' => '', // Код пустой
|
'cf_1580' => '', // Код пустой
|
||||||
|
'cf_2624' => '0', // ✅ Данные подтверждены = "Нет" (по умолчанию для новых контактов)
|
||||||
'assigned_user_id' => vtws_getWebserviceEntityId('Users', $current_user->id)
|
'assigned_user_id' => vtws_getWebserviceEntityId('Users', $current_user->id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -102,7 +114,8 @@ function vtws_createwebcontact($mobile, $firstname = '', $lastname = '', $email
|
|||||||
$contact = vtws_create('Contacts', $params, $current_user);
|
$contact = vtws_create('Contacts', $params, $current_user);
|
||||||
$output = substr($contact['id'], 3);
|
$output = substr($contact['id'], 3);
|
||||||
$isNew = true; // Контакт только что создан!
|
$isNew = true; // Контакт только что создан!
|
||||||
$logstring = date('Y-m-d H:i:s').' ✅ Создан новый Web Контакт с id '.$output.PHP_EOL;
|
$cf_2624_value = "0"; // Новый контакт - данные не подтверждены
|
||||||
|
$logstring = date('Y-m-d H:i:s').' ✅ Создан новый Web Контакт с id '.$output.', cf_2624=0'.PHP_EOL;
|
||||||
file_put_contents('logs/CreateWebContact.log', $logstring, FILE_APPEND);
|
file_put_contents('logs/CreateWebContact.log', $logstring, FILE_APPEND);
|
||||||
} catch (WebServiceException $ex) {
|
} catch (WebServiceException $ex) {
|
||||||
$logstring = date('Y-m-d H:i:s').' ❌ Ошибка создания: '.$ex->getMessage().PHP_EOL;
|
$logstring = date('Y-m-d H:i:s').' ❌ Ошибка создания: '.$ex->getMessage().PHP_EOL;
|
||||||
@@ -111,10 +124,11 @@ function vtws_createwebcontact($mobile, $firstname = '', $lastname = '', $email
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращаем JSON с флагом is_new
|
// Возвращаем JSON с флагом is_new и значением cf_2624
|
||||||
$result = array(
|
$result = array(
|
||||||
'contact_id' => $output,
|
'contact_id' => $output,
|
||||||
'is_new' => $isNew
|
'is_new' => $isNew,
|
||||||
|
'cf_2624' => $cf_2624_value // ✅ "1" = данные подтверждены, "0" = не подтверждены
|
||||||
);
|
);
|
||||||
|
|
||||||
$logstring = date('Y-m-d H:i:s').' Return: '.json_encode($result).PHP_EOL;
|
$logstring = date('Y-m-d H:i:s').' Return: '.json_encode($result).PHP_EOL;
|
||||||
|
|||||||
BIN
storage/2025/December/week1/399640_Ходатайство_по_делу_.pdf
Normal file
BIN
storage/2025/December/week1/399640_Ходатайство_по_делу_.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
2025-12-02 15:00:08
|
2025-12-03 15:05:09
|
||||||
132
ticket_form/SESSION_LOG_2025-12-03.md
Normal file
132
ticket_form/SESSION_LOG_2025-12-03.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Лог сессии 2025-12-03
|
||||||
|
|
||||||
|
## Задача: Получение cf_2624 из MySQL при загрузке черновика
|
||||||
|
|
||||||
|
### Проблема
|
||||||
|
Пользователь заметил, что для `claim_id: "226564ce-d7cf-48ee-a820-690e8f5ec8e5"` доступно редактирование, хотя в CRM стоит галка "Данные подтверждены" (`cf_2624 = "1"`).
|
||||||
|
|
||||||
|
### Решение
|
||||||
|
Вместо передачи `cf_2624` через события Redis, реализован прямой SQL запрос к MySQL БД vtiger CRM при загрузке черновика.
|
||||||
|
|
||||||
|
## Изменения
|
||||||
|
|
||||||
|
### 1. Добавлены credentials для MySQL CRM в `config.py`
|
||||||
|
```python
|
||||||
|
# MySQL CRM (vtiger CRM)
|
||||||
|
mysql_crm_host: str = "localhost"
|
||||||
|
mysql_crm_port: int = 3306
|
||||||
|
mysql_crm_db: str = "ci20465_72new"
|
||||||
|
mysql_crm_user: str = "ci20465_72new"
|
||||||
|
mysql_crm_password: str = "EcY979Rn"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Создан сервис `CrmMySQLService`
|
||||||
|
**Файл:** `ticket_form/backend/app/services/crm_mysql_service.py`
|
||||||
|
|
||||||
|
- Подключение к MySQL БД vtiger CRM
|
||||||
|
- Методы: `fetch_one()`, `fetch_all()`, `execute()`
|
||||||
|
- Использует `aiomysql` для асинхронных запросов
|
||||||
|
|
||||||
|
### 3. Обновлён `main.py`
|
||||||
|
- Добавлено подключение к MySQL CRM при старте
|
||||||
|
- Добавлено закрытие соединения при остановке
|
||||||
|
|
||||||
|
### 4. Обновлён `claims.py` - метод `get_draft()`
|
||||||
|
**Эндпоинт:** `GET /api/v1/claims/drafts/{claim_id}`
|
||||||
|
|
||||||
|
**Изменения:**
|
||||||
|
- Убран webservice API (getchallenge → login → retrieve)
|
||||||
|
- Добавлен прямой SQL запрос к MySQL для получения `cf_2624`
|
||||||
|
- Получаем все данные контакта, включая `cf_2624`
|
||||||
|
- Добавлено логирование для отладки
|
||||||
|
|
||||||
|
**SQL запрос:**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
cd.contactid,
|
||||||
|
cd.firstname,
|
||||||
|
cd.lastname,
|
||||||
|
cd.email,
|
||||||
|
cd.mobile,
|
||||||
|
ccf.cf_2624 AS cf_2624
|
||||||
|
FROM vtiger_contactdetails cd
|
||||||
|
LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
|
||||||
|
WHERE cd.contactid = %s
|
||||||
|
AND ce.deleted = 0
|
||||||
|
LIMIT 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Логика:**
|
||||||
|
- Если `cf_2624 = "1"` → `contact_data_confirmed = True`, `contact_data_can_edit = False`
|
||||||
|
- Если `cf_2624 = "0"` или `NULL` → `contact_data_confirmed = False`, `contact_data_can_edit = True`
|
||||||
|
|
||||||
|
### 5. Обновлены SQL файлы и документация
|
||||||
|
- `N8N_POSTGRESQL_GET_CONTACT_DATA.sql` → `N8N_MYSQL_GET_CONTACT_DATA.sql`
|
||||||
|
- Изменён синтаксис: `$1` → `?` (для n8n MySQL ноды)
|
||||||
|
- Обновлена документация `BACKEND_GET_CONTACT_CF_2624_FROM_POSTGRESQL.md`
|
||||||
|
- Создан `N8N_MYSQL_GET_CONTACT_DATA.md`
|
||||||
|
|
||||||
|
## Преимущества нового подхода
|
||||||
|
|
||||||
|
1. ✅ **Проще** - один SQL запрос вместо цепочки HTTP запросов
|
||||||
|
2. ✅ **Быстрее** - прямой запрос к БД
|
||||||
|
3. ✅ **Надёжнее** - не зависит от webservice API
|
||||||
|
4. ✅ **Актуальнее** - всегда получаем свежие данные из БД
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
**MySQL запрос:**
|
||||||
|
```bash
|
||||||
|
mysql -h localhost -u ci20465_72new -p'EcY979Rn' ci20465_72new \
|
||||||
|
-e "SELECT contactid, cf_2624 FROM vtiger_contactscf WHERE contactid = '399542' LIMIT 1;"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Результат:**
|
||||||
|
```
|
||||||
|
contactid cf_2624
|
||||||
|
399542 1
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ В MySQL `cf_2624 = "1"` для `contact_id = "399542"` - данные подтверждены.
|
||||||
|
|
||||||
|
## Текущий статус
|
||||||
|
|
||||||
|
- ✅ Код обновлён
|
||||||
|
- ✅ Бэкенд перезапущен
|
||||||
|
- ⚠️ Требуется проверка: почему в ответе API `contact_data_confirmed` и `contact_data_can_edit` равны `null`
|
||||||
|
|
||||||
|
**Возможные причины:**
|
||||||
|
1. Запрос к MySQL не выполняется (нет логов)
|
||||||
|
2. `contact_id` не передаётся или имеет неправильный тип
|
||||||
|
3. Ошибка в SQL запросе (не логируется)
|
||||||
|
|
||||||
|
**Добавлено логирование:**
|
||||||
|
```python
|
||||||
|
logger.info(f"🔍 Получен contact_id из черновика: {contact_id} (type: {type(contact_id)})")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Следующие шаги
|
||||||
|
|
||||||
|
1. Проверить логи при загрузке черновика
|
||||||
|
2. Убедиться, что `contact_id` передаётся корректно
|
||||||
|
3. Проверить, что SQL запрос выполняется успешно
|
||||||
|
4. Убедиться, что фронтенд правильно использует `contact_data_confirmed` для блокировки полей
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Файлы изменены
|
||||||
|
|
||||||
|
- `ticket_form/backend/app/config.py` - добавлены credentials для MySQL CRM
|
||||||
|
- `ticket_form/backend/app/services/crm_mysql_service.py` - новый сервис
|
||||||
|
- `ticket_form/backend/app/main.py` - подключение к MySQL CRM
|
||||||
|
- `ticket_form/backend/app/api/claims.py` - прямой SQL запрос к MySQL
|
||||||
|
- `ticket_form/docs/N8N_MYSQL_GET_CONTACT_DATA.sql` - SQL запрос для n8n
|
||||||
|
- `ticket_form/docs/N8N_MYSQL_GET_CONTACT_DATA.md` - документация
|
||||||
|
- `ticket_form/docs/BACKEND_GET_CONTACT_CF_2624_FROM_POSTGRESQL.md` - обновлена документация
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Время работы:** 2025-12-03 16:00-16:30
|
||||||
|
**Статус:** В процессе отладки
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from ..services.redis_service import redis_service
|
from ..services.redis_service import redis_service
|
||||||
from ..services.database import db
|
from ..services.database import db
|
||||||
|
from ..services.crm_mysql_service import crm_mysql_service
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1/claims", tags=["Claims"])
|
router = APIRouter(prefix="/api/v1/claims", tags=["Claims"])
|
||||||
@@ -456,18 +457,114 @@ async def get_draft(claim_id: str):
|
|||||||
if documents_required:
|
if documents_required:
|
||||||
logger.info(f"🔍 documents_required: {documents_required[:2]}...") # Первые 2 для примера
|
logger.info(f"🔍 documents_required: {documents_required[:2]}...") # Первые 2 для примера
|
||||||
|
|
||||||
|
# ✅ Проверяем флаг подтверждения данных контакта из CRM (поле cf_2624)
|
||||||
|
# Простой способ: делаем прямой SQL запрос к БД (таблицы vtiger_*)
|
||||||
|
# ПРИМЕЧАНИЕ: Если таблицы vtiger_* находятся в MySQL (а не PostgreSQL),
|
||||||
|
# нужно использовать отдельный connection через policy_service или создать новый MySQL connection
|
||||||
|
unified_id = row.get('unified_id')
|
||||||
|
contact_data_confirmed = False
|
||||||
|
contact_data_can_edit = True
|
||||||
|
contact_data_from_crm = None
|
||||||
|
|
||||||
|
# Получаем contact_id из payload
|
||||||
|
contact_id = payload.get('contact_id') if isinstance(payload, dict) else None
|
||||||
|
|
||||||
|
# Преобразуем contact_id в строку, если он есть
|
||||||
|
if contact_id:
|
||||||
|
contact_id = str(contact_id).strip()
|
||||||
|
logger.info(f"🔍 Получен contact_id из черновика: {contact_id} (type: {type(contact_id)})")
|
||||||
|
|
||||||
|
if contact_id:
|
||||||
|
try:
|
||||||
|
# ✅ Прямой SQL запрос к MySQL для получения cf_2624
|
||||||
|
# Таблицы vtiger_* находятся в MySQL БД
|
||||||
|
contact_query = """
|
||||||
|
SELECT
|
||||||
|
cd.contactid,
|
||||||
|
cd.firstname,
|
||||||
|
cd.lastname,
|
||||||
|
cd.email,
|
||||||
|
cd.mobile,
|
||||||
|
cd.phone,
|
||||||
|
cs.birthday,
|
||||||
|
ca.mailingstreet,
|
||||||
|
ca.mailingcity,
|
||||||
|
ca.mailingstate,
|
||||||
|
ca.mailingzip,
|
||||||
|
ca.mailingcountry,
|
||||||
|
ccf.cf_1157 AS middle_name,
|
||||||
|
ccf.cf_1263 AS birthplace,
|
||||||
|
ccf.cf_1257 AS inn,
|
||||||
|
ccf.cf_1849 AS requisites,
|
||||||
|
ccf.cf_1580 AS code,
|
||||||
|
ccf.cf_1706 AS sms,
|
||||||
|
ccf.cf_2624 AS cf_2624
|
||||||
|
FROM vtiger_contactdetails cd
|
||||||
|
LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_contactsubdetails cs ON cs.contactsubscriptionid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_contactaddress ca ON ca.contactaddressid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
|
||||||
|
WHERE cd.contactid = %s
|
||||||
|
AND ce.deleted = 0
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
contact_row = await crm_mysql_service.fetch_one(contact_query, contact_id)
|
||||||
|
|
||||||
|
if contact_row:
|
||||||
|
# Формируем объект с данными контакта
|
||||||
|
contact_data_from_crm = {
|
||||||
|
"contactid": contact_row.get("contactid"),
|
||||||
|
"firstname": contact_row.get("firstname"),
|
||||||
|
"lastname": contact_row.get("lastname"),
|
||||||
|
"email": contact_row.get("email"),
|
||||||
|
"mobile": contact_row.get("mobile"),
|
||||||
|
"phone": contact_row.get("phone"),
|
||||||
|
"birthday": contact_row.get("birthday"),
|
||||||
|
"mailingstreet": contact_row.get("mailingstreet"),
|
||||||
|
"mailingcity": contact_row.get("mailingcity"),
|
||||||
|
"mailingstate": contact_row.get("mailingstate"),
|
||||||
|
"mailingzip": contact_row.get("mailingzip"),
|
||||||
|
"mailingcountry": contact_row.get("mailingcountry"),
|
||||||
|
"cf_1157": contact_row.get("middle_name"), # Отчество
|
||||||
|
"cf_1263": contact_row.get("birthplace"), # Место рождения
|
||||||
|
"cf_1257": contact_row.get("inn"), # ИНН
|
||||||
|
"cf_1849": contact_row.get("requisites"), # Реквизиты
|
||||||
|
"cf_1580": contact_row.get("code"), # Код
|
||||||
|
"cf_1706": contact_row.get("sms"), # SMS
|
||||||
|
"cf_2624": contact_row.get("cf_2624") or "0" # ✅ Данные подтверждены
|
||||||
|
}
|
||||||
|
|
||||||
|
# ✅ Проверяем кастомное поле "Данные подтверждены" (cf_2624)
|
||||||
|
confirmed_field = contact_data_from_crm.get("cf_2624", "0")
|
||||||
|
contact_data_confirmed = confirmed_field == "1" or confirmed_field == "true" or confirmed_field is True
|
||||||
|
contact_data_can_edit = not contact_data_confirmed
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"🔒 Статус данных контакта из MySQL CRM: confirmed={contact_data_confirmed}, "
|
||||||
|
f"field_value={confirmed_field}, contact_id={contact_id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(f"⚠️ Контакт не найден в MySQL CRM: contact_id={contact_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠️ Не удалось загрузить данные контакта из MySQL CRM: {str(e)}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"claim": {
|
"claim": {
|
||||||
"id": str(row['id']),
|
"id": str(row['id']),
|
||||||
"claim_id": final_claim_id, # ✅ Используем claim_id из payload, если его нет в row
|
"claim_id": final_claim_id,
|
||||||
"session_token": row.get('session_token'),
|
"session_token": row.get('session_token'),
|
||||||
"status_code": row.get('status_code'),
|
"status_code": row.get('status_code'),
|
||||||
"channel": row.get('channel'), # ✅ Добавляем channel для отладки
|
"channel": row.get('channel'),
|
||||||
"created_at": row['created_at'].isoformat() if row.get('created_at') else None,
|
"created_at": row['created_at'].isoformat() if row.get('created_at') else None,
|
||||||
"updated_at": row['updated_at'].isoformat() if row.get('updated_at') else None,
|
"updated_at": row['updated_at'].isoformat() if row.get('updated_at') else None,
|
||||||
"payload": payload
|
"payload": payload
|
||||||
}
|
},
|
||||||
|
# ✅ Флаги подтверждения данных контакта (из CRM поля cf_2624)
|
||||||
|
"contact_data_confirmed": contact_data_confirmed,
|
||||||
|
"contact_data_can_edit": contact_data_can_edit,
|
||||||
|
"contact_data_from_crm": contact_data_from_crm # Данные из CRM (всегда загружаем, если есть contact_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
|
|||||||
@@ -218,19 +218,41 @@ async def stream_events(task_id: str):
|
|||||||
# ✅ Обработка ocr_status ready: загружаем form_draft из PostgreSQL
|
# ✅ Обработка ocr_status ready: загружаем form_draft из PostgreSQL
|
||||||
if actual_event.get('event_type') == 'ocr_status' and actual_event.get('status') == 'ready':
|
if actual_event.get('event_type') == 'ocr_status' and actual_event.get('status') == 'ready':
|
||||||
claim_id = actual_event.get('claim_id') or actual_event.get('data', {}).get('claim_id')
|
claim_id = actual_event.get('claim_id') or actual_event.get('data', {}).get('claim_id')
|
||||||
|
# ✅ Получаем cf_2624 из события (Данные подтверждены)
|
||||||
|
cf_2624 = actual_event.get('cf_2624')
|
||||||
|
|
||||||
if claim_id:
|
if claim_id:
|
||||||
logger.info(f"🔍 OCR ready event received, loading form_draft for claim_id={claim_id}")
|
logger.info(f"🔍 OCR ready event received, loading form_draft for claim_id={claim_id}, cf_2624={cf_2624}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# ✅ Если есть cf_2624 в событии - сохраняем в черновик
|
||||||
|
if cf_2624 is not None:
|
||||||
|
try:
|
||||||
|
update_query = """
|
||||||
|
UPDATE clpr_claims
|
||||||
|
SET payload = jsonb_set(
|
||||||
|
COALESCE(payload, '{}'::jsonb),
|
||||||
|
'{cf_2624}',
|
||||||
|
$1::jsonb
|
||||||
|
)
|
||||||
|
WHERE id::text = $2 OR payload->>'claim_id' = $2
|
||||||
|
RETURNING id;
|
||||||
|
"""
|
||||||
|
await db.execute(update_query, json.dumps(cf_2624), claim_id)
|
||||||
|
logger.info(f"✅ Сохранён cf_2624={cf_2624} в черновик claim_id={claim_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠️ Не удалось сохранить cf_2624 в черновик: {e}")
|
||||||
|
|
||||||
# Загружаем form_draft и documents из PostgreSQL
|
# Загружаем form_draft и documents из PostgreSQL
|
||||||
query = """
|
query = """
|
||||||
SELECT
|
SELECT
|
||||||
c.id,
|
c.id,
|
||||||
c.payload->'form_draft' as form_draft,
|
c.payload->'form_draft' as form_draft,
|
||||||
c.payload->'documents_required' as documents_required,
|
c.payload->'documents_required' as documents_required,
|
||||||
c.payload->'documents_meta' as documents_meta
|
c.payload->'documents_meta' as documents_meta,
|
||||||
|
c.payload->>'cf_2624' as cf_2624
|
||||||
FROM clpr_claims c
|
FROM clpr_claims c
|
||||||
WHERE c.id::text = $1
|
WHERE c.id::text = $1 OR c.payload->>'claim_id' = $1
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -241,6 +263,7 @@ async def stream_events(task_id: str):
|
|||||||
form_draft_raw = row.get('form_draft')
|
form_draft_raw = row.get('form_draft')
|
||||||
documents_required_raw = row.get('documents_required')
|
documents_required_raw = row.get('documents_required')
|
||||||
documents_meta_raw = row.get('documents_meta')
|
documents_meta_raw = row.get('documents_meta')
|
||||||
|
cf_2624_from_db = row.get('cf_2624') # ✅ Получаем cf_2624 из БД
|
||||||
|
|
||||||
# Парсим если строка
|
# Парсим если строка
|
||||||
def parse_json_field(val):
|
def parse_json_field(val):
|
||||||
@@ -266,7 +289,10 @@ async def stream_events(task_id: str):
|
|||||||
'documents_meta': documents_meta,
|
'documents_meta': documents_meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(f"✅ Form draft loaded from PostgreSQL for claim_id={claim_id}, has_form_draft={form_draft is not None}")
|
# ✅ Добавляем cf_2624 в событие (из БД или из события)
|
||||||
|
actual_event['cf_2624'] = cf_2624_from_db or cf_2624 or "0"
|
||||||
|
|
||||||
|
logger.info(f"✅ Form draft loaded from PostgreSQL for claim_id={claim_id}, has_form_draft={form_draft is not None}, cf_2624={actual_event.get('cf_2624')}")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠️ Claim not found in PostgreSQL: claim_id={claim_id}")
|
logger.warning(f"⚠️ Claim not found in PostgreSQL: claim_id={claim_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ class Settings(BaseSettings):
|
|||||||
mysql_user: str = "root"
|
mysql_user: str = "root"
|
||||||
mysql_password: str = ""
|
mysql_password: str = ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# MYSQL CRM (vtiger CRM)
|
||||||
|
# ============================================
|
||||||
|
mysql_crm_host: str = "localhost"
|
||||||
|
mysql_crm_port: int = 3306
|
||||||
|
mysql_crm_db: str = "ci20465_72new"
|
||||||
|
mysql_crm_user: str = "ci20465_72new"
|
||||||
|
mysql_crm_password: str = "EcY979Rn"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def database_url(self) -> str:
|
def database_url(self) -> str:
|
||||||
"""Формирует URL для подключения к PostgreSQL"""
|
"""Формирует URL для подключения к PostgreSQL"""
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from .services.database import db
|
|||||||
from .services.redis_service import redis_service
|
from .services.redis_service import redis_service
|
||||||
from .services.rabbitmq_service import rabbitmq_service
|
from .services.rabbitmq_service import rabbitmq_service
|
||||||
from .services.policy_service import policy_service
|
from .services.policy_service import policy_service
|
||||||
|
from .services.crm_mysql_service import crm_mysql_service
|
||||||
from .services.s3_service import s3_service
|
from .services.s3_service import s3_service
|
||||||
from .api import sms, claims, policy, upload, draft, events, n8n_proxy, session, documents
|
from .api import sms, claims, policy, upload, draft, events, n8n_proxy, session, documents
|
||||||
|
|
||||||
@@ -56,6 +57,12 @@ async def lifespan(app: FastAPI):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ MySQL Policy DB not available: {e}")
|
logger.warning(f"⚠️ MySQL Policy DB not available: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Подключаем MySQL CRM (vtiger)
|
||||||
|
await crm_mysql_service.connect()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠️ MySQL CRM DB not available: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Подключаем S3 (для загрузки файлов)
|
# Подключаем S3 (для загрузки файлов)
|
||||||
s3_service.connect()
|
s3_service.connect()
|
||||||
@@ -73,6 +80,7 @@ async def lifespan(app: FastAPI):
|
|||||||
await redis_service.disconnect()
|
await redis_service.disconnect()
|
||||||
await rabbitmq_service.disconnect()
|
await rabbitmq_service.disconnect()
|
||||||
await policy_service.close()
|
await policy_service.close()
|
||||||
|
await crm_mysql_service.close()
|
||||||
|
|
||||||
logger.info("👋 Ticket Form Intake Platform stopped")
|
logger.info("👋 Ticket Form Intake Platform stopped")
|
||||||
|
|
||||||
|
|||||||
117
ticket_form/backend/app/services/crm_mysql_service.py
Normal file
117
ticket_form/backend/app/services/crm_mysql_service.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
"""
|
||||||
|
CRM MySQL Service - Подключение к MySQL БД vtiger CRM
|
||||||
|
"""
|
||||||
|
import aiomysql
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
from ..config import settings
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CrmMySQLService:
|
||||||
|
"""Сервис для работы с MySQL БД vtiger CRM"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.pool: Optional[aiomysql.Pool] = None
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
"""Подключение к MySQL БД vtiger CRM"""
|
||||||
|
try:
|
||||||
|
self.pool = await aiomysql.create_pool(
|
||||||
|
host=settings.mysql_crm_host,
|
||||||
|
port=settings.mysql_crm_port,
|
||||||
|
user=settings.mysql_crm_user,
|
||||||
|
password=settings.mysql_crm_password,
|
||||||
|
db=settings.mysql_crm_db,
|
||||||
|
autocommit=True,
|
||||||
|
minsize=1,
|
||||||
|
maxsize=5
|
||||||
|
)
|
||||||
|
logger.info(f"✅ MySQL CRM DB connected: {settings.mysql_crm_host}:{settings.mysql_crm_port}/{settings.mysql_crm_db}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ MySQL CRM DB connection error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def fetch_one(self, query: str, *args) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Выполнить SQL запрос и вернуть одну запись
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: SQL запрос с плейсхолдерами %s
|
||||||
|
*args: Параметры для запроса
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict с данными или None если не найдено
|
||||||
|
"""
|
||||||
|
if not self.pool:
|
||||||
|
await self.connect()
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with self.pool.acquire() as conn:
|
||||||
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
|
await cursor.execute(query, args)
|
||||||
|
result = await cursor.fetchone()
|
||||||
|
return dict(result) if result else None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Error executing query: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def fetch_all(self, query: str, *args) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Выполнить SQL запрос и вернуть все записи
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: SQL запрос с плейсхолдерами %s
|
||||||
|
*args: Параметры для запроса
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict] с данными
|
||||||
|
"""
|
||||||
|
if not self.pool:
|
||||||
|
await self.connect()
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with self.pool.acquire() as conn:
|
||||||
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
|
await cursor.execute(query, args)
|
||||||
|
results = await cursor.fetchall()
|
||||||
|
return [dict(row) for row in results] if results else []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Error executing query: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def execute(self, query: str, *args) -> int:
|
||||||
|
"""
|
||||||
|
Выполнить SQL запрос (INSERT, UPDATE, DELETE)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: SQL запрос с плейсхолдерами %s
|
||||||
|
*args: Параметры для запроса
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Количество затронутых строк
|
||||||
|
"""
|
||||||
|
if not self.pool:
|
||||||
|
await self.connect()
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with self.pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute(query, args)
|
||||||
|
return cursor.rowcount
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Error executing query: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
"""Закрыть пул подключений"""
|
||||||
|
if self.pool:
|
||||||
|
self.pool.close()
|
||||||
|
await self.pool.wait_closed()
|
||||||
|
logger.info("MySQL CRM DB pool closed")
|
||||||
|
|
||||||
|
|
||||||
|
# Глобальный экземпляр
|
||||||
|
crm_mysql_service = CrmMySQLService()
|
||||||
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# Получение cf_2624 из MySQL при загрузке черновика
|
||||||
|
|
||||||
|
## ✅ Упрощённый подход
|
||||||
|
|
||||||
|
Вместо передачи `cf_2624` через события Redis, просто делаем прямой SQL запрос к MySQL при загрузке черновика.
|
||||||
|
|
||||||
|
## Где это происходит
|
||||||
|
|
||||||
|
**Файл:** `ticket_form/backend/app/api/claims.py`
|
||||||
|
**Эндпоинт:** `GET /api/v1/claims/drafts/{claim_id}`
|
||||||
|
**Функция:** `get_draft()`
|
||||||
|
|
||||||
|
## Как работает
|
||||||
|
|
||||||
|
1. **Получаем `contact_id` из черновика:**
|
||||||
|
```python
|
||||||
|
contact_id = payload.get('contact_id')
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Делаем SQL запрос к MySQL:**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
cd.contactid,
|
||||||
|
cd.firstname,
|
||||||
|
cd.lastname,
|
||||||
|
cd.email,
|
||||||
|
cd.mobile,
|
||||||
|
ccf.cf_2624 AS cf_2624
|
||||||
|
FROM vtiger_contactdetails cd
|
||||||
|
LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
|
||||||
|
WHERE cd.contactid = %s
|
||||||
|
AND ce.deleted = 0
|
||||||
|
LIMIT 1
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Используем `cf_2624` для блокировки полей:**
|
||||||
|
```python
|
||||||
|
contact_data_confirmed = (cf_2624 == "1")
|
||||||
|
contact_data_can_edit = not contact_data_confirmed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Преимущества
|
||||||
|
|
||||||
|
1. ✅ **Проще** - один SQL запрос вместо цепочки событий
|
||||||
|
2. ✅ **Быстрее** - прямой запрос к БД
|
||||||
|
3. ✅ **Надёжнее** - не зависит от событий Redis
|
||||||
|
4. ✅ **Актуальнее** - всегда получаем свежие данные из БД
|
||||||
|
|
||||||
|
## Что не нужно делать
|
||||||
|
|
||||||
|
- ❌ Передавать `cf_2624` через события Redis
|
||||||
|
- ❌ Сохранять `cf_2624` в черновик при обработке событий
|
||||||
|
- ❌ Использовать webservice API для получения `cf_2624`
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
1. ✅ При загрузке черновика делается SQL запрос к PostgreSQL
|
||||||
|
2. ✅ Получаем `cf_2624` из таблицы `vtiger_contactscf`
|
||||||
|
3. ✅ Используем для блокировки полей на фронтенде
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Реализация
|
||||||
|
|
||||||
|
### MySQL Connection для CRM
|
||||||
|
|
||||||
|
Создан отдельный сервис `CrmMySQLService` для подключения к MySQL БД vtiger CRM:
|
||||||
|
|
||||||
|
**Файл:** `ticket_form/backend/app/services/crm_mysql_service.py`
|
||||||
|
|
||||||
|
**Credentials (из config.php):**
|
||||||
|
- Host: `localhost`
|
||||||
|
- Port: `3306`
|
||||||
|
- Database: `ci20465_72new`
|
||||||
|
- User: `ci20465_72new`
|
||||||
|
- Password: `EcY979Rn`
|
||||||
|
|
||||||
|
### Использование в коде
|
||||||
|
|
||||||
|
```python
|
||||||
|
from ..services.crm_mysql_service import crm_mysql_service
|
||||||
|
|
||||||
|
# SQL запрос с MySQL синтаксисом (%s вместо $1)
|
||||||
|
contact_query = """
|
||||||
|
SELECT ... FROM vtiger_contactdetails cd
|
||||||
|
WHERE cd.contactid = %s
|
||||||
|
"""
|
||||||
|
contact_row = await crm_mysql_service.fetch_one(contact_query, contact_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Отличия от PostgreSQL
|
||||||
|
|
||||||
|
- Параметры: `%s` вместо `$1`
|
||||||
|
- Синтаксис JOIN: тот же
|
||||||
|
- LIMIT: тот же
|
||||||
|
|
||||||
135
ticket_form/docs/CF_2624_IMPLEMENTATION_SUMMARY.md
Normal file
135
ticket_form/docs/CF_2624_IMPLEMENTATION_SUMMARY.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# Реализация проверки cf_2624 при формировании заявления
|
||||||
|
|
||||||
|
## ✅ Что сделано
|
||||||
|
|
||||||
|
### 1. Backend API (`/drafts/{claim_id}`)
|
||||||
|
- ✅ Получает `cf_2624` из CRM через webservice `retrieve`
|
||||||
|
- ✅ Преобразует в `contact_data_confirmed` (boolean)
|
||||||
|
- ✅ Возвращает в ответе API вместе с `contact_data_from_crm`
|
||||||
|
|
||||||
|
**Файл:** `ticket_form/backend/app/api/claims.py` (строки 459-539)
|
||||||
|
|
||||||
|
### 2. Frontend - Загрузка черновика
|
||||||
|
- ✅ Получает `contact_data_confirmed` из ответа API
|
||||||
|
- ✅ Сохраняет в `formData`
|
||||||
|
- ✅ Передаёт в `claimPlanData` для `StepClaimConfirmation`
|
||||||
|
|
||||||
|
**Файл:** `ticket_form/frontend/src/pages/ClaimForm.tsx` (строки 564-848)
|
||||||
|
|
||||||
|
### 3. Frontend - Форма подтверждения
|
||||||
|
- ✅ `StepClaimConfirmation` получает `contact_data_confirmed` из `claimPlanData`
|
||||||
|
- ✅ Передаёт в `generateConfirmationFormHTML`
|
||||||
|
- ✅ Форма блокирует персональные данные если `contact_data_confirmed = true`
|
||||||
|
|
||||||
|
**Файлы:**
|
||||||
|
- `ticket_form/frontend/src/components/form/StepClaimConfirmation.tsx` (строки 89-96)
|
||||||
|
- `ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts` (строки 4, 293, 724-740, 840, 907-915)
|
||||||
|
|
||||||
|
### 4. CreateWebContact
|
||||||
|
- ✅ Возвращает `cf_2624` в JSON ответе
|
||||||
|
- ✅ Для новых контактов: `cf_2624 = "0"`
|
||||||
|
- ✅ Для существующих: берёт значение из CRM
|
||||||
|
|
||||||
|
**Файл:** `include/Webservices/CreateWebContact.php`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⏳ Что нужно сделать
|
||||||
|
|
||||||
|
### 1. Обновить n8n workflow `6mxRJ2LLHmQXyaDz`
|
||||||
|
|
||||||
|
**После ноды `CreateWebContacКлиентправ`:**
|
||||||
|
|
||||||
|
Добавить ноду `Code: Extract Contact Data Confirmed`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Парсим результат CreateWebContact
|
||||||
|
const rawResult = $node["CreateWebContacКлиентправ"].json.result;
|
||||||
|
const contactData = JSON.parse(rawResult);
|
||||||
|
|
||||||
|
// Извлекаем cf_2624 (Данные подтверждены)
|
||||||
|
const cf_2624 = contactData.cf_2624 || "0";
|
||||||
|
const contact_data_confirmed = cf_2624 === "1";
|
||||||
|
|
||||||
|
return {
|
||||||
|
contact_id: contactData.contact_id,
|
||||||
|
is_new_contact: contactData.is_new,
|
||||||
|
cf_2624: cf_2624,
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: !contact_data_confirmed
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**В ноде `Code in JavaScriptКлиентправ` (формирование ответа):**
|
||||||
|
|
||||||
|
Добавить в return:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const contactStatus = $('Code: Extract Contact Data Confirmed').first().json;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// ... существующие поля ...
|
||||||
|
contact_data_confirmed: contactStatus.contact_data_confirmed || false,
|
||||||
|
contact_data_can_edit: contactStatus.contact_data_can_edit !== false,
|
||||||
|
cf_2624: contactStatus.cf_2624 || "0",
|
||||||
|
// ... остальные поля ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**См. подробности:** `ticket_form/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Логика работы
|
||||||
|
|
||||||
|
### Сценарий 1: Загрузка черновика
|
||||||
|
1. Пользователь выбирает черновик
|
||||||
|
2. Frontend вызывает `/api/v1/claims/drafts/{claim_id}`
|
||||||
|
3. Backend получает `cf_2624` из CRM
|
||||||
|
4. Backend возвращает `contact_data_confirmed = (cf_2624 === "1")`
|
||||||
|
5. Frontend передаёт флаг в форму подтверждения
|
||||||
|
6. Форма блокирует поля если `contact_data_confirmed = true`
|
||||||
|
|
||||||
|
### Сценарий 2: Новое заявление (через n8n)
|
||||||
|
1. Пользователь вводит телефон
|
||||||
|
2. n8n вызывает `CreateWebContact`
|
||||||
|
3. `CreateWebContact` возвращает `cf_2624` в ответе
|
||||||
|
4. n8n извлекает `cf_2624` и передаёт в ответе для фронтенда
|
||||||
|
5. Frontend получает `contact_data_confirmed` из ответа n8n
|
||||||
|
6. Форма блокирует поля если `contact_data_confirmed = true`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Какие поля блокируются
|
||||||
|
|
||||||
|
Если `contact_data_confirmed = true`, блокируются следующие поля:
|
||||||
|
- ✅ Фамилия (`lastname`)
|
||||||
|
- ✅ Имя (`firstname`)
|
||||||
|
- ✅ Отчество (`secondname`, `middle_name`)
|
||||||
|
- ✅ ИНН (`inn`)
|
||||||
|
- ✅ Дата рождения (`birthday`)
|
||||||
|
- ✅ Место рождения (`birthplace`, `birth_place`)
|
||||||
|
- ✅ Адрес (`mailingstreet`, `address`)
|
||||||
|
- ✅ Email (`email`)
|
||||||
|
|
||||||
|
**Телефон (`mobile`) всегда только для чтения** (не зависит от флага)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Проверка
|
||||||
|
|
||||||
|
1. ✅ Создать контакт в CRM → `cf_2624` должен быть "0"
|
||||||
|
2. ✅ Загрузить черновик → поля должны быть редактируемыми
|
||||||
|
3. ⏳ Установить `cf_2624 = "1"` в CRM
|
||||||
|
4. ⏳ Загрузить черновик → поля должны быть заблокированы
|
||||||
|
5. ⏳ Проверить предупреждение "⚠️ Данные подтверждены" в форме
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Документация
|
||||||
|
|
||||||
|
- `ticket_form/docs/CRM_CONTACT_DATA_CONFIRMED_FIELD.md` - Описание поля cf_2624
|
||||||
|
- `ticket_form/docs/CREATE_WEB_CONTACT_RESPONSE_FORMAT.md` - Формат ответа CreateWebContact
|
||||||
|
- `ticket_form/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md` - Обновление n8n workflow
|
||||||
|
- `ticket_form/docs/CODE_CREATE_WEB_CONTACT_FINAL.js` - Код для n8n (обновлён)
|
||||||
|
|
||||||
113
ticket_form/docs/CF_2624_IN_OCR_STATUS_EVENT.md
Normal file
113
ticket_form/docs/CF_2624_IN_OCR_STATUS_EVENT.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Добавление cf_2624 в событие ocr_status ready
|
||||||
|
|
||||||
|
## ✅ Да, правильно!
|
||||||
|
|
||||||
|
Событие `ocr_status` с `status: "ready"` должно содержать поле `cf_2624` и сохраняться в черновик.
|
||||||
|
|
||||||
|
## Формат события в Redis
|
||||||
|
|
||||||
|
**Канал:** `ocr_events:sess_5fc7cdd1-a848-4e92-aed4-3ee4bfb19b4c`
|
||||||
|
|
||||||
|
**Событие:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event_type": "ocr_status",
|
||||||
|
"status": "ready",
|
||||||
|
"claim_id": "ef853bac-f54b-46aa-adf8-f0c9c0cd76bc",
|
||||||
|
"message": "Заявление сформировано",
|
||||||
|
"timestamp": "2025-12-03T12:44:12.347Z",
|
||||||
|
"cf_2624": "0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что происходит
|
||||||
|
|
||||||
|
### 1. n8n workflow публикует событие
|
||||||
|
|
||||||
|
После сохранения черновика (после `claimsave`) n8n публикует событие в Redis канал `ocr_events:{session_id}` с полем `cf_2624`.
|
||||||
|
|
||||||
|
**Где добавить:** После ноды `claimsave`, перед публикацией в Redis.
|
||||||
|
|
||||||
|
**См. подробности:** `ticket_form/docs/N8N_ADD_CF_2624_TO_OCR_STATUS_EVENT.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Backend обрабатывает событие
|
||||||
|
|
||||||
|
Backend получает событие из Redis и:
|
||||||
|
- ✅ Загружает `form_draft` из PostgreSQL
|
||||||
|
- ✅ **Сохраняет `cf_2624` в черновик** (в `payload.cf_2624`)
|
||||||
|
- ✅ Отправляет событие на фронтенд через SSE
|
||||||
|
|
||||||
|
**Файл:** `ticket_form/backend/app/api/events.py` (строки 218-273)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Сохранение в черновик
|
||||||
|
|
||||||
|
`cf_2624` сохраняется в таблицу `clpr_claims` в поле `payload.cf_2624`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE clpr_claims
|
||||||
|
SET payload = jsonb_set(
|
||||||
|
COALESCE(payload, '{}'::jsonb),
|
||||||
|
'{cf_2624}',
|
||||||
|
'"0"'::jsonb -- или '"1"'
|
||||||
|
)
|
||||||
|
WHERE id::text = $1 OR payload->>'claim_id' = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок работы
|
||||||
|
|
||||||
|
1. **n8n workflow:**
|
||||||
|
- `CreateWebContacКлиентправ` → получает `cf_2624` из CRM
|
||||||
|
- `claimsave` → сохраняет черновик
|
||||||
|
- `Code: Prepare OCR Status Event` → формирует событие с `cf_2624`
|
||||||
|
- `HTTP Request` или `Redis Publish` → публикует в `ocr_events:{session_id}`
|
||||||
|
|
||||||
|
2. **Backend:**
|
||||||
|
- Получает событие из Redis
|
||||||
|
- Сохраняет `cf_2624` в черновик
|
||||||
|
- Загружает `form_draft` из PostgreSQL
|
||||||
|
- Отправляет на фронтенд через SSE
|
||||||
|
|
||||||
|
3. **Фронтенд:**
|
||||||
|
- Получает событие через SSE
|
||||||
|
- Использует `cf_2624` для блокировки полей
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
1. ✅ Событие публикуется в `ocr_events:{session_id}` с `cf_2624`
|
||||||
|
2. ✅ Backend сохраняет `cf_2624` в черновик (`payload.cf_2624`)
|
||||||
|
3. ✅ При загрузке черновика `cf_2624` доступен в `payload.cf_2624`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SQL для проверки
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Проверить, что cf_2624 сохранён в черновик
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
payload->>'claim_id' as claim_id,
|
||||||
|
payload->>'cf_2624' as cf_2624,
|
||||||
|
updated_at
|
||||||
|
FROM clpr_claims
|
||||||
|
WHERE payload->>'claim_id' = 'ef853bac-f54b-46aa-adf8-f0c9c0cd76bc'
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
LIMIT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Итого
|
||||||
|
|
||||||
|
✅ **Да, правильно!** Событие `ocr_status` с `status: "ready"` должно содержать `cf_2624`, и это значение будет:
|
||||||
|
- Публиковаться в Redis канал `ocr_events:{session_id}`
|
||||||
|
- Сохраняться в черновик в `payload.cf_2624`
|
||||||
|
- Использоваться для блокировки полей на фронтенде
|
||||||
|
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
// Парсим результат CreateWebContact
|
// Парсим результат CreateWebContact
|
||||||
const rawResult = $node["CreateWebContact"].json.result;
|
const rawResult = $node["CreateWebContact"].json.result;
|
||||||
|
|
||||||
const contactData = JSON.parse(rawResult); // {"contact_id": "396625", "is_new": false}
|
const contactData = JSON.parse(rawResult); // {"contact_id": "396625", "is_new": false, "cf_2624": "1"}
|
||||||
|
|
||||||
|
// ✅ Извлекаем cf_2624 (Данные подтверждены)
|
||||||
|
// "1" = данные подтверждены, "0" = не подтверждены
|
||||||
|
const cf_2624 = contactData.cf_2624 || "0";
|
||||||
|
const contact_data_confirmed = cf_2624 === "1";
|
||||||
|
|
||||||
const phone = $('Edit Fields').first().json.phone;
|
const phone = $('Edit Fields').first().json.phone;
|
||||||
|
|
||||||
@@ -18,6 +23,8 @@ const sessionData = {
|
|||||||
contact_id: contactData.contact_id, // ← распарсенный ID из CreateWebContact
|
contact_id: contactData.contact_id, // ← распарсенный ID из CreateWebContact
|
||||||
phone: phone,
|
phone: phone,
|
||||||
is_new_contact: contactData.is_new, // ← флаг нового контакта
|
is_new_contact: contactData.is_new, // ← флаг нового контакта
|
||||||
|
cf_2624: cf_2624, // ✅ Сохраняем cf_2624 в сессию
|
||||||
|
contact_data_confirmed: contact_data_confirmed, // ✅ Сохраняем флаг подтверждения
|
||||||
status: "draft",
|
status: "draft",
|
||||||
current_step: 1,
|
current_step: 1,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
@@ -34,6 +41,10 @@ return {
|
|||||||
contact_id: contactData.contact_id,
|
contact_id: contactData.contact_id,
|
||||||
is_new_contact: contactData.is_new,
|
is_new_contact: contactData.is_new,
|
||||||
phone: phone,
|
phone: phone,
|
||||||
|
// ✅ Флаги подтверждения данных контакта (из cf_2624)
|
||||||
|
cf_2624: cf_2624,
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: !contact_data_confirmed,
|
||||||
redis_key: `session:${session_id}`, // ✅ Используем session_id для ключа Redis
|
redis_key: `session:${session_id}`, // ✅ Используем session_id для ключа Redis
|
||||||
redis_value: JSON.stringify(sessionData),
|
redis_value: JSON.stringify(sessionData),
|
||||||
ttl: 604800
|
ttl: 604800
|
||||||
|
|||||||
56
ticket_form/docs/CREATE_WEB_CONTACT_RESPONSE_FORMAT.md
Normal file
56
ticket_form/docs/CREATE_WEB_CONTACT_RESPONSE_FORMAT.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Формат ответа CreateWebContact
|
||||||
|
|
||||||
|
## Обновление: добавлено поле cf_2624
|
||||||
|
|
||||||
|
### Старый формат:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"contact_id": "396625",
|
||||||
|
"is_new": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Новый формат (с cf_2624):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"contact_id": "396625",
|
||||||
|
"is_new": false,
|
||||||
|
"cf_2624": "1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Описание полей:
|
||||||
|
|
||||||
|
- **contact_id** (string) - ID контакта в CRM
|
||||||
|
- **is_new** (boolean) - `true` если контакт только что создан, `false` если найден существующий
|
||||||
|
- **cf_2624** (string) - "Данные подтверждены":
|
||||||
|
- `"1"` = "Да" (данные подтверждены)
|
||||||
|
- `"0"` = "Нет" (данные не подтверждены)
|
||||||
|
|
||||||
|
## Использование в n8n:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Парсим результат CreateWebContact
|
||||||
|
const rawResult = $node["CreateWebContact"].json.result;
|
||||||
|
const contactData = JSON.parse(rawResult);
|
||||||
|
|
||||||
|
// Получаем данные
|
||||||
|
const contact_id = contactData.contact_id;
|
||||||
|
const is_new = contactData.is_new;
|
||||||
|
const data_confirmed = contactData.cf_2624 === "1"; // true/false
|
||||||
|
|
||||||
|
// Используем в дальнейшей логике
|
||||||
|
if (data_confirmed) {
|
||||||
|
// Данные подтверждены - блокируем редактирование
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Логика работы:
|
||||||
|
|
||||||
|
1. **Новый контакт** (`is_new: true`):
|
||||||
|
- `cf_2624` всегда `"0"` (данные не подтверждены)
|
||||||
|
|
||||||
|
2. **Существующий контакт** (`is_new: false`):
|
||||||
|
- `cf_2624` берётся из базы данных CRM
|
||||||
|
- Если поле пустое → возвращается `"0"`
|
||||||
|
|
||||||
149
ticket_form/docs/CRM_CONTACT_DATA_CONFIRMED_FIELD.md
Normal file
149
ticket_form/docs/CRM_CONTACT_DATA_CONFIRMED_FIELD.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Добавление поля "Данные подтверждены" в CRM
|
||||||
|
|
||||||
|
## Шаг 1: Создание кастомного поля в CRM
|
||||||
|
|
||||||
|
1. Зайти в CRM → Настройки → Кастомные поля → Модуль "Контакты"
|
||||||
|
2. Создать новое поле:
|
||||||
|
- **Название:** "Данные подтверждены"
|
||||||
|
- **Тип:** "Да/Нет" (Checkbox) или "Список" (Picklist) со значениями "Да"/"Нет"
|
||||||
|
- **Код поля:** `cf_2624` ✅ (уже создано)
|
||||||
|
- **По умолчанию:** "Нет" (false)
|
||||||
|
|
||||||
|
3. **ВАЖНО:** Записать номер поля (например, `cf_2624`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 2: Обновление backend для проверки поля в CRM
|
||||||
|
|
||||||
|
### Файл: `ticket_form/backend/app/api/claims.py`
|
||||||
|
|
||||||
|
В функции `get_draft()` вместо проверки PostgreSQL, проверяем поле в CRM:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# ✅ Проверяем флаг подтверждения данных контакта из CRM
|
||||||
|
unified_id = row.get('unified_id')
|
||||||
|
contact_data_confirmed = False
|
||||||
|
contact_data_can_edit = True
|
||||||
|
contact_data_confirmed_at = None
|
||||||
|
contact_data_from_crm = None
|
||||||
|
|
||||||
|
if unified_id:
|
||||||
|
# Получаем contact_id из payload
|
||||||
|
contact_id = payload.get('contact_id') if isinstance(payload, dict) else None
|
||||||
|
|
||||||
|
if contact_id:
|
||||||
|
try:
|
||||||
|
# Получаем данные контакта из CRM
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
# 1. Get Challenge
|
||||||
|
challenge_response = await client.get(
|
||||||
|
f"{settings.crm_webservice_url}",
|
||||||
|
params={"operation": "getchallenge", "username": "api"}
|
||||||
|
)
|
||||||
|
challenge_data = challenge_response.json()
|
||||||
|
token = challenge_data.get("result", {}).get("token", "")
|
||||||
|
|
||||||
|
# 2. Login
|
||||||
|
import hashlib
|
||||||
|
salt = "4r9ANex8PT2IuRV"
|
||||||
|
access_key = hashlib.md5((token + salt).encode()).hexdigest()
|
||||||
|
|
||||||
|
login_response = await client.post(
|
||||||
|
f"{settings.crm_webservice_url}",
|
||||||
|
data={
|
||||||
|
"operation": "login",
|
||||||
|
"username": "api",
|
||||||
|
"accessKey": access_key
|
||||||
|
}
|
||||||
|
)
|
||||||
|
login_data = login_response.json()
|
||||||
|
session_name = login_data.get("result", {}).get("sessionName", "")
|
||||||
|
|
||||||
|
# 3. Retrieve Contact
|
||||||
|
retrieve_response = await client.post(
|
||||||
|
f"{settings.crm_webservice_url}",
|
||||||
|
data={
|
||||||
|
"operation": "retrieve",
|
||||||
|
"sessionName": session_name,
|
||||||
|
"id": f"12x{contact_id}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
retrieve_data = retrieve_response.json()
|
||||||
|
|
||||||
|
if retrieve_data.get("success") and retrieve_data.get("result"):
|
||||||
|
contact_data_from_crm = retrieve_data["result"]
|
||||||
|
|
||||||
|
# ✅ Проверяем кастомное поле "Данные подтверждены"
|
||||||
|
confirmed_field = contact_data_from_crm.get("cf_2624", "0") # "1" = да, "0" = нет
|
||||||
|
contact_data_confirmed = confirmed_field == "1" or confirmed_field == "true"
|
||||||
|
contact_data_can_edit = not contact_data_confirmed
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"🔒 Статус данных контакта из CRM: confirmed={contact_data_confirmed}, "
|
||||||
|
f"field_value={confirmed_field}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠️ Не удалось загрузить данные из CRM: {str(e)}")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 3: Обновление n8n workflow для установки поля
|
||||||
|
|
||||||
|
### В workflow `6mxRJ2LLHmQXyaDz`
|
||||||
|
|
||||||
|
После подтверждения формы (после SMS-верификации) добавить ноду:
|
||||||
|
|
||||||
|
**Название:** `HTTP Request: Set Contact Data Confirmed`
|
||||||
|
|
||||||
|
**Метод:** POST
|
||||||
|
|
||||||
|
**URL:** `{{ $env.CRM_WEBSERVICE_URL }}`
|
||||||
|
|
||||||
|
**Body (form-data):**
|
||||||
|
```
|
||||||
|
operation: revise
|
||||||
|
sessionName: {{ $('Login to CRM').json.sessionName }}
|
||||||
|
id: 12x{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}
|
||||||
|
cf_2624: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Где:**
|
||||||
|
- `cf_2624` - поле "Данные подтверждены"
|
||||||
|
- `1` = "Да" (данные подтверждены)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 4: Обновление UpsertContact (если используется)
|
||||||
|
|
||||||
|
Если используется `UpsertContact.php`, добавить поддержку нового поля:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// В функции vtws_upsertcontact()
|
||||||
|
if (!empty($data_confirmed)) {
|
||||||
|
$params['cf_2624'] = $data_confirmed; // "1" или "0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Преимущества подхода:
|
||||||
|
|
||||||
|
1. ✅ **CRM - источник истины** - все данные в одном месте
|
||||||
|
2. ✅ **Нет синхронизации** - не нужно синхронизировать флаги между PostgreSQL и CRM
|
||||||
|
3. ✅ **Простота** - один флаг в CRM, проверяем его напрямую
|
||||||
|
4. ✅ **Видимость** - менеджеры видят статус в карточке контакта
|
||||||
|
5. ✅ **Гибкость** - можно менять статус вручную в CRM
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка:
|
||||||
|
|
||||||
|
1. ✅ Поле создано в CRM: `cf_2624`
|
||||||
|
2. ⏳ Обновить код backend (использовать `cf_2624`)
|
||||||
|
3. ⏳ Обновить n8n workflow (использовать `cf_2624`)
|
||||||
|
4. ⏳ Протестировать:
|
||||||
|
- Создать контакт → поле должно быть "Нет"
|
||||||
|
- Подтвердить форму → поле должно стать "Да"
|
||||||
|
- Загрузить черновик → поля должны быть заблокированы
|
||||||
|
|
||||||
217
ticket_form/docs/FRONTEND_UPDATE_CONTACT_DATA_CONFIRMED.md
Normal file
217
ticket_form/docs/FRONTEND_UPDATE_CONTACT_DATA_CONFIRMED.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# Обновление фронтенда: Блокировка редактирования подтверждённых данных
|
||||||
|
|
||||||
|
## Изменения
|
||||||
|
|
||||||
|
### 1. Step1Phone.tsx - Получение флага из n8n
|
||||||
|
|
||||||
|
**После получения ответа от n8n (после строки ~150):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Извлекаем флаг подтверждения данных
|
||||||
|
const contact_data_confirmed = result.contact_data_confirmed || false;
|
||||||
|
const contact_data_can_edit = result.contact_data_can_edit !== false; // По умолчанию true
|
||||||
|
const contact_data_confirmed_at = result.contact_data_confirmed_at || null;
|
||||||
|
|
||||||
|
// Сохраняем в formData
|
||||||
|
updateFormData({
|
||||||
|
// ... существующие поля ...
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: contact_data_can_edit,
|
||||||
|
contact_data_confirmed_at: contact_data_confirmed_at,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. generateConfirmationFormHTML.ts - Блокировка полей
|
||||||
|
|
||||||
|
**Добавить параметр `contact_data_confirmed` в функцию:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function generateConfirmationFormHTML(
|
||||||
|
data: any,
|
||||||
|
contact_data_confirmed: boolean = false
|
||||||
|
): string {
|
||||||
|
// ... существующий код ...
|
||||||
|
|
||||||
|
// В функции createInputField добавить проверку:
|
||||||
|
function createInputField(root: string, key: string, value: any, label: string, type: string = 'text') {
|
||||||
|
const isReadOnly = contact_data_confirmed && (
|
||||||
|
key === 'firstname' ||
|
||||||
|
key === 'lastname' ||
|
||||||
|
key === 'middle_name' ||
|
||||||
|
key === 'inn' ||
|
||||||
|
key === 'birthday' ||
|
||||||
|
key === 'birthplace' ||
|
||||||
|
key === 'mailingstreet' ||
|
||||||
|
key === 'email'
|
||||||
|
);
|
||||||
|
|
||||||
|
const readonlyAttr = isReadOnly ? 'readonly' : '';
|
||||||
|
const readonlyClass = isReadOnly ? 'readonly-field' : '';
|
||||||
|
|
||||||
|
// ... остальной код с добавлением readonlyAttr и readonlyClass ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Добавить CSS для readonly полей:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
.readonly-field {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. StepClaimConfirmation.tsx - Передача флага в форму
|
||||||
|
|
||||||
|
**В useEffect (после строки ~90):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Получаем флаг подтверждения из claimPlanData или formData
|
||||||
|
const contact_data_confirmed =
|
||||||
|
claimPlanData?.contact_data_confirmed ||
|
||||||
|
claimPlanData?.propertyName?.meta?.contact_data_confirmed ||
|
||||||
|
formData?.contact_data_confirmed ||
|
||||||
|
false;
|
||||||
|
|
||||||
|
// Передаём в generateConfirmationFormHTML
|
||||||
|
const html = generateConfirmationFormHTML(formData, contact_data_confirmed);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Добавить кнопку "Изменить данные" (опционально)
|
||||||
|
|
||||||
|
**В generateConfirmationFormHTML.ts:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// После заголовка формы, если contact_data_confirmed = true
|
||||||
|
if (contact_data_confirmed) {
|
||||||
|
html += `
|
||||||
|
<div style="margin-bottom: 16px; padding: 12px; background: #fff7e6; border: 1px solid #ffd591; border-radius: 4px;">
|
||||||
|
<p style="margin: 0 0 8px 0; color: #ad6800;">
|
||||||
|
<strong>⚠️ Данные подтверждены</strong>
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; font-size: 14px; color: #ad6800;">
|
||||||
|
Для изменения данных требуется подтверждение через SMS.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="btn-edit-data"
|
||||||
|
style="margin-top: 8px; padding: 6px 16px; background: #fa8c16; color: white; border: none; border-radius: 4px; cursor: pointer;"
|
||||||
|
>
|
||||||
|
Изменить данные
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**В JavaScript внутри формы:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Обработчик кнопки "Изменить данные"
|
||||||
|
const editBtn = document.getElementById('btn-edit-data');
|
||||||
|
if (editBtn) {
|
||||||
|
editBtn.addEventListener('click', function() {
|
||||||
|
// Отправляем сообщение родительскому окну
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'request_edit_contact_data',
|
||||||
|
eventData: {
|
||||||
|
phone: state.user?.mobile || '',
|
||||||
|
unified_id: state.meta?.unified_id || ''
|
||||||
|
}
|
||||||
|
}, '*');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Обработка запроса на изменение данных
|
||||||
|
|
||||||
|
**В StepClaimConfirmation.tsx:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (event: MessageEvent) => {
|
||||||
|
// ... существующие обработчики ...
|
||||||
|
|
||||||
|
if (event.data.type === 'request_edit_contact_data') {
|
||||||
|
const { phone, unified_id } = event.data.eventData;
|
||||||
|
|
||||||
|
// Показываем модалку SMS для подтверждения
|
||||||
|
setSmsModalVisible(true);
|
||||||
|
setSmsCodeSent(false);
|
||||||
|
sendSMSCode(phone);
|
||||||
|
|
||||||
|
// Сохраняем флаг, что это запрос на изменение данных
|
||||||
|
setPendingFormData({
|
||||||
|
...pendingFormData,
|
||||||
|
is_edit_request: true,
|
||||||
|
unified_id: unified_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
return () => window.removeEventListener('message', handleMessage);
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. После SMS подтверждения - сброс флага
|
||||||
|
|
||||||
|
**В verifySMSCode (после успешной верификации):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Если это запрос на изменение данных
|
||||||
|
if (pendingFormData?.is_edit_request) {
|
||||||
|
// Отправляем запрос в n8n для сброса флага
|
||||||
|
await fetch('/api/v1/claims/contact-data/reset-confirmed', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
unified_id: pendingFormData.unified_id,
|
||||||
|
sms_code: code
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обновляем флаг в formData
|
||||||
|
updateFormData({
|
||||||
|
contact_data_confirmed: false,
|
||||||
|
contact_data_can_edit: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Перезагружаем форму с разблокированными полями
|
||||||
|
// (можно просто обновить страницу или пересоздать форму)
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок реализации
|
||||||
|
|
||||||
|
1. ✅ Обновить Step1Phone для получения флага
|
||||||
|
2. ✅ Обновить generateConfirmationFormHTML для блокировки полей
|
||||||
|
3. ✅ Обновить StepClaimConfirmation для передачи флага
|
||||||
|
4. ⏳ Добавить кнопку "Изменить данные" (опционально)
|
||||||
|
5. ⏳ Реализовать механизм переподтверждения через SMS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
После обновления проверить:
|
||||||
|
- ✅ Флаг получается из n8n
|
||||||
|
- ✅ Поля блокируются при `contact_data_confirmed = true`
|
||||||
|
- ✅ Данные из CRM загружаются и отображаются
|
||||||
|
- ✅ Кнопка "Изменить данные" работает (если реализована)
|
||||||
|
|
||||||
209
ticket_form/docs/N8N_ADD_CF_2624_TO_OCR_STATUS_EVENT.md
Normal file
209
ticket_form/docs/N8N_ADD_CF_2624_TO_OCR_STATUS_EVENT.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# Добавление cf_2624 в событие ocr_status ready
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
После сохранения черновика (после `claimsave`) публиковать событие `ocr_status` с `status: "ready"` в Redis канал `ocr_events:{session_id}` с полем `cf_2624`.
|
||||||
|
|
||||||
|
## Формат события
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event_type": "ocr_status",
|
||||||
|
"status": "ready",
|
||||||
|
"claim_id": "ef853bac-f54b-46aa-adf8-f0c9c0cd76bc",
|
||||||
|
"message": "Заявление сформировано",
|
||||||
|
"timestamp": "2025-12-03T12:44:12.347Z",
|
||||||
|
"cf_2624": "0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Где добавить в n8n workflow
|
||||||
|
|
||||||
|
### Вариант 1: После ноды `claimsave` (PostgreSQL)
|
||||||
|
|
||||||
|
**Название ноды:** `Code: Prepare OCR Status Event`
|
||||||
|
|
||||||
|
**Расположение:** После ноды `claimsave` (PostgreSQL), перед нодой публикации в Redis
|
||||||
|
|
||||||
|
**Код:**
|
||||||
|
```javascript
|
||||||
|
// Получаем результат из claimsave
|
||||||
|
const claimResult = $input.first().json;
|
||||||
|
const claim = claimResult.claim || claimResult;
|
||||||
|
|
||||||
|
// Получаем contact_id из claim
|
||||||
|
const contact_id = claim.contact_id;
|
||||||
|
|
||||||
|
// ✅ Получаем cf_2624 из PostgreSQL (если есть нода Get Contact Data)
|
||||||
|
let cf_2624 = "0"; // По умолчанию "0" (не подтверждено)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Пытаемся получить из предыдущей ноды PostgreSQL: Get Contact Data
|
||||||
|
const contactData = $('PostgreSQL: Get Contact Data')?.first()?.json;
|
||||||
|
if (contactData && contactData.cf_2624) {
|
||||||
|
cf_2624 = contactData.cf_2624;
|
||||||
|
} else {
|
||||||
|
// Альтернатива: получаем из CreateWebContact
|
||||||
|
const createWebContactResult = $node["CreateWebContacКлиентправ"]?.json?.result || "";
|
||||||
|
if (createWebContactResult) {
|
||||||
|
const contactData = typeof createWebContactResult === 'string'
|
||||||
|
? JSON.parse(createWebContactResult)
|
||||||
|
: createWebContactResult;
|
||||||
|
if (contactData.cf_2624) {
|
||||||
|
cf_2624 = contactData.cf_2624;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Не удалось получить cf_2624, используем значение по умолчанию "0"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем событие для Redis
|
||||||
|
const event = {
|
||||||
|
event_type: 'ocr_status',
|
||||||
|
status: 'ready',
|
||||||
|
claim_id: claim.claim_id || claim.id,
|
||||||
|
message: 'Заявление сформировано',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
cf_2624: cf_2624 // ✅ Добавляем cf_2624
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('📤 Подготовлено событие ocr_status ready:', {
|
||||||
|
claim_id: event.claim_id,
|
||||||
|
cf_2624: event.cf_2624,
|
||||||
|
contact_id: contact_id
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: {
|
||||||
|
// Данные для публикации в Redis
|
||||||
|
channel: `ocr_events:${claim.session_token || claim.session_id}`,
|
||||||
|
message: JSON.stringify(event),
|
||||||
|
|
||||||
|
// Передаём дальше для следующих нод
|
||||||
|
claim_id: event.claim_id,
|
||||||
|
session_token: claim.session_token || claim.session_id,
|
||||||
|
cf_2624: cf_2624
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Вариант 2: Прямо в ноде публикации (HTTP Request или Redis Publish)
|
||||||
|
|
||||||
|
**Если используется HTTP Request:**
|
||||||
|
|
||||||
|
**URL:** `{{ $env.BACKEND_URL }}/api/v1/events/{{ $json.session_token }}`
|
||||||
|
|
||||||
|
**Body (JSON):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event_type": "ocr_status",
|
||||||
|
"status": "ready",
|
||||||
|
"message": "Заявление сформировано",
|
||||||
|
"data": {
|
||||||
|
"claim_id": "{{ $json.claim_id }}",
|
||||||
|
"cf_2624": "{{ $json.cf_2624 || '0' }}"
|
||||||
|
},
|
||||||
|
"timestamp": "{{ $now.toISO() }}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Если используется Redis Publish:**
|
||||||
|
|
||||||
|
**Channel:** `ocr_events:{{ $json.session_token }}`
|
||||||
|
|
||||||
|
**Message:**
|
||||||
|
```javascript
|
||||||
|
={{ JSON.stringify({
|
||||||
|
event_type: 'ocr_status',
|
||||||
|
status: 'ready',
|
||||||
|
claim_id: $json.claim_id,
|
||||||
|
message: 'Заявление сформировано',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
cf_2624: $json.cf_2624 || '0'
|
||||||
|
}) }}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок нод в workflow
|
||||||
|
|
||||||
|
1. **CreateWebContacКлиентправ** → получаем `contact_id` и `cf_2624`
|
||||||
|
2. **PostgreSQL: Get Contact Data** (опционально) → получаем полные данные контакта включая `cf_2624`
|
||||||
|
3. **claimsave** (PostgreSQL) → сохраняем черновик
|
||||||
|
4. **Code: Prepare OCR Status Event** → формируем событие с `cf_2624`
|
||||||
|
5. **HTTP Request** или **Redis Publish** → публикуем событие в `ocr_events:{session_id}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сохранение в черновик
|
||||||
|
|
||||||
|
Событие с `cf_2624` будет:
|
||||||
|
1. ✅ Публиковаться в Redis канал `ocr_events:{session_id}`
|
||||||
|
2. ✅ Обрабатываться backend'ом (загружает `form_draft` из PostgreSQL)
|
||||||
|
3. ⏳ **Нужно добавить:** Сохранение `cf_2624` в черновик при обработке события
|
||||||
|
|
||||||
|
### Обновление backend для сохранения cf_2624
|
||||||
|
|
||||||
|
В файле `ticket_form/backend/app/api/events.py` (строка 218-267):
|
||||||
|
|
||||||
|
После загрузки `form_draft` из PostgreSQL, если в событии есть `cf_2624`, нужно сохранить его в черновик:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# ✅ Обработка ocr_status ready: загружаем form_draft из PostgreSQL
|
||||||
|
if actual_event.get('event_type') == 'ocr_status' and actual_event.get('status') == 'ready':
|
||||||
|
claim_id = actual_event.get('claim_id') or actual_event.get('data', {}).get('claim_id')
|
||||||
|
cf_2624 = actual_event.get('cf_2624') # ✅ Получаем cf_2624 из события
|
||||||
|
|
||||||
|
if claim_id:
|
||||||
|
# ... существующий код загрузки form_draft ...
|
||||||
|
|
||||||
|
# ✅ Если есть cf_2624 в событии - сохраняем в черновик
|
||||||
|
if cf_2624:
|
||||||
|
try:
|
||||||
|
update_query = """
|
||||||
|
UPDATE clpr_claims
|
||||||
|
SET payload = jsonb_set(
|
||||||
|
payload,
|
||||||
|
'{cf_2624}',
|
||||||
|
$1::jsonb
|
||||||
|
)
|
||||||
|
WHERE id::text = $2
|
||||||
|
RETURNING id;
|
||||||
|
"""
|
||||||
|
await db.execute(update_query, json.dumps(cf_2624), claim_id)
|
||||||
|
logger.info(f"✅ Сохранён cf_2624={cf_2624} в черновик claim_id={claim_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠️ Не удалось сохранить cf_2624: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
1. ✅ Событие публикуется в `ocr_events:{session_id}` с `cf_2624`
|
||||||
|
2. ⏳ Backend обрабатывает событие и сохраняет `cf_2624` в черновик
|
||||||
|
3. ⏳ При загрузке черновика `cf_2624` доступен в `payload.cf_2624`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пример полного события
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event_type": "ocr_status",
|
||||||
|
"status": "ready",
|
||||||
|
"claim_id": "ef853bac-f54b-46aa-adf8-f0c9c0cd76bc",
|
||||||
|
"message": "Заявление сформировано",
|
||||||
|
"timestamp": "2025-12-03T12:44:12.347Z",
|
||||||
|
"cf_2624": "0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Это событие будет:
|
||||||
|
- ✅ Публиковаться в Redis канал `ocr_events:sess_5fc7cdd1-a848-4e92-aed4-3ee4bfb19b4c`
|
||||||
|
- ✅ Обрабатываться backend'ом
|
||||||
|
- ✅ Сохраняться в черновик в поле `payload.cf_2624`
|
||||||
|
|
||||||
44
ticket_form/docs/N8N_CODE_CHECK_CONTACT_DATA_CONFIRMED.js
Normal file
44
ticket_form/docs/N8N_CODE_CHECK_CONTACT_DATA_CONFIRMED.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// Code Node для n8n: Проверка подтверждения данных контакта
|
||||||
|
// ============================================================================
|
||||||
|
// Назначение: Проверить, подтверждены ли данные контакта пользователя
|
||||||
|
// и нужно ли блокировать редактирование
|
||||||
|
//
|
||||||
|
// Использование: После получения unified_id, перед загрузкой данных формы
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Получаем unified_id из предыдущих шагов
|
||||||
|
const unified_id = $('user_get').first().json.unified_id ||
|
||||||
|
$('Edit Fields').first().json.unified_id ||
|
||||||
|
$json.unified_id;
|
||||||
|
|
||||||
|
if (!unified_id) {
|
||||||
|
throw new Error('unified_id не найден');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выполняем SQL запрос для проверки статуса
|
||||||
|
// (это должно быть в PostgreSQL ноде, но для примера показываю логику)
|
||||||
|
|
||||||
|
// SQL запрос:
|
||||||
|
// SELECT * FROM clpr_get_contact_data_status($1);
|
||||||
|
// Параметр: unified_id
|
||||||
|
|
||||||
|
// Ожидаемый результат:
|
||||||
|
// {
|
||||||
|
// is_confirmed: true/false,
|
||||||
|
// confirmed_at: "2025-12-02T14:30:00Z" или null,
|
||||||
|
// can_edit: true/false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Для Code Node (если нужно обработать результат):
|
||||||
|
const status = $('PostgreSQL Check Status').first().json; // Предполагаем, что есть такая нода
|
||||||
|
|
||||||
|
return {
|
||||||
|
unified_id: unified_id,
|
||||||
|
is_confirmed: status.is_confirmed || false,
|
||||||
|
confirmed_at: status.confirmed_at || null,
|
||||||
|
can_edit: status.can_edit !== false, // По умолчанию можно редактировать
|
||||||
|
// Флаг для фронтенда
|
||||||
|
lock_editing: status.is_confirmed || false
|
||||||
|
};
|
||||||
|
|
||||||
264
ticket_form/docs/N8N_CODE_IN_JAVASCRIPT_КЛИЕНТПРАВ_FULL.js
Normal file
264
ticket_form/docs/N8N_CODE_IN_JAVASCRIPT_КЛИЕНТПРАВ_FULL.js
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
// ========================================
|
||||||
|
// Code Node: Code in JavaScriptКлиентправ
|
||||||
|
// Формирование Response для фронтенда с поддержкой cf_2624
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// --- 1. Генерация UUIDv4 ---
|
||||||
|
function generateUUIDv4() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||||
|
const r = Math.random() * 16 | 0;
|
||||||
|
const v = c === 'x' ? r : ((r & 0x3) | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. Парсим контакт из CreateWebContacКлиентправ ---
|
||||||
|
const createWebContactNode = $node["CreateWebContacКлиентправ"] || $node["CreateWebContact"];
|
||||||
|
const rawResult = createWebContactNode?.json?.result || "";
|
||||||
|
|
||||||
|
let contactData = {};
|
||||||
|
try {
|
||||||
|
contactData = typeof rawResult === 'string'
|
||||||
|
? JSON.parse(rawResult)
|
||||||
|
: rawResult;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ Ошибка парсинга CreateWebContact:', e);
|
||||||
|
contactData = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Извлекаем cf_2624 (Данные подтверждены) из CreateWebContact
|
||||||
|
// "1" = данные подтверждены, "0" = не подтверждены
|
||||||
|
const cf_2624 = contactData.cf_2624 || "0";
|
||||||
|
const contact_data_confirmed = cf_2624 === "1" || cf_2624 === "true" || cf_2624 === true;
|
||||||
|
const contact_data_can_edit = !contact_data_confirmed;
|
||||||
|
|
||||||
|
console.log('🔒 Статус данных контакта из CreateWebContact:', {
|
||||||
|
contact_id: contactData.contact_id,
|
||||||
|
is_new: contactData.is_new,
|
||||||
|
cf_2624: cf_2624,
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: contact_data_can_edit
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 2.1. Получаем полные данные контакта из PostgreSQL (если есть) ---
|
||||||
|
let contactFromDB = null;
|
||||||
|
try {
|
||||||
|
// Пытаемся найти ноду PostgreSQL, которая получила данные контакта
|
||||||
|
const possiblePostgresNodes = [
|
||||||
|
'PostgreSQL: Get Contact Data',
|
||||||
|
'Get Contact from DB',
|
||||||
|
'PostgreSQL',
|
||||||
|
'Get Contact Details'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const nodeName of possiblePostgresNodes) {
|
||||||
|
try {
|
||||||
|
const node = $(nodeName)?.first();
|
||||||
|
if (node && node.json) {
|
||||||
|
// Проверяем, что это данные контакта (есть contactid)
|
||||||
|
if (node.json.contactid || node.json.contact_id) {
|
||||||
|
contactFromDB = node.json;
|
||||||
|
console.log('✅ Получены данные контакта из PostgreSQL:', {
|
||||||
|
contactid: contactFromDB.contactid || contactFromDB.contact_id,
|
||||||
|
firstname: contactFromDB.firstname,
|
||||||
|
lastname: contactFromDB.lastname
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Альтернативный способ: ищем по структуре данных
|
||||||
|
if (!contactFromDB) {
|
||||||
|
// Может быть в предыдущей ноде с результатом запроса
|
||||||
|
const inputData = $input.all();
|
||||||
|
for (const item of inputData) {
|
||||||
|
if (item.json && (item.json.contactid || item.json.contact_id)) {
|
||||||
|
contactFromDB = item.json;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Не удалось получить данные контакта из PostgreSQL:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если данные из БД получены - используем их для дополнения информации
|
||||||
|
if (contactFromDB) {
|
||||||
|
console.log('📋 Данные контакта из БД:', {
|
||||||
|
contactid: contactFromDB.contactid,
|
||||||
|
firstname: contactFromDB.firstname,
|
||||||
|
lastname: contactFromDB.lastname,
|
||||||
|
email: contactFromDB.email,
|
||||||
|
mobile: contactFromDB.mobile,
|
||||||
|
birthday: contactFromDB.birthday,
|
||||||
|
mailingstreet: contactFromDB.mailingstreet,
|
||||||
|
middle_name: contactFromDB.middle_name,
|
||||||
|
birthplace: contactFromDB.birthplace,
|
||||||
|
inn: contactFromDB.inn
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Телефон из Edit Fields ---
|
||||||
|
let phone = null;
|
||||||
|
try {
|
||||||
|
const editFields = $('Edit Fields')?.first();
|
||||||
|
if (editFields && editFields.json) {
|
||||||
|
phone = editFields.json.phone;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Не удалось получить phone из Edit Fields:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. unified_id из user_get ---
|
||||||
|
let unified_id = null;
|
||||||
|
try {
|
||||||
|
const possibleUserNodes = ['user_get', 'Find or Create User', 'PostgreSQL: Find User'];
|
||||||
|
for (const nodeName of possibleUserNodes) {
|
||||||
|
try {
|
||||||
|
const node = $node[nodeName];
|
||||||
|
if (node && node.json && node.json.unified_id) {
|
||||||
|
unified_id = node.json.unified_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Нода не существует или не выполнена - продолжаем поиск
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unified_id) {
|
||||||
|
console.warn('⚠️ unified_id не получен из ноды user_get. Проверьте, что нода выполнена.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Не удалось получить unified_id:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 5. Генерируем session_id (если не получен из предыдущих нод) ---
|
||||||
|
let session_id = null;
|
||||||
|
|
||||||
|
// Пытаемся получить session_id из предыдущих нод
|
||||||
|
try {
|
||||||
|
const possibleSessionNodes = [
|
||||||
|
'Code in JavaScript1',
|
||||||
|
'Code in JavaScript',
|
||||||
|
'Set Session Data',
|
||||||
|
'Create Session'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const nodeName of possibleSessionNodes) {
|
||||||
|
try {
|
||||||
|
const node = $(nodeName)?.first();
|
||||||
|
if (node && node.json) {
|
||||||
|
if (node.json.session_id) {
|
||||||
|
session_id = node.json.session_id;
|
||||||
|
break;
|
||||||
|
} else if (node.json.redis_value) {
|
||||||
|
const parsed = JSON.parse(node.json.redis_value);
|
||||||
|
if (parsed.session_id) {
|
||||||
|
session_id = parsed.session_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пытаемся получить из Edit Fields
|
||||||
|
if (!session_id) {
|
||||||
|
try {
|
||||||
|
const editFields = $('Edit Fields')?.first();
|
||||||
|
if (editFields && editFields.json && editFields.json.session_id) {
|
||||||
|
session_id = editFields.json.session_id;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Игнорируем
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Не удалось получить session_id из предыдущих нод:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если session_id не найден - генерируем новый
|
||||||
|
if (!session_id) {
|
||||||
|
session_id = 'sess_' + generateUUIDv4();
|
||||||
|
console.log('✅ Сгенерирован новый session_id:', session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 6. Формируем sessionData для Redis ---
|
||||||
|
const sessionData = {
|
||||||
|
session_id, // ← теперь сохраняем внутрь
|
||||||
|
unified_id,
|
||||||
|
contact_id: contactData.contact_id,
|
||||||
|
phone,
|
||||||
|
is_new_contact: contactData.is_new || contactData.is_new_contact || false,
|
||||||
|
// ✅ Флаги подтверждения данных контакта (из cf_2624)
|
||||||
|
cf_2624: cf_2624,
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: contact_data_can_edit,
|
||||||
|
// ✅ Данные контакта из PostgreSQL (если получены)
|
||||||
|
contact_from_db: contactFromDB ? {
|
||||||
|
contactid: contactFromDB.contactid || contactFromDB.contact_id,
|
||||||
|
firstname: contactFromDB.firstname,
|
||||||
|
lastname: contactFromDB.lastname,
|
||||||
|
email: contactFromDB.email,
|
||||||
|
mobile: contactFromDB.mobile,
|
||||||
|
phone: contactFromDB.phone,
|
||||||
|
birthday: contactFromDB.birthday,
|
||||||
|
mailingstreet: contactFromDB.mailingstreet,
|
||||||
|
mailingcity: contactFromDB.mailingcity,
|
||||||
|
mailingstate: contactFromDB.mailingstate,
|
||||||
|
mailingzip: contactFromDB.mailingzip,
|
||||||
|
mailingcountry: contactFromDB.mailingcountry,
|
||||||
|
middle_name: contactFromDB.middle_name,
|
||||||
|
birthplace: contactFromDB.birthplace,
|
||||||
|
inn: contactFromDB.inn,
|
||||||
|
requisites: contactFromDB.requisites,
|
||||||
|
code: contactFromDB.code,
|
||||||
|
sms: contactFromDB.sms
|
||||||
|
} : null,
|
||||||
|
status: "draft",
|
||||||
|
current_step: 1,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
documents: {},
|
||||||
|
email: contactFromDB?.email || null,
|
||||||
|
bank_name: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 7. Возвращаем результат в формате items ---
|
||||||
|
const result = {
|
||||||
|
json: {
|
||||||
|
session: session_id,
|
||||||
|
session_id,
|
||||||
|
unified_id,
|
||||||
|
contact_id: contactData.contact_id,
|
||||||
|
is_new_contact: contactData.is_new || contactData.is_new_contact || false,
|
||||||
|
phone,
|
||||||
|
// ✅ Флаги подтверждения данных контакта (из cf_2624)
|
||||||
|
cf_2624: cf_2624,
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: contact_data_can_edit,
|
||||||
|
redis_key: `session:${session_id}`,
|
||||||
|
redis_value: JSON.stringify(sessionData),
|
||||||
|
ttl: 604800
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Логируем финальный ответ для отладки
|
||||||
|
console.log('✅ Сформирован ответ для фронтенда:', {
|
||||||
|
session_id: result.json.session_id,
|
||||||
|
has_unified_id: !!result.json.unified_id,
|
||||||
|
has_contact_id: !!result.json.contact_id,
|
||||||
|
contact_data_confirmed: result.json.contact_data_confirmed,
|
||||||
|
cf_2624: result.json.cf_2624,
|
||||||
|
is_new_contact: result.json.is_new_contact
|
||||||
|
});
|
||||||
|
|
||||||
|
return [result];
|
||||||
|
|
||||||
51
ticket_form/docs/N8N_CODE_SET_CONTACT_DATA_CONFIRMED.js
Normal file
51
ticket_form/docs/N8N_CODE_SET_CONTACT_DATA_CONFIRMED.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// Code Node для n8n: Установка флага подтверждения данных
|
||||||
|
// ============================================================================
|
||||||
|
// Назначение: Установить флаг contact_data_confirmed_at после подтверждения формы
|
||||||
|
//
|
||||||
|
// Использование: После успешного сохранения данных в CRM через claim_confirmed
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Получаем unified_id
|
||||||
|
const unified_id = $('user_get').first().json.unified_id ||
|
||||||
|
$json.unified_id;
|
||||||
|
|
||||||
|
if (!unified_id) {
|
||||||
|
throw new Error('unified_id не найден для установки флага подтверждения');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем contact_id из CRM (если есть)
|
||||||
|
const contact_id = $node['CreateWebContacКлиентправ']?.json?.result?.contact_id ||
|
||||||
|
$json.contact_id ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
// Проверяем, есть ли данные в CRM (для автоматического подтверждения)
|
||||||
|
// Если contact_id > 0, значит данные уже есть в CRM - подтверждаем автоматически
|
||||||
|
const has_crm_data = contact_id && parseInt(contact_id) > 0;
|
||||||
|
|
||||||
|
// Формируем данные для PostgreSQL
|
||||||
|
return {
|
||||||
|
unified_id: unified_id,
|
||||||
|
contact_id: contact_id,
|
||||||
|
has_crm_data: has_crm_data,
|
||||||
|
// Флаг для SQL функции
|
||||||
|
should_confirm: true, // Всегда подтверждаем после сохранения формы
|
||||||
|
confirmed_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SQL запрос для PostgreSQL ноды (после этого Code Node):
|
||||||
|
// ============================================================================
|
||||||
|
// SELECT clpr_set_contact_data_confirmed($1, $2::timestamptz);
|
||||||
|
//
|
||||||
|
// Параметры:
|
||||||
|
// $1 = {{ $json.unified_id }}
|
||||||
|
// $2 = {{ $json.confirmed_at }}
|
||||||
|
//
|
||||||
|
// ИЛИ для автоматического подтверждения существующих данных:
|
||||||
|
// SELECT clpr_auto_confirm_if_crm_has_data($1, $2::integer);
|
||||||
|
//
|
||||||
|
// Параметры:
|
||||||
|
// $1 = {{ $json.unified_id }}
|
||||||
|
// $2 = {{ $json.contact_id }}
|
||||||
|
|
||||||
73
ticket_form/docs/N8N_MYSQL_GET_CONTACT_DATA.md
Normal file
73
ticket_form/docs/N8N_MYSQL_GET_CONTACT_DATA.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Получение данных контакта из MySQL в n8n
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
В n8n workflow нужно получить полные данные контакта из MySQL БД vtiger CRM перед формированием финального ответа.
|
||||||
|
|
||||||
|
## SQL запрос
|
||||||
|
|
||||||
|
**Файл:** `ticket_form/docs/N8N_POSTGRESQL_GET_CONTACT_DATA.sql` (название файла устарело, но запрос для MySQL)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
cd.contactid,
|
||||||
|
cd.firstname,
|
||||||
|
cd.lastname,
|
||||||
|
cd.email,
|
||||||
|
cd.mobile,
|
||||||
|
cd.phone,
|
||||||
|
cs.birthday,
|
||||||
|
ca.mailingstreet,
|
||||||
|
ca.mailingcity,
|
||||||
|
ca.mailingstate,
|
||||||
|
ca.mailingzip,
|
||||||
|
ca.mailingcountry,
|
||||||
|
ccf.cf_1157 AS middle_name,
|
||||||
|
ccf.cf_1263 AS birthplace,
|
||||||
|
ccf.cf_1257 AS inn,
|
||||||
|
ccf.cf_1849 AS requisites,
|
||||||
|
ccf.cf_1580 AS code,
|
||||||
|
ccf.cf_1706 AS sms,
|
||||||
|
ccf.cf_2624 AS cf_2624
|
||||||
|
FROM vtiger_contactdetails cd
|
||||||
|
LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_contactsubdetails cs ON cs.contactsubscriptionid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_contactaddress ca ON ca.contactaddressid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
|
||||||
|
WHERE cd.contactid = ?
|
||||||
|
AND ce.deleted = 0
|
||||||
|
LIMIT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка ноды MySQL в n8n
|
||||||
|
|
||||||
|
1. **Тип ноды:** MySQL
|
||||||
|
2. **Operation:** Execute Query
|
||||||
|
3. **Query:** (см. выше)
|
||||||
|
4. **Parameters:**
|
||||||
|
- `?` = `{{ JSON.parse($node["CreateWebContacКлиентправ"].json.result).contact_id }}`
|
||||||
|
|
||||||
|
## Credentials для MySQL
|
||||||
|
|
||||||
|
- **Host:** `localhost`
|
||||||
|
- **Port:** `3306`
|
||||||
|
- **Database:** `ci20465_72new`
|
||||||
|
- **User:** `ci20465_72new`
|
||||||
|
- **Password:** `EcY979Rn`
|
||||||
|
|
||||||
|
## Использование в Code node
|
||||||
|
|
||||||
|
После выполнения MySQL запроса, данные доступны в Code node:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pgContactNode = $('MySQL: Get Contact Data')?.first();
|
||||||
|
if (pgContactNode && pgContactNode.json && pgContactNode.json.length > 0) {
|
||||||
|
const contactFromDb = pgContactNode.json[0];
|
||||||
|
// Используем contactFromDb.cf_2624, contactFromDb.firstname, и т.д.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Примечание:** Название файла `N8N_POSTGRESQL_GET_CONTACT_DATA.sql` устарело, но запрос работает для MySQL.
|
||||||
|
|
||||||
62
ticket_form/docs/N8N_SET_CF_2624_CONTACT_CONFIRMED.md
Normal file
62
ticket_form/docs/N8N_SET_CF_2624_CONTACT_CONFIRMED.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Установка поля cf_2624 "Данные подтверждены" в n8n workflow
|
||||||
|
|
||||||
|
## Обновление workflow 6mxRJ2LLHmQXyaDz
|
||||||
|
|
||||||
|
### После подтверждения формы (после SMS-верификации)
|
||||||
|
|
||||||
|
**Добавить ноду:** `HTTP Request: Set Contact Data Confirmed`
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- **Method:** POST
|
||||||
|
- **URL:** `{{ $env.CRM_WEBSERVICE_URL }}` (или полный URL CRM webservice)
|
||||||
|
- **Body Type:** form-data
|
||||||
|
|
||||||
|
**Body (form-data):**
|
||||||
|
```
|
||||||
|
operation: revise
|
||||||
|
sessionName: {{ $('Login to CRM').json.sessionName }}
|
||||||
|
id: 12x{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}
|
||||||
|
cf_2624: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Где:**
|
||||||
|
- `cf_2624` - поле "Данные подтверждены" в CRM
|
||||||
|
- `1` = "Да" (данные подтверждены)
|
||||||
|
- `0` = "Нет" (данные не подтверждены)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Альтернативный вариант: через Code Node
|
||||||
|
|
||||||
|
Если нужно более гибкое управление, можно использовать Code Node:
|
||||||
|
|
||||||
|
**Название:** `Code: Set Contact Data Confirmed`
|
||||||
|
|
||||||
|
**Код:**
|
||||||
|
```javascript
|
||||||
|
// Получаем contact_id из CreateWebContact
|
||||||
|
const contactResult = JSON.parse($node['CreateWebContacКлиентправ'].json.result);
|
||||||
|
const contact_id = contactResult.contact_id;
|
||||||
|
|
||||||
|
// Получаем sessionName из Login to CRM
|
||||||
|
const sessionName = $('Login to CRM').json.sessionName;
|
||||||
|
|
||||||
|
// Формируем данные для обновления
|
||||||
|
return {
|
||||||
|
operation: 'revise',
|
||||||
|
sessionName: sessionName,
|
||||||
|
id: `12x${contact_id}`,
|
||||||
|
cf_2624: '1' // Устанавливаем "Да" (данные подтверждены)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем подключить к **HTTP Request** ноде, которая отправит эти данные в CRM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка работы:
|
||||||
|
|
||||||
|
1. После SMS-верификации и подтверждения формы
|
||||||
|
2. Проверить в CRM, что у контакта поле `cf_2624` = "Да"
|
||||||
|
3. При следующей загрузке черновика поля должны быть заблокированы
|
||||||
|
|
||||||
146
ticket_form/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md
Normal file
146
ticket_form/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# Обновление n8n workflow: Использование cf_2624 из CreateWebContact
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
При формировании заявления проверять значение `cf_2624` из ответа `CreateWebContact`:
|
||||||
|
- Если `cf_2624 = "0"` → данные можно редактировать
|
||||||
|
- Если `cf_2624 = "1"` → данные только для просмотра (readonly)
|
||||||
|
|
||||||
|
## Изменения в workflow 6mxRJ2LLHmQXyaDz
|
||||||
|
|
||||||
|
### 1. После ноды `CreateWebContacКлиентправ`
|
||||||
|
|
||||||
|
**Название ноды:** `Code: Extract Contact Data Confirmed`
|
||||||
|
|
||||||
|
**Код:**
|
||||||
|
```javascript
|
||||||
|
// Парсим результат CreateWebContact
|
||||||
|
const rawResult = $node["CreateWebContacКлиентправ"].json.result;
|
||||||
|
const contactData = JSON.parse(rawResult);
|
||||||
|
|
||||||
|
// Извлекаем cf_2624 (Данные подтверждены)
|
||||||
|
// "1" = данные подтверждены, "0" = не подтверждены
|
||||||
|
const cf_2624 = contactData.cf_2624 || "0";
|
||||||
|
const contact_data_confirmed = cf_2624 === "1";
|
||||||
|
|
||||||
|
console.log('🔒 Статус данных контакта:', {
|
||||||
|
contact_id: contactData.contact_id,
|
||||||
|
is_new: contactData.is_new,
|
||||||
|
cf_2624: cf_2624,
|
||||||
|
contact_data_confirmed: contact_data_confirmed
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
contact_id: contactData.contact_id,
|
||||||
|
is_new_contact: contactData.is_new,
|
||||||
|
cf_2624: cf_2624,
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: !contact_data_confirmed
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. В ноде `Code in JavaScriptКлиентправ` (формирование ответа для фронтенда)
|
||||||
|
|
||||||
|
**Добавить в return:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Получаем данные о подтверждении из предыдущей ноды
|
||||||
|
const contactStatus = $('Code: Extract Contact Data Confirmed').first().json;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// ... существующие поля ...
|
||||||
|
session: session_id,
|
||||||
|
session_id: session_id,
|
||||||
|
unified_id: unified_id,
|
||||||
|
contact_id: contactStatus.contact_id,
|
||||||
|
is_new_contact: contactStatus.is_new_contact,
|
||||||
|
|
||||||
|
// ✅ Флаги подтверждения данных контакта (из cf_2624)
|
||||||
|
contact_data_confirmed: contactStatus.contact_data_confirmed || false,
|
||||||
|
contact_data_can_edit: contactStatus.contact_data_can_edit !== false,
|
||||||
|
cf_2624: contactStatus.cf_2624 || "0",
|
||||||
|
|
||||||
|
// ... остальные поля ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. При загрузке черновика (если используется отдельный workflow)
|
||||||
|
|
||||||
|
**Если есть нода для загрузки черновика:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Получаем contact_id из черновика
|
||||||
|
const contact_id = $json.contact_id || $json.payload?.contact_id;
|
||||||
|
|
||||||
|
if (contact_id) {
|
||||||
|
// Вызываем CreateWebContact для получения cf_2624
|
||||||
|
// (или используем retrieve из CRM)
|
||||||
|
|
||||||
|
// Для простоты можно использовать retrieve:
|
||||||
|
const retrieveResult = await $http.post('{{ $env.CRM_WEBSERVICE_URL }}', {
|
||||||
|
operation: 'retrieve',
|
||||||
|
sessionName: $('Login to CRM').json.sessionName,
|
||||||
|
id: `12x${contact_id}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const cf_2624 = retrieveResult.result?.cf_2624 || "0";
|
||||||
|
const contact_data_confirmed = cf_2624 === "1";
|
||||||
|
|
||||||
|
return {
|
||||||
|
// ... данные черновика ...
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: !contact_data_confirmed,
|
||||||
|
cf_2624: cf_2624
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Логика работы
|
||||||
|
|
||||||
|
1. **При создании/поиске контакта:**
|
||||||
|
- `CreateWebContact` возвращает `cf_2624` в ответе
|
||||||
|
- Извлекаем значение и передаём в ответе для фронтенда
|
||||||
|
|
||||||
|
2. **При загрузке черновика:**
|
||||||
|
- Backend API `/drafts/{claim_id}` уже получает `cf_2624` из CRM
|
||||||
|
- Фронтенд получает `contact_data_confirmed` из ответа API
|
||||||
|
- Передаёт в `StepClaimConfirmation` → `generateConfirmationFormHTML`
|
||||||
|
|
||||||
|
3. **При формировании заявления:**
|
||||||
|
- Если `cf_2624 = "1"` → поля персональных данных блокируются (readonly)
|
||||||
|
- Если `cf_2624 = "0"` → поля можно редактировать
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
1. ✅ `CreateWebContact` возвращает `cf_2624` в ответе
|
||||||
|
2. ⏳ n8n workflow извлекает `cf_2624` и передаёт в ответе
|
||||||
|
3. ⏳ Фронтенд получает `contact_data_confirmed` и блокирует поля
|
||||||
|
4. ⏳ Backend API `/drafts/{claim_id}` получает `cf_2624` из CRM
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пример ответа от n8n:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"result": {
|
||||||
|
"session": "sess_...",
|
||||||
|
"contact_id": "399542",
|
||||||
|
"unified_id": "usr_...",
|
||||||
|
"contact_data_confirmed": true,
|
||||||
|
"contact_data_can_edit": false,
|
||||||
|
"cf_2624": "1",
|
||||||
|
"is_new_contact": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
135
ticket_form/docs/N8N_WORKFLOW_6mxRJ2LLHmQXyaDz_CHANGES.md
Normal file
135
ticket_form/docs/N8N_WORKFLOW_6mxRJ2LLHmQXyaDz_CHANGES.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# Конкретные изменения в workflow 6mxRJ2LLHmQXyaDz
|
||||||
|
|
||||||
|
## Что менять:
|
||||||
|
|
||||||
|
### 1. После ноды `user_get` → добавить PostgreSQL ноду (ПЕРВАЯ)
|
||||||
|
|
||||||
|
**Название ноды:** `PostgreSQL: Auto Confirm Contact Data`
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- **Operation:** Execute Query
|
||||||
|
- **Query:**
|
||||||
|
```sql
|
||||||
|
SELECT clpr_auto_confirm_if_crm_has_data($1, $2::integer);
|
||||||
|
```
|
||||||
|
- **Parameters:**
|
||||||
|
- `$1` = `{{ $json.unified_id }}` ← используем данные из предыдущей ноды (user_get)
|
||||||
|
- `$2` = `{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}`
|
||||||
|
|
||||||
|
**Подключение:**
|
||||||
|
- `user_get` → `PostgreSQL: Auto Confirm Contact Data` → `Execute a SQL query2`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. После ноды `PostgreSQL: Auto Confirm Contact Data` → добавить PostgreSQL ноду (ВТОРАЯ)
|
||||||
|
|
||||||
|
**Название ноды:** `PostgreSQL: Check Contact Data Status`
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- **Operation:** Execute Query
|
||||||
|
- **Query:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM clpr_get_contact_data_status($1);
|
||||||
|
```
|
||||||
|
- **Parameters:**
|
||||||
|
- `$1` = `{{ $json.unified_id }}` ← unified_id передаётся дальше по цепочке
|
||||||
|
|
||||||
|
**Подключение:**
|
||||||
|
- `PostgreSQL: Auto Confirm Contact Data` → `PostgreSQL: Check Contact Data Status` → `Execute a SQL query2`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. В ноде `Code in JavaScript` (та что перед `Respond to Webhook1`) → добавить флаг в ответ
|
||||||
|
|
||||||
|
**Найти эту строку:**
|
||||||
|
```javascript
|
||||||
|
// Unified ID из PostgreSQL (обязательно!)
|
||||||
|
unified_id: userData.unified_id, // из ноды user_get (PostgreSQL: Find or Create User)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Добавить ПОСЛЕ неё:**
|
||||||
|
```javascript
|
||||||
|
// Флаг подтверждения данных контакта
|
||||||
|
contact_data_confirmed: $('PostgreSQL: Check Contact Data Status').first().json.is_confirmed || false,
|
||||||
|
contact_data_can_edit: $('PostgreSQL: Check Contact Data Status').first().json.can_edit !== false,
|
||||||
|
contact_data_confirmed_at: $('PostgreSQL: Check Contact Data Status').first().json.confirmed_at || null,
|
||||||
|
```
|
||||||
|
|
||||||
|
**Полный return должен быть:**
|
||||||
|
```javascript
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
result: {
|
||||||
|
session: $('Code in JavaScript3').first().json.session_id,
|
||||||
|
contact_id: sessionData.contact_id || claimResult.contact_id,
|
||||||
|
project_id: sessionData.project_id,
|
||||||
|
|
||||||
|
// Unified ID из PostgreSQL (обязательно!)
|
||||||
|
unified_id: userData.unified_id,
|
||||||
|
|
||||||
|
// Флаг подтверждения данных контакта
|
||||||
|
contact_data_confirmed: $('PostgreSQL: Check Contact Data Status').first().json.is_confirmed || false,
|
||||||
|
contact_data_can_edit: $('PostgreSQL: Check Contact Data Status').first().json.can_edit !== false,
|
||||||
|
contact_data_confirmed_at: $('PostgreSQL: Check Contact Data Status').first().json.confirmed_at || null,
|
||||||
|
|
||||||
|
// Данные заявки
|
||||||
|
ticket_id: claimResult.ticket_id,
|
||||||
|
ticket_number: claimResult.ticket_number,
|
||||||
|
title: claimResult.title,
|
||||||
|
category: claimResult.category,
|
||||||
|
status: claimResult.status,
|
||||||
|
|
||||||
|
// Метаданные
|
||||||
|
event_type: sessionData.event_type,
|
||||||
|
current_step: sessionData.current_step || 1,
|
||||||
|
updated_at: sessionData.updated_at || new Date().toISOString(),
|
||||||
|
|
||||||
|
// Дополнительно
|
||||||
|
is_new_contact: claimResult.is_new_contact || false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Итого: 3 изменения
|
||||||
|
|
||||||
|
1. ✅ Добавить ноду `PostgreSQL: Auto Confirm Contact Data` после `CreateWebContacКлиентправ`
|
||||||
|
2. ✅ Добавить ноду `PostgreSQL: Check Contact Data Status` после `user_get`
|
||||||
|
3. ✅ Добавить 3 строки в `Code in JavaScript` перед `Respond to Webhook1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок выполнения в workflow:
|
||||||
|
|
||||||
|
```
|
||||||
|
contact → Edit Fields → Get Challenge → ... → Login to CRM → form_id
|
||||||
|
↓
|
||||||
|
CreateWebContacКлиентправ
|
||||||
|
↓
|
||||||
|
[НОВАЯ] PostgreSQL: Auto Confirm Contact Data
|
||||||
|
↓
|
||||||
|
Code in JavaScriptКлиентправ
|
||||||
|
↓
|
||||||
|
user_get
|
||||||
|
↓
|
||||||
|
[НОВАЯ] PostgreSQL: Check Contact Data Status
|
||||||
|
↓
|
||||||
|
Execute a SQL query2
|
||||||
|
↓
|
||||||
|
...
|
||||||
|
↓
|
||||||
|
Code in JavaScript (← ДОБАВИТЬ ФЛАГИ)
|
||||||
|
↓
|
||||||
|
Respond to Webhook1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка:
|
||||||
|
|
||||||
|
После изменений в ответе n8n должны быть поля:
|
||||||
|
- `contact_data_confirmed` (true/false)
|
||||||
|
- `contact_data_can_edit` (true/false)
|
||||||
|
- `contact_data_confirmed_at` (дата или null)
|
||||||
|
|
||||||
99
ticket_form/docs/N8N_WORKFLOW_ADD_POSTGRESQL_CONTACT.md
Normal file
99
ticket_form/docs/N8N_WORKFLOW_ADD_POSTGRESQL_CONTACT.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Добавление ноды PostgreSQL для получения данных контакта
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Добавить ноду PostgreSQL перед "Code in JavaScriptКлиентправ" для получения полных данных контакта из CRM.
|
||||||
|
|
||||||
|
## Шаги
|
||||||
|
|
||||||
|
### 1. Добавить ноду PostgreSQL
|
||||||
|
|
||||||
|
**Название ноды:** `PostgreSQL: Get Contact Data`
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- **Operation:** Execute Query
|
||||||
|
- **Query:** (см. файл `N8N_POSTGRESQL_GET_CONTACT_DATA.sql`)
|
||||||
|
|
||||||
|
**SQL запрос:**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
cd.contactid,
|
||||||
|
cd.firstname,
|
||||||
|
cd.lastname,
|
||||||
|
cd.email,
|
||||||
|
cd.mobile,
|
||||||
|
cd.phone,
|
||||||
|
cs.birthday,
|
||||||
|
ca.mailingstreet,
|
||||||
|
ca.mailingcity,
|
||||||
|
ca.mailingstate,
|
||||||
|
ca.mailingzip,
|
||||||
|
ca.mailingcountry,
|
||||||
|
ccf.cf_1157 AS middle_name,
|
||||||
|
ccf.cf_1263 AS birthplace,
|
||||||
|
ccf.cf_1257 AS inn,
|
||||||
|
ccf.cf_1849 AS requisites,
|
||||||
|
ccf.cf_1580 AS code,
|
||||||
|
ccf.cf_1706 AS sms,
|
||||||
|
ccf.cf_2624 AS cf_2624
|
||||||
|
FROM vtiger_contactdetails cd
|
||||||
|
LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_contactsubdetails cs ON cs.contactsubscriptionid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_contactaddress ca ON ca.contactaddressid = cd.contactid
|
||||||
|
LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
|
||||||
|
WHERE cd.contactid = $1
|
||||||
|
AND ce.deleted = 0
|
||||||
|
LIMIT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры запроса:**
|
||||||
|
- `$1` = `{{ JSON.parse($node["CreateWebContacКлиентправ"].json.result).contact_id }}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Порядок нод в workflow
|
||||||
|
|
||||||
|
1. **CreateWebContacКлиентправ** → создаёт/находит контакт
|
||||||
|
2. **PostgreSQL: Get Contact Data** → получает полные данные контакта
|
||||||
|
3. **Code in JavaScriptКлиентправ** → использует данные из обеих нод
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Что получает Code node
|
||||||
|
|
||||||
|
После добавления ноды PostgreSQL, Code node получит доступ к:
|
||||||
|
- `$('PostgreSQL: Get Contact Data').first().json` - полные данные контакта
|
||||||
|
|
||||||
|
**Доступные поля:**
|
||||||
|
- `contactid` - ID контакта
|
||||||
|
- `firstname`, `lastname` - ФИО
|
||||||
|
- `email`, `mobile`, `phone` - Контакты
|
||||||
|
- `birthday` - Дата рождения
|
||||||
|
- `mailingstreet`, `mailingcity`, etc. - Адрес
|
||||||
|
- `middle_name` (cf_1157) - Отчество
|
||||||
|
- `birthplace` (cf_1263) - Место рождения
|
||||||
|
- `inn` (cf_1257) - ИНН
|
||||||
|
- `requisites` (cf_1849) - Реквизиты
|
||||||
|
- `code` (cf_1580) - Код
|
||||||
|
- `sms` (cf_1706) - SMS
|
||||||
|
- `cf_2624` - Данные подтверждены
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Использование в Code node
|
||||||
|
|
||||||
|
Код в "Code in JavaScriptКлиентправ" автоматически найдёт данные из PostgreSQL ноды и добавит их в `sessionData.contact_from_db`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Альтернатива: если нет доступа к PostgreSQL
|
||||||
|
|
||||||
|
Если нет прямого доступа к PostgreSQL, можно использовать HTTP Request к backend API:
|
||||||
|
|
||||||
|
**Название ноды:** `HTTP Request: Get Contact Data`
|
||||||
|
|
||||||
|
**Метод:** GET
|
||||||
|
**URL:** `{{ $env.BACKEND_URL }}/api/v1/contacts/{{ JSON.parse($node["CreateWebContacКлиентправ"].json.result).contact_id }}`
|
||||||
|
|
||||||
|
Но лучше использовать PostgreSQL напрямую для скорости.
|
||||||
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Обновление workflow 6mxRJ2LLHmQXyaDz: Подтверждение данных контакта
|
||||||
|
|
||||||
|
## Изменения в workflow
|
||||||
|
|
||||||
|
### 1. После ноды `CreateWebContacКлиентправ`
|
||||||
|
|
||||||
|
**Добавить ноду:** `PostgreSQL: Auto Confirm if CRM has data`
|
||||||
|
|
||||||
|
**SQL запрос:**
|
||||||
|
```sql
|
||||||
|
SELECT clpr_auto_confirm_if_crm_has_data($1, $2::integer);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `$1` = `{{ $('user_get').first().json.unified_id }}`
|
||||||
|
- `$2` = `{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}`
|
||||||
|
|
||||||
|
**Назначение:** Если данные уже есть в CRM (contact_id > 0), автоматически ставим флаг подтверждения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. После ноды `Code in JavaScriptКлиентправ`
|
||||||
|
|
||||||
|
**Добавить ноду:** `PostgreSQL: Check Contact Data Status`
|
||||||
|
|
||||||
|
**SQL запрос:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM clpr_get_contact_data_status($1);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `$1` = `{{ $('user_get').first().json.unified_id }}`
|
||||||
|
|
||||||
|
**Назначение:** Проверяем, подтверждены ли данные. Результат передаём дальше.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. В ответе для фронтенда (нода `Code in JavaScript`)
|
||||||
|
|
||||||
|
**Добавить в return:**
|
||||||
|
```javascript
|
||||||
|
const contactStatus = $('PostgreSQL: Check Contact Data Status').first().json;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// ... существующие поля ...
|
||||||
|
contact_data_confirmed: contactStatus.is_confirmed || false,
|
||||||
|
contact_data_can_edit: contactStatus.can_edit !== false,
|
||||||
|
contact_data_confirmed_at: contactStatus.confirmed_at || null
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. После подтверждения формы (workflow для `claim_confirmed`)
|
||||||
|
|
||||||
|
**Добавить ноду:** `PostgreSQL: Set Contact Data Confirmed`
|
||||||
|
|
||||||
|
**SQL запрос:**
|
||||||
|
```sql
|
||||||
|
SELECT clpr_set_contact_data_confirmed($1, NOW());
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `$1` = `{{ $json.unified_id }}`
|
||||||
|
|
||||||
|
**Назначение:** Устанавливаем флаг подтверждения после успешного сохранения данных.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок выполнения
|
||||||
|
|
||||||
|
1. **Создание контакта** → `CreateWebContacКлиентправ`
|
||||||
|
2. **Автоподтверждение** → Если данные есть в CRM → `clpr_auto_confirm_if_crm_has_data`
|
||||||
|
3. **Проверка статуса** → `clpr_get_contact_data_status` → передаём фронтенду
|
||||||
|
4. **Фронтенд** → Если `contact_data_confirmed = true` → блокируем редактирование
|
||||||
|
5. **После подтверждения** → `clpr_set_contact_data_confirmed` → устанавливаем флаг
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка в n8n
|
||||||
|
|
||||||
|
После обновления workflow проверить:
|
||||||
|
- ✅ Флаг устанавливается при наличии данных в CRM
|
||||||
|
- ✅ Флаг устанавливается после подтверждения формы
|
||||||
|
- ✅ Статус передаётся фронтенду
|
||||||
|
- ✅ Фронтенд блокирует редактирование при `contact_data_confirmed = true`
|
||||||
|
|
||||||
@@ -86,8 +86,14 @@ export default function StepClaimConfirmation({
|
|||||||
console.log('📋 formData.propertyName:', formData.propertyName);
|
console.log('📋 formData.propertyName:', formData.propertyName);
|
||||||
console.log('📋 formData.propertyName?.meta:', formData.propertyName?.meta);
|
console.log('📋 formData.propertyName?.meta:', formData.propertyName?.meta);
|
||||||
|
|
||||||
|
// ✅ Получаем флаги подтверждения данных из claimPlanData или formData
|
||||||
|
const contact_data_confirmed =
|
||||||
|
claimPlanData?.contact_data_confirmed ||
|
||||||
|
claimPlanData?.propertyName?.meta?.contact_data_confirmed ||
|
||||||
|
false;
|
||||||
|
|
||||||
// Генерируем HTML форму здесь, на нашей стороне
|
// Генерируем HTML форму здесь, на нашей стороне
|
||||||
const html = generateConfirmationFormHTML(formData);
|
const html = generateConfirmationFormHTML(formData, contact_data_confirmed);
|
||||||
setHtmlContent(html);
|
setHtmlContent(html);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, [claimPlanData]);
|
}, [claimPlanData]);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Функция генерации HTML формы подтверждения заявления
|
// Функция генерации HTML формы подтверждения заявления
|
||||||
// Основана на структуре из n8n Code node "Mini-app Подтверждение данных"
|
// Основана на структуре из n8n Code node "Mini-app Подтверждение данных"
|
||||||
|
|
||||||
export function generateConfirmationFormHTML(data: any): string {
|
export function generateConfirmationFormHTML(data: any, contact_data_confirmed: boolean = false): string {
|
||||||
// Извлекаем SMS данные (до нормализации, так как структура может быть разной)
|
// Извлекаем SMS данные (до нормализации, так как структура может быть разной)
|
||||||
const smsInputData = {
|
const smsInputData = {
|
||||||
prefix: data.sms_meta?.prefix || data.prefix || '',
|
prefix: data.sms_meta?.prefix || data.prefix || '',
|
||||||
@@ -290,6 +290,7 @@ export function generateConfirmationFormHTML(data: any): string {
|
|||||||
telegram_id: telegramId,
|
telegram_id: telegramId,
|
||||||
token: data.token || '',
|
token: data.token || '',
|
||||||
sms_meta: smsMetaData,
|
sms_meta: smsMetaData,
|
||||||
|
contact_data_confirmed: contact_data_confirmed || false, // ✅ Флаг подтверждения данных контакта
|
||||||
});
|
});
|
||||||
caseJson = caseJson.replace(/</g, '\\u003c');
|
caseJson = caseJson.replace(/</g, '\\u003c');
|
||||||
|
|
||||||
@@ -719,6 +720,26 @@ export function generateConfirmationFormHTML(data: any): string {
|
|||||||
var dataIndex = index !== undefined ? ' data-index="' + index + '"' : '';
|
var dataIndex = index !== undefined ? ' data-index="' + index + '"' : '';
|
||||||
var extra = '';
|
var extra = '';
|
||||||
|
|
||||||
|
// ✅ Проверяем, нужно ли блокировать поле (для подтверждённых данных applicant)
|
||||||
|
var isLockedField = contact_data_confirmed && root === 'user' && (
|
||||||
|
key === 'firstname' ||
|
||||||
|
key === 'lastname' ||
|
||||||
|
key === 'middle_name' ||
|
||||||
|
key === 'secondname' || // Отчество (может быть в разных форматах)
|
||||||
|
key === 'inn' ||
|
||||||
|
key === 'birthday' ||
|
||||||
|
key === 'birth_place' ||
|
||||||
|
key === 'birthplace' ||
|
||||||
|
key === 'address' ||
|
||||||
|
key === 'mailingstreet' ||
|
||||||
|
key === 'email'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLockedField) {
|
||||||
|
// Блокируем поле - используем readonly
|
||||||
|
return createReadonlyField(root, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
if (key === 'inn') {
|
if (key === 'inn') {
|
||||||
var isIndividual = (root === 'user');
|
var isIndividual = (root === 'user');
|
||||||
if (isIndividual) {
|
if (isIndividual) {
|
||||||
@@ -755,6 +776,13 @@ export function generateConfirmationFormHTML(data: any): string {
|
|||||||
dateValue = parts[2] + '-' + parts[1] + '-' + parts[0];
|
dateValue = parts[2] + '-' + parts[1] + '-' + parts[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Проверяем, нужно ли блокировать поле (для подтверждённых данных)
|
||||||
|
var isLockedField = contact_data_confirmed && root === 'user' && key === 'birthday';
|
||||||
|
if (isLockedField) {
|
||||||
|
return '<input type="date" class="inline-field readonly-field date-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" value="' + esc(dateValue) + '" readonly />';
|
||||||
|
}
|
||||||
|
|
||||||
return '<input type="date" class="inline-field bind date-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" value="' + esc(dateValue) + '" />';
|
return '<input type="date" class="inline-field bind date-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" value="' + esc(dateValue) + '" />';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,6 +836,9 @@ export function generateConfirmationFormHTML(data: any): string {
|
|||||||
console.log('injected.case:', injected.case);
|
console.log('injected.case:', injected.case);
|
||||||
console.log('injected.propertyName:', injected.propertyName);
|
console.log('injected.propertyName:', injected.propertyName);
|
||||||
|
|
||||||
|
// ✅ Извлекаем флаг подтверждения данных из injected
|
||||||
|
var contact_data_confirmed = injected.contact_data_confirmed || false;
|
||||||
|
|
||||||
// Достаём объект кейса из «типичных» мест
|
// Достаём объект кейса из «типичных» мест
|
||||||
var dataCandidate = null;
|
var dataCandidate = null;
|
||||||
|
|
||||||
@@ -872,6 +903,17 @@ export function generateConfirmationFormHTML(data: any): string {
|
|||||||
|
|
||||||
var html = '';
|
var html = '';
|
||||||
|
|
||||||
|
// ✅ Предупреждение о заблокированных данных (если данные подтверждены)
|
||||||
|
if (contact_data_confirmed) {
|
||||||
|
html += '<div style="margin-bottom: 16px; padding: 12px; background: #fff7e6; border: 1px solid #ffd591; border-radius: 4px;">';
|
||||||
|
html += '<p style="margin: 0; color: #ad6800; font-size: 14px;">';
|
||||||
|
html += '<strong>⚠️ Данные подтверждены</strong><br>';
|
||||||
|
html += 'Ваши персональные данные (ФИО, ИНН, дата рождения, адрес) заблокированы для редактирования. ';
|
||||||
|
html += 'Для изменения данных обратитесь в поддержку.';
|
||||||
|
html += '</p>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
html += '<div style="text-align:center;margin-bottom:32px">';
|
html += '<div style="text-align:center;margin-bottom:32px">';
|
||||||
html += '<h2 style="font-size:20px;margin:0 0 16px;color:#1f2937">В МОО «Клиентправ»</h2>';
|
html += '<h2 style="font-size:20px;margin:0 0 16px;color:#1f2937">В МОО «Клиентправ»</h2>';
|
||||||
html += '<p style="margin:0;color:#6b7280">help@clientright.ru</p>';
|
html += '<p style="margin:0;color:#6b7280">help@clientright.ru</p>';
|
||||||
|
|||||||
@@ -560,6 +560,19 @@ export default function ClaimForm() {
|
|||||||
const claim = data.claim;
|
const claim = data.claim;
|
||||||
const payload = claim.payload || {};
|
const payload = claim.payload || {};
|
||||||
|
|
||||||
|
// ✅ Сохраняем флаги подтверждения данных контакта
|
||||||
|
const contact_data_confirmed = data.contact_data_confirmed || false;
|
||||||
|
const contact_data_can_edit = data.contact_data_can_edit !== false; // По умолчанию true
|
||||||
|
const contact_data_confirmed_at = data.contact_data_confirmed_at || null;
|
||||||
|
const contact_data_from_crm = data.contact_data_from_crm || null;
|
||||||
|
|
||||||
|
console.log('🔒 Статус данных контакта:', {
|
||||||
|
contact_data_confirmed,
|
||||||
|
contact_data_can_edit,
|
||||||
|
contact_data_confirmed_at,
|
||||||
|
has_crm_data: !!contact_data_from_crm
|
||||||
|
});
|
||||||
|
|
||||||
// ✅ Для telegram черновиков данные могут быть в payload.body
|
// ✅ Для telegram черновиков данные могут быть в payload.body
|
||||||
const body = payload.body || {};
|
const body = payload.body || {};
|
||||||
const isTelegramFormat = !!payload.body;
|
const isTelegramFormat = !!payload.body;
|
||||||
@@ -806,10 +819,33 @@ export default function ClaimForm() {
|
|||||||
|
|
||||||
console.log('✅ claimPlanData для формы подтверждения:', claimPlanData);
|
console.log('✅ claimPlanData для формы подтверждения:', claimPlanData);
|
||||||
|
|
||||||
|
// ✅ Если данные подтверждены и есть данные из CRM - используем их
|
||||||
|
if (contact_data_confirmed && contact_data_from_crm) {
|
||||||
|
// Обновляем applicant данные из CRM
|
||||||
|
if (claimPlanData?.propertyName?.applicant) {
|
||||||
|
claimPlanData.propertyName.applicant = {
|
||||||
|
...claimPlanData.propertyName.applicant,
|
||||||
|
first_name: contact_data_from_crm.firstname || claimPlanData.propertyName.applicant.first_name,
|
||||||
|
last_name: contact_data_from_crm.lastname || claimPlanData.propertyName.applicant.last_name,
|
||||||
|
middle_name: contact_data_from_crm.cf_1157 || claimPlanData.propertyName.applicant.middle_name,
|
||||||
|
inn: contact_data_from_crm.cf_1257 || claimPlanData.propertyName.applicant.inn,
|
||||||
|
birth_date: contact_data_from_crm.birthday || claimPlanData.propertyName.applicant.birth_date,
|
||||||
|
birth_place: contact_data_from_crm.cf_1263 || claimPlanData.propertyName.applicant.birth_place,
|
||||||
|
address: contact_data_from_crm.mailingstreet || claimPlanData.propertyName.applicant.address,
|
||||||
|
email: contact_data_from_crm.email || claimPlanData.propertyName.applicant.email,
|
||||||
|
phone: contact_data_from_crm.mobile || claimPlanData.propertyName.applicant.phone,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Сохраняем данные заявления в formData
|
// Сохраняем данные заявления в formData
|
||||||
updateFormData({
|
updateFormData({
|
||||||
claimPlanData: claimPlanData,
|
claimPlanData: claimPlanData,
|
||||||
showClaimConfirmation: true,
|
showClaimConfirmation: true,
|
||||||
|
// ✅ Флаги подтверждения данных
|
||||||
|
contact_data_confirmed: contact_data_confirmed,
|
||||||
|
contact_data_can_edit: contact_data_can_edit,
|
||||||
|
contact_data_confirmed_at: contact_data_confirmed_at,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Переход к шагу подтверждения произойдёт автоматически через useEffect
|
// Переход к шагу подтверждения произойдёт автоматически через useEffect
|
||||||
|
|||||||
Reference in New Issue
Block a user