from fastapi import APIRouter, HTTPException from typing import Optional from decimal import Decimal from database import get_session from models import FuelRecord, Vehicle from schemas import ( FuelRecordCreate, FuelRecordUpdate, FuelRecordDelete, FuelRecordResponse, ) router = APIRouter(prefix="/fuel-records", tags=["fuel_records"]) def calculate_fuel_consumption(session, record: FuelRecord) -> Optional[float]: """计算单次油耗 油耗计算逻辑: 1. 找到上一条加满油的记录 2. 计算两次加满之间的里程差 3. 计算两次加满之间的累计加油量(包括中间未加满的记录) 4. 油耗 = (累计加油量 / 里程差) * 100 """ if not record.is_full_tank: return None # 找到上一条加满油的记录(排除已删除) prev_record = ( session.query(FuelRecord) .filter( FuelRecord.vehicle_id == record.vehicle_id, FuelRecord.date < record.date, FuelRecord.is_full_tank == True, FuelRecord.is_deleted == False, ) .order_by(FuelRecord.date.desc(), FuelRecord.mileage.desc()) .first() ) # 如果同一天有更早的记录,也考虑进去 if not prev_record: prev_record = ( session.query(FuelRecord) .filter( FuelRecord.vehicle_id == record.vehicle_id, FuelRecord.date == record.date, FuelRecord.mileage < record.mileage, FuelRecord.is_full_tank == True, FuelRecord.is_deleted == False, ) .order_by(FuelRecord.mileage.desc()) .first() ) if not prev_record: return None mileage_diff = record.mileage - prev_record.mileage if mileage_diff <= 0: return None # 计算两次加满之间的累计加油量(包括中间未加满的记录) # 从上一次加满之后(不包括)到当前这次(包括)的所有加油记录 intermediate_records = ( session.query(FuelRecord) .filter( FuelRecord.vehicle_id == record.vehicle_id, FuelRecord.date >= prev_record.date, FuelRecord.date <= record.date, FuelRecord.mileage > prev_record.mileage, FuelRecord.mileage <= record.mileage, FuelRecord.is_deleted == False, ) .all() ) total_fuel = sum(float(r.fuel_amount) for r in intermediate_records) fuel_consumption = (total_fuel / mileage_diff) * 100 return round(fuel_consumption, 2) def get_record_with_consumption(session, record: FuelRecord) -> FuelRecordResponse: """获取记录并计算油耗""" fuel_consumption = calculate_fuel_consumption(session, record) return FuelRecordResponse( id=record.id, vehicle_id=record.vehicle_id, date=record.date, mileage=record.mileage, fuel_amount=float(record.fuel_amount), fuel_price=float(record.fuel_price) if record.fuel_price else None, total_cost=float(record.total_cost), is_full_tank=record.is_full_tank, notes=record.notes, fuel_consumption=fuel_consumption, is_deleted=record.is_deleted, created_at=record.created_at, updated_at=record.updated_at, ) @router.get("/list", response_model=list[FuelRecordResponse]) def get_fuel_records(vehicle_id: Optional[int] = None, limit: int = 50): """获取加油记录列表(排除已删除)""" with get_session() as session: query = session.query(FuelRecord).filter(FuelRecord.is_deleted == False) if vehicle_id: query = query.filter(FuelRecord.vehicle_id == vehicle_id) records = ( query.order_by(FuelRecord.date.desc(), FuelRecord.mileage.desc()) .limit(limit) .all() ) return [get_record_with_consumption(session, r) for r in records] @router.post("/create", response_model=FuelRecordResponse) def create_fuel_record(record: FuelRecordCreate): """添加加油记录""" with get_session() as session: # 验证车辆存在 vehicle = session.query(Vehicle).filter(Vehicle.id == record.vehicle_id).first() if not vehicle: raise HTTPException(status_code=404, detail="Vehicle not found") # 自动计算总价或单价 fuel_price = record.fuel_price total_cost = record.total_cost if fuel_price is None and total_cost is not None: # 只有总价,计算单价 fuel_price = total_cost / record.fuel_amount elif total_cost is None and fuel_price is not None: # 只有单价,计算总价 total_cost = fuel_price * record.fuel_amount elif total_cost is None and fuel_price is None: raise HTTPException( status_code=400, detail="Either fuel_price or total_cost must be provided", ) # 如果两者都有,使用传入的值(不重新计算,保留实付金额) db_record = FuelRecord( vehicle_id=record.vehicle_id, date=record.date, mileage=record.mileage, fuel_amount=Decimal(str(record.fuel_amount)), fuel_price=Decimal(str(fuel_price)) if fuel_price else None, total_cost=Decimal(str(total_cost)), is_full_tank=record.is_full_tank, notes=record.notes or "", ) session.add(db_record) session.commit() session.refresh(db_record) return get_record_with_consumption(session, db_record) @router.post("/update", response_model=FuelRecordResponse) def update_fuel_record(record_update: FuelRecordUpdate): """更新加油记录""" with get_session() as session: record = ( session.query(FuelRecord).filter(FuelRecord.id == record_update.id).first() ) if not record: raise HTTPException(status_code=404, detail="Fuel record not found") # 更新字段 if record_update.date is not None: record.date = record_update.date if record_update.mileage is not None: record.mileage = record_update.mileage if record_update.fuel_amount is not None: record.fuel_amount = Decimal(str(record_update.fuel_amount)) if record_update.is_full_tank is not None: record.is_full_tank = record_update.is_full_tank if record_update.notes is not None: record.notes = record_update.notes # 处理价格和总价 fuel_amount = float(record.fuel_amount) if ( record_update.fuel_price is not None and record_update.total_cost is not None ): record.fuel_price = Decimal(str(record_update.fuel_price)) record.total_cost = Decimal(str(record_update.total_cost)) elif record_update.fuel_price is not None: record.fuel_price = Decimal(str(record_update.fuel_price)) record.total_cost = Decimal(str(record_update.fuel_price * fuel_amount)) elif record_update.total_cost is not None: record.total_cost = Decimal(str(record_update.total_cost)) if fuel_amount > 0: record.fuel_price = Decimal(str(record_update.total_cost / fuel_amount)) session.commit() session.refresh(record) return get_record_with_consumption(session, record) @router.post("/delete") def delete_fuel_record(record: FuelRecordDelete): """软删除加油记录""" with get_session() as session: db_record = ( session.query(FuelRecord) .filter(FuelRecord.id == record.id, FuelRecord.is_deleted == False) .first() ) if not db_record: raise HTTPException(status_code=404, detail="Fuel record not found") db_record.is_deleted = True session.commit() return {"message": "Fuel record deleted successfully"}