305 lines
10 KiB
Python
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 []
|