Files
drivers_bot/app/models/car.py
2026-05-14 21:19:37 +09:00

370 lines
21 KiB
Python

from datetime import date, datetime
from decimal import Decimal
from sqlalchemy import (
JSON,
Boolean,
Date,
DateTime,
ForeignKey,
Integer,
Numeric,
String,
Text,
UniqueConstraint,
func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base
class Car(Base):
__tablename__ = "cars"
id: Mapped[int] = mapped_column(primary_key=True)
owner_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(160))
make: Mapped[str | None] = mapped_column(String(80))
model: Mapped[str | None] = mapped_column(String(80))
trim: Mapped[str | None] = mapped_column(String(120))
generation: Mapped[str | None] = mapped_column(String(120))
body_type: Mapped[str | None] = mapped_column(String(80))
year: Mapped[int | None]
plate_number: Mapped[str | None] = mapped_column(String(32))
vin: Mapped[str | None] = mapped_column(String(32))
license_plate_display: Mapped[str | None] = mapped_column(String(32))
license_plate_normalized: Mapped[str | None] = mapped_column(String(32), index=True)
license_plate_country: Mapped[str | None] = mapped_column(String(2), index=True)
vin_normalized: Mapped[str | None] = mapped_column(String(17), unique=True, index=True)
fuel_type: Mapped[str | None] = mapped_column(String(32))
engine_volume_l: Mapped[Decimal | None] = mapped_column(Numeric(5, 2))
transmission: Mapped[str | None] = mapped_column(String(40))
drive_type: Mapped[str | None] = mapped_column(String(40))
target_consumption_l_per_100km: Mapped[Decimal | None] = mapped_column(Numeric(6, 2))
fuel_tank_volume_l: Mapped[Decimal | None] = mapped_column(Numeric(6, 2))
engine_oil_type: Mapped[str | None] = mapped_column(String(80))
engine_oil_volume_l: Mapped[Decimal | None] = mapped_column(Numeric(5, 2))
transmission_fluid_type: Mapped[str | None] = mapped_column(String(80))
transmission_fluid_volume_l: Mapped[Decimal | None] = mapped_column(Numeric(5, 2))
coolant_type: Mapped[str | None] = mapped_column(String(80))
brake_fluid_type: Mapped[str | None] = mapped_column(String(80))
tire_pressure_front_bar: Mapped[Decimal | None] = mapped_column(Numeric(4, 2))
tire_pressure_rear_bar: Mapped[Decimal | None] = mapped_column(Numeric(4, 2))
tire_size: Mapped[str | None] = mapped_column(String(80))
oil_change_interval_km: Mapped[int | None] = mapped_column(Integer)
oil_change_interval_months: Mapped[int | None] = mapped_column(Integer)
purchase_date: Mapped[date | None] = mapped_column(Date)
purchase_price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
purchase_currency: Mapped[str | None] = mapped_column(String(3))
purchase_type: Mapped[str] = mapped_column(String(24), default="unknown", server_default="unknown")
currency: Mapped[str] = mapped_column(String(3), default="RUB", server_default="RUB")
include_depreciation: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
expected_ownership_months: Mapped[int | None] = mapped_column(Integer)
expected_residual_value: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
loan_principal: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
loan_down_payment: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
loan_term_months: Mapped[int | None] = mapped_column(Integer)
loan_annual_interest_rate: Mapped[Decimal | None] = mapped_column(Numeric(6, 3))
loan_first_payment_date: Mapped[date | None] = mapped_column(Date)
loan_payment_day: Mapped[int | None] = mapped_column(Integer)
loan_payment_type: Mapped[str] = mapped_column(String(24), default="annuity", server_default="annuity")
loan_currency: Mapped[str | None] = mapped_column(String(3))
loan_comment: Mapped[str | None] = mapped_column(Text)
current_odometer: Mapped[int | None]
notes: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
owner = relationship("User", back_populates="cars")
fuel_entries = relationship("FuelEntry", back_populates="car", cascade="all, delete-orphan")
service_entries = relationship("ServiceEntry", back_populates="car", cascade="all, delete-orphan")
expense_entries = relationship("ExpenseEntry", back_populates="car", cascade="all, delete-orphan")
service_links = relationship("CarServiceLink", back_populates="car", cascade="all, delete-orphan")
odometer_history = relationship("OdometerHistory", back_populates="car", cascade="all, delete-orphan")
class CarMake(Base):
__tablename__ = "car_makes"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(80), unique=True, index=True)
country: Mapped[str | None] = mapped_column(String(80))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
models = relationship("CarModel", back_populates="make", cascade="all, delete-orphan")
class CarModel(Base):
__tablename__ = "car_models"
__table_args__ = (UniqueConstraint("make_id", "name", name="uq_car_models_make_name"),)
id: Mapped[int] = mapped_column(primary_key=True)
make_id: Mapped[int] = mapped_column(ForeignKey("car_makes.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(100), index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
make = relationship("CarMake", back_populates="models")
trims = relationship("CarTrim", back_populates="model", cascade="all, delete-orphan")
class CarTrim(Base):
__tablename__ = "car_trims"
__table_args__ = (UniqueConstraint("model_id", "name", name="uq_car_trims_model_name"),)
id: Mapped[int] = mapped_column(primary_key=True)
model_id: Mapped[int] = mapped_column(ForeignKey("car_models.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(120), index=True)
body_type: Mapped[str | None] = mapped_column(String(60))
fuel_type: Mapped[str | None] = mapped_column(String(32))
transmission: Mapped[str | None] = mapped_column(String(32))
drive_type: Mapped[str | None] = mapped_column(String(32))
year_from: Mapped[int | None] = mapped_column(Integer)
year_to: Mapped[int | None] = mapped_column(Integer)
market: Mapped[str | None] = mapped_column(String(80))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
model = relationship("CarModel", back_populates="trims")
class ServiceCenter(Base):
__tablename__ = "service_centers"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(160), unique=True, index=True)
legal_name: Mapped[str | None] = mapped_column(String(240))
display_name: Mapped[str | None] = mapped_column(String(160), index=True)
country: Mapped[str | None] = mapped_column(String(2), index=True)
city: Mapped[str | None] = mapped_column(String(120))
telegram_chat_id: Mapped[str | None] = mapped_column(String(80), unique=True, index=True)
phone: Mapped[str | None] = mapped_column(String(40))
contact_phone: Mapped[str | None] = mapped_column(String(40))
address: Mapped[str | None] = mapped_column(String(240))
description: Mapped[str | None] = mapped_column(Text)
specializations: Mapped[list | None] = mapped_column(JSON)
working_hours: Mapped[str | None] = mapped_column(String(240))
facade_photo_url: Mapped[str | None] = mapped_column(String(500))
document_photo_urls: Mapped[list | None] = mapped_column(JSON)
additional_photo_urls: Mapped[list | None] = mapped_column(JSON)
contact_person: Mapped[str | None] = mapped_column(String(160))
rating_avg: Mapped[Decimal | None] = mapped_column(Numeric(3, 2))
reviews_count: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
business_registration_number: Mapped[str | None] = mapped_column(String(80))
verification_status: Mapped[str] = mapped_column(String(24), default="pending", server_default="pending", index=True)
owner_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
suspended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
car_links = relationship("CarServiceLink", back_populates="service_center", cascade="all, delete-orphan")
inbox_messages = relationship("ServiceInboxMessage", back_populates="service_center")
employees = relationship("ServiceEmployee", back_populates="service_center", cascade="all, delete-orphan")
visits = relationship("ServiceVisit", back_populates="service_center")
reviews = relationship("ServiceCenterReview", back_populates="service_center", cascade="all, delete-orphan")
class CarServiceLink(Base):
__tablename__ = "car_service_links"
__table_args__ = (UniqueConstraint("car_id", "service_center_id", name="uq_car_service_link"),)
id: Mapped[int] = mapped_column(primary_key=True)
car_id: Mapped[int] = mapped_column(ForeignKey("cars.id", ondelete="CASCADE"), index=True)
service_center_id: Mapped[int] = mapped_column(ForeignKey("service_centers.id", ondelete="CASCADE"), index=True)
external_vehicle_ref: Mapped[str | None] = mapped_column(String(120), index=True)
access_level: Mapped[str] = mapped_column(String(32), default="basic", server_default="basic", index=True)
status: Mapped[str] = mapped_column(String(32), default="pending", server_default="pending", index=True)
requested_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), index=True)
approved_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), index=True)
approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
is_active: Mapped[bool] = mapped_column(default=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
car = relationship("Car", back_populates="service_links")
service_center = relationship("ServiceCenter", back_populates="car_links")
class OdometerHistory(Base):
__tablename__ = "odometer_history"
id: Mapped[int] = mapped_column(primary_key=True)
car_id: Mapped[int] = mapped_column(ForeignKey("cars.id", ondelete="CASCADE"), index=True)
previous_odometer: Mapped[int | None] = mapped_column(Integer)
new_odometer: Mapped[int] = mapped_column(Integer)
source_record_type: Mapped[str] = mapped_column(String(40), index=True)
source_record_id: Mapped[int | None] = mapped_column(Integer, index=True)
changed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
changed_by: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), index=True)
confirmation_required: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
user_confirmed: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
car = relationship("Car", back_populates="odometer_history")
class ServiceInboxMessage(Base):
__tablename__ = "service_inbox_messages"
id: Mapped[int] = mapped_column(primary_key=True)
service_center_id: Mapped[int | None] = mapped_column(ForeignKey("service_centers.id", ondelete="SET NULL"), index=True)
car_id: Mapped[int | None] = mapped_column(ForeignKey("cars.id", ondelete="SET NULL"), index=True)
source_chat_id: Mapped[str | None] = mapped_column(String(80), index=True)
raw_text: Mapped[str] = mapped_column(Text)
parsed_status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
parsed_payload: Mapped[str | None] = mapped_column(Text)
error: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
service_center = relationship("ServiceCenter", back_populates="inbox_messages")
class VehicleAccess(Base):
__tablename__ = "vehicle_access"
__table_args__ = (UniqueConstraint("vehicle_id", "user_id", "role", name="uq_vehicle_access_user_role"),)
id: Mapped[int] = mapped_column(primary_key=True)
vehicle_id: Mapped[int] = mapped_column(ForeignKey("cars.id", ondelete="CASCADE"), index=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
role: Mapped[str] = mapped_column(String(24), default="owner", server_default="owner", index=True)
status: Mapped[str] = mapped_column(String(24), default="active", server_default="active", index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
class ServiceCenterVerification(Base):
__tablename__ = "service_center_verifications"
id: Mapped[int] = mapped_column(primary_key=True)
service_center_id: Mapped[int] = mapped_column(ForeignKey("service_centers.id", ondelete="CASCADE"), index=True)
submitted_documents: Mapped[list | None] = mapped_column(JSON)
comment: Mapped[str | None] = mapped_column(Text)
status: Mapped[str] = mapped_column(String(24), default="pending", server_default="pending", index=True)
reviewed_by: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), index=True)
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class ServiceEmployee(Base):
__tablename__ = "service_employees"
__table_args__ = (UniqueConstraint("service_center_id", "user_id", name="uq_service_employee_user"),)
id: Mapped[int] = mapped_column(primary_key=True)
service_center_id: Mapped[int] = mapped_column(ForeignKey("service_centers.id", ondelete="CASCADE"), index=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
role: Mapped[str] = mapped_column(String(32), default="receptionist", server_default="receptionist", index=True)
permissions: Mapped[dict | None] = mapped_column(JSON)
status: Mapped[str] = mapped_column(String(24), default="active", server_default="active", index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
service_center = relationship("ServiceCenter", back_populates="employees")
class ServiceVisit(Base):
__tablename__ = "service_visits"
id: Mapped[int] = mapped_column(primary_key=True)
service_center_id: Mapped[int] = mapped_column(ForeignKey("service_centers.id", ondelete="CASCADE"), index=True)
vehicle_id: Mapped[int] = mapped_column(ForeignKey("cars.id", ondelete="CASCADE"), index=True)
created_by_employee_id: Mapped[int | None] = mapped_column(ForeignKey("service_employees.id", ondelete="SET NULL"), index=True)
visit_date: Mapped[date] = mapped_column(Date, index=True)
odometer: Mapped[int | None]
status: Mapped[str] = mapped_column(String(40), default="draft", server_default="draft", index=True)
notes: Mapped[str | None] = mapped_column(Text)
total_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
currency: Mapped[str] = mapped_column(String(3), default="RUB", server_default="RUB")
owner_resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
service_center = relationship("ServiceCenter", back_populates="visits")
work_items = relationship("ServiceWorkItem", back_populates="visit", cascade="all, delete-orphan")
class ServiceWorkItem(Base):
__tablename__ = "service_work_items"
id: Mapped[int] = mapped_column(primary_key=True)
service_visit_id: Mapped[int] = mapped_column(ForeignKey("service_visits.id", ondelete="CASCADE"), index=True)
work_type: Mapped[str] = mapped_column(String(40), default="other", server_default="other", index=True)
title: Mapped[str] = mapped_column(String(180))
description: Mapped[str | None] = mapped_column(Text)
parts: Mapped[list | None] = mapped_column(JSON)
oil_brand: Mapped[str | None] = mapped_column(String(80))
oil_viscosity: Mapped[str | None] = mapped_column(String(40))
oil_volume: Mapped[Decimal | None] = mapped_column(Numeric(5, 2))
next_due_odometer: Mapped[int | None]
next_due_date: Mapped[date | None] = mapped_column(Date)
price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
visit = relationship("ServiceVisit", back_populates="work_items")
class ServiceCenterReview(Base):
__tablename__ = "service_center_reviews"
__table_args__ = (UniqueConstraint("service_center_id", "user_id", name="uq_service_review_user"),)
id: Mapped[int] = mapped_column(primary_key=True)
service_center_id: Mapped[int] = mapped_column(ForeignKey("service_centers.id", ondelete="CASCADE"), index=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
rating: Mapped[int] = mapped_column(Integer)
text: Mapped[str | None] = mapped_column(Text)
photo_urls: Mapped[list | None] = mapped_column(JSON)
status: Mapped[str] = mapped_column(String(24), default="published", server_default="published", index=True)
service_response: Mapped[str | None] = mapped_column(Text)
service_responded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
service_center = relationship("ServiceCenter", back_populates="reviews")
comments = relationship("ServiceCenterReviewComment", back_populates="review", cascade="all, delete-orphan")
class ServiceCenterReviewComment(Base):
__tablename__ = "service_center_review_comments"
id: Mapped[int] = mapped_column(primary_key=True)
review_id: Mapped[int] = mapped_column(ForeignKey("service_center_reviews.id", ondelete="CASCADE"), index=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
text: Mapped[str] = mapped_column(Text)
status: Mapped[str] = mapped_column(String(24), default="published", server_default="published", index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
review = relationship("ServiceCenterReview", back_populates="comments")
class VehicleDataChangeRequest(Base):
__tablename__ = "vehicle_data_change_requests"
id: Mapped[int] = mapped_column(primary_key=True)
vehicle_id: Mapped[int] = mapped_column(ForeignKey("cars.id", ondelete="CASCADE"), index=True)
requested_by_service_center_id: Mapped[int | None] = mapped_column(ForeignKey("service_centers.id", ondelete="SET NULL"), index=True)
requested_by_employee_id: Mapped[int | None] = mapped_column(ForeignKey("service_employees.id", ondelete="SET NULL"), index=True)
field_name: Mapped[str] = mapped_column(String(80), index=True)
old_value: Mapped[str | None] = mapped_column(Text)
new_value: Mapped[str | None] = mapped_column(Text)
status: Mapped[str] = mapped_column(String(24), default="pending", server_default="pending", index=True)
owner_user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[int] = mapped_column(primary_key=True)
actor_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), index=True)
actor_role: Mapped[str | None] = mapped_column(String(64))
action: Mapped[str] = mapped_column(String(120), index=True)
target_type: Mapped[str] = mapped_column(String(80), index=True)
target_id: Mapped[str | None] = mapped_column(String(80), index=True)
ip: Mapped[str | None] = mapped_column(String(64))
user_agent: Mapped[str | None] = mapped_column(String(256))
metadata_json: Mapped[dict | None] = mapped_column(JSON)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)