feat: Send form approval data to webhook without waiting for response
Simplified approach: - Removed backend endpoint /approve (will use direct webhook) - Updated saveFormData to send data directly to n8n webhook - Fire-and-forget approach: no waiting for response - Show success message 'Ваше заявление отправлено!' after SMS verification - Uses webhook URL: https://n8n.clientright.pro/webhook/eebe58d4-0bcd-4d09-9d62-39868b110960 Flow: 1. User confirms form → SMS modal appears 2. SMS code sent automatically 3. User enters code → verified 4. Data sent to webhook (fire-and-forget) 5. Success message shown 6. Navigate to next step Files: - frontend/src/components/form/StepClaimConfirmation.tsx - backend/app/api/claims.py (removed /approve endpoint)
This commit is contained in:
@@ -541,6 +541,92 @@ async def load_wizard_data(claim_id: str):
|
|||||||
raise HTTPException(status_code=500, detail=f"Ошибка при загрузке данных визарда: {str(e)}")
|
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")
|
@router.post("/description")
|
||||||
async def publish_ticket_form_description(payload: TicketFormDescriptionRequest):
|
async def publish_ticket_form_description(payload: TicketFormDescriptionRequest):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -92,12 +92,56 @@ export default function StepClaimConfirmation({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, [claimPlanData]);
|
}, [claimPlanData]);
|
||||||
|
|
||||||
// Функция сохранения данных формы (TODO: реализовать сохранение)
|
// Функция сохранения данных формы - отправка в webhook без ожидания ответа
|
||||||
const saveFormData = useCallback(async (formData: any) => {
|
const saveFormData = useCallback(async (formData: any) => {
|
||||||
console.log('💾 Сохраняем данные формы:', formData);
|
console.log('💾 Отправляем данные формы в webhook:', formData);
|
||||||
// TODO: Реализовать сохранение данных в бэкенд/n8n
|
|
||||||
// Здесь будет вызов API для сохранения отредактированных данных формы
|
// Получаем данные из 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-кода
|
// Функция отправки SMS-кода
|
||||||
const sendSMSCode = useCallback(async (phone: string) => {
|
const sendSMSCode = useCallback(async (phone: string) => {
|
||||||
@@ -147,13 +191,18 @@ export default function StepClaimConfirmation({
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
message.success('Код подтвержден!');
|
message.success('Код подтвержден!');
|
||||||
// Закрываем модалку и продолжаем с сохранением данных
|
// Закрываем модалку
|
||||||
setSmsModalVisible(false);
|
setSmsModalVisible(false);
|
||||||
setSmsCodeSent(false);
|
setSmsCodeSent(false);
|
||||||
smsForm.resetFields();
|
smsForm.resetFields();
|
||||||
|
|
||||||
// Сохраняем данные и переходим дальше
|
// Отправляем данные в webhook без ожидания ответа
|
||||||
await saveFormData(pendingFormData);
|
saveFormData(pendingFormData);
|
||||||
|
|
||||||
|
// Показываем сообщение об успешной отправке
|
||||||
|
message.success('Ваше заявление отправлено!');
|
||||||
|
|
||||||
|
// Переходим дальше
|
||||||
onNext();
|
onNext();
|
||||||
} else {
|
} else {
|
||||||
message.error(result.detail || 'Неверный код');
|
message.error(result.detail || 'Неверный код');
|
||||||
|
|||||||
Reference in New Issue
Block a user