"""车辆尿素 API""" from datetime import date, datetime from typing import Any, List, Optional from pydantic import BaseModel, Field from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request, status 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.utils.excel_export import ( EXPORT_MAX_ROWS, ExportColumn, ExportValidationError, export_rows_to_xlsx_response, filter_export_columns, ) from app.schemas.pl.vehicle_urea import ( VehicleUreaApplyCreate, VehicleUreaApplyListResponse, VehicleUreaApplyRead, VehicleUreaApplyUpdate, VehicleUreaDisburseBoardListResponse, VehicleUreaDisburseCreate, VehicleUreaDisburseRead, VehicleUreaDisburseUpdate, ) from app.services import pl_vehicle_urea_service as svc apply_router = APIRouter(prefix="/vehicle-urea-apply", tags=["运输计划-车辆尿素申请"]) disburse_router = APIRouter(prefix="/vehicle-urea-disburse", tags=["运输计划-车辆尿素发放"]) 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 _merge_query(request: Request, **declared: Any) -> dict: params = dict(declared) for key, value in request.query_params.multi_items(): if key not in params or params[key] in (None, ""): params[key] = value return params def _apply_list_kwargs(params: dict) -> dict: return dict( q=_param_get(params, "q"), apply_no=_param_get(params, "applyNo", "apply_no"), license_plate=_param_get(params, "licensePlate", "license_plate"), status=_param_get(params, "status"), status_in=_param_get(params, "statusIn", "status_in"), apply_date_from=_param_get(params, "applyDateFrom", "apply_date_from"), apply_date_to=_param_get(params, "applyDateTo", "apply_date_to"), warehouse_in_date_from=_param_get( params, "warehouseInDateFrom", "warehouse_in_date_from" ), warehouse_in_date_to=_param_get( params, "warehouseInDateTo", "warehouse_in_date_to" ), applicant_id=_param_get(params, "applicantId", "applicant_id"), ) def _disburse_list_kwargs(params: dict) -> dict: return dict( q=_param_get(params, "q"), apply_no=_param_get(params, "applyNo", "apply_no"), license_plate=_param_get(params, "licensePlate", "license_plate"), apply_status=_param_get(params, "applyStatus", "apply_status"), apply_status_in=_param_get(params, "applyStatusIn", "apply_status_in"), apply_date_from=_param_get(params, "applyDateFrom", "apply_date_from"), apply_date_to=_param_get(params, "applyDateTo", "apply_date_to"), disburse_date_from=_param_get(params, "disburseDateFrom", "disburse_date_from"), disburse_date_to=_param_get(params, "disburseDateTo", "disburse_date_to"), ) @apply_router.get("/", response_model=VehicleUreaApplyListResponse) def list_applies( request: Request, q: Optional[str] = Query(default=None), status: Optional[str] = Query(default=None), apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"), apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"), applicant_id: Optional[str] = Query(default=None, alias="applicantId"), 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), ) -> VehicleUreaApplyListResponse: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) params = _merge_query( request, q=q, status=status, applyDateFrom=apply_date_from, applyDateTo=apply_date_to, applicantId=applicant_id, ) items, total = svc.list_applies( db, tenant_no, user, **_apply_list_kwargs(params), page=page, page_size=page_size, ) return VehicleUreaApplyListResponse( items=items, total=total, page=page, pageSize=page_size ) UREA_APPLY_EXPORT_COLUMNS = [ ExportColumn(title="申请单号", field="applyNo"), ExportColumn(title="车牌号", field="licensePlate"), ExportColumn(title="申请时间", field="applyDate"), ExportColumn(title="入库时间", field="warehouseInDate"), ExportColumn(title="单位", field="unit"), ExportColumn(title="入库数量", field="quantity"), ExportColumn(title="申请人", field="applicantName"), ExportColumn(title="状态", field="status"), ] class UreaApplyExportBody(BaseModel): q: Optional[str] = None status: Optional[str] = None status_in: Optional[str] = Field(default=None, alias="statusIn") apply_no: Optional[str] = Field(default=None, alias="applyNo") license_plate: Optional[str] = Field(default=None, alias="licensePlate") apply_date_from: Optional[date] = Field(default=None, alias="applyDateFrom") apply_date_to: Optional[date] = Field(default=None, alias="applyDateTo") warehouse_in_date_from: Optional[date] = Field( default=None, alias="warehouseInDateFrom" ) warehouse_in_date_to: Optional[date] = Field( default=None, alias="warehouseInDateTo" ) ids: Optional[List[str]] = None columns: Optional[List[str]] = None model_config = {"populate_by_name": True, "extra": "allow"} @apply_router.post("/export") def export_urea_applies( body: UreaApplyExportBody, 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.model_dump(by_alias=True, exclude_none=True) items, _ = svc.list_applies( db, tenant_no, user, **_apply_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) for k, v in list(data.items()): if hasattr(v, 'isoformat'): data[k] = str(v)[:19] rows.append(data) cols = filter_export_columns(UREA_APPLY_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 @apply_router.get("/{id}", response_model=VehicleUreaApplyRead) def get_apply( id: str, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleUreaApplyRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.get_apply(db, tenant_no, user, id) @apply_router.post("/", response_model=VehicleUreaApplyRead, status_code=status.HTTP_201_CREATED) def create_apply( payload: VehicleUreaApplyCreate, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleUreaApplyRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.create_apply(db, tenant_no, user, payload) @apply_router.put("/{id}", response_model=VehicleUreaApplyRead) def update_apply( id: str, payload: VehicleUreaApplyUpdate, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleUreaApplyRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.update_apply(db, tenant_no, user, id, payload) @apply_router.post("/{id}/cancel", response_model=VehicleUreaApplyRead) def cancel_apply( id: str, body: Optional[CancelReasonBody] = Body(default=None), db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleUreaApplyRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.cancel_apply( db, tenant_no, user, id, cancel_reason=body.cancel_reason if body else None ) @apply_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT) def delete_applies( ids: str, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> None: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) apply_ids = [item for item in ids.split(",") if item] svc.delete_applies(db, tenant_no, user, apply_ids) @disburse_router.get("/disburse-board", response_model=VehicleUreaDisburseBoardListResponse) def list_disburse_board( request: Request, q: Optional[str] = Query(default=None), apply_status: Optional[str] = Query(default=None, alias="applyStatus"), apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"), apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"), disburse_date_from: Optional[date] = Query(default=None, alias="disburseDateFrom"), disburse_date_to: Optional[date] = Query(default=None, alias="disburseDateTo"), 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), ) -> VehicleUreaDisburseBoardListResponse: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) params = _merge_query( request, q=q, applyStatus=apply_status, applyDateFrom=apply_date_from, applyDateTo=apply_date_to, disburseDateFrom=disburse_date_from, disburseDateTo=disburse_date_to, ) items, total = svc.list_disburse_board( db, tenant_no, user, **_disburse_list_kwargs(params), page=page, page_size=page_size, ) return VehicleUreaDisburseBoardListResponse( items=items, total=total, page=page, pageSize=page_size ) UREA_DISBURSE_EXPORT_COLUMNS = [ ExportColumn(title="申请单号", field="applyNo"), ExportColumn(title="车牌号", field="licensePlate"), ExportColumn(title="申请时间", field="applyDate"), ExportColumn(title="入库时间", field="warehouseInDate"), ExportColumn(title="申请人", field="applicantName"), ExportColumn(title="单位", field="unit"), ExportColumn(title="入库数量", field="quantity"), ExportColumn(title="申请状态", field="applyStatus"), ExportColumn(title="发放日期", field="disburseDate"), ExportColumn(title="发放金额", field="amount"), ] class UreaDisburseExportBody(BaseModel): q: Optional[str] = None apply_status: Optional[str] = Field(default=None, alias="applyStatus") apply_status_in: Optional[str] = Field(default=None, alias="applyStatusIn") apply_no: Optional[str] = Field(default=None, alias="applyNo") license_plate: Optional[str] = Field(default=None, alias="licensePlate") apply_date_from: Optional[date] = Field(default=None, alias="applyDateFrom") apply_date_to: Optional[date] = Field(default=None, alias="applyDateTo") disburse_date_from: Optional[date] = Field(default=None, alias="disburseDateFrom") disburse_date_to: Optional[date] = Field(default=None, alias="disburseDateTo") ids: Optional[List[str]] = None columns: Optional[List[str]] = None model_config = {"populate_by_name": True, "extra": "allow"} @disburse_router.post("/disburse-board/export") def export_urea_disburse_board( body: UreaDisburseExportBody, 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.model_dump(by_alias=True, exclude_none=True) items, _ = svc.list_disburse_board( db, tenant_no, user, **_disburse_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) for k, v in list(data.items()): if hasattr(v, 'isoformat'): data[k] = str(v)[:19] rows.append(data) cols = filter_export_columns(UREA_DISBURSE_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 @disburse_router.get("/{id}", response_model=VehicleUreaDisburseRead) def get_disburse( id: str, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleUreaDisburseRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.get_disburse(db, tenant_no, user, id) @disburse_router.post("/", response_model=VehicleUreaDisburseRead, status_code=status.HTTP_201_CREATED) def create_disburse( payload: VehicleUreaDisburseCreate, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleUreaDisburseRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.create_disburse(db, tenant_no, user, payload) @disburse_router.put("/{id}", response_model=VehicleUreaDisburseRead) def update_disburse( id: str, payload: VehicleUreaDisburseUpdate, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> VehicleUreaDisburseRead: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) return svc.update_disburse(db, tenant_no, user, id, payload) @disburse_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT) def delete_disburses( ids: str, db: Session = Depends(get_db), user: DbUserContext = Depends(get_db_current_user), ) -> None: tenant_no = get_tenant_no(db, user.to_tenant_user_dict()) disburse_ids = [item for item in ids.split(",") if item] svc.delete_disburses(db, tenant_no, user, disburse_ids)