add vehicle service profile settings
This commit is contained in:
73
app/api/service_centers.py
Normal file
73
app/api/service_centers.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.session import get_session
|
||||
from app.models.car import Car, CarServiceLink, ServiceCenter, ServiceInboxMessage
|
||||
from app.schemas.service_center import (
|
||||
CarServiceLinkCreate,
|
||||
CarServiceLinkRead,
|
||||
ServiceCenterCreate,
|
||||
ServiceCenterRead,
|
||||
ServiceInboxCreate,
|
||||
ServiceInboxRead,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/service-centers", tags=["service-centers"])
|
||||
|
||||
|
||||
@router.post("", response_model=ServiceCenterRead, status_code=status.HTTP_201_CREATED)
|
||||
async def create_service_center(
|
||||
payload: ServiceCenterCreate, session: AsyncSession = Depends(get_session)
|
||||
) -> ServiceCenter:
|
||||
center = ServiceCenter(**payload.model_dump())
|
||||
session.add(center)
|
||||
await session.commit()
|
||||
await session.refresh(center)
|
||||
return center
|
||||
|
||||
|
||||
@router.get("", response_model=list[ServiceCenterRead])
|
||||
async def list_service_centers(session: AsyncSession = Depends(get_session)) -> list[ServiceCenter]:
|
||||
result = await session.execute(select(ServiceCenter).order_by(ServiceCenter.name))
|
||||
return list(result.scalars())
|
||||
|
||||
|
||||
@router.post("/links", response_model=CarServiceLinkRead, status_code=status.HTTP_201_CREATED)
|
||||
async def link_car_to_service(
|
||||
payload: CarServiceLinkCreate, session: AsyncSession = Depends(get_session)
|
||||
) -> CarServiceLink:
|
||||
if await session.get(Car, payload.car_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Car not found")
|
||||
if await session.get(ServiceCenter, payload.service_center_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Service center not found")
|
||||
link = CarServiceLink(**payload.model_dump())
|
||||
session.add(link)
|
||||
await session.commit()
|
||||
await session.refresh(link)
|
||||
return link
|
||||
|
||||
|
||||
@router.post("/inbox", response_model=ServiceInboxRead, status_code=status.HTTP_201_CREATED)
|
||||
async def receive_service_message(
|
||||
payload: ServiceInboxCreate, session: AsyncSession = Depends(get_session)
|
||||
) -> ServiceInboxMessage:
|
||||
service_center_id = payload.service_center_id
|
||||
if not service_center_id and payload.source_chat_id:
|
||||
result = await session.execute(
|
||||
select(ServiceCenter).where(ServiceCenter.telegram_chat_id == payload.source_chat_id)
|
||||
)
|
||||
center = result.scalar_one_or_none()
|
||||
service_center_id = center.id if center else None
|
||||
|
||||
message = ServiceInboxMessage(
|
||||
source_chat_id=payload.source_chat_id,
|
||||
raw_text=payload.raw_text,
|
||||
car_id=payload.car_id,
|
||||
service_center_id=service_center_id,
|
||||
parsed_status="pending",
|
||||
)
|
||||
session.add(message)
|
||||
await session.commit()
|
||||
await session.refresh(message)
|
||||
return message
|
||||
@@ -121,6 +121,16 @@ async def seed_mock_usage(session: AsyncSession, owner: User) -> None:
|
||||
year=year,
|
||||
plate_number=f"{MOCK_PLATE_PREFIX}-{index:02d}",
|
||||
fuel_type=fuel_type,
|
||||
target_consumption_l_per_100km=None if fuel_type == "electric" else Decimal("7.80"),
|
||||
fuel_tank_volume_l=None if fuel_type == "electric" else Decimal("58.00"),
|
||||
engine_oil_type=None if fuel_type == "electric" else "5W-30 API SP",
|
||||
engine_oil_volume_l=None if fuel_type == "electric" else Decimal("4.50"),
|
||||
transmission_fluid_type="EV reduction gear oil" if fuel_type == "electric" else "ATF / CVTF по допуску",
|
||||
transmission_fluid_volume_l=Decimal("1.20") if fuel_type == "electric" else Decimal("7.00"),
|
||||
coolant_type="LLC",
|
||||
brake_fluid_type="DOT 4",
|
||||
tire_pressure_front_bar=Decimal("2.30"),
|
||||
tire_pressure_rear_bar=Decimal("2.20"),
|
||||
purchase_date=date(year, min(index, 12), 10),
|
||||
purchase_price=price,
|
||||
current_odometer=start_odo,
|
||||
|
||||
@@ -2,7 +2,7 @@ from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.api import cars, catalog, entries, ocr, users
|
||||
from app.api import cars, catalog, entries, ocr, service_centers, users
|
||||
|
||||
app = FastAPI(title="Drivers Bot API", version="0.1.0")
|
||||
|
||||
@@ -19,6 +19,7 @@ app.include_router(catalog.router, prefix="/api")
|
||||
app.include_router(cars.router, prefix="/api")
|
||||
app.include_router(entries.router, prefix="/api")
|
||||
app.include_router(ocr.router, prefix="/api")
|
||||
app.include_router(service_centers.router, prefix="/api")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, Integer, Numeric, String, UniqueConstraint, func
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
@@ -20,6 +20,16 @@ class Car(Base):
|
||||
plate_number: Mapped[str | None] = mapped_column(String(32))
|
||||
vin: Mapped[str | None] = mapped_column(String(32))
|
||||
fuel_type: Mapped[str | None] = mapped_column(String(32))
|
||||
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))
|
||||
purchase_date: Mapped[date | None] = mapped_column(Date)
|
||||
purchase_price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
|
||||
current_odometer: Mapped[int | None]
|
||||
@@ -31,6 +41,7 @@ class Car(Base):
|
||||
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")
|
||||
service_links = relationship("CarServiceLink", back_populates="car", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class CarMake(Base):
|
||||
@@ -74,3 +85,48 @@ class CarTrim(Base):
|
||||
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)
|
||||
telegram_chat_id: Mapped[str | None] = mapped_column(String(80), unique=True, index=True)
|
||||
contact_phone: Mapped[str | None] = mapped_column(String(40))
|
||||
address: Mapped[str | None] = mapped_column(String(240))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
car_links = relationship("CarServiceLink", back_populates="service_center", cascade="all, delete-orphan")
|
||||
inbox_messages = relationship("ServiceInboxMessage", back_populates="service_center")
|
||||
|
||||
|
||||
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)
|
||||
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 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")
|
||||
|
||||
@@ -13,6 +13,16 @@ class CarBase(BaseModel):
|
||||
plate_number: str | None = None
|
||||
vin: str | None = None
|
||||
fuel_type: str | None = None
|
||||
target_consumption_l_per_100km: Decimal | None = None
|
||||
fuel_tank_volume_l: 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
|
||||
purchase_date: date | None = None
|
||||
purchase_price: Decimal | None = None
|
||||
current_odometer: int | None = None
|
||||
@@ -31,6 +41,16 @@ class CarUpdate(BaseModel):
|
||||
plate_number: str | None = None
|
||||
vin: str | None = None
|
||||
fuel_type: str | None = None
|
||||
target_consumption_l_per_100km: Decimal | None = None
|
||||
fuel_tank_volume_l: 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
|
||||
purchase_date: date | None = None
|
||||
purchase_price: Decimal | None = None
|
||||
current_odometer: int | None = None
|
||||
|
||||
48
app/schemas/service_center.py
Normal file
48
app/schemas/service_center.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class ServiceCenterCreate(BaseModel):
|
||||
name: str
|
||||
telegram_chat_id: str | None = None
|
||||
contact_phone: str | None = None
|
||||
address: str | None = None
|
||||
|
||||
|
||||
class ServiceCenterRead(ServiceCenterCreate):
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class CarServiceLinkCreate(BaseModel):
|
||||
car_id: int
|
||||
service_center_id: int
|
||||
external_vehicle_ref: str | None = None
|
||||
is_active: bool = True
|
||||
|
||||
|
||||
class CarServiceLinkRead(CarServiceLinkCreate):
|
||||
id: int
|
||||
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
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
Reference in New Issue
Block a user