""" 统一的 Pydantic 数据模型 """ from pydantic import BaseModel, field_validator, ConfigDict from typing import Optional, List, Union from datetime import date, datetime # ==================== 车辆相关模型 ==================== class VehicleBase(BaseModel): """车辆基础模型""" name: str purchase_date: Optional[date] = None initial_mileage: int = 0 class VehicleCreate(VehicleBase): """创建车辆请求模型""" pass class VehicleUpdate(BaseModel): """更新车辆请求模型""" id: int name: Optional[str] = None purchase_date: Optional[date] = None initial_mileage: Optional[int] = None class VehicleDelete(BaseModel): """删除车辆请求模型""" id: int class VehicleResponse(VehicleBase): """车辆响应模型""" id: int is_deleted: bool = False created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class Config: from_attributes = True class VehicleStats(BaseModel): """车辆统计信息""" total_mileage: int total_fuel_cost: float total_fuel_amount: float avg_fuel_consumption: Optional[float] # ==================== 加油记录相关模型 ==================== class FuelRecordBase(BaseModel): """加油记录基础模型""" vehicle_id: int date: date mileage: int fuel_amount: float fuel_price: Optional[float] = None total_cost: Optional[float] = None is_full_tank: bool = True notes: Optional[str] = "" class FuelRecordCreate(FuelRecordBase): """创建加油记录请求模型""" @field_validator("total_cost", "fuel_price", mode="before") @classmethod def validate_costs(cls, v): if v is not None: return round(v, 2) return v class FuelRecordUpdate(BaseModel): """更新加油记录请求模型""" model_config = ConfigDict(arbitrary_types_allowed=True) id: int date: Union[date, str, None] = None mileage: Optional[int] = None fuel_amount: Optional[float] = None fuel_price: Optional[float] = None total_cost: Optional[float] = None is_full_tank: Optional[bool] = None notes: Optional[str] = None @field_validator("date", mode="before") @classmethod def parse_date(cls, v): if v is None: return None if isinstance(v, date): return v if isinstance(v, str): return date.fromisoformat(v) raise ValueError("Invalid date format") class FuelRecordDelete(BaseModel): """删除加油记录请求模型""" id: int class FuelRecordResponse(BaseModel): """加油记录响应模型""" id: int vehicle_id: int date: date mileage: int fuel_amount: float fuel_price: Optional[float] total_cost: float is_full_tank: bool notes: str fuel_consumption: Optional[float] = None # 单次油耗,计算得出 is_deleted: bool = False created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class Config: from_attributes = True # ==================== 费用记录相关模型 ==================== # 费用类型枚举 COST_TYPES = ["保养", "维修", "保险", "停车", "过路费", "洗车", "违章", "其他"] class CostBase(BaseModel): """费用记录基础模型""" vehicle_id: int date: date type: str amount: float mileage: Optional[int] = None notes: Optional[str] = "" is_installment: Optional[bool] = False installment_months: Optional[int] = 12 class CostCreate(CostBase): """创建费用记录请求模型""" pass class CostUpdate(BaseModel): """更新费用记录请求模型""" model_config = ConfigDict(arbitrary_types_allowed=True) id: int date: Union[date, str, None] = None type: Optional[str] = None amount: Optional[float] = None mileage: Optional[int] = None notes: Optional[str] = None is_installment: Optional[bool] = None installment_months: Optional[int] = None @field_validator("date", mode="before") @classmethod def parse_date(cls, v): if v is None: return None if isinstance(v, date): return v if isinstance(v, str): return date.fromisoformat(v) raise ValueError("Invalid date format") class CostDelete(BaseModel): """删除费用记录请求模型""" id: int class CostResponse(BaseModel): """费用记录响应模型""" id: int vehicle_id: int date: date type: str amount: float mileage: Optional[int] notes: str is_installment: bool installment_months: int is_deleted: bool = False created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class Config: from_attributes = True # ==================== 仪表板相关模型 ==================== class FuelRecordItem(BaseModel): """仪表板中使用的加油记录项""" id: int date: date mileage: int fuel_amount: float total_cost: float fuel_consumption: Optional[float] class DashboardData(BaseModel): """仪表板数据响应""" vehicle_id: int vehicle_name: str purchase_date: Optional[date] total_mileage: int total_fuel_cost: float avg_fuel_consumption: Optional[float] avg_daily_km: Optional[float] recent_fuel_records: List[FuelRecordItem] # ==================== 通用响应模型 ==================== class MessageResponse(BaseModel): """通用消息响应""" message: str class CostTypesResponse(BaseModel): """费用类型列表响应""" types: List[str]