624 lines
24 KiB
Python
624 lines
24 KiB
Python
"""车辆 ETC/油卡充值 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_recharge import (
|
||
VehicleRechargeApplyCreate,
|
||
VehicleRechargeApplyListResponse,
|
||
VehicleRechargeApplyOption,
|
||
VehicleRechargeApplyRead,
|
||
VehicleRechargeApplyUpdate,
|
||
VehicleRechargeDisburseBoardListResponse,
|
||
VehicleRechargeRecordCreate,
|
||
VehicleRechargeRecordListResponse,
|
||
VehicleRechargeRecordRead,
|
||
VehicleRechargeRecordUpdate,
|
||
)
|
||
from app.services import pl_vehicle_recharge_service as svc
|
||
|
||
apply_router = APIRouter(prefix="/vehicle-recharge-apply", tags=["运输计划-ETC油卡申请"])
|
||
record_router = APIRouter(prefix="/vehicle-recharge-record", tags=["运输计划-ETC油卡发放"])
|
||
|
||
RECHARGE_TYPE_LABELS = {"etc": "ETC", "fuel": "油卡"}
|
||
RECHARGE_APPLY_STATUS_LABELS = {
|
||
"draft": "草稿",
|
||
"pending": "待发放",
|
||
"in_approval": "审批中",
|
||
"rejected": "已驳回",
|
||
"disbursed": "已发放",
|
||
"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 _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"),
|
||
recharge_type=_param_get(params, "rechargeType", "recharge_type"),
|
||
recharge_type_in=_param_get(params, "rechargeTypeIn", "recharge_type_in"),
|
||
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"),
|
||
applicant_id=_param_get(params, "applicantId", "applicant_id"),
|
||
applicant_name=_param_get(params, "applicantName", "applicant_name"),
|
||
remark=_param_get(params, "remark"),
|
||
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"),
|
||
create_time_from=_param_get(params, "createTimeFrom", "create_time_from"),
|
||
create_time_to=_param_get(params, "createTimeTo", "create_time_to"),
|
||
sort_field=_param_get(params, "sortField", "sort_field"),
|
||
sort_order=_param_get(params, "sortOrder", "sort_order"),
|
||
mine_vehicles=_as_bool(_param_get(params, "mineVehicles", "mine_vehicles")),
|
||
)
|
||
|
||
|
||
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"),
|
||
recharge_type=_param_get(params, "rechargeType", "recharge_type"),
|
||
recharge_type_in=_param_get(params, "rechargeTypeIn", "recharge_type_in"),
|
||
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"),
|
||
recharge_date_from=_param_get(params, "rechargeDateFrom", "recharge_date_from"),
|
||
recharge_date_to=_param_get(params, "rechargeDateTo", "recharge_date_to"),
|
||
applicant_name=_param_get(params, "applicantName", "applicant_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"),
|
||
recharge_order_no=_param_get(params, "rechargeOrderNo", "recharge_order_no"),
|
||
card_no=_param_get(params, "cardNo", "card_no"),
|
||
gas_station=_param_get(params, "gasStation", "gas_station"),
|
||
amount_op=_param_get(params, "amountOp", "amount_op"),
|
||
amount=_param_get(params, "amount"),
|
||
amount_min=_param_get(params, "amountMin", "amount_min"),
|
||
amount_max=_param_get(params, "amountMax", "amount_max"),
|
||
record_create_by_name=_param_get(
|
||
params, "recordCreateByName", "record_create_by_name"
|
||
),
|
||
sort_field=_param_get(params, "sortField", "sort_field"),
|
||
sort_order=_param_get(params, "sortOrder", "sort_order"),
|
||
)
|
||
|
||
|
||
@apply_router.get("/", response_model=VehicleRechargeApplyListResponse)
|
||
def list_applies(
|
||
request: Request,
|
||
q: Optional[str] = Query(default=None),
|
||
recharge_type: Optional[str] = Query(default=None, alias="rechargeType"),
|
||
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"),
|
||
mine_vehicles: Optional[bool] = Query(default=None, alias="mineVehicles"),
|
||
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),
|
||
) -> VehicleRechargeApplyListResponse:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
params = _merge_query(
|
||
request,
|
||
q=q,
|
||
rechargeType=recharge_type,
|
||
status=status,
|
||
applyDateFrom=apply_date_from,
|
||
applyDateTo=apply_date_to,
|
||
applicantId=applicant_id,
|
||
mineVehicles=mine_vehicles,
|
||
)
|
||
items, total = svc.list_applies(
|
||
db,
|
||
tenant_no,
|
||
user,
|
||
**_apply_list_kwargs(params),
|
||
page=page,
|
||
page_size=page_size,
|
||
)
|
||
return VehicleRechargeApplyListResponse(
|
||
items=items, total=total, page=page, pageSize=page_size
|
||
)
|
||
|
||
|
||
@apply_router.get("/pending-options", response_model=list[VehicleRechargeApplyOption])
|
||
def pending_apply_options(
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> list[VehicleRechargeApplyOption]:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
rows = svc.list_pending_apply_options(db, tenant_no, user)
|
||
return [VehicleRechargeApplyOption.model_validate(row) for row in rows]
|
||
|
||
|
||
|
||
RECHARGE_APPLY_EXPORT_COLUMNS = [
|
||
ExportColumn(title="申请单号", field="applyNo"),
|
||
ExportColumn(title="车牌号", field="licensePlate"),
|
||
ExportColumn(title="充值类型", field="rechargeType"),
|
||
ExportColumn(title="申请日期", field="applyDate"),
|
||
ExportColumn(title="期初公里数", field="odometerKm"),
|
||
ExportColumn(title="申请人", field="applicantName"),
|
||
ExportColumn(title="附件", field="attachmentUrls"),
|
||
ExportColumn(title="备注", field="remark"),
|
||
ExportColumn(title="状态", field="status"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class RechargeApplyExportBody(BaseModel):
|
||
q: Optional[str] = None
|
||
recharge_type: Optional[str] = Field(default=None, alias="rechargeType")
|
||
status: Optional[str] = None
|
||
apply_date_from: Optional[date] = Field(default=None, alias="applyDateFrom")
|
||
apply_date_to: Optional[date] = Field(default=None, alias="applyDateTo")
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
export_format: Optional[str] = Field(
|
||
default=None,
|
||
alias="exportFormat",
|
||
description="空=Excel;archive_zip=数据及附件 ZIP",
|
||
)
|
||
model_config = {"populate_by_name": True, "extra": "allow"}
|
||
|
||
|
||
@apply_router.post("/export")
|
||
def export_recharge_applies(
|
||
body: RechargeApplyExportBody,
|
||
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_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)
|
||
data["rechargeType"] = RECHARGE_TYPE_LABELS.get(
|
||
data.get("rechargeType") or "", data.get("rechargeType")
|
||
)
|
||
data["status"] = RECHARGE_APPLY_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)
|
||
|
||
export_format = (body.export_format or "").strip().lower()
|
||
cols = filter_export_columns(RECHARGE_APPLY_EXPORT_COLUMNS, body.columns)
|
||
|
||
if export_format == "archive_zip":
|
||
from app.services.export_attachment_zip import (
|
||
build_attachment_zip_streaming_response,
|
||
urls_to_attachment_items,
|
||
)
|
||
|
||
if not rows:
|
||
raise ExportValidationError("没有可导出的数据")
|
||
|
||
# ZIP 总表用超链接列,去掉原始 URL 列
|
||
zip_cols = [c for c in cols if c.field != "attachmentUrls"]
|
||
attachments_by_id: dict = {}
|
||
for i, data in enumerate(rows, start=1):
|
||
data["rowNo"] = i
|
||
rid = str(data.get("id") or "").strip()
|
||
if not rid:
|
||
continue
|
||
attachments_by_id[rid] = urls_to_attachment_items(
|
||
data.get("attachmentUrls")
|
||
)
|
||
stamp = datetime.now().strftime("%Y%m%d%H%M")
|
||
return build_attachment_zip_streaming_response(
|
||
db=db,
|
||
tenant_no=tenant_no,
|
||
columns=zip_cols,
|
||
rows=rows,
|
||
attachments_by_id=attachments_by_id,
|
||
stamp=stamp,
|
||
package_label="充值申请",
|
||
id_field="id",
|
||
sheet_title="充值申请",
|
||
xlsx_basename="(总表)充值申请.xlsx",
|
||
)
|
||
|
||
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=VehicleRechargeApplyRead)
|
||
def get_apply(
|
||
id: str,
|
||
mine_vehicles: bool = Query(default=False, alias="mineVehicles"),
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeApplyRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.get_apply(db, tenant_no, user, id, mine_vehicles=mine_vehicles)
|
||
|
||
|
||
@apply_router.post("/{id}/submit", response_model=VehicleRechargeApplyRead)
|
||
def submit_apply(
|
||
id: str,
|
||
mine_vehicles: bool = Query(default=False, alias="mineVehicles"),
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeApplyRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.submit_apply(db, tenant_no, user, id, mine_vehicles=mine_vehicles)
|
||
|
||
|
||
@apply_router.post("/", response_model=VehicleRechargeApplyRead, status_code=status.HTTP_201_CREATED)
|
||
def create_apply(
|
||
payload: VehicleRechargeApplyCreate,
|
||
mine_vehicles: bool = Query(default=False, alias="mineVehicles"),
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeApplyRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.create_apply(
|
||
db, tenant_no, user, payload, mine_vehicles=mine_vehicles
|
||
)
|
||
|
||
|
||
@apply_router.put("/{id}", response_model=VehicleRechargeApplyRead)
|
||
def update_apply(
|
||
id: str,
|
||
payload: VehicleRechargeApplyUpdate,
|
||
mine_vehicles: bool = Query(default=False, alias="mineVehicles"),
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeApplyRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.update_apply(
|
||
db, tenant_no, user, id, payload, mine_vehicles=mine_vehicles
|
||
)
|
||
|
||
|
||
@apply_router.post("/{id}/cancel", response_model=VehicleRechargeApplyRead)
|
||
def cancel_apply(
|
||
id: str,
|
||
body: Optional[CancelReasonBody] = Body(default=None),
|
||
mine_vehicles: bool = Query(default=False, alias="mineVehicles"),
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeApplyRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.cancel_apply(
|
||
db,
|
||
tenant_no,
|
||
user,
|
||
id,
|
||
mine_vehicles=mine_vehicles,
|
||
cancel_reason=body.cancel_reason if body else None,
|
||
)
|
||
|
||
|
||
@apply_router.post("/{id}/withdraw", response_model=VehicleRechargeApplyRead)
|
||
def withdraw_apply(
|
||
id: str,
|
||
mine_vehicles: bool = Query(default=False, alias="mineVehicles"),
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeApplyRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.withdraw_apply(
|
||
db, tenant_no, user, id, mine_vehicles=mine_vehicles
|
||
)
|
||
|
||
|
||
@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)
|
||
|
||
|
||
@record_router.get("/", response_model=VehicleRechargeRecordListResponse)
|
||
def list_records(
|
||
q: Optional[str] = Query(default=None),
|
||
recharge_type: Optional[str] = Query(default=None, alias="rechargeType"),
|
||
recharge_date_from: Optional[date] = Query(default=None, alias="rechargeDateFrom"),
|
||
recharge_date_to: Optional[date] = Query(default=None, alias="rechargeDateTo"),
|
||
gas_station: Optional[str] = Query(default=None, alias="gasStation"),
|
||
apply_no: Optional[str] = Query(default=None, alias="applyNo"),
|
||
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),
|
||
) -> VehicleRechargeRecordListResponse:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
items, total = svc.list_records(
|
||
db,
|
||
tenant_no,
|
||
user,
|
||
q=q,
|
||
recharge_type=recharge_type,
|
||
recharge_date_from=recharge_date_from,
|
||
recharge_date_to=recharge_date_to,
|
||
gas_station=gas_station,
|
||
apply_no=apply_no,
|
||
page=page,
|
||
page_size=page_size,
|
||
)
|
||
return VehicleRechargeRecordListResponse(
|
||
items=items, total=total, page=page, pageSize=page_size
|
||
)
|
||
|
||
|
||
@record_router.get("/disburse-board", response_model=VehicleRechargeDisburseBoardListResponse)
|
||
def list_disburse_board(
|
||
request: Request,
|
||
q: Optional[str] = Query(default=None),
|
||
recharge_type: Optional[str] = Query(default=None, alias="rechargeType"),
|
||
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"),
|
||
recharge_date_from: Optional[date] = Query(default=None, alias="rechargeDateFrom"),
|
||
recharge_date_to: Optional[date] = Query(default=None, alias="rechargeDateTo"),
|
||
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),
|
||
) -> VehicleRechargeDisburseBoardListResponse:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
params = _merge_query(
|
||
request,
|
||
q=q,
|
||
rechargeType=recharge_type,
|
||
applyStatus=apply_status,
|
||
applyDateFrom=apply_date_from,
|
||
applyDateTo=apply_date_to,
|
||
rechargeDateFrom=recharge_date_from,
|
||
rechargeDateTo=recharge_date_to,
|
||
)
|
||
items, total = svc.list_disburse_board(
|
||
db,
|
||
tenant_no,
|
||
user,
|
||
**_disburse_list_kwargs(params),
|
||
page=page,
|
||
page_size=page_size,
|
||
)
|
||
return VehicleRechargeDisburseBoardListResponse(
|
||
items=items, total=total, page=page, pageSize=page_size
|
||
)
|
||
|
||
|
||
|
||
RECHARGE_DISBURSE_EXPORT_COLUMNS = [
|
||
ExportColumn(title="申请单号", field="applyNo"),
|
||
ExportColumn(title="车牌号", field="licensePlate"),
|
||
ExportColumn(title="充值类型", field="rechargeType"),
|
||
ExportColumn(title="申请日期", field="applyDate"),
|
||
ExportColumn(title="申请人", field="applicantName"),
|
||
ExportColumn(title="申请状态", field="applyStatus"),
|
||
ExportColumn(title="期初公里数", field="odometerKm"),
|
||
ExportColumn(title="充值订单号", field="rechargeOrderNo"),
|
||
ExportColumn(title="充值日期", field="rechargeDate"),
|
||
ExportColumn(title="充值金额", field="amount"),
|
||
ExportColumn(title="卡号", field="cardNo"),
|
||
ExportColumn(title="加油站", field="gasStation"),
|
||
ExportColumn(title="发放附件", field="recordAttachmentUrls"),
|
||
ExportColumn(title="登记人", field="recordCreateByName"),
|
||
ExportColumn(title="登记备注", field="recordRemark"),
|
||
]
|
||
|
||
|
||
class RechargeDisburseExportBody(BaseModel):
|
||
q: Optional[str] = None
|
||
recharge_type: Optional[str] = Field(default=None, alias="rechargeType")
|
||
apply_status: Optional[str] = Field(default=None, alias="applyStatus")
|
||
apply_date_from: Optional[date] = Field(default=None, alias="applyDateFrom")
|
||
apply_date_to: Optional[date] = Field(default=None, alias="applyDateTo")
|
||
recharge_date_from: Optional[date] = Field(default=None, alias="rechargeDateFrom")
|
||
recharge_date_to: Optional[date] = Field(default=None, alias="rechargeDateTo")
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
export_format: Optional[str] = Field(
|
||
default=None,
|
||
alias="exportFormat",
|
||
description="空=Excel;archive_zip=数据及附件 ZIP",
|
||
)
|
||
model_config = {"populate_by_name": True, "extra": "allow"}
|
||
|
||
|
||
@record_router.post("/disburse-board/export")
|
||
def export_recharge_disburse_board(
|
||
body: RechargeDisburseExportBody,
|
||
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_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)
|
||
data["rechargeType"] = RECHARGE_TYPE_LABELS.get(
|
||
data.get("rechargeType") or "", data.get("rechargeType")
|
||
)
|
||
data["applyStatus"] = RECHARGE_APPLY_STATUS_LABELS.get(
|
||
data.get("applyStatus") or "", data.get("applyStatus")
|
||
)
|
||
for k, v in list(data.items()):
|
||
if hasattr(v, 'isoformat'):
|
||
data[k] = str(v)[:19]
|
||
rows.append(data)
|
||
|
||
export_format = (body.export_format or "").strip().lower()
|
||
cols = filter_export_columns(RECHARGE_DISBURSE_EXPORT_COLUMNS, body.columns)
|
||
|
||
if export_format == "archive_zip":
|
||
from app.services.export_attachment_zip import (
|
||
build_attachment_zip_streaming_response,
|
||
urls_to_attachment_items,
|
||
)
|
||
|
||
if not rows:
|
||
raise ExportValidationError("没有可导出的数据")
|
||
|
||
# ZIP 总表用超链接列,去掉原始 URL 列
|
||
zip_cols = [c for c in cols if c.field != "recordAttachmentUrls"]
|
||
attachments_by_id: dict = {}
|
||
for i, data in enumerate(rows, start=1):
|
||
data["rowNo"] = i
|
||
rid = str(data.get("applyId") or "").strip()
|
||
if not rid:
|
||
continue
|
||
attachments_by_id[rid] = urls_to_attachment_items(
|
||
data.get("recordAttachmentUrls")
|
||
)
|
||
stamp = datetime.now().strftime("%Y%m%d%H%M")
|
||
return build_attachment_zip_streaming_response(
|
||
db=db,
|
||
tenant_no=tenant_no,
|
||
columns=zip_cols,
|
||
rows=rows,
|
||
attachments_by_id=attachments_by_id,
|
||
stamp=stamp,
|
||
package_label="充值发放",
|
||
id_field="applyId",
|
||
sheet_title="充值发放",
|
||
xlsx_basename="(总表)充值发放.xlsx",
|
||
)
|
||
|
||
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
|
||
|
||
|
||
@record_router.get("/{id}", response_model=VehicleRechargeRecordRead)
|
||
def get_record(
|
||
id: str,
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeRecordRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.get_record(db, tenant_no, user, id)
|
||
|
||
|
||
@record_router.post("/", response_model=VehicleRechargeRecordRead, status_code=status.HTTP_201_CREATED)
|
||
def create_record(
|
||
payload: VehicleRechargeRecordCreate,
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeRecordRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.create_record(db, tenant_no, user, payload)
|
||
|
||
|
||
@record_router.put("/{id}", response_model=VehicleRechargeRecordRead)
|
||
def update_record(
|
||
id: str,
|
||
payload: VehicleRechargeRecordUpdate,
|
||
db: Session = Depends(get_db),
|
||
user: DbUserContext = Depends(get_db_current_user),
|
||
) -> VehicleRechargeRecordRead:
|
||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||
return svc.update_record(db, tenant_no, user, id, payload)
|
||
|
||
|
||
@record_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
|
||
def delete_records(
|
||
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())
|
||
record_ids = [item for item in ids.split(",") if item]
|
||
svc.delete_records(db, tenant_no, user, record_ids)
|