222 lines
7.9 KiB
Python
222 lines
7.9 KiB
Python
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"}
|