| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- """运输计划管理 - 运费结算 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=100, 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=100, 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 []
|