diff --git a/backend/app/api/policy.py b/backend/app/api/policy.py index e0d288f..d498ba7 100644 --- a/backend/app/api/policy.py +++ b/backend/app/api/policy.py @@ -10,8 +10,8 @@ router = APIRouter(prefix="/api/v1/policy", tags=["Policy"]) class PolicyCheckRequest(BaseModel): """Запрос на проверку полиса""" - voucher: str - inn: str | None = None + voucher: str # Полный номер полиса вида E1000-302538524 + email: str # Email обязателен @router.post("/check") @@ -19,14 +19,14 @@ async def check_policy(request: PolicyCheckRequest): """ Проверить полис в БД - - **voucher**: Номер полиса - - **inn**: ИНН (опционально) + - **voucher**: Номер полиса вида E1000-302538524 + - **email**: Email заявителя (обязательно) Returns: - found: true/false - policy_data: данные полиса если найден """ - policy = await policy_service.check_policy(request.voucher, request.inn) + policy = await policy_service.check_policy(request.voucher) if policy: return { diff --git a/backend/app/main.py b/backend/app/main.py index bcbb7a0..866099d 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -47,6 +47,12 @@ async def lifespan(app: FastAPI): except Exception as e: logger.warning(f"⚠️ RabbitMQ not available: {e}") + try: + # Подключаем MySQL (для проверки полисов) + await policy_service.connect() + except Exception as e: + logger.warning(f"⚠️ MySQL Policy DB not available: {e}") + logger.info("✅ ERV Platform started successfully!") yield @@ -57,6 +63,7 @@ async def lifespan(app: FastAPI): await db.disconnect() await redis_service.disconnect() await rabbitmq_service.disconnect() + await policy_service.close() logger.info("👋 ERV Platform stopped") diff --git a/backend/app/services/policy_service.py b/backend/app/services/policy_service.py index 8331407..7960146 100644 --- a/backend/app/services/policy_service.py +++ b/backend/app/services/policy_service.py @@ -34,13 +34,12 @@ class PolicyService: logger.error(f"❌ MySQL Policy DB connection error: {e}") raise - async def check_policy(self, voucher: str, inn: Optional[str] = None) -> Optional[Dict[str, Any]]: + async def check_policy(self, voucher: str) -> Optional[Dict[str, Any]]: """ Проверить полис в БД Args: - voucher: Номер полиса - inn: ИНН (опционально) + voucher: Номер полиса вида E1000-302538524 Returns: Dict с данными полиса или None если не найден @@ -51,18 +50,10 @@ class PolicyService: try: async with self.pool.acquire() as conn: async with conn.cursor(aiomysql.DictCursor) as cursor: - # Базовый запрос - query = "SELECT * FROM erv_vouchers WHERE voucher = %s" - params = [voucher] + # Запрос поиска по номеру полиса + query = "SELECT * FROM erv_vouchers WHERE voucher = %s LIMIT 1" - # Если указан ИНН, добавляем проверку - if inn: - query += " AND inn = %s" - params.append(inn) - - query += " LIMIT 1" - - await cursor.execute(query, params) + await cursor.execute(query, [voucher]) result = await cursor.fetchone() if result: diff --git a/frontend/src/components/form/Step1Policy.tsx b/frontend/src/components/form/Step1Policy.tsx index 5feae19..7a161a5 100644 --- a/frontend/src/components/form/Step1Policy.tsx +++ b/frontend/src/components/form/Step1Policy.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Form, Input, Button, message } from 'antd'; -import { FileProtectOutlined } from '@ant-design/icons'; +import { FileProtectOutlined, MailOutlined } from '@ant-design/icons'; interface Props { formData: any; @@ -8,10 +8,29 @@ interface Props { onNext: () => void; } +// Функция автозамены кириллицы на латиницу +const cyrillicToLatin = (text: string): string => { + const map: Record = { + 'А': 'A', 'В': 'B', 'С': 'C', 'Е': 'E', 'Н': 'H', 'К': 'K', + 'М': 'M', 'О': 'O', 'Р': 'P', 'Т': 'T', 'Х': 'X', + 'а': 'a', 'в': 'b', 'с': 'c', 'е': 'e', 'н': 'h', 'к': 'k', + 'м': 'm', 'о': 'o', 'р': 'p', 'т': 't', 'х': 'x' + }; + + return text.split('').map(char => map[char] || char).join(''); +}; + export default function Step1Policy({ formData, updateFormData, onNext }: Props) { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); + // Обработчик изменения поля полиса с автозаменой + const handleVoucherChange = (e: React.ChangeEvent) => { + const value = e.target.value; + const converted = cyrillicToLatin(value.toUpperCase()); + form.setFieldValue('voucher', converted); + }; + const checkPolicy = async () => { try { const values = await form.validateFields(); @@ -22,9 +41,8 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props) method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - policy_number: values.policyNumber, - policy_series: values.policySeries, - inn: values.inn, + voucher: values.voucher, + email: values.email, }), }); @@ -32,11 +50,11 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props) if (response.ok) { if (result.found) { - message.success(`Полис найден! ${result.holder_name}`); + message.success(`Полис найден! Владелец: ${result.policy_data?.holder_name || 'не указан'}`); updateFormData(values); onNext(); } else { - message.warning('Полис не найден. Загрузите скан полиса на следующем шаге.'); + message.warning('Полис не найден в базе. Продолжайте — на следующем шаге загрузите скан.'); updateFormData(values); onNext(); } @@ -63,36 +81,39 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props) > } - placeholder="123456789" + placeholder="E1000-302538524" size="large" + onChange={handleVoucherChange} + maxLength={15} /> - - - - - - - - - + } + placeholder="example@mail.ru" + size="large" + type="email" + /> @@ -115,4 +136,3 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props) ); } - diff --git a/frontend/src/pages/ClaimForm.tsx b/frontend/src/pages/ClaimForm.tsx index 142ba75..b9fcb43 100644 --- a/frontend/src/pages/ClaimForm.tsx +++ b/frontend/src/pages/ClaimForm.tsx @@ -9,11 +9,8 @@ const { Step } = Steps; interface FormData { // Шаг 1 - phone: string; - email?: string; - inn?: string; - policyNumber: string; - policySeries?: string; + voucher: string; // Полис вида E1000-302538524 + email: string; // Email обязателен // Шаг 2 incidentDate?: string; @@ -22,6 +19,7 @@ interface FormData { uploadedFiles?: string[]; // Шаг 3 + phone: string; paymentMethod: string; bankName?: string; cardNumber?: string; @@ -31,8 +29,9 @@ interface FormData { export default function ClaimForm() { const [currentStep, setCurrentStep] = useState(0); const [formData, setFormData] = useState({ + voucher: '', + email: '', phone: '', - policyNumber: '', paymentMethod: 'sbp', }); const [isPhoneVerified, setIsPhoneVerified] = useState(false); @@ -57,11 +56,9 @@ export default function ClaimForm() { 'Content-Type': 'application/json', }, body: JSON.stringify({ - phone: formData.phone, + voucher: formData.voucher, email: formData.email, - inn: formData.inn, - policy_number: formData.policyNumber, - policy_series: formData.policySeries, + phone: formData.phone, incident_date: formData.incidentDate, incident_description: formData.incidentDescription, transport_type: formData.transportType, @@ -79,8 +76,9 @@ export default function ClaimForm() { message.success(`Заявка ${result.claim_number} успешно создана!`); // Сброс формы setFormData({ + voucher: '', + email: '', phone: '', - policyNumber: '', paymentMethod: 'sbp', }); setCurrentStep(0);