carcost/api/schemas.py
2026-04-10 23:24:51 +08:00

260 lines
5.6 KiB
Python

"""
统一的 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]