Improve CarPass product UX and service flows

This commit is contained in:
VPN SaaS Dev
2026-05-14 19:33:25 +09:00
parent b85db333d8
commit caa5f6d3db
36 changed files with 1836 additions and 366 deletions

View File

@@ -1,7 +1,7 @@
from datetime import date, datetime
from decimal import Decimal
from pydantic import BaseModel, ConfigDict, field_validator
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.services.vehicle_identity import normalize_license_plate, validate_vin
@@ -16,6 +16,13 @@ class ServiceCenterCreate(BaseModel):
business_registration_number: str | None = None
telegram_chat_id: str | None = None
contact_phone: str | None = None
description: str | None = None
specializations: list[str] | None = None
working_hours: str | None = None
facade_photo_url: str | None = None
document_photo_urls: list[str] | None = None
additional_photo_urls: list[str] | None = None
contact_person: str | None = None
class ServiceCenterRead(ServiceCenterCreate):
@@ -26,6 +33,27 @@ class ServiceCenterRead(ServiceCenterCreate):
created_at: datetime
verified_at: datetime | None = None
suspended_at: datetime | None = None
rating_avg: Decimal | None = None
reviews_count: int = 0
model_config = ConfigDict(from_attributes=True)
class ServiceCenterPublicRead(BaseModel):
id: int
display_name: str | None = None
name: str
country: str | None = None
city: str | None = None
address: str | None = None
phone: str | None = None
description: str | None = None
specializations: list[str] | None = None
working_hours: str | None = None
facade_photo_url: str | None = None
verification_status: str
rating_avg: Decimal | None = None
reviews_count: int = 0
model_config = ConfigDict(from_attributes=True)
@@ -91,8 +119,15 @@ class VehicleCreate(BaseModel):
license_plate_country: str | None = None
vin: str | None = None
current_odometer: int | None = None
fuel_type: str | None = None
engine_oil_type: str | None = None
engine_oil_volume_l: Decimal | None = None
fuel_tank_volume_l: Decimal | None = None
target_consumption_l_per_100km: Decimal | None = None
purchase_date: date | None = None
purchase_price: Decimal | None = None
currency: str = "RUB"
include_depreciation: bool = False
@field_validator("vin")
@classmethod
@@ -109,6 +144,13 @@ class VehicleUpdate(BaseModel):
license_plate_country: str | None = None
vin: str | None = None
current_odometer: int | None = None
fuel_type: str | None = None
fuel_tank_volume_l: Decimal | None = None
target_consumption_l_per_100km: Decimal | None = None
purchase_date: date | None = None
purchase_price: Decimal | None = None
currency: str | None = None
include_depreciation: bool | None = None
engine_oil_type: str | None = None
engine_oil_volume_l: Decimal | None = None
@@ -129,6 +171,13 @@ class VehicleRead(BaseModel):
license_plate_country: str | None = None
vin_normalized: str | None = None
current_odometer: int | None = None
fuel_type: str | None = None
fuel_tank_volume_l: Decimal | None = None
target_consumption_l_per_100km: Decimal | None = None
purchase_date: date | None = None
purchase_price: Decimal | None = None
currency: str = "RUB"
include_depreciation: bool = False
engine_oil_type: str | None = None
engine_oil_volume_l: Decimal | None = None
created_at: datetime
@@ -226,11 +275,70 @@ class CarServiceLinkCreate(BaseModel):
car_id: int
service_center_id: int
external_vehicle_ref: str | None = None
access_level: str = "basic"
is_active: bool = True
class CarServiceLinkRead(CarServiceLinkCreate):
id: int
status: str = "pending"
requested_by_user_id: int | None = None
approved_by_user_id: int | None = None
approved_at: datetime | None = None
revoked_at: datetime | None = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class ServiceCenterAccessRequest(BaseModel):
car_id: int
access_level: str = "basic"
external_vehicle_ref: str | None = None
@field_validator("access_level")
@classmethod
def validate_access_level(cls, value: str) -> str:
allowed = {"basic", "service_history", "full"}
if value not in allowed:
raise ValueError(f"access_level must be one of {', '.join(sorted(allowed))}")
return value
class ServiceCenterReviewCreate(BaseModel):
rating: int = Field(ge=1, le=5)
text: str | None = None
photo_urls: list[str] | None = None
@model_validator(mode="after")
def validate_review(self) -> "ServiceCenterReviewCreate":
if self.text is not None and len(self.text.strip()) < 3:
raise ValueError("review text is too short")
return self
class ServiceCenterReviewRead(ServiceCenterReviewCreate):
id: int
service_center_id: int
user_id: int
status: str
service_response: str | None = None
service_responded_at: datetime | None = None
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class ServiceCenterReviewCommentCreate(BaseModel):
text: str = Field(min_length=2, max_length=2000)
class ServiceCenterReviewCommentRead(ServiceCenterReviewCommentCreate):
id: int
review_id: int
user_id: int
status: str
created_at: datetime
model_config = ConfigDict(from_attributes=True)