| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- """运输计划管理 - 运费结算 API"""
- from datetime import date, datetime
- from typing import Any, List, Optional
- from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request, status
- from pydantic import BaseModel, ConfigDict, Field
- 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.schemas.pl.cancel_reason import CancelReasonBody
- from app.utils.excel_export import (
- EXPORT_MAX_ROWS,
- ExportColumn,
- ExportValidationError,
- export_rows_to_xlsx_response,
- filter_export_columns,
- )
- from app.models.pl_models import PlFreightSettlement, PlPlan, PlTransport
- 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}"
- def _merge_query(request: Request, **known) -> dict:
- out = {k: v for k, v in known.items() if v is not None and v != ""}
- for k, v in request.query_params.multi_items():
- if k in ("page", "pageSize"):
- continue
- if k not in out and v not in (None, ""):
- out[k] = v
- return out
- def _param_get(params: dict, *keys: str) -> Any:
- for key in keys:
- if key in params and params[key] not in (None, ""):
- return params[key]
- return None
- def _body_to_params(body: BaseModel) -> dict:
- data = body.model_dump(by_alias=True, exclude_none=True)
- for key in ("ids", "columns"):
- data.pop(key, None)
- extra = getattr(body, "model_extra", None) or {}
- for key, value in extra.items():
- if key not in data and value not in (None, ""):
- data[key] = value
- return data
- def _unsettled_column_filter_kwargs(params: dict) -> dict:
- return {
- "plan_no": _param_get(params, "planNo", "plan_no"),
- "plan_no_in": _param_get(params, "planNoIn", "plan_no_in"),
- "plan_type": _param_get(params, "planType", "plan_type"),
- "plan_type_in": _param_get(params, "planTypeIn", "plan_type_in"),
- "license_plate": _param_get(params, "licensePlate", "license_plate"),
- "license_plate_in": _param_get(params, "licensePlateIn", "license_plate_in"),
- "transport_company": _param_get(params, "transportCompany", "transport_company"),
- "transport_company_in": _param_get(params, "transportCompanyIn", "transport_company_in"),
- "freight_amount_op": _param_get(params, "freightAmountOp", "freight_amount_op"),
- "freight_amount": _param_get(params, "freightAmount", "freight_amount"),
- "freight_amount_min": _param_get(params, "freightAmountMin", "freight_amount_min"),
- "freight_amount_max": _param_get(params, "freightAmountMax", "freight_amount_max"),
- "freight_type": _param_get(params, "freightType", "freight_type"),
- "freight_type_in": _param_get(params, "freightTypeIn", "freight_type_in"),
- "transport_qty_op": _param_get(params, "transportQtyOp", "transport_qty_op"),
- "transport_qty": _param_get(params, "transportQty", "transport_qty"),
- "transport_qty_min": _param_get(params, "transportQtyMin", "transport_qty_min"),
- "transport_qty_max": _param_get(params, "transportQtyMax", "transport_qty_max"),
- "has_invoice": _param_get(params, "hasInvoice", "has_invoice"),
- "has_invoice_in": _param_get(params, "hasInvoiceIn", "has_invoice_in"),
- "freight_remark": _param_get(params, "freightRemark", "freight_remark"),
- "freight_remark_in": _param_get(params, "freightRemarkIn", "freight_remark_in"),
- "plan_date_from": _param_get(params, "planDateFrom", "plan_date_from"),
- "plan_date_to": _param_get(params, "planDateTo", "plan_date_to"),
- "freight_price_op": _param_get(params, "freightPriceOp", "freight_price_op"),
- "freight_price": _param_get(params, "freightPrice", "freight_price"),
- "freight_price_min": _param_get(params, "freightPriceMin", "freight_price_min"),
- "freight_price_max": _param_get(params, "freightPriceMax", "freight_price_max"),
- "archive_time_from": _param_get(params, "archiveTimeFrom", "archive_time_from"),
- "archive_time_to": _param_get(params, "archiveTimeTo", "archive_time_to"),
- "load_customers": _param_get(params, "loadCustomers", "load_customers"),
- "delivery_customers": _param_get(
- params, "deliveryCustomers", "delivery_customers"
- ),
- }
- def _settlement_column_filter_kwargs(params: dict) -> dict:
- return {
- "settlement_no": _param_get(params, "settlementNo", "settlement_no"),
- "total_freight_op": _param_get(params, "totalFreightOp", "total_freight_op"),
- "total_freight": _param_get(params, "totalFreight", "total_freight"),
- "total_freight_min": _param_get(params, "totalFreightMin", "total_freight_min"),
- "total_freight_max": _param_get(params, "totalFreightMax", "total_freight_max"),
- "create_time_from": _param_get(params, "createTimeFrom", "create_time_from"),
- "create_time_to": _param_get(params, "createTimeTo", "create_time_to"),
- "submitter_name": _param_get(params, "submitterName", "submitter_name"),
- "current_approver_name": _param_get(
- params, "currentApproverName", "current_approver_name"
- ),
- "approval_finished_time_from": _param_get(
- params, "approvalFinishedTimeFrom", "approval_finished_time_from"
- ),
- "approval_finished_time_to": _param_get(
- params, "approvalFinishedTimeTo", "approval_finished_time_to"
- ),
- "disburse_amount_op": _param_get(
- params, "disburseAmountOp", "disburse_amount_op"
- ),
- "disburse_amount": _param_get(params, "disburseAmount", "disburse_amount"),
- "disburse_amount_min": _param_get(
- params, "disburseAmountMin", "disburse_amount_min"
- ),
- "disburse_amount_max": _param_get(
- params, "disburseAmountMax", "disburse_amount_max"
- ),
- "disburse_time_from": _param_get(
- params, "disburseTimeFrom", "disburse_time_from"
- ),
- "disburse_time_to": _param_get(params, "disburseTimeTo", "disburse_time_to"),
- }
- class FreightColumnFacetBody(BaseModel):
- """运费未结算列头可选值(去重;可附带列表上下文)"""
- model_config = ConfigDict(extra="allow")
- field: str = Field(..., description="planNo | licensePlate | transportCompany | freightRemark")
- keyword: Optional[str] = Field(default=None, description="可选值关键字")
- limit: Optional[int] = Field(default=500, ge=1, le=500, description="返回条数上限")
- @router.post("/column-facet-values")
- def freight_column_facet_values(
- body: FreightColumnFacetBody,
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ):
- """运费未结算列头可选值:在未结算列表上下文(排除本列)下去重。"""
- from sqlalchemy import func as sa_func
- from app.core.tenant_context import get_tenant_no
- from app.services.pl.facet_context import strip_simple_facet_self_params
- from app.services.pl.freight_list_filters import build_unsettled_plan_column_filters
- from app.services.pl_freight_settlement_service import (
- _occupied_transport_ids_subquery,
- unsettled_plans_q_filter,
- )
- tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
- lim = min(max(1, int(body.limit or 500)), 500)
- kw = (body.keyword or "").strip()
- field = str(body.field or "").strip()
- params = strip_simple_facet_self_params(_body_to_params(body), body.field)
- occupied = _occupied_transport_ids_subquery(db, tenant_no).subquery()
- base = (
- db.query(PlPlan.id)
- .join(PlTransport, PlTransport.id == PlPlan.transport_id)
- .filter(
- PlPlan.tenant_no == tenant_no,
- PlPlan.del_flag == 0,
- PlPlan.archive_status == "archived",
- PlPlan.has_freight == 1,
- PlPlan.follow_status != "cancelled",
- PlPlan.transport_id.isnot(None),
- PlTransport.del_flag == 0,
- PlTransport.freight_amount > 0,
- ~PlPlan.transport_id.in_(occupied),
- )
- )
- qv = _param_get(params, "q")
- if qv:
- base = base.filter(unsettled_plans_q_filter(str(qv)))
- carrier_id = _param_get(params, "carrierId", "carrier_id")
- if carrier_id:
- base = base.filter(PlTransport.carrier_id == carrier_id)
- column_filters = build_unsettled_plan_column_filters(
- **_unsettled_column_filter_kwargs(params)
- )
- if column_filters:
- base = base.filter(*column_filters)
- from app.services.pl_data_scope import apply_pl_scope
- base = apply_pl_scope(base, PlPlan, user, db, tenant_no)
- plan_id_sq = base.subquery()
- def _finish(rows):
- out = []
- for row in rows:
- value = str(row.value).strip() if row.value is not None else ""
- if not value:
- continue
- out.append({"value": value, "label": value, "count": int(row.cnt or 0)})
- return out
- if field == "transportCompany":
- col = PlTransport.carrier_name
- q = (
- db.query(col.label("value"), sa_func.count(PlPlan.id).label("cnt"))
- .select_from(PlPlan)
- .join(PlTransport, PlTransport.id == PlPlan.transport_id)
- .filter(
- PlPlan.id.in_(plan_id_sq),
- col.isnot(None),
- col != "",
- )
- )
- else:
- col_map = {
- "planNo": PlPlan.plan_no,
- "licensePlate": PlPlan.license_plate,
- "freightRemark": PlPlan.freight_remark,
- }
- col = col_map.get(field)
- if col is None:
- raise HTTPException(status_code=400, detail="不支持的列")
- q = (
- db.query(col.label("value"), sa_func.count(PlPlan.id).label("cnt"))
- .select_from(PlPlan)
- .filter(
- PlPlan.id.in_(plan_id_sq),
- col.isnot(None),
- col != "",
- )
- )
- if kw:
- q = q.filter(col.like(f"%{kw}%"))
- rows = q.group_by(col).order_by(col.asc()).limit(lim).all()
- return {"list": _finish(rows)}
- @router.get("/unsettled-plans", response_model=UnsettledPlanListResponse)
- def list_unsettled_plans(
- request: Request,
- q: Optional[str] = Query(default=None),
- carrier_id: Optional[str] = Query(default=None, alias="carrierId"),
- plan_no: Optional[str] = Query(default=None, alias="planNo"),
- plan_type: Optional[str] = Query(default=None, alias="planType"),
- plan_type_in: Optional[str] = Query(default=None, alias="planTypeIn"),
- 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())
- params = _merge_query(
- request,
- q=q,
- carrierId=carrier_id,
- planNo=plan_no,
- planType=plan_type,
- planTypeIn=plan_type_in,
- licensePlate=license_plate,
- )
- total, items = freight_svc.list_unsettled_plans(
- db,
- tenant_no=tenant_no,
- user=user,
- q=_param_get(params, "q"),
- carrier_id=_param_get(params, "carrierId", "carrier_id"),
- **_unsettled_column_filter_kwargs(params),
- page=page,
- page_size=page_size,
- )
- return UnsettledPlanListResponse(total=total, list=items)
- @router.get("/", response_model=FreightSettlementListResponse)
- def list_settlements(
- request: Request,
- q: Optional[str] = Query(default=None),
- carrier_id: Optional[str] = Query(default=None, alias="carrierId"),
- settlement_no: Optional[str] = Query(default=None, alias="settlementNo"),
- settlement_status: Optional[str] = Query(default=None, alias="settlementStatus"),
- settlement_status_in: Optional[str] = Query(default=None, alias="settlementStatusIn"),
- 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())
- params = _merge_query(
- request,
- q=q,
- carrierId=carrier_id,
- settlementNo=settlement_no,
- settlementStatus=settlement_status,
- settlementStatusIn=settlement_status_in,
- tab=tab,
- )
- total, items = freight_svc.list_settlements_enriched(
- db,
- tenant_no=tenant_no,
- user_id=user.id,
- user=user,
- q=_param_get(params, "q"),
- carrier_id=_param_get(params, "carrierId", "carrier_id"),
- settlement_status=_param_get(params, "settlementStatus", "settlement_status"),
- settlement_status_in=_param_get(
- params, "settlementStatusIn", "settlement_status_in"
- ),
- tab=_param_get(params, "tab"),
- page=page,
- page_size=page_size,
- **_settlement_column_filter_kwargs(params),
- )
- return FreightSettlementListResponse(total=total, list=items)
- UNSETTLED_EXPORT_COLUMNS = [
- ExportColumn(title="计划编号", field="planNo"),
- ExportColumn(title="计划日期", field="planDate"),
- ExportColumn(title="单据类型", field="planType"),
- ExportColumn(title="装货客户", field="loadCustomers"),
- ExportColumn(title="送货客户", field="deliveryCustomers"),
- ExportColumn(title="车牌号", field="licensePlate"),
- ExportColumn(title="承运商", field="transportCompany"),
- ExportColumn(title="运费(元)", field="freightAmount"),
- ExportColumn(title="运费计算方式", field="freightType"),
- ExportColumn(title="运输量", field="transportQty"),
- ExportColumn(title="运输单价", field="freightPrice"),
- ExportColumn(title="是否开票", field="hasInvoice"),
- ExportColumn(title="归档时间", field="archiveTime"),
- ]
- PLAN_TYPE_LABELS = {"direct": "直销单", "inbound": "入库单", "outbound": "出库单"}
- FREIGHT_TYPE_LABELS = {"by_weight": "按重量", "by_trip": "按趟次"}
- SETTLEMENT_STATUS_LABELS = {
- "pending": "草稿",
- "rejected": "已驳回",
- "approving": "审批中",
- "approved": "待发放",
- "confirmed": "已发放",
- "cancelled": "已作废",
- }
- class UnsettledExportBody(BaseModel):
- q: Optional[str] = None
- carrier_id: Optional[str] = Field(default=None, alias="carrierId")
- plan_no: Optional[str] = Field(default=None, alias="planNo")
- plan_type: Optional[str] = Field(default=None, alias="planType")
- plan_type_in: Optional[str] = Field(default=None, alias="planTypeIn")
- license_plate: Optional[str] = Field(default=None, alias="licensePlate")
- ids: Optional[List[str]] = None
- columns: Optional[List[str]] = None
- model_config = {"populate_by_name": True, "extra": "allow"}
- @router.post("/unsettled-plans/export")
- def export_unsettled_plans(
- body: UnsettledExportBody,
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ):
- try:
- tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
- params = _body_to_params(body)
- _, items = freight_svc.list_unsettled_plans(
- db,
- tenant_no=tenant_no,
- user=user,
- q=_param_get(params, "q"),
- carrier_id=_param_get(params, "carrierId", "carrier_id"),
- **_unsettled_column_filter_kwargs(params),
- ids=body.ids,
- page=1,
- page_size=EXPORT_MAX_ROWS,
- max_page_size=EXPORT_MAX_ROWS,
- )
- rows = []
- for item in items:
- data = dict(item)
- data["deliveryCustomers"] = "\n".join(data.get("deliveryCustomers") or [])
- data["loadCustomers"] = "\n".join(data.get("loadCustomers") or [])
- data["planType"] = PLAN_TYPE_LABELS.get(str(data.get("planType") or ""), data.get("planType"))
- freight_type = str(data.get("freightType") or "")
- data["freightType"] = FREIGHT_TYPE_LABELS.get(freight_type, freight_type)
- data["hasInvoice"] = "是" if int(data.get("hasInvoice") or 0) == 1 else "否"
- for k, v in list(data.items()):
- if hasattr(v, "isoformat"):
- data[k] = str(v)[:19]
- rows.append(data)
- cols = filter_export_columns(UNSETTLED_EXPORT_COLUMNS, body.columns)
- date_str = datetime.now().strftime("%Y%m%d")
- return export_rows_to_xlsx_response(
- db, tenant_no=tenant_no, sheet_title="待结算计划", columns=cols, rows=rows,
- filename_stem=f"待结算计划_{date_str}",
- )
- except ExportValidationError as e:
- raise HTTPException(status_code=400, detail=str(e)) from e
- SETTLEMENT_EXPORT_COLUMNS = [
- ExportColumn(title="结算单号", field="settlementNo"),
- ExportColumn(title="运费合计(元)", field="totalFreight"),
- ExportColumn(title="关联计划数", field="planCount"),
- ExportColumn(title="车牌摘要", field="licensePlateSummary"),
- ExportColumn(title="结算状态", field="settlementStatus"),
- ExportColumn(title="发放金额(元)", field="disburseAmount"),
- ExportColumn(title="发放时间", field="disburseTime"),
- ExportColumn(title="当前审批人", field="currentApproverName"),
- ExportColumn(title="提交人", field="submitterName"),
- ExportColumn(title="创建时间", field="createTime"),
- ExportColumn(title="审批完成时间", field="approvalFinishedTime"),
- ]
- class SettlementExportBody(BaseModel):
- q: Optional[str] = None
- carrier_id: Optional[str] = Field(default=None, alias="carrierId")
- settlement_no: Optional[str] = Field(default=None, alias="settlementNo")
- settlement_status: Optional[str] = Field(default=None, alias="settlementStatus")
- settlement_status_in: Optional[str] = Field(default=None, alias="settlementStatusIn")
- tab: Optional[str] = None
- ids: Optional[List[str]] = None
- columns: Optional[List[str]] = None
- model_config = {"populate_by_name": True, "extra": "allow"}
- @router.post("/export")
- def export_settlements(
- body: SettlementExportBody,
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ):
- try:
- tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
- params = _body_to_params(body)
- _, items = freight_svc.list_settlements_enriched(
- db,
- tenant_no=tenant_no,
- user_id=user.id,
- user=user,
- q=_param_get(params, "q"),
- carrier_id=_param_get(params, "carrierId", "carrier_id"),
- settlement_status=_param_get(params, "settlementStatus", "settlement_status"),
- settlement_status_in=_param_get(
- params, "settlementStatusIn", "settlement_status_in"
- ),
- tab=_param_get(params, "tab"),
- ids=body.ids,
- page=1,
- page_size=EXPORT_MAX_ROWS,
- max_page_size=EXPORT_MAX_ROWS,
- **_settlement_column_filter_kwargs(params),
- )
- rows = []
- for item in items:
- data = dict(item)
- stv = data.get("settlementStatus")
- if stv is not None:
- data["settlementStatus"] = SETTLEMENT_STATUS_LABELS.get(str(stv), stv)
- for k, v in list(data.items()):
- if hasattr(v, "isoformat"):
- data[k] = str(v)[:19]
- rows.append(data)
- cols = filter_export_columns(SETTLEMENT_EXPORT_COLUMNS, body.columns)
- date_str = datetime.now().strftime("%Y%m%d")
- return export_rows_to_xlsx_response(
- db, tenant_no=tenant_no, sheet_title="运费结算", columns=cols, rows=rows,
- filename_stem=f"运费结算_{date_str}",
- )
- except ExportValidationError as e:
- raise HTTPException(status_code=400, detail=str(e)) from e
- @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,
- body: Optional[CancelReasonBody] = Body(default=None),
- 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,
- cancel_reason=body.cancel_reason if body else None,
- )
- @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 []
|