from datetime import date, datetime from decimal import Decimal from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from app.services.vehicle_identity import normalize_license_plate, validate_vin class ServiceCenterCreate(BaseModel): legal_name: str | None = None display_name: str country: str | None = None city: str | None = None address: str | None = None phone: str | None = None 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): id: int name: str verification_status: str owner_user_id: int | None = None 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) class ServiceCenterVerificationCreate(BaseModel): submitted_documents: list[dict] | None = None comment: str | None = None class ServiceCenterVerificationRead(ServiceCenterVerificationCreate): id: int service_center_id: int status: str reviewed_by: int | None = None reviewed_at: datetime | None = None created_at: datetime model_config = ConfigDict(from_attributes=True) class ServiceEmployeeInvite(BaseModel): telegram_id: int role: str = "receptionist" permissions: dict | None = None class ServiceEmployeeRead(BaseModel): id: int service_center_id: int user_id: int role: str permissions: dict | None = None status: str created_at: datetime model_config = ConfigDict(from_attributes=True) class VehicleAccessGrant(BaseModel): service_center_id: int | None = None user_id: int | None = None role: str = "viewer" class VehicleAccessRead(BaseModel): id: int vehicle_id: int user_id: int role: str status: str created_at: datetime revoked_at: datetime | None = None model_config = ConfigDict(from_attributes=True) class VehicleCreate(BaseModel): name: str make: str | None = None model: str | None = None trim: str | None = None generation: str | None = None body_type: str | None = None year: int | None = None license_plate: str | None = None license_plate_country: str | None = None vin: str | None = None current_odometer: int | None = None fuel_type: str | None = None engine_volume_l: Decimal | None = None transmission: str | None = None drive_type: str | None = None engine_oil_type: str | None = None engine_oil_volume_l: Decimal | None = None transmission_fluid_type: str | None = None transmission_fluid_volume_l: Decimal | None = None coolant_type: str | None = None brake_fluid_type: str | None = None tire_pressure_front_bar: Decimal | None = None tire_pressure_rear_bar: Decimal | None = None tire_size: str | None = None oil_change_interval_km: int | None = None oil_change_interval_months: int | 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 purchase_currency: str | None = None purchase_type: str = "unknown" currency: str = "RUB" include_depreciation: bool = False expected_ownership_months: int | None = None expected_residual_value: Decimal | None = None loan_principal: Decimal | None = None loan_down_payment: Decimal | None = None loan_term_months: int | None = None loan_annual_interest_rate: Decimal | None = None loan_first_payment_date: date | None = None loan_payment_day: int | None = None loan_payment_type: str = "annuity" loan_currency: str | None = None loan_comment: str | None = None notes: str | None = None @field_validator("vin") @classmethod def validate_vin_field(cls, value: str | None) -> str | None: return validate_vin(value) class VehicleUpdate(BaseModel): name: str | None = None make: str | None = None model: str | None = None trim: str | None = None generation: str | None = None body_type: str | None = None year: int | None = None license_plate: str | None = None license_plate_country: str | None = None vin: str | None = None current_odometer: int | None = None fuel_type: str | None = None engine_volume_l: Decimal | None = None transmission: str | None = None drive_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 purchase_currency: str | None = None purchase_type: str | None = None currency: str | None = None include_depreciation: bool | None = None expected_ownership_months: int | None = None expected_residual_value: Decimal | None = None engine_oil_type: str | None = None engine_oil_volume_l: Decimal | None = None transmission_fluid_type: str | None = None transmission_fluid_volume_l: Decimal | None = None coolant_type: str | None = None brake_fluid_type: str | None = None tire_pressure_front_bar: Decimal | None = None tire_pressure_rear_bar: Decimal | None = None tire_size: str | None = None oil_change_interval_km: int | None = None oil_change_interval_months: int | None = None loan_principal: Decimal | None = None loan_down_payment: Decimal | None = None loan_term_months: int | None = None loan_annual_interest_rate: Decimal | None = None loan_first_payment_date: date | None = None loan_payment_day: int | None = None loan_payment_type: str | None = None loan_currency: str | None = None loan_comment: str | None = None notes: str | None = None @field_validator("vin") @classmethod def validate_vin_field(cls, value: str | None) -> str | None: return validate_vin(value) class VehicleRead(BaseModel): id: int owner_id: int name: str make: str | None = None model: str | None = None trim: str | None = None generation: str | None = None body_type: str | None = None year: int | None = None license_plate_display: str | None = None license_plate_country: str | None = None vin_normalized: str | None = None current_odometer: int | None = None fuel_type: str | None = None engine_volume_l: Decimal | None = None transmission: str | None = None drive_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 purchase_currency: str | None = None purchase_type: str = "unknown" currency: str = "RUB" include_depreciation: bool = False expected_ownership_months: int | None = None expected_residual_value: Decimal | None = None engine_oil_type: str | None = None engine_oil_volume_l: Decimal | None = None transmission_fluid_type: str | None = None transmission_fluid_volume_l: Decimal | None = None coolant_type: str | None = None brake_fluid_type: str | None = None tire_pressure_front_bar: Decimal | None = None tire_pressure_rear_bar: Decimal | None = None tire_size: str | None = None oil_change_interval_km: int | None = None oil_change_interval_months: int | None = None loan_principal: Decimal | None = None loan_down_payment: Decimal | None = None loan_term_months: int | None = None loan_annual_interest_rate: Decimal | None = None loan_first_payment_date: date | None = None loan_payment_day: int | None = None loan_payment_type: str = "annuity" loan_currency: str | None = None loan_comment: str | None = None notes: str | None = None created_at: datetime model_config = ConfigDict(from_attributes=True) class ServiceVisitCreate(BaseModel): vehicle_id: int visit_date: date odometer: int | None = None notes: str | None = None total_cost: Decimal | None = None currency: str = "RUB" class ServiceVisitRead(ServiceVisitCreate): id: int service_center_id: int created_by_employee_id: int | None = None status: str owner_resolved_at: datetime | None = None created_at: datetime model_config = ConfigDict(from_attributes=True) class ServiceWorkItemCreate(BaseModel): work_type: str = "other" title: str description: str | None = None parts: list[dict] | None = None oil_brand: str | None = None oil_viscosity: str | None = None oil_volume: Decimal | None = None next_due_odometer: int | None = None next_due_date: date | None = None price: Decimal | None = None class ServiceWorkItemRead(ServiceWorkItemCreate): id: int service_visit_id: int created_at: datetime model_config = ConfigDict(from_attributes=True) class VehicleDataChangeRequestCreate(BaseModel): vehicle_id: int field_name: str new_value: str | None = None class VehicleDataChangeRequestRead(VehicleDataChangeRequestCreate): id: int requested_by_service_center_id: int | None = None requested_by_employee_id: int | None = None old_value: str | None = None status: str owner_user_id: int created_at: datetime resolved_at: datetime | None = None model_config = ConfigDict(from_attributes=True) class VehicleSearchRequest(BaseModel): license_plate: str | None = None country_code: str | None = None vin: str | None = None @field_validator("vin") @classmethod def validate_vin_field(cls, value: str | None) -> str | None: return validate_vin(value) @field_validator("license_plate") @classmethod def normalize_plate_field(cls, value: str | None) -> str | None: return normalize_license_plate(value) class VehicleSearchResult(BaseModel): vehicle_id: int | None = None make: str | None = None model: str | None = None year: int | None = None masked_license_plate: str | None = None masked_vin: str | None = None access_status: str = "none" 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) class ServiceInboxCreate(BaseModel): source_chat_id: str | None = None raw_text: str car_id: int | None = None service_center_id: int | None = None class ServiceInboxRead(ServiceInboxCreate): id: int parsed_status: str parsed_payload: str | None = None error: str | None = None created_at: datetime class AdminModerationDecision(BaseModel): comment: str | None = None reason: str | None = None model_config = ConfigDict(from_attributes=True)