Files
shanxia2.0/backend/app/api/pl/freight.py
T
2026-06-21 09:19:07 +08:00

334 lines
10 KiB
Python

"""运输计划管理 - 运费结算 API"""
from datetime import date
from typing import Optional
import json
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func
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.core.utils import generate_uuid
from app.models.pl_models import (
PlFreightSettlement,
PlFreightSettlementTransport,
PlRemittanceVoucher,
PlTransport,
)
from app.schemas.pl.freight import (
FreightSettlementCreate,
FreightSettlementListQuery,
FreightSettlementListResponse,
FreightSettlementRead,
FreightSettlementUpdate,
FreightSettlementWithTransports,
RemittanceVoucherCreate,
RemittanceVoucherRead,
)
router = APIRouter(prefix="/freight", tags=["运输计划-运费结算"])
def _generate_settlement_no(db: Session) -> str:
"""生成结算单编号"""
today = date.today()
prefix = f"SXJS{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()
)
if last:
last_seq = int(last.settlement_no[-4:])
seq = last_seq + 1
else:
seq = 1
return f"{prefix}{seq:04d}"
# =============================================================================
# 结算单列表
# =============================================================================
@router.get("/", response_model=FreightSettlementListResponse)
def list_settlements(
q: Optional[str] = Query(default=None, description="模糊搜索"),
settlement_status: Optional[str] = Query(default=None, alias="settlementStatus", description="结算状态"),
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())
query = db.query(PlFreightSettlement).filter(
PlFreightSettlement.tenant_no == tenant_no,
PlFreightSettlement.del_flag == 0,
)
if settlement_status:
query = query.filter(PlFreightSettlement.settlement_status == settlement_status)
if q:
query = query.filter(PlFreightSettlement.settlement_no.contains(q))
total = query.count()
offset = (page - 1) * page_size
settlements = query.order_by(PlFreightSettlement.create_time.desc()).offset(offset).limit(page_size).all()
return FreightSettlementListResponse(total=total, list=settlements)
# =============================================================================
# 生成结算单
# =============================================================================
@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())
# 计算总运费
transports = (
db.query(PlTransport)
.filter(
PlTransport.id.in_(payload.transport_ids),
PlTransport.tenant_no == tenant_no,
PlTransport.del_flag == 0,
)
.all()
)
if not transports:
raise HTTPException(status_code=400, detail="请选择有效的运输单")
total_freight = sum(float(t.freight_amount or 0) for t in transports)
settlement_no = _generate_settlement_no(db)
settlement = PlFreightSettlement(
id=generate_uuid(),
tenant_no=tenant_no,
settlement_no=settlement_no,
total_freight=total_freight,
settlement_status="pending",
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(settlement)
# 关联运输单
for t in transports:
st = PlFreightSettlementTransport(
id=generate_uuid(),
tenant_no=tenant_no,
settlement_id=settlement.id,
transport_id=t.id,
freight_amount=t.freight_amount,
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(st)
db.commit()
db.refresh(settlement)
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())
settlement = _get_settlement(db, tenant_no, id)
# 获取关联的运输单
transport_links = (
db.query(PlFreightSettlementTransport)
.filter(
PlFreightSettlementTransport.settlement_id == id,
PlFreightSettlementTransport.del_flag == 0,
)
.all()
)
transport_ids = [t.transport_id for t in transport_links]
transports = (
db.query(PlTransport)
.filter(PlTransport.id.in_(transport_ids), PlTransport.del_flag == 0)
.all()
)
result = FreightSettlementWithTransports.model_validate(settlement)
result.transports = transports
return result
# =============================================================================
# 更新结算单
# =============================================================================
@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())
settlement = _get_settlement(db, tenant_no, id)
update_data = payload.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(settlement, key, value)
settlement.update_by = user.id
db.commit()
db.refresh(settlement)
return settlement
# =============================================================================
# 提交结算单
# =============================================================================
@router.post("/{id}/submit", response_model=FreightSettlementRead)
def submit_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())
settlement = _get_settlement(db, tenant_no, id)
if settlement.settlement_status != "pending":
raise HTTPException(status_code=400, detail="当前状态不允许提交")
settlement.settlement_status = "submitted"
settlement.update_by = user.id
db.commit()
db.refresh(settlement)
return settlement
# =============================================================================
# 审核结算单
# =============================================================================
@router.post("/{id}/approve", response_model=FreightSettlementRead)
def approve_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())
settlement = _get_settlement(db, tenant_no, id)
if settlement.settlement_status not in ("submitted",):
raise HTTPException(status_code=400, detail="当前状态不允许审核")
settlement.settlement_status = "confirmed"
settlement.update_by = user.id
db.commit()
db.refresh(settlement)
return settlement
# =============================================================================
# 汇款凭证
# =============================================================================
@router.post("/voucher", status_code=status.HTTP_201_CREATED)
def create_voucher(
payload: RemittanceVoucherCreate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> RemittanceVoucherRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
voucher = PlRemittanceVoucher(
id=generate_uuid(),
tenant_no=tenant_no,
settlement_id=payload.settlement_id,
remittance_date=payload.remittance_date,
remittance_amount=payload.remittance_amount,
voucher_urls=json.dumps(payload.voucher_urls) if payload.voucher_urls else None,
remark=payload.remark,
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(voucher)
db.commit()
db.refresh(voucher)
result = RemittanceVoucherRead.model_validate(voucher)
if voucher.voucher_urls:
result.voucher_urls = voucher.voucher_urls
return result
@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())
vouchers = (
db.query(PlRemittanceVoucher)
.filter(
PlRemittanceVoucher.settlement_id == settlement_id,
PlRemittanceVoucher.tenant_no == tenant_no,
PlRemittanceVoucher.del_flag == 0,
)
.order_by(PlRemittanceVoucher.create_time.desc())
.all()
)
return vouchers
# =============================================================================
# 辅助函数
# =============================================================================
def _get_settlement(db: Session, tenant_no: str, settlement_id: str) -> PlFreightSettlement:
settlement = (
db.query(PlFreightSettlement)
.filter(
PlFreightSettlement.id == settlement_id,
PlFreightSettlement.tenant_no == tenant_no,
PlFreightSettlement.del_flag == 0,
)
.first()
)
if not settlement:
raise HTTPException(status_code=404, detail="结算单不存在")
return settlement