"""车辆加油登记 API""" from datetime import date, datetime from typing import Any, List, Optional from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request, status from pydantic import BaseModel, Field from sqlalchemy.orm import Session from app.core.database import get_db from app.core.db_auth import DbUserContext, get_db_current_user from app.core.tenant_context import get_tenant_no from app.schemas.pl.cancel_reason import CancelReasonBody from app.schemas.pl.vehicle_fuel import ( VehicleFuelPlateMonthSummaryResponse, VehicleFuelRegisterCreate, VehicleFuelRegisterListResponse, VehicleFuelRegisterRead, VehicleFuelRegisterUpdate, ) from app.services import pl_vehicle_fuel_service as svc from app.utils.excel_export import ( EXPORT_MAX_ROWS, ExportColumn, ExportValidationError, export_rows_to_xlsx_response, filter_export_columns, ) router = APIRouter(prefix="/vehicle-fuel-register", tags=["运输计划-加油登记"]) FUEL_CATEGORY_LABELS = { "bailian": "百链", "gas_station": "加油站", "shanxia_factory": "山峡", } FUEL_STATUS_LABELS = {"normal": "正常", "cancelled": "已作废"} def _merge_query(request: Request, **known) -> dict: out = {k: v for k, v in known.items() if v is not None and v != ""} for k, v in request.query_params.multi_items(): if k in ("page", "pageSize"): continue if k not in out and v not in (None, ""): out[k] = v return out def _param_get(params: dict, *keys: str) -> Any: for key in keys: if key in params and params[key] not in (None, ""): return params[key] return None def _as_bool(value: Any) -> bool: if isinstance(value, bool): return value if value is None: return False return str(value).strip().lower() in {"1", "true", "yes", "y"} def _body_to_params(body: BaseModel) -> dict: data = body.model_dump(by_alias=True, exclude_none=True) for key in ("ids", "columns"): data.pop(key, None) extra = getattr(body, "model_extra", None) or {} for key, value in extra.items(): if key not in data and value not in (None, ""): data[key] = value return data def _list_kwargs(params: dict) -> dict: return dict( q=_param_get(params, "q"), register_no=_param_get(params, "registerNo", "register_no"), license_plate=_param_get(params, "licensePlate", "license_plate"), fuel_category=_param_get(params, "fuelCategory", "fuel_category"), fuel_category_in=_param_get(params, "fuelCategoryIn", "fuel_category_in"), status=_param_get(params, "status"), status_in=_param_get(params, "statusIn", "status_in"), register_date_from=_param_get(params, "registerDateFrom", "register_date_from"), register_date_to=_param_get(params, "registerDateTo", "register_date_to"), registrant_id=_param_get(params, "registrantId", "registrant_id"), registrant_name=_param_get(params, "registrantName", "registrant_name"), odometer_km_op=_param_get(params, "odometerKmOp", "odometer_km_op"), odometer_km=_param_get(params, "odometerKm", "odometer_km"), odometer_km_min=_param_get(params, "odometerKmMin", "odometer_km_min"), odometer_km_max=_param_get(params, "odometerKmMax", "odometer_km_max"), fuel_liters_op=_param_get(params, "fuelLitersOp", "fuel_liters_op"), fuel_liters=_param_get(params, "fuelLiters", "fuel_liters"), fuel_liters_min=_param_get(params, "fuelLitersMin", "fuel_liters_min"), fuel_liters_max=_param_get(params, "fuelLitersMax", "fuel_liters_max"), fuel_tons_op=_param_get(params, "fuelTonsOp", "fuel_tons_op"), fuel_tons=_param_get(params, "fuelTons", "fuel_tons"), fuel_tons_min=_param_get(params, "fuelTonsMin", "fuel_tons_min"), fuel_tons_max=_param_get(params, "fuelTonsMax", "fuel_tons_max"), create_time_from=_param_get(params, "createTimeFrom", "create_time_from"), create_time_to=_param_get(params, "createTimeTo", "create_time_to"), mine_only=_as_bool(_param_get(params, "mineOnly", "mine_only")), ) @router.get("/", response_model=VehicleFuelRegisterListResponse) def list_registers( request: Request, q: Optional[str] = Query(default=None), fuel_category: Optional[str] = Query(default=None, alias="fuelCategory"), status: Optional[str] = Query(default=None), register_date_from: Optional[date] = Query(default=None, alias="registerDateFrom"), register_date_to: Optional[date] = Query(default=None, alias="registerDateTo"), registrant_id: Optional[str] = Query(default=None, alias="registrantId"), mine_only: Optional[bool] = Query(default=None, alias="mineOnly"), page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=500, alias="pageSize"), db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleFuelRegisterListResponse: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) params = _merge_query( request, q=q, fuelCategory=fuel_category, status=status, registerDateFrom=register_date_from, registerDateTo=register_date_to, registrantId=registrant_id, mineOnly=mine_only, ) items, total = svc.list_registers( db, tenant_no, user, **_list_kwargs(params), page=page, page_size=page_size, ) return VehicleFuelRegisterListResponse( items=items, total=total, page=page, pageSize=page_size ) FUEL_EXPORT_COLUMNS = [ ExportColumn(title="序号", field="seqNo"), ExportColumn(title="登记单号", field="registerNo"), ExportColumn(title="加油类别", field="fuelCategory"), ExportColumn(title="日期", field="registerDate"), ExportColumn(title="登记人", field="registrantName"), ExportColumn(title="车牌号", field="licensePlate"), ExportColumn(title="期初公里数", field="odometerKm"), ExportColumn(title="加油升数", field="fuelLiters"), ExportColumn(title="加油/吨", field="fuelTons"), ExportColumn(title="状态", field="status"), ExportColumn(title="备注", field="remark"), ExportColumn(title="创建时间", field="createTime"), ] class FuelRegisterExportBody(BaseModel): q: Optional[str] = None fuel_category: Optional[str] = Field(default=None, alias="fuelCategory") status: Optional[str] = None register_date_from: Optional[date] = Field(default=None, alias="registerDateFrom") register_date_to: Optional[date] = Field(default=None, alias="registerDateTo") registrant_id: Optional[str] = Field(default=None, alias="registrantId") mine_only: Optional[bool] = Field(default=None, alias="mineOnly") ids: Optional[List[str]] = None columns: Optional[List[str]] = None model_config = {"populate_by_name": True, "extra": "allow"} @router.post("/export") def export_registers( body: FuelRegisterExportBody, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ): try: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) params = _body_to_params(body) items, _ = svc.list_registers( db, tenant_no, user, **_list_kwargs(params), ids=body.ids, page=1, page_size=EXPORT_MAX_ROWS, max_page_size=EXPORT_MAX_ROWS, ) rows = [] for item in items: data = item.model_dump(by_alias=True) data["fuelCategory"] = FUEL_CATEGORY_LABELS.get( data.get("fuelCategory") or "", data.get("fuelCategory") ) data["status"] = FUEL_STATUS_LABELS.get( data.get("status") or "", data.get("status") ) for k, v in list(data.items()): if hasattr(v, "isoformat"): data[k] = str(v)[:19] rows.append(data) cols = filter_export_columns(FUEL_EXPORT_COLUMNS, body.columns) date_str = datetime.now().strftime("%Y%m%d") return export_rows_to_xlsx_response( db, tenant_no=tenant_no, sheet_title="加油登记", columns=cols, rows=rows, filename_stem=f"加油登记_{date_str}", ) except ExportValidationError as e: raise HTTPException(status_code=400, detail=str(e)) from e @router.get("/plate-month-summary", response_model=VehicleFuelPlateMonthSummaryResponse) def plate_month_summary( db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleFuelPlateMonthSummaryResponse: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.list_plate_month_summary(db, tenant_no, user) @router.get("/{id}", response_model=VehicleFuelRegisterRead) def get_register( id: str, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleFuelRegisterRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.get_register(db, tenant_no, user, id) @router.post("/", response_model=VehicleFuelRegisterRead, status_code=status.HTTP_201_CREATED) def create_register( payload: VehicleFuelRegisterCreate, mine_only: bool = Query(default=False, alias="mineOnly"), db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleFuelRegisterRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.create_register(db, tenant_no, user, payload, force_self=mine_only) @router.put("/{id}", response_model=VehicleFuelRegisterRead) def update_register( id: str, payload: VehicleFuelRegisterUpdate, mine_only: bool = Query(default=False, alias="mineOnly"), db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleFuelRegisterRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.update_register( db, tenant_no, user, id, payload, force_self=mine_only ) @router.post("/{id}/cancel", response_model=VehicleFuelRegisterRead) def cancel_register( id: str, body: Optional[CancelReasonBody] = Body(default=None), mine_only: bool = Query(default=False, alias="mineOnly"), db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleFuelRegisterRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.cancel_register( db, tenant_no, user, id, force_self=mine_only, cancel_reason=body.cancel_reason if body else None, ) @router.post("/{id}/restore", response_model=VehicleFuelRegisterRead) def restore_register( id: str, mine_only: bool = Query(default=False, alias="mineOnly"), db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleFuelRegisterRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.restore_register(db, tenant_no, user, id, force_self=mine_only)