carcost/api/routers/fuel_records.py
yuany3721 71e11eaf30 feat: Add OIDC authentication with Authentik and refactor project structure
Backend:
- Add auth.py for JWT token verification
- Update main.py to protect all routes with auth middleware
- Remove dashboard router (frontend handles aggregation)
- Add Docker support with Dockerfile and docker-compose.yml

Frontend:
- Add OIDC authentication using oidc-client-ts with PKCE flow
- Create router.js with auth guards for automatic login/logout
- Add api.js for unified Axios instance with auth headers
- Add composables: useAuth.js, useVehicleData.js for caching
- Add views/Main.vue as main application page
- Simplify App.vue to router-view container
- Add deploy-web.sh deployment script

Documentation:
- Update AGENTS.md with new architecture and auth flow
2026-04-12 13:31:27 +08:00

183 lines
6.5 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"])
@router.get("/list", response_model=list[FuelRecordResponse])
def get_fuel_records(vehicle_id: Optional[int] = None):
"""获取加油记录列表(排除已删除),返回原始数据不计算油耗"""
session = get_session()
try:
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()
).all()
# 直接返回原始数据,不计算油耗
return [
FuelRecordResponse(
id=r.id,
vehicle_id=r.vehicle_id,
date=r.date,
mileage=r.mileage,
fuel_amount=float(r.fuel_amount),
fuel_price=float(r.fuel_price) if r.fuel_price else None,
display_cost=float(r.display_cost) if r.display_cost else None,
actual_cost=float(r.actual_cost),
is_full_tank=r.is_full_tank,
notes=r.notes,
fuel_consumption=None, # 油耗由前端计算
is_deleted=r.is_deleted,
created_at=r.created_at,
updated_at=r.updated_at,
)
for r in records
]
finally:
session.close()
@router.post("/create", response_model=FuelRecordResponse)
def create_fuel_record(record: FuelRecordCreate):
"""添加加油记录"""
session = get_session()
try:
# 验证车辆存在
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
display_cost = record.display_cost
actual_cost = record.actual_cost
# 如果没有单价,根据总价计算
if fuel_price is None and actual_cost is not None and record.fuel_amount > 0:
fuel_price = actual_cost / record.fuel_amount
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,
display_cost=Decimal(str(display_cost)) if display_cost else None,
actual_cost=Decimal(str(actual_cost)),
is_full_tank=record.is_full_tank,
notes=record.notes or "",
)
session.add(db_record)
session.commit()
session.refresh(db_record)
# 返回原始数据,不计算油耗
return FuelRecordResponse(
id=db_record.id,
vehicle_id=db_record.vehicle_id,
date=db_record.date,
mileage=db_record.mileage,
fuel_amount=float(db_record.fuel_amount),
fuel_price=float(db_record.fuel_price) if db_record.fuel_price else None,
display_cost=float(db_record.display_cost)
if db_record.display_cost
else None,
actual_cost=float(db_record.actual_cost),
is_full_tank=db_record.is_full_tank,
notes=db_record.notes,
fuel_consumption=None, # 油耗由前端计算
is_deleted=db_record.is_deleted,
created_at=db_record.created_at,
updated_at=db_record.updated_at,
)
finally:
session.close()
@router.post("/update", response_model=FuelRecordResponse)
def update_fuel_record(record_update: FuelRecordUpdate):
"""更新加油记录"""
session = get_session()
try:
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.fuel_price is not None:
record.fuel_price = Decimal(str(record_update.fuel_price))
if record_update.display_cost is not None:
record.display_cost = Decimal(str(record_update.display_cost))
if record_update.actual_cost is not None:
record.actual_cost = Decimal(str(record_update.actual_cost))
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
session.commit()
session.refresh(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,
display_cost=float(record.display_cost) if record.display_cost else None,
actual_cost=float(record.actual_cost),
is_full_tank=record.is_full_tank,
notes=record.notes,
fuel_consumption=None, # 油耗由前端计算
is_deleted=record.is_deleted,
created_at=record.created_at,
updated_at=record.updated_at,
)
finally:
session.close()
@router.post("/delete")
def delete_fuel_record(record: FuelRecordDelete):
"""软删除加油记录"""
session = get_session()
try:
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"}
finally:
session.close()