Files
shanxia2.0/backend/app/api/pl/freight.py
T
2026-08-21 16:50:47 +08:00

305 lines
10 KiB
Python

"""运输计划管理 - 运费结算 API"""
from datetime import date
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, 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.models.pl_models import PlFreightSettlement
from app.schemas.pl.freight import (
FreightSettlementApprovalActionBody,
FreightSettlementByTransportRead,
FreightSettlementCreate,
FreightSettlementDisburseBody,
FreightSettlementListResponse,
FreightSettlementRead,
FreightSettlementSubmitApprovalBody,
FreightSettlementUpdate,
FreightSettlementWithTransports,
RemittanceVoucherRead,
UnsettledPlanListResponse,
)
from app.services import pl_freight_settlement_service as freight_svc
router = APIRouter(prefix="/freight", tags=["运输计划-运费结算"])
def _generate_settlement_no(db: Session) -> str:
today = date.today()
prefix = f"YF{today.strftime('%Y%m%d')}"
last = (
db.query(PlFreightSettlement)
.filter(
PlFreightSettlement.settlement_no.like(f"{prefix}%"),
PlFreightSettlement.del_flag == 0,
)
.order_by(PlFreightSettlement.settlement_no.desc())
.first()
)
seq = int(last.settlement_no[-2:]) + 1 if last else 1
return f"{prefix}{seq:02d}"
@router.get("/unsettled-plans", response_model=UnsettledPlanListResponse)
def list_unsettled_plans(
q: Optional[str] = Query(default=None),
carrier_id: Optional[str] = Query(default=None, alias="carrierId"),
plan_type: Optional[str] = Query(default=None, alias="planType"),
license_plate: Optional[str] = Query(default=None, alias="licensePlate"),
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),
) -> UnsettledPlanListResponse:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
total, items = freight_svc.list_unsettled_plans(
db,
tenant_no=tenant_no,
user=user,
q=q,
carrier_id=carrier_id,
plan_type=plan_type,
license_plate=license_plate,
page=page,
page_size=page_size,
)
return UnsettledPlanListResponse(total=total, list=items)
@router.get("/", response_model=FreightSettlementListResponse)
def list_settlements(
q: Optional[str] = Query(default=None),
carrier_id: Optional[str] = Query(default=None, alias="carrierId"),
settlement_status: Optional[str] = Query(default=None, alias="settlementStatus"),
tab: Optional[str] = Query(
default=None,
description="bills|approval_pending|approval_done|disburse|settled",
),
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),
) -> FreightSettlementListResponse:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
total, items = freight_svc.list_settlements_enriched(
db,
tenant_no=tenant_no,
user_id=user.id,
user=user,
q=q,
carrier_id=carrier_id,
settlement_status=settlement_status,
tab=tab,
page=page,
page_size=page_size,
)
return FreightSettlementListResponse(total=total, list=items)
@router.get("/by-transport/{transport_id}", response_model=Optional[FreightSettlementByTransportRead])
def get_settlement_by_transport(
transport_id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> Optional[FreightSettlementByTransportRead]:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
detail = freight_svc.get_settlement_by_transport_id(
db, tenant_no=tenant_no, transport_id=transport_id, user=user
)
if not detail:
return None
return FreightSettlementByTransportRead.model_validate(detail)
@router.post("/", response_model=FreightSettlementRead, status_code=status.HTTP_201_CREATED)
def create_settlement(
payload: FreightSettlementCreate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
settlement = freight_svc.create_settlement(
db,
tenant_no=tenant_no,
plan_ids=payload.plan_ids,
transport_ids=payload.transport_ids,
user_id=user.id,
dept_id=user.dept_id,
settlement_no=_generate_settlement_no(db),
)
return settlement
@router.get("/{id}", response_model=FreightSettlementWithTransports)
def get_settlement(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementWithTransports:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
detail = freight_svc.get_settlement_detail(
db, tenant_no=tenant_no, settlement_id=id, user_id=user.id, user=user
)
return FreightSettlementWithTransports.model_validate(detail)
@router.put("/{id}", response_model=FreightSettlementRead)
def update_settlement(
id: str,
payload: FreightSettlementUpdate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
return freight_svc.update_settlement_draft(
db,
tenant_no=tenant_no,
settlement_id=id,
payload=payload,
user_id=user.id,
user=user,
)
@router.post("/{id}/cancel", response_model=FreightSettlementRead)
def cancel_settlement(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
return freight_svc.cancel_draft_settlement(
db, tenant_no=tenant_no, settlement_id=id, user_id=user.id, user=user
)
@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_settlement(
id: 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())
freight_svc.delete_draft_settlement(
db, tenant_no=tenant_no, settlement_id=id, user_id=user.id, user=user
)
@router.post("/{id}/submit-approval", response_model=FreightSettlementRead)
def submit_settlement_approval(
id: str,
payload: FreightSettlementSubmitApprovalBody,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
return freight_svc.submit_settlement_approval(
db,
tenant_no=tenant_no,
settlement_id=id,
body=payload,
user_id=user.id,
dept_id=user.dept_id,
user=user,
)
@router.post("/approval/{approval_id}", response_model=FreightSettlementRead)
def process_settlement_approval(
approval_id: str,
payload: FreightSettlementApprovalActionBody,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
if payload.action not in ("approve", "reject"):
raise HTTPException(status_code=400, detail="action 必须为 approve 或 reject")
return freight_svc.process_settlement_approval(
db,
tenant_no=tenant_no,
approval_id=approval_id,
action=payload.action,
comment=payload.comment,
user_id=user.id,
)
@router.post("/{id}/withdraw-approval", response_model=FreightSettlementRead)
def withdraw_settlement_approval(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
return freight_svc.withdraw_settlement_approval(
db, tenant_no=tenant_no, settlement_id=id, user_id=user.id, user=user
)
@router.get("/{id}/approval-flow")
def get_settlement_approval_flow(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
):
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
freight_svc.get_settlement_row(db, tenant_no, id, user=user)
return freight_svc.get_approval_flow(db, id)
@router.post("/{id}/disburse", response_model=FreightSettlementRead)
def disburse_settlement(
id: str,
payload: FreightSettlementDisburseBody,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
return freight_svc.disburse_settlement(
db,
tenant_no=tenant_no,
settlement_id=id,
body=payload,
user_id=user.id,
dept_id=user.dept_id,
user=user,
)
# 向后兼容旧接口
@router.post("/{id}/submit", response_model=FreightSettlementRead, deprecated=True)
def submit_settlement_legacy(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
raise HTTPException(status_code=400, detail="请使用 submit-approval 接口并选择审批人")
@router.post("/{id}/approve", response_model=FreightSettlementRead, deprecated=True)
def approve_settlement_legacy(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> FreightSettlementRead:
raise HTTPException(status_code=400, detail="请使用 approval/{approvalId} 接口审批")
@router.get("/voucher/{settlement_id}", response_model=list[RemittanceVoucherRead])
def list_vouchers(
settlement_id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> list[RemittanceVoucherRead]:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
freight_svc.get_settlement_row(db, tenant_no, settlement_id, user=user)
detail = freight_svc.get_settlement_detail(
db, tenant_no=tenant_no, settlement_id=settlement_id, user_id=user.id, user=user
)
return detail.get("vouchers") or []