|
|
@@ -1,13 +1,21 @@
|
|
|
"""运输计划管理 - 运费结算 API"""
|
|
|
-from datetime import date
|
|
|
-from typing import Optional
|
|
|
+from datetime import date, datetime
|
|
|
+from typing import List, Optional
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
|
+from pydantic import BaseModel, 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.utils.excel_export import (
|
|
|
+ EXPORT_MAX_ROWS,
|
|
|
+ ExportColumn,
|
|
|
+ ExportValidationError,
|
|
|
+ export_rows_to_xlsx_response,
|
|
|
+ filter_export_columns,
|
|
|
+)
|
|
|
from app.models.pl_models import PlFreightSettlement
|
|
|
from app.schemas.pl.freight import (
|
|
|
FreightSettlementApprovalActionBody,
|
|
|
@@ -101,6 +109,131 @@ def list_settlements(
|
|
|
return FreightSettlementListResponse(total=total, list=items)
|
|
|
|
|
|
|
|
|
+
|
|
|
+UNSETTLED_EXPORT_COLUMNS = [
|
|
|
+ ExportColumn(title="计划编号", field="planNo"),
|
|
|
+ ExportColumn(title="计划日期", field="planDate"),
|
|
|
+ ExportColumn(title="单据类型", field="planType"),
|
|
|
+ ExportColumn(title="客户/供应商", field="partnerName"),
|
|
|
+ ExportColumn(title="车牌号", field="licensePlate"),
|
|
|
+ ExportColumn(title="承运商", field="transportCompany"),
|
|
|
+ ExportColumn(title="运费(元)", field="freightAmount"),
|
|
|
+ ExportColumn(title="归档时间", field="archiveTime"),
|
|
|
+]
|
|
|
+
|
|
|
+PLAN_TYPE_LABELS = {"direct": "直销单", "inbound": "入库单", "outbound": "出库单"}
|
|
|
+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_type: Optional[str] = Field(default=None, alias="planType")
|
|
|
+ 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}
|
|
|
+
|
|
|
+
|
|
|
+@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())
|
|
|
+ _, items = freight_svc.list_unsettled_plans(
|
|
|
+ db, tenant_no=tenant_no, user=user,
|
|
|
+ q=body.q, carrier_id=body.carrier_id, plan_type=body.plan_type,
|
|
|
+ license_plate=body.license_plate, 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["partnerName"] = data.get("customerName") or data.get("supplierName") or ""
|
|
|
+ data["planType"] = PLAN_TYPE_LABELS.get(str(data.get("planType") or ""), data.get("planType"))
|
|
|
+ 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_status: Optional[str] = Field(default=None, alias="settlementStatus")
|
|
|
+ tab: Optional[str] = None
|
|
|
+ ids: Optional[List[str]] = None
|
|
|
+ columns: Optional[List[str]] = None
|
|
|
+ model_config = {"populate_by_name": True}
|
|
|
+
|
|
|
+
|
|
|
+@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())
|
|
|
+ _, items = freight_svc.list_settlements_enriched(
|
|
|
+ db, tenant_no=tenant_no, user_id=user.id, user=user,
|
|
|
+ q=body.q, carrier_id=body.carrier_id,
|
|
|
+ settlement_status=body.settlement_status, tab=body.tab, ids=body.ids,
|
|
|
+ page=1, page_size=EXPORT_MAX_ROWS, max_page_size=EXPORT_MAX_ROWS,
|
|
|
+ )
|
|
|
+ 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,
|