refactor: Load claim confirmation data from DB instead of SSE for drafts
Problem: - When draft is fully filled, we subscribed to Redis SSE channel claim:plan - But all data already exists in PostgreSQL database - No need to wait for n8n to publish data - we can load it directly Solution: 1. Removed subscribeToClaimPlanForDraft() function - No longer subscribes to SSE channel for drafts - Removed EventSource cleanup code 2. Added transformDraftToClaimPlanFormat() function - Transforms draft data from DB format to propertyName format - Extracts data from payload/body (telegram/web_form formats) - Maps documents_meta to attachments array - Formats applicant, case, contract_or_service, offenders, claim, meta - Returns data in array format expected by confirmation form 3. Updated loadDraft() logic: - When draft is ready for confirmation (all steps filled + draft status) - Calls transformDraftToClaimPlanFormat() instead of subscribing to SSE - Immediately shows confirmation form with data from DB Flow: 1. User selects fully filled draft 2. System checks completeness (description, plan, answers, documents) 3. If ready → transforms DB data to propertyName format 4. Shows confirmation form immediately (no SSE wait) Benefits: - ✅ Faster: no waiting for n8n to publish data - ✅ More reliable: data always available from DB - ✅ Simpler: no SSE connection management for drafts - ✅ Works offline: doesn't depend on Redis pub/sub Files: - frontend/src/pages/ClaimForm.tsx: Added transform function, removed SSE subscription
This commit is contained in:
@@ -230,19 +230,6 @@ export default function ClaimForm() {
|
||||
}
|
||||
}, [formData.showClaimConfirmation, formData.claimPlanData]);
|
||||
|
||||
// Cleanup: закрываем SSE соединение при размонтировании
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (claimPlanEventSourceRef.current) {
|
||||
claimPlanEventSourceRef.current.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
}
|
||||
if (claimPlanTimeoutRef.current) {
|
||||
clearTimeout(claimPlanTimeoutRef.current);
|
||||
claimPlanTimeoutRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Динамически определяем список шагов на основе выбранного eventType
|
||||
const documentConfigs = formData.eventType ? getDocumentsForEventType(formData.eventType) : [];
|
||||
@@ -284,97 +271,116 @@ export default function ClaimForm() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Подписка на канал claim:plan для получения данных заявления (для черновиков)
|
||||
const subscribeToClaimPlanForDraft = useCallback((sessionToken: string, claimId: string) => {
|
||||
console.log('📡 Подписка на канал claim:plan для черновика:', { sessionToken, claimId });
|
||||
// Преобразование данных черновика в формат propertyName для формы подтверждения
|
||||
const transformDraftToClaimPlanFormat = useCallback((data: {
|
||||
claim: any;
|
||||
payload: any;
|
||||
body: any;
|
||||
isTelegramFormat: boolean;
|
||||
finalClaimId: string;
|
||||
actualSessionId: string;
|
||||
currentFormData: FormData;
|
||||
}) => {
|
||||
const { claim, payload, body, finalClaimId, actualSessionId, currentFormData } = data;
|
||||
|
||||
// Закрываем предыдущее соединение, если есть
|
||||
if (claimPlanEventSourceRef.current) {
|
||||
claimPlanEventSourceRef.current.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
}
|
||||
// Извлекаем данные из body (telegram) или напрямую из payload (web_form)
|
||||
const applicantData = body.applicant || payload.applicant || {};
|
||||
const caseData = body.case || payload.case || {};
|
||||
const contractData = body.contract_or_service || payload.contract_or_service || {};
|
||||
const offendersData = body.offenders || payload.offenders || [];
|
||||
const claimData = body.claim || payload.claim || {};
|
||||
const metaData = body.meta || payload.meta || {};
|
||||
const documentsMeta = body.documents_meta || payload.documents_meta || [];
|
||||
|
||||
// Очищаем предыдущий таймаут
|
||||
if (claimPlanTimeoutRef.current) {
|
||||
clearTimeout(claimPlanTimeoutRef.current);
|
||||
claimPlanTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
// Создаём новое SSE соединение
|
||||
const eventSource = new EventSource(`/api/v1/claim-plan/${sessionToken}`);
|
||||
claimPlanEventSourceRef.current = eventSource;
|
||||
|
||||
eventSource.onopen = () => {
|
||||
console.log('✅ Подключено к каналу claim:plan для черновика');
|
||||
addDebugEvent('claim-plan', 'info', '📡 Ожидание данных заявления для черновика...');
|
||||
message.loading('Загрузка данных заявления...', 0);
|
||||
};
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// Извлекаем ответы на вопросы из wizard_answers
|
||||
const wizardAnswers = body.answers || payload.answers || {};
|
||||
let answersParsed = wizardAnswers;
|
||||
if (typeof wizardAnswers === 'string') {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('📥 Получены данные из claim:plan для черновика:', data);
|
||||
|
||||
if (data.event_type === 'claim_plan_ready' && data.status === 'ready') {
|
||||
// Данные заявления получены!
|
||||
message.destroy(); // Убираем loading сообщение
|
||||
message.success('Данные заявления загружены!');
|
||||
|
||||
// Сохраняем данные заявления в formData
|
||||
updateFormData({
|
||||
claimPlanData: data.data, // Данные от n8n
|
||||
showClaimConfirmation: true, // Флаг для показа формы подтверждения
|
||||
});
|
||||
|
||||
// Закрываем SSE соединение
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
|
||||
// Переход к шагу подтверждения произойдёт автоматически через useEffect
|
||||
} else if (data.event_type === 'claim_plan_error' || data.status === 'error') {
|
||||
message.destroy();
|
||||
message.error(data.message || 'Ошибка получения данных заявления');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
// Переходим к визарду вместо подтверждения
|
||||
setCurrentStep(2);
|
||||
} else if (data.event_type === 'claim_plan_timeout' || data.status === 'timeout') {
|
||||
message.destroy();
|
||||
message.warning('Данные заявления ещё обрабатываются. Вы можете продолжить редактирование.');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
// Переходим к визарду вместо подтверждения
|
||||
setCurrentStep(2);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка парсинга данных из claim:plan:', error);
|
||||
message.destroy();
|
||||
message.error('Ошибка обработки данных заявления');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
setCurrentStep(2);
|
||||
answersParsed = JSON.parse(wizardAnswers);
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Не удалось распарсить answers:', e);
|
||||
answersParsed = {};
|
||||
}
|
||||
}
|
||||
|
||||
// Формируем attachments_names из documents_meta
|
||||
const attachmentsNames = documentsMeta.map((doc: any) => {
|
||||
return doc.original_file_name || doc.file_name || doc.field_name || 'Документ';
|
||||
});
|
||||
|
||||
// Формируем attachments с полной информацией
|
||||
const attachments = documentsMeta.map((doc: any) => ({
|
||||
label: doc.original_file_name || doc.file_name || doc.field_name || 'Документ',
|
||||
url: doc.file_id ? `https://s3.twcstorage.ru${doc.file_id}` : '',
|
||||
file_id: doc.file_id || '',
|
||||
stored_file_name: doc.file_name || '',
|
||||
original_file_name: doc.original_file_name || doc.file_name || '',
|
||||
field_name: doc.field_name || '',
|
||||
uploaded_at: doc.uploaded_at || new Date().toISOString(),
|
||||
}));
|
||||
|
||||
// Формируем propertyName в нужном формате
|
||||
const propertyName = {
|
||||
applicant: {
|
||||
first_name: applicantData.first_name || null,
|
||||
middle_name: applicantData.middle_name || null,
|
||||
last_name: applicantData.last_name || null,
|
||||
full_name: applicantData.full_name || null,
|
||||
birth_date: applicantData.birth_date || null,
|
||||
birth_date_fmt: applicantData.birth_date_fmt || null,
|
||||
birth_place: applicantData.birth_place || null,
|
||||
inn: applicantData.inn || null,
|
||||
address: applicantData.address || null,
|
||||
phone: claim.phone || payload.phone || body.phone || currentFormData.phone || null,
|
||||
email: claim.email || payload.email || body.email || currentFormData.email || null,
|
||||
},
|
||||
case: {
|
||||
category: caseData.category || payload.case_type || 'consumer',
|
||||
direction: caseData.direction || 'web_form',
|
||||
country: caseData.country || null,
|
||||
},
|
||||
contract_or_service: {
|
||||
agreement_date: contractData.agreement_date || null,
|
||||
agreement_date_fmt: contractData.agreement_date_fmt || null,
|
||||
amount_paid: contractData.amount_paid || null,
|
||||
amount_paid_fmt: contractData.amount_paid_fmt || null,
|
||||
subject: contractData.subject || payload.problem_description || body.problem_description || null,
|
||||
period_start: contractData.period_start || null,
|
||||
period_start_fmt: contractData.period_start_fmt || null,
|
||||
period_end: contractData.period_end || null,
|
||||
period_end_fmt: contractData.period_end_fmt || null,
|
||||
period_text: contractData.period_text || null,
|
||||
},
|
||||
offenders: offendersData.length > 0 ? offendersData : [],
|
||||
claim: {
|
||||
reason: claimData.reason || caseData.category || 'consumer',
|
||||
description: claimData.description || payload.problem_description || body.problem_description || null,
|
||||
},
|
||||
meta: {
|
||||
claim_id: finalClaimId,
|
||||
unified_id: claim.unified_id || currentFormData.unified_id || null,
|
||||
status: claim.status_code || 'draft',
|
||||
created_at: claim.created_at || new Date().toISOString(),
|
||||
updated_at: claim.updated_at || new Date().toISOString(),
|
||||
user_id: metaData.user_id || null,
|
||||
},
|
||||
attachments: attachments,
|
||||
attachments_count: attachments.length,
|
||||
attachments_names: attachmentsNames,
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('❌ Ошибка SSE соединения claim:plan:', error);
|
||||
message.destroy();
|
||||
message.warning('Не удалось получить данные заявления. Вы можете продолжить редактирование.');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
setCurrentStep(2); // Переходим к визарду вместо подтверждения
|
||||
};
|
||||
|
||||
// Таймаут на 5 минут
|
||||
claimPlanTimeoutRef.current = setTimeout(() => {
|
||||
console.warn('⏰ Таймаут ожидания данных заявления для черновика');
|
||||
message.destroy();
|
||||
message.warning('Превышено время ожидания данных заявления. Вы можете продолжить редактирование.');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
setCurrentStep(2);
|
||||
}, 300000); // 5 минут
|
||||
}, [updateFormData, addDebugEvent]);
|
||||
// Возвращаем данные в формате массива (как ожидает форма подтверждения)
|
||||
return [{
|
||||
propertyName: propertyName,
|
||||
session_token: actualSessionId,
|
||||
prefix: '',
|
||||
telegram_id: null,
|
||||
claim_id: finalClaimId,
|
||||
unified_id: claim.unified_id || currentFormData.unified_id || null,
|
||||
user_id: metaData.user_id || null,
|
||||
}];
|
||||
}, []);
|
||||
|
||||
// Загрузка черновика
|
||||
const loadDraft = useCallback(async (claimId: string) => {
|
||||
@@ -505,16 +511,29 @@ export default function ClaimForm() {
|
||||
|
||||
// ✅ Если все шаги заполнены и статус = draft → переходим к форме подтверждения
|
||||
if (isReadyForConfirmation) {
|
||||
console.log('✅ Все шаги заполнены, переходим к форме подтверждения');
|
||||
console.log('✅ Все шаги заполнены, преобразуем данные для формы подтверждения');
|
||||
|
||||
setIsPhoneVerified(true);
|
||||
|
||||
// Подписываемся на канал claim:plan для получения данных заявления
|
||||
subscribeToClaimPlanForDraft(actualSessionId, finalClaimId);
|
||||
// Преобразуем данные из БД в формат propertyName для формы подтверждения
|
||||
const claimPlanData = transformDraftToClaimPlanFormat({
|
||||
claim,
|
||||
payload,
|
||||
body,
|
||||
isTelegramFormat,
|
||||
finalClaimId,
|
||||
actualSessionId,
|
||||
currentFormData: formData,
|
||||
});
|
||||
|
||||
// Пока устанавливаем шаг визарда, переход к подтверждению произойдёт автоматически
|
||||
// когда данные будут получены через SSE
|
||||
setCurrentStep(2); // StepWizardPlan
|
||||
// Сохраняем данные заявления в formData
|
||||
updateFormData({
|
||||
claimPlanData: claimPlanData,
|
||||
showClaimConfirmation: true,
|
||||
});
|
||||
|
||||
// Переход к шагу подтверждения произойдёт автоматически через useEffect
|
||||
setCurrentStep(2); // StepWizardPlan (временно, useEffect переключит на подтверждение)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user