feat: Обновлена форма проверки полиса + автозамена кириллицы
Изменения в форме (Шаг 1): - Полис в одну строку: E1000-302538524 (было: отдельно серия и номер) - Email теперь обязателен (было: опционально) - Убран ИНН (было: опционально) - Автозамена кириллицы на латиницу (Е→E, О→O и т.д.) - Валидация формата: буква + 4 цифры + тире + 9 цифр Изменения в Backend API: - PolicyCheckRequest: voucher + email (убран inn) - policy_service: упрощен запрос к MySQL - Добавлено подключение MySQL в lifespan Изменения в ClaimForm: - FormData обновлен: voucher вместо policyNumber/policySeries - Убрано поле inn из всей логики Статус: Frontend работает, MySQL требует настройки доступа
This commit is contained in:
@@ -10,8 +10,8 @@ router = APIRouter(prefix="/api/v1/policy", tags=["Policy"])
|
|||||||
|
|
||||||
class PolicyCheckRequest(BaseModel):
|
class PolicyCheckRequest(BaseModel):
|
||||||
"""Запрос на проверку полиса"""
|
"""Запрос на проверку полиса"""
|
||||||
voucher: str
|
voucher: str # Полный номер полиса вида E1000-302538524
|
||||||
inn: str | None = None
|
email: str # Email обязателен
|
||||||
|
|
||||||
|
|
||||||
@router.post("/check")
|
@router.post("/check")
|
||||||
@@ -19,14 +19,14 @@ async def check_policy(request: PolicyCheckRequest):
|
|||||||
"""
|
"""
|
||||||
Проверить полис в БД
|
Проверить полис в БД
|
||||||
|
|
||||||
- **voucher**: Номер полиса
|
- **voucher**: Номер полиса вида E1000-302538524
|
||||||
- **inn**: ИНН (опционально)
|
- **email**: Email заявителя (обязательно)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- found: true/false
|
- found: true/false
|
||||||
- policy_data: данные полиса если найден
|
- policy_data: данные полиса если найден
|
||||||
"""
|
"""
|
||||||
policy = await policy_service.check_policy(request.voucher, request.inn)
|
policy = await policy_service.check_policy(request.voucher)
|
||||||
|
|
||||||
if policy:
|
if policy:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ async def lifespan(app: FastAPI):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ RabbitMQ not available: {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!")
|
logger.info("✅ ERV Platform started successfully!")
|
||||||
|
|
||||||
yield
|
yield
|
||||||
@@ -57,6 +63,7 @@ async def lifespan(app: FastAPI):
|
|||||||
await db.disconnect()
|
await db.disconnect()
|
||||||
await redis_service.disconnect()
|
await redis_service.disconnect()
|
||||||
await rabbitmq_service.disconnect()
|
await rabbitmq_service.disconnect()
|
||||||
|
await policy_service.close()
|
||||||
|
|
||||||
logger.info("👋 ERV Platform stopped")
|
logger.info("👋 ERV Platform stopped")
|
||||||
|
|
||||||
|
|||||||
@@ -34,13 +34,12 @@ class PolicyService:
|
|||||||
logger.error(f"❌ MySQL Policy DB connection error: {e}")
|
logger.error(f"❌ MySQL Policy DB connection error: {e}")
|
||||||
raise
|
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:
|
Args:
|
||||||
voucher: Номер полиса
|
voucher: Номер полиса вида E1000-302538524
|
||||||
inn: ИНН (опционально)
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict с данными полиса или None если не найден
|
Dict с данными полиса или None если не найден
|
||||||
@@ -51,18 +50,10 @@ class PolicyService:
|
|||||||
try:
|
try:
|
||||||
async with self.pool.acquire() as conn:
|
async with self.pool.acquire() as conn:
|
||||||
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
# Базовый запрос
|
# Запрос поиска по номеру полиса
|
||||||
query = "SELECT * FROM erv_vouchers WHERE voucher = %s"
|
query = "SELECT * FROM erv_vouchers WHERE voucher = %s LIMIT 1"
|
||||||
params = [voucher]
|
|
||||||
|
|
||||||
# Если указан ИНН, добавляем проверку
|
await cursor.execute(query, [voucher])
|
||||||
if inn:
|
|
||||||
query += " AND inn = %s"
|
|
||||||
params.append(inn)
|
|
||||||
|
|
||||||
query += " LIMIT 1"
|
|
||||||
|
|
||||||
await cursor.execute(query, params)
|
|
||||||
result = await cursor.fetchone()
|
result = await cursor.fetchone()
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Form, Input, Button, message } from 'antd';
|
import { Form, Input, Button, message } from 'antd';
|
||||||
import { FileProtectOutlined } from '@ant-design/icons';
|
import { FileProtectOutlined, MailOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
formData: any;
|
formData: any;
|
||||||
@@ -8,10 +8,29 @@ interface Props {
|
|||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция автозамены кириллицы на латиницу
|
||||||
|
const cyrillicToLatin = (text: string): string => {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
'А': '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) {
|
export default function Step1Policy({ formData, updateFormData, onNext }: Props) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// Обработчик изменения поля полиса с автозаменой
|
||||||
|
const handleVoucherChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
const converted = cyrillicToLatin(value.toUpperCase());
|
||||||
|
form.setFieldValue('voucher', converted);
|
||||||
|
};
|
||||||
|
|
||||||
const checkPolicy = async () => {
|
const checkPolicy = async () => {
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
@@ -22,9 +41,8 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
policy_number: values.policyNumber,
|
voucher: values.voucher,
|
||||||
policy_series: values.policySeries,
|
email: values.email,
|
||||||
inn: values.inn,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,11 +50,11 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (result.found) {
|
if (result.found) {
|
||||||
message.success(`Полис найден! ${result.holder_name}`);
|
message.success(`Полис найден! Владелец: ${result.policy_data?.holder_name || 'не указан'}`);
|
||||||
updateFormData(values);
|
updateFormData(values);
|
||||||
onNext();
|
onNext();
|
||||||
} else {
|
} else {
|
||||||
message.warning('Полис не найден. Загрузите скан полиса на следующем шаге.');
|
message.warning('Полис не найден в базе. Продолжайте — на следующем шаге загрузите скан.');
|
||||||
updateFormData(values);
|
updateFormData(values);
|
||||||
onNext();
|
onNext();
|
||||||
}
|
}
|
||||||
@@ -63,36 +81,39 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
|
|||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Номер полиса"
|
label="Номер полиса"
|
||||||
name="policyNumber"
|
name="voucher"
|
||||||
rules={[{ required: true, message: 'Введите номер полиса' }]}
|
rules={[
|
||||||
|
{ required: true, message: 'Введите номер полиса' },
|
||||||
|
{
|
||||||
|
pattern: /^[A-Z]\d{4}-\d{9}$/,
|
||||||
|
message: 'Формат: E1000-302538524 (буква, 4 цифры, тире, 9 цифр)'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
tooltip="Формат: E1000-302538524. Кириллица автоматически заменяется на латиницу"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<FileProtectOutlined />}
|
prefix={<FileProtectOutlined />}
|
||||||
placeholder="123456789"
|
placeholder="E1000-302538524"
|
||||||
size="large"
|
size="large"
|
||||||
|
onChange={handleVoucherChange}
|
||||||
|
maxLength={15}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Серия полиса (необязательно)"
|
label="Электронная почта"
|
||||||
name="policySeries"
|
|
||||||
>
|
|
||||||
<Input placeholder="AB" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label="ИНН (необязательно)"
|
|
||||||
name="inn"
|
|
||||||
>
|
|
||||||
<Input placeholder="1234567890" maxLength={12} size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label="Email (необязательно)"
|
|
||||||
name="email"
|
name="email"
|
||||||
rules={[{ type: 'email', message: 'Неверный формат email' }]}
|
rules={[
|
||||||
|
{ required: true, message: 'Введите email' },
|
||||||
|
{ type: 'email', message: 'Неверный формат email' }
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="example@mail.ru" size="large" />
|
<Input
|
||||||
|
prefix={<MailOutlined />}
|
||||||
|
placeholder="example@mail.ru"
|
||||||
|
size="large"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
@@ -115,4 +136,3 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
|
|||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ const { Step } = Steps;
|
|||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
// Шаг 1
|
// Шаг 1
|
||||||
phone: string;
|
voucher: string; // Полис вида E1000-302538524
|
||||||
email?: string;
|
email: string; // Email обязателен
|
||||||
inn?: string;
|
|
||||||
policyNumber: string;
|
|
||||||
policySeries?: string;
|
|
||||||
|
|
||||||
// Шаг 2
|
// Шаг 2
|
||||||
incidentDate?: string;
|
incidentDate?: string;
|
||||||
@@ -22,6 +19,7 @@ interface FormData {
|
|||||||
uploadedFiles?: string[];
|
uploadedFiles?: string[];
|
||||||
|
|
||||||
// Шаг 3
|
// Шаг 3
|
||||||
|
phone: string;
|
||||||
paymentMethod: string;
|
paymentMethod: string;
|
||||||
bankName?: string;
|
bankName?: string;
|
||||||
cardNumber?: string;
|
cardNumber?: string;
|
||||||
@@ -31,8 +29,9 @@ interface FormData {
|
|||||||
export default function ClaimForm() {
|
export default function ClaimForm() {
|
||||||
const [currentStep, setCurrentStep] = useState(0);
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
const [formData, setFormData] = useState<FormData>({
|
const [formData, setFormData] = useState<FormData>({
|
||||||
|
voucher: '',
|
||||||
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
policyNumber: '',
|
|
||||||
paymentMethod: 'sbp',
|
paymentMethod: 'sbp',
|
||||||
});
|
});
|
||||||
const [isPhoneVerified, setIsPhoneVerified] = useState(false);
|
const [isPhoneVerified, setIsPhoneVerified] = useState(false);
|
||||||
@@ -57,11 +56,9 @@ export default function ClaimForm() {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
phone: formData.phone,
|
voucher: formData.voucher,
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
inn: formData.inn,
|
phone: formData.phone,
|
||||||
policy_number: formData.policyNumber,
|
|
||||||
policy_series: formData.policySeries,
|
|
||||||
incident_date: formData.incidentDate,
|
incident_date: formData.incidentDate,
|
||||||
incident_description: formData.incidentDescription,
|
incident_description: formData.incidentDescription,
|
||||||
transport_type: formData.transportType,
|
transport_type: formData.transportType,
|
||||||
@@ -79,8 +76,9 @@ export default function ClaimForm() {
|
|||||||
message.success(`Заявка ${result.claim_number} успешно создана!`);
|
message.success(`Заявка ${result.claim_number} успешно создана!`);
|
||||||
// Сброс формы
|
// Сброс формы
|
||||||
setFormData({
|
setFormData({
|
||||||
|
voucher: '',
|
||||||
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
policyNumber: '',
|
|
||||||
paymentMethod: 'sbp',
|
paymentMethod: 'sbp',
|
||||||
});
|
});
|
||||||
setCurrentStep(0);
|
setCurrentStep(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user