334 lines
10 KiB
Python
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
|