diff --git a/backend/app/api/claims.py b/backend/app/api/claims.py index f2cf288..701e128 100644 --- a/backend/app/api/claims.py +++ b/backend/app/api/claims.py @@ -541,6 +541,92 @@ async def load_wizard_data(claim_id: str): raise HTTPException(status_code=500, detail=f"Ошибка при загрузке данных визарда: {str(e)}") +@router.post("/approve") +async def approve_claim_form(request: Request): + """ + Сохранение данных подтвержденной формы после SMS-апрува + + Принимает отредактированные данные формы подтверждения и отправляет их в n8n webhook. + """ + try: + body = await request.json() + + logger.info( + "📨 TicketForm approval received", + extra={ + "claim_id": body.get("claim_id"), + "session_id": body.get("session_id"), + }, + ) + + # Формируем payload для n8n + n8n_payload = { + "stage": "form_approve", + "form_id": "ticket_form", + "session_id": body.get("session_id"), + "claim_id": body.get("claim_id"), + "unified_id": body.get("unified_id"), + "phone": body.get("phone"), + "sms_verified": True, # Флаг что SMS код подтвержден + + # Данные формы подтверждения + "form_data": body.get("form_data", {}), + "user": body.get("user", {}), + "project": body.get("project", {}), + "offenders": body.get("offenders", []), + "meta": body.get("meta", {}), + + # Оригинальные данные для сравнения + "original_data": body.get("original_data", {}), + } + + # Проксируем запрос к n8n + async with httpx.AsyncClient(timeout=60.0) as client: + response = await client.post( + N8N_FORM_APPROVE_WEBHOOK, + json=n8n_payload, + headers={"Content-Type": "application/json"}, + ) + + text = response.text or "" + + if response.status_code == 200: + logger.info( + "✅ TicketForm approval webhook OK", + extra={"response_preview": text[:500]}, + ) + try: + return json.loads(text) + except Exception: + return { + "success": True, + "message": "Form approval processed (non-JSON response from n8n)", + "raw": text, + } + + logger.error( + "❌ TicketForm approval webhook error", + extra={ + "status_code": response.status_code, + "body": text[:500], + }, + ) + raise HTTPException( + status_code=response.status_code, + detail=f"n8n error: {text}", + ) + + except httpx.TimeoutException: + logger.error("⏱️ n8n approval webhook timeout") + raise HTTPException(status_code=504, detail="Таймаут подключения к n8n") + except Exception as e: + logger.exception("❌ Ошибка при сохранении подтвержденной формы") + raise HTTPException( + status_code=500, + detail=f"Ошибка при сохранении подтвержденной формы: {str(e)}", + ) + + @router.post("/description") async def publish_ticket_form_description(payload: TicketFormDescriptionRequest): """ diff --git a/frontend/src/components/form/StepClaimConfirmation.tsx b/frontend/src/components/form/StepClaimConfirmation.tsx index 1432299..833bd84 100644 --- a/frontend/src/components/form/StepClaimConfirmation.tsx +++ b/frontend/src/components/form/StepClaimConfirmation.tsx @@ -92,12 +92,56 @@ export default function StepClaimConfirmation({ setLoading(false); }, [claimPlanData]); - // Функция сохранения данных формы (TODO: реализовать сохранение) + // Функция сохранения данных формы - отправка в webhook без ожидания ответа const saveFormData = useCallback(async (formData: any) => { - console.log('💾 Сохраняем данные формы:', formData); - // TODO: Реализовать сохранение данных в бэкенд/n8n - // Здесь будет вызов API для сохранения отредактированных данных формы - }, []); + console.log('💾 Отправляем данные формы в webhook:', formData); + + // Получаем данные из claimPlanData для формирования payload + const claimId = claimPlanData?.claim_id || claimPlanData?.propertyName?.meta?.claim_id || ''; + const unifiedId = claimPlanData?.unified_id || claimPlanData?.propertyName?.meta?.unified_id || ''; + const sessionToken = claimPlanData?.session_token || ''; + const phone = claimPlanData?.propertyName?.applicant?.phone || + claimPlanData?.propertyName?.user?.mobile || + claimPlanData?.phone || ''; + + // Формируем payload для webhook + const payload = { + stage: 'form_approve', + form_id: 'ticket_form', + session_id: sessionToken, + session_token: sessionToken, + claim_id: claimId, + unified_id: unifiedId, + phone: phone, + sms_verified: true, // Флаг что SMS код подтвержден + + // Данные формы подтверждения + form_data: formData, + user: formData?.user || {}, + project: formData?.project || {}, + offenders: formData?.offenders || [], + meta: formData?.meta || {}, + + // Оригинальные данные для сравнения (если есть) + original_data: formData?.originalData || {}, + }; + + // Отправляем в webhook без ожидания ответа (fire-and-forget) + // Используем fetch с keepalive для надежности, но не ждем ответа + fetch('https://n8n.clientright.pro/webhook/eebe58d4-0bcd-4d09-9d62-39868b110960', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + keepalive: true, // Продолжить отправку даже если страница закрывается + }).catch((error) => { + // Тихо логируем ошибки, но не блокируем пользователя + console.error('Ошибка отправки данных формы в webhook:', error); + }); + + console.log('✅ Данные формы отправлены в webhook (fire-and-forget)'); + }, [claimPlanData]); // Функция отправки SMS-кода const sendSMSCode = useCallback(async (phone: string) => { @@ -147,13 +191,18 @@ export default function StepClaimConfirmation({ if (response.ok) { message.success('Код подтвержден!'); - // Закрываем модалку и продолжаем с сохранением данных + // Закрываем модалку setSmsModalVisible(false); setSmsCodeSent(false); smsForm.resetFields(); - // Сохраняем данные и переходим дальше - await saveFormData(pendingFormData); + // Отправляем данные в webhook без ожидания ответа + saveFormData(pendingFormData); + + // Показываем сообщение об успешной отправке + message.success('Ваше заявление отправлено!'); + + // Переходим дальше onNext(); } else { message.error(result.detail || 'Неверный код');