701 lines
41 KiB
Python
701 lines
41 KiB
Python
"""运输计划管理 - 计划 Schema"""
|
||
from datetime import date, datetime
|
||
from decimal import Decimal
|
||
from typing import Optional
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
from app.schemas.pl.datetime_parse import OptionalPlanDateTime
|
||
from app.schemas.pl.resource_snapshot import PlanResourceSnapshotMixin
|
||
|
||
# =============================================================================
|
||
# 计划产品明细 Schema
|
||
# =============================================================================
|
||
|
||
|
||
class PlanProductBase(BaseModel):
|
||
product_id: str = Field(..., alias="productId", description="产品ID")
|
||
product_no: str = Field(..., max_length=50, alias="productNo", description="产品编号")
|
||
product_name: str = Field(..., max_length=200, alias="productName", description="产品名称")
|
||
brand_mode: Optional[str] = Field(default=None, alias="brandMode", description="required/any")
|
||
brand_id: Optional[str] = Field(default=None, alias="brandId", description="指定品牌ID")
|
||
brand_name: Optional[str] = Field(default=None, alias="brandName", description="品牌名快照")
|
||
model: Optional[str] = Field(default=None, max_length=100, description="规格型号")
|
||
unit: str = Field(default="吨", max_length=20, description="单位")
|
||
category_name: Optional[str] = Field(default=None, max_length=200, alias="categoryName", description="产品分类")
|
||
package_spec: Optional[str] = Field(default=None, max_length=200, alias="packageSpec", description="包装规格")
|
||
delivery_method: Optional[str] = Field(default=None, max_length=50, alias="deliveryMethod", description="送货方式")
|
||
quantity: Optional[Decimal] = Field(default=None, description="合同数量")
|
||
actual_qty: Optional[Decimal] = Field(default=None, alias="actualQty", description="实际数量")
|
||
tax_price: Optional[Decimal] = Field(default=None, alias="taxPrice", description="含税单价")
|
||
tax_amount: Optional[Decimal] = Field(default=None, alias="taxAmount", description="税费")
|
||
tax_subtotal: Optional[Decimal] = Field(default=None, alias="taxSubtotal", description="含税总价")
|
||
is_explosive: int = Field(default=0, alias="isExplosive", description="是否易制爆")
|
||
is_toxic: int = Field(default=0, alias="isToxic", description="是否易制毒")
|
||
purchase_cert_url: Optional[str] = Field(
|
||
default=None, alias="purchaseCertUrl", description="购买证URL,逗号分隔"
|
||
)
|
||
transport_cert_url: Optional[str] = Field(
|
||
default=None, alias="transportCertUrl", description="运输证URL,逗号分隔"
|
||
)
|
||
legal_use_cert_url: Optional[str] = Field(
|
||
default=None, alias="legalUseCertUrl", description="合法使用证明URL,逗号分隔"
|
||
)
|
||
inbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="inboundEstimateQty", description="入库预估数")
|
||
inbound_qty: Optional[Decimal] = Field(default=None, alias="inboundQty", description="入库数")
|
||
inbound_price: Optional[Decimal] = Field(default=None, alias="inboundPrice", description="入库价")
|
||
inbound_amount: Optional[Decimal] = Field(default=None, alias="inboundAmount", description="入库金额")
|
||
outbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="outboundEstimateQty", description="出库预估数")
|
||
outbound_qty: Optional[Decimal] = Field(default=None, alias="outboundQty", description="出库数")
|
||
outbound_price: Optional[Decimal] = Field(default=None, alias="outboundPrice", description="出库单价")
|
||
outbound_amount: Optional[Decimal] = Field(default=None, alias="outboundAmount", description="出库金额")
|
||
remark: Optional[str] = Field(default=None, max_length=512, description="备注")
|
||
|
||
|
||
class PlanProductCreate(PlanProductBase):
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanProductUpdate(BaseModel):
|
||
product_id: Optional[str] = Field(default=None, alias="productId")
|
||
product_no: Optional[str] = Field(default=None, max_length=50, alias="productNo")
|
||
product_name: Optional[str] = Field(default=None, max_length=200, alias="productName")
|
||
model: Optional[str] = Field(default=None, max_length=100)
|
||
unit: Optional[str] = Field(default=None, max_length=20)
|
||
is_explosive: Optional[int] = Field(default=None, alias="isExplosive")
|
||
is_toxic: Optional[int] = Field(default=None, alias="isToxic")
|
||
purchase_cert_url: Optional[str] = Field(default=None, alias="purchaseCertUrl")
|
||
transport_cert_url: Optional[str] = Field(default=None, alias="transportCertUrl")
|
||
legal_use_cert_url: Optional[str] = Field(default=None, alias="legalUseCertUrl")
|
||
inbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="inboundEstimateQty")
|
||
inbound_qty: Optional[Decimal] = Field(default=None, alias="inboundQty")
|
||
inbound_price: Optional[Decimal] = Field(default=None, alias="inboundPrice")
|
||
inbound_amount: Optional[Decimal] = Field(default=None, alias="inboundAmount")
|
||
outbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="outboundEstimateQty")
|
||
outbound_qty: Optional[Decimal] = Field(default=None, alias="outboundQty")
|
||
outbound_price: Optional[Decimal] = Field(default=None, alias="outboundPrice")
|
||
outbound_amount: Optional[Decimal] = Field(default=None, alias="outboundAmount")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveBrandQtyItem(BaseModel):
|
||
brand_id: str = Field(default="", alias="brandId")
|
||
brand_name: Optional[str] = Field(default=None, alias="brandName")
|
||
qty: Decimal = Field(..., ge=0)
|
||
unit_price: Optional[Decimal] = Field(default=None, alias="unitPrice", ge=0, description="本品牌单价")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanProductRead(PlanProductBase):
|
||
id: str
|
||
plan_id: str = Field(alias="planId")
|
||
tenant_no: str = Field(alias="tenantNo")
|
||
create_time: datetime = Field(alias="createTime")
|
||
update_time: datetime = Field(alias="updateTime")
|
||
brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(
|
||
default=None, alias="brandQtys", description="归档品牌数量明细"
|
||
)
|
||
model_config = {"from_attributes": True, "populate_by_name": True}
|
||
|
||
|
||
# =============================================================================
|
||
# 装货/送货段 Schema
|
||
# =============================================================================
|
||
|
||
|
||
class PlanStopProductBody(BaseModel):
|
||
plan_product_id: Optional[str] = Field(
|
||
default=None,
|
||
alias="planProductId",
|
||
description="计划产品行ID(新建时可空,由后端按 productId 匹配)",
|
||
)
|
||
product_id: str = Field(..., alias="productId", description="产品ID")
|
||
product_no: str = Field(..., max_length=50, alias="productNo", description="产品编号")
|
||
product_name: str = Field(..., max_length=200, alias="productName", description="产品名称")
|
||
model: Optional[str] = Field(default=None, max_length=100, description="规格型号")
|
||
unit: str = Field(default="吨", max_length=20, description="单位")
|
||
package_spec: Optional[str] = Field(default=None, max_length=200, alias="packageSpec", description="包装规格")
|
||
delivery_method: Optional[str] = Field(default=None, max_length=50, alias="deliveryMethod", description="送货方式")
|
||
plan_qty: Optional[Decimal] = Field(default=None, alias="planQty", description="本段计划数量")
|
||
remark: Optional[str] = Field(default=None, max_length=512, description="备注")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanStopProductRead(PlanStopProductBody):
|
||
id: str
|
||
plan_id: str = Field(alias="planId")
|
||
inbound_qty: Optional[Decimal] = Field(default=None, alias="inboundQty", description="入库数量(装货段归档)")
|
||
inbound_price: Optional[Decimal] = Field(default=None, alias="inboundPrice", description="入库单价(装货段归档)")
|
||
inbound_amount: Optional[Decimal] = Field(default=None, alias="inboundAmount", description="入库金额(装货段归档)")
|
||
outbound_qty: Optional[Decimal] = Field(default=None, alias="outboundQty", description="出库数量(送货段归档)")
|
||
outbound_price: Optional[Decimal] = Field(default=None, alias="outboundPrice", description="出库单价(送货段归档)")
|
||
outbound_amount: Optional[Decimal] = Field(default=None, alias="outboundAmount", description="出库金额(送货段归档)")
|
||
brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(
|
||
default=None, alias="brandQtys", description="本段归档品牌数量"
|
||
)
|
||
tenant_no: str = Field(alias="tenantNo")
|
||
order_num: Optional[int] = Field(default=None, alias="orderNum")
|
||
create_time: datetime = Field(alias="createTime")
|
||
update_time: datetime = Field(alias="updateTime")
|
||
model_config = {"from_attributes": True, "populate_by_name": True}
|
||
|
||
|
||
class PlanLoadBody(BaseModel):
|
||
customer_id: Optional[str] = Field(default=None, alias="customerId", description="装货客户ID")
|
||
customer_name: Optional[str] = Field(default=None, max_length=200, alias="customerName", description="装货客户名称")
|
||
address: Optional[str] = Field(default=None, max_length=500, description="装货地址")
|
||
lng: Optional[Decimal] = Field(default=None, description="经度")
|
||
lat: Optional[Decimal] = Field(default=None, description="纬度")
|
||
load_time: OptionalPlanDateTime = Field(default=None, alias="loadTime", description="装货时间")
|
||
contact: Optional[str] = Field(default=None, max_length=200, description="联系方式")
|
||
notes: Optional[str] = Field(default=None, max_length=500, description="注意事项")
|
||
generate_dibang: int = Field(default=0, alias="generateDibang", description="是否生成地磅单 0-否 1-是")
|
||
dibang_direction: Optional[str] = Field(
|
||
default=None, alias="dibangDirection", description="地磅方向 outbound-出库 inbound-入库 none-无"
|
||
)
|
||
products: list[PlanStopProductBody] = Field(default_factory=list, description="装货段产品明细")
|
||
contracts: Optional[list["PlanContractLink"]] = Field(default=None, description="装货段合同列表")
|
||
remark: Optional[str] = Field(default=None, max_length=512, description="备注")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanDeliveryBody(BaseModel):
|
||
seq: int = Field(default=1, description="顺序号(打印、链接展示)")
|
||
customer_id: Optional[str] = Field(default=None, alias="customerId", description="送货客户ID")
|
||
customer_name: Optional[str] = Field(default=None, max_length=200, alias="customerName", description="送货客户名称")
|
||
address: Optional[str] = Field(default=None, max_length=500, description="送货地址")
|
||
lng: Optional[Decimal] = Field(default=None, description="经度")
|
||
lat: Optional[Decimal] = Field(default=None, description="纬度")
|
||
delivery_time: OptionalPlanDateTime = Field(default=None, alias="deliveryTime", description="送货时间")
|
||
contact: Optional[str] = Field(default=None, max_length=200, description="联系方式")
|
||
notes: Optional[str] = Field(default=None, max_length=500, description="注意事项")
|
||
generate_dibang: int = Field(default=0, alias="generateDibang", description="是否生成地磅单 0-否 1-是")
|
||
dibang_direction: Optional[str] = Field(
|
||
default=None, alias="dibangDirection", description="地磅方向 outbound-出库 inbound-入库 none-无"
|
||
)
|
||
products: list[PlanStopProductBody] = Field(default_factory=list, description="送货段产品明细")
|
||
contracts: Optional[list["PlanContractLink"]] = Field(default=None, description="送货段合同列表")
|
||
remark: Optional[str] = Field(default=None, max_length=512, description="备注")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanLoadRead(PlanLoadBody):
|
||
id: str
|
||
plan_id: str = Field(alias="planId")
|
||
products: list[PlanStopProductRead] = Field(default_factory=list, description="装货段产品明细")
|
||
tenant_no: str = Field(alias="tenantNo")
|
||
order_num: Optional[int] = Field(default=None, alias="orderNum")
|
||
create_time: datetime = Field(alias="createTime")
|
||
update_time: datetime = Field(alias="updateTime")
|
||
model_config = {"from_attributes": True, "populate_by_name": True}
|
||
|
||
|
||
class PlanDeliveryRead(PlanDeliveryBody):
|
||
id: str
|
||
plan_id: str = Field(alias="planId")
|
||
products: list[PlanStopProductRead] = Field(default_factory=list, description="送货段产品明细")
|
||
tenant_no: str = Field(alias="tenantNo")
|
||
order_num: Optional[int] = Field(default=None, alias="orderNum")
|
||
create_time: datetime = Field(alias="createTime")
|
||
update_time: datetime = Field(alias="updateTime")
|
||
model_config = {"from_attributes": True, "populate_by_name": True}
|
||
|
||
|
||
# =============================================================================
|
||
# 计划主表 Schema
|
||
# =============================================================================
|
||
|
||
|
||
class PlanBase(BaseModel):
|
||
belongs_company: str = Field(..., alias="belongsCompany", description="归属公司 shanxia-安庆山峡 yundong-安徽云栋")
|
||
plan_type: str = Field(
|
||
default="",
|
||
alias="planType",
|
||
description="单据类型 direct-直销单 inbound-入库单 outbound-出库单;未选择可为空",
|
||
)
|
||
follow_status: str = Field(
|
||
default="pending_arrange",
|
||
alias="followStatus",
|
||
description="跟进状态 wait_notice/pending_arrange/arranged/completed/cancelled",
|
||
)
|
||
transport_status: str = Field(default="pending", alias="transportStatus", description="运输安排状态")
|
||
archive_status: str = Field(default="unarchived", alias="archiveStatus", description="归档状态 unarchived/draft/archived")
|
||
complete_status: str = Field(default="no", alias="completeStatus", description="完成状态")
|
||
contract_id: Optional[str] = Field(default=None, alias="contractId", description="CRM合同ID")
|
||
contract_no: Optional[str] = Field(default=None, max_length=50, alias="contractNo", description="合同编号")
|
||
supplier_id: Optional[str] = Field(default=None, alias="supplierId", description="供应商ID")
|
||
supplier_name: Optional[str] = Field(default=None, max_length=200, alias="supplierName", description="供应商名称")
|
||
destination_id: Optional[str] = Field(default=None, alias="destinationId", description="目的地ID")
|
||
destination_name: Optional[str] = Field(default=None, max_length=200, alias="destinationName", description="目的地名称")
|
||
customer_id: Optional[str] = Field(default=None, alias="customerId", description="客户ID")
|
||
customer_name: Optional[str] = Field(default=None, max_length=200, alias="customerName", description="客户名称")
|
||
contact_name: Optional[str] = Field(default=None, max_length=100, alias="contactName", description="联系人")
|
||
contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone", description="联系电话")
|
||
party_a: Optional[str] = Field(default=None, max_length=200, alias="partyA", description="甲方")
|
||
party_b: Optional[str] = Field(default=None, max_length=200, alias="partyB", description="乙方")
|
||
contract_start_date: Optional[date] = Field(default=None, alias="contractStartDate", description="合同开始日期")
|
||
contract_end_date: Optional[date] = Field(default=None, alias="contractEndDate", description="合同结束日期")
|
||
delivery_method: Optional[str] = Field(default=None, alias="deliveryMethod", description="送货方式")
|
||
settlement_method: Optional[str] = Field(default=None, max_length=50, alias="settlementMethod", description="结算方式")
|
||
has_freight: int = Field(default=0, alias="hasFreight", description="是否有运输费用")
|
||
freight_remark: Optional[str] = Field(default=None, max_length=512, alias="freightRemark", description="运费备注")
|
||
pickup_address: Optional[str] = Field(default=None, max_length=500, alias="pickupAddress", description="取货地址")
|
||
pickup_lng: Optional[Decimal] = Field(default=None, alias="pickupLng", description="取货经度")
|
||
pickup_lat: Optional[Decimal] = Field(default=None, alias="pickupLat", description="取货纬度")
|
||
pickup_time: OptionalPlanDateTime = Field(default=None, alias="pickupTime", description="取货时间")
|
||
pickup_contact: Optional[str] = Field(default=None, max_length=200, alias="pickupContact", description="取货联系方式")
|
||
pickup_notes: Optional[str] = Field(default=None, max_length=500, alias="pickupNotes", description="取货注意事项")
|
||
destination_address: Optional[str] = Field(default=None, max_length=500, alias="destinationAddress", description="目的地地址")
|
||
destination_lng: Optional[Decimal] = Field(default=None, alias="destinationLng", description="送货经度")
|
||
destination_lat: Optional[Decimal] = Field(default=None, alias="destinationLat", description="送货纬度")
|
||
delivery_time: OptionalPlanDateTime = Field(default=None, alias="deliveryTime", description="送达时间")
|
||
delivery_contact: Optional[str] = Field(default=None, max_length=200, alias="deliveryContact", description="送货联系方式")
|
||
destination_notes: Optional[str] = Field(default=None, max_length=500, alias="destinationNotes", description="目的地注意事项")
|
||
notify_message: Optional[str] = Field(default=None, alias="notifyMessage", description="通知信息")
|
||
follow_user_id: Optional[str] = Field(default=None, alias="followUserId", description="跟进人ID")
|
||
follow_user_name: Optional[str] = Field(default=None, max_length=100, alias="followUserName", description="跟进人姓名")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class PlanContractLink(BaseModel):
|
||
contract_id: str = Field(..., alias="contractId", description="CRM合同ID")
|
||
contract_no: Optional[str] = Field(default=None, max_length=50, alias="contractNo", description="合同编号")
|
||
stop_id: Optional[str] = Field(default=None, alias="stopId", description="停靠段ID(装货段或送货段)")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanCreate(PlanBase):
|
||
create_date: Optional[date] = Field(
|
||
default=None, alias="createDate", description="创建日期;缺省则服务端用当天"
|
||
)
|
||
plan_date: Optional[date] = Field(default=None, alias="planDate", description="计划日期(非必填)")
|
||
products: Optional[list[PlanProductCreate]] = Field(default=None, description="产品明细列表")
|
||
supplier_contracts: Optional[list[PlanContractLink]] = Field(
|
||
default=None, alias="supplierContracts", description="供方合同列表"
|
||
)
|
||
demander_contracts: Optional[list[PlanContractLink]] = Field(
|
||
default=None, alias="demanderContracts", description="需方合同列表"
|
||
)
|
||
load: Optional[PlanLoadBody] = Field(default=None, description="装货段")
|
||
deliveries: Optional[list[PlanDeliveryBody]] = Field(default=None, description="送货段列表")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanTransportCreateBlock(BaseModel):
|
||
vehicle_id: str = Field(..., alias="vehicleId", description="车辆ID")
|
||
driver_id: Optional[str] = Field(default=None, alias="driverId", description="司机/人员ID")
|
||
carrier_id: Optional[str] = Field(default=None, alias="carrierId", description="承运商ID")
|
||
distance_km: Optional[Decimal] = Field(default=None, alias="distanceKm", description="运输公里数")
|
||
freight_type: str = Field(default="by_weight", alias="freightType", description="运费计算方式")
|
||
transport_qty: Optional[Decimal] = Field(default=None, alias="transportQty", description="运输量")
|
||
freight_price: Optional[Decimal] = Field(default=None, alias="freightPrice", description="运输单价")
|
||
freight_amount: Optional[Decimal] = Field(default=None, alias="freightAmount", description="运费金额")
|
||
has_invoice: Optional[int] = Field(default=None, alias="hasInvoice", description="是否开票(已废弃,由运费结算维护)")
|
||
remark: Optional[str] = None
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanUpdate(BaseModel):
|
||
create_date: Optional[date] = Field(default=None, alias="createDate")
|
||
plan_date: Optional[date] = Field(default=None, alias="planDate")
|
||
belongs_company: Optional[str] = Field(default=None, alias="belongsCompany")
|
||
plan_type: Optional[str] = Field(default=None, alias="planType")
|
||
follow_status: Optional[str] = Field(default=None, alias="followStatus")
|
||
transport_status: Optional[str] = Field(default=None, alias="transportStatus")
|
||
supplier_id: Optional[str] = Field(default=None, alias="supplierId")
|
||
supplier_name: Optional[str] = Field(default=None, max_length=200, alias="supplierName")
|
||
destination_id: Optional[str] = Field(default=None, alias="destinationId")
|
||
destination_name: Optional[str] = Field(default=None, max_length=200, alias="destinationName")
|
||
customer_id: Optional[str] = Field(default=None, alias="customerId")
|
||
customer_name: Optional[str] = Field(default=None, max_length=200, alias="customerName")
|
||
contact_name: Optional[str] = Field(default=None, max_length=100, alias="contactName")
|
||
contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone")
|
||
party_a: Optional[str] = Field(default=None, max_length=200, alias="partyA")
|
||
party_b: Optional[str] = Field(default=None, max_length=200, alias="partyB")
|
||
contract_start_date: Optional[date] = Field(default=None, alias="contractStartDate")
|
||
contract_end_date: Optional[date] = Field(default=None, alias="contractEndDate")
|
||
delivery_method: Optional[str] = Field(default=None, alias="deliveryMethod")
|
||
settlement_method: Optional[str] = Field(default=None, max_length=50, alias="settlementMethod")
|
||
has_freight: Optional[int] = Field(default=None, alias="hasFreight")
|
||
freight_remark: Optional[str] = Field(default=None, max_length=512, alias="freightRemark")
|
||
pickup_address: Optional[str] = Field(default=None, max_length=500, alias="pickupAddress")
|
||
pickup_lng: Optional[Decimal] = Field(default=None, alias="pickupLng")
|
||
pickup_lat: Optional[Decimal] = Field(default=None, alias="pickupLat")
|
||
pickup_time: OptionalPlanDateTime = Field(default=None, alias="pickupTime")
|
||
pickup_contact: Optional[str] = Field(default=None, max_length=200, alias="pickupContact")
|
||
pickup_notes: Optional[str] = Field(default=None, max_length=500, alias="pickupNotes")
|
||
destination_address: Optional[str] = Field(default=None, max_length=500, alias="destinationAddress")
|
||
destination_lng: Optional[Decimal] = Field(default=None, alias="destinationLng")
|
||
destination_lat: Optional[Decimal] = Field(default=None, alias="destinationLat")
|
||
delivery_time: OptionalPlanDateTime = Field(default=None, alias="deliveryTime")
|
||
delivery_contact: Optional[str] = Field(default=None, max_length=200, alias="deliveryContact")
|
||
destination_notes: Optional[str] = Field(default=None, max_length=500, alias="destinationNotes")
|
||
notify_message: Optional[str] = Field(default=None, alias="notifyMessage")
|
||
follow_user_id: Optional[str] = Field(default=None, alias="followUserId")
|
||
follow_user_name: Optional[str] = Field(default=None, alias="followUserName")
|
||
remark: Optional[str] = None
|
||
products: Optional[list[PlanProductCreate]] = None
|
||
supplier_contracts: Optional[list[PlanContractLink]] = Field(
|
||
default=None, alias="supplierContracts", description="供方合同列表"
|
||
)
|
||
demander_contracts: Optional[list[PlanContractLink]] = Field(
|
||
default=None, alias="demanderContracts", description="需方合同列表"
|
||
)
|
||
transport: Optional[PlanTransportCreateBlock] = Field(default=None, description="运输预选/更新(有车牌时同步车辆与运费)")
|
||
load: Optional[PlanLoadBody] = Field(default=None, description="装货段")
|
||
deliveries: Optional[list[PlanDeliveryBody]] = Field(default=None, description="送货段列表")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class HazardCertReadyBody(BaseModel):
|
||
ready: bool = Field(..., description="true=确认证书齐备 false=取消齐备")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class ErpEnteredBody(BaseModel):
|
||
entered: bool = Field(..., description="true=已录入ERP false=未录入")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanDocumentAttachmentItem(BaseModel):
|
||
url: str = Field(..., description="附件URL")
|
||
label: str = Field(..., description="来源标签,如司机/现场")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanRead(PlanBase, PlanResourceSnapshotMixin):
|
||
id: str
|
||
plan_no: str = Field(alias="planNo")
|
||
create_date: date = Field(alias="createDate")
|
||
plan_date: Optional[date] = Field(default=None, alias="planDate")
|
||
hazard_cert_ready: int = Field(default=0, alias="hazardCertReady", description="危化品证书是否已人工确认齐备")
|
||
hazard_cert_ready_by: Optional[str] = Field(default=None, alias="hazardCertReadyBy", description="危化品证书确认人")
|
||
hazard_cert_ready_time: Optional[datetime] = Field(default=None, alias="hazardCertReadyTime", description="危化品证书确认时间")
|
||
erp_entered: int = Field(default=0, alias="erpEntered", description="是否已录入ERP 0-否 1-是")
|
||
vehicle_id: Optional[str] = Field(default=None, alias="vehicleId")
|
||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||
driver_id: Optional[str] = Field(default=None, alias="driverId")
|
||
driver_name: Optional[str] = Field(default=None, alias="driverName")
|
||
personnel_summary: Optional[str] = Field(default=None, alias="personnelSummary")
|
||
transport_id: Optional[str] = Field(default=None, alias="transportId")
|
||
transport_no: Optional[str] = Field(default=None, alias="transportNo")
|
||
total_amount: Decimal = Field(alias="totalAmount")
|
||
freight_amount: Decimal = Field(alias="freightAmount")
|
||
tenant_no: str = Field(alias="tenantNo")
|
||
order_num: Optional[int] = Field(default=None, alias="orderNum")
|
||
create_by: Optional[str] = Field(default=None, alias="createBy")
|
||
create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
|
||
create_time: datetime = Field(alias="createTime")
|
||
update_by: Optional[str] = Field(default=None, alias="updateBy")
|
||
update_time: datetime = Field(alias="updateTime")
|
||
del_flag: int = Field(default=0, alias="delFlag")
|
||
supplier_contracts: list[PlanContractLink] = Field(
|
||
default_factory=list, alias="supplierContracts", description="供方合同列表"
|
||
)
|
||
demander_contracts: list[PlanContractLink] = Field(
|
||
default_factory=list, alias="demanderContracts", description="需方合同列表"
|
||
)
|
||
model_config = {"from_attributes": True, "populate_by_name": True}
|
||
|
||
|
||
class PlanWithProducts(PlanRead):
|
||
products: list[PlanProductRead] = []
|
||
load: Optional[PlanLoadRead] = Field(default=None, description="装货段")
|
||
deliveries: list[PlanDeliveryRead] = Field(default_factory=list, description="送货段列表")
|
||
load_customer_name: Optional[str] = Field(
|
||
default=None,
|
||
alias="loadCustomerName",
|
||
description="装货客户名称摘要(列表展示)",
|
||
)
|
||
delivery_customer_summary: Optional[str] = Field(
|
||
default=None,
|
||
alias="deliveryCustomerSummary",
|
||
description="送货客户摘要(列表展示)",
|
||
)
|
||
warnings: list[str] = Field(default_factory=list, description="保存告警(如数量不平衡)")
|
||
can_copy_to_driver: bool = Field(
|
||
default=False,
|
||
alias="canCopyToDriver",
|
||
description="列表/查看:是否可「复制给司机」(已安排且有司机链接或现场/地磅关联)",
|
||
)
|
||
can_copy_to_site: bool = Field(
|
||
default=False,
|
||
alias="canCopyToSite",
|
||
description="是否可「复制给现场」",
|
||
)
|
||
can_copy_pound_qrcode: bool = Field(
|
||
default=False,
|
||
alias="canCopyPoundQrcode",
|
||
description="是否可「复制磅单二维码」",
|
||
)
|
||
document_attachments: list[PlanDocumentAttachmentItem] = Field(
|
||
default_factory=list,
|
||
alias="documentAttachments",
|
||
description="单据附件(司机/现场/出入库现场等)",
|
||
)
|
||
|
||
|
||
# =============================================================================
|
||
# 送(提)货单打印(按送货客户聚合)
|
||
# =============================================================================
|
||
|
||
|
||
class DeliveryPrintCustomerOption(BaseModel):
|
||
customer_id: str = Field(alias="customerId", description="送货客户ID或名称键")
|
||
customer_name: str = Field(alias="customerName", description="送货客户名称")
|
||
delivery_count: int = Field(default=1, alias="deliveryCount", description="送货段数量")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class DeliverySlipPrintRead(BaseModel):
|
||
plan_id: str = Field(alias="planId")
|
||
plan_no: str = Field(alias="planNo")
|
||
plan_date: Optional[str] = Field(default=None, alias="planDate")
|
||
customer_id: Optional[str] = Field(default=None, alias="customerId")
|
||
receiving_unit: Optional[str] = Field(default=None, alias="receivingUnit")
|
||
receive_contact: Optional[str] = Field(default=None, alias="receiveContact")
|
||
delivery_address: Optional[str] = Field(default=None, alias="deliveryAddress")
|
||
products: list[PlanStopProductRead] = Field(default_factory=list)
|
||
customers: list[DeliveryPrintCustomerOption] = Field(default_factory=list)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanInboundSlipTemplateSave(BaseModel):
|
||
slip_template_content: str = Field(..., alias="slipTemplateContent", description="入库单打印模板HTML")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanDeliverySlipTemplateSave(BaseModel):
|
||
delivery_slip_template_content: str = Field(
|
||
..., alias="slipTemplateContent", description="送(提)货单打印模板HTML"
|
||
)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanSubpackSlipTemplateSave(BaseModel):
|
||
subpack_slip_template_content: str = Field(
|
||
..., alias="slipTemplateContent", description="分装单打印模板HTML"
|
||
)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
# =============================================================================
|
||
# 从合同创建计划
|
||
# =============================================================================
|
||
|
||
|
||
class CreateFromContractRequest(BaseModel):
|
||
contract_id: str = Field(..., alias="contractId", description="CRM合同ID")
|
||
create_date: Optional[date] = Field(
|
||
default=None, alias="createDate", description="创建日期;缺省当天"
|
||
)
|
||
plan_date: Optional[date] = Field(default=None, alias="planDate", description="计划日期(非必填)")
|
||
follow_user_id: Optional[str] = Field(default=None, alias="followUserId", description="跟进人ID")
|
||
follow_user_name: Optional[str] = Field(default=None, max_length=100, alias="followUserName", description="跟进人姓名")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanCreateFull(PlanCreate):
|
||
contract_id: Optional[str] = Field(default=None, alias="contractId", description="旧字段:单一合同ID,已废弃为唯一源")
|
||
transport: Optional[PlanTransportCreateBlock] = Field(default=None, description="运输安排(有车牌时创建运输)")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
# =============================================================================
|
||
# 计划列表查询
|
||
# =============================================================================
|
||
|
||
|
||
class PlanListQuery(BaseModel):
|
||
q: Optional[str] = Field(default=None, description="模糊搜索计划号/客户名称/产品名称/车牌号")
|
||
plan_no: Optional[str] = Field(default=None, alias="planNo", description="计划编号")
|
||
plan_type: Optional[str] = Field(default=None, alias="planType", description="单据类型")
|
||
follow_status: Optional[str] = Field(default=None, alias="followStatus", description="跟进状态")
|
||
transport_status: Optional[str] = Field(default=None, alias="transportStatus", description="运输状态")
|
||
archive_status: Optional[str] = Field(default=None, alias="archiveStatus", description="归档状态")
|
||
customer_name: Optional[str] = Field(default=None, alias="customerName", description="客户名称")
|
||
product_name: Optional[str] = Field(default=None, alias="productName", description="产品名称")
|
||
license_plate: Optional[str] = Field(default=None, alias="licensePlate", description="车牌号")
|
||
plan_date_from: Optional[date] = Field(default=None, alias="planDateFrom", description="计划日期开始")
|
||
plan_date_to: Optional[date] = Field(default=None, alias="planDateTo", description="计划日期结束")
|
||
has_hazard_product: Optional[int] = Field(
|
||
default=None, alias="hasHazardProduct", description="1=仅含易制毒/易制爆产品的计划"
|
||
)
|
||
cert_upload_status: Optional[str] = Field(
|
||
default=None,
|
||
alias="certUploadStatus",
|
||
description="incomplete=至少一张必需危化证书未上传",
|
||
)
|
||
page: int = Field(default=1, ge=1, description="页码")
|
||
page_size: int = Field(default=20, ge=1, le=500, alias="pageSize", description="每页数量")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanProductCertItem(BaseModel):
|
||
id: str = Field(..., description="计划产品明细ID")
|
||
purchase_cert_url: Optional[str] = Field(default=None, alias="purchaseCertUrl", description="购买证URL,逗号分隔")
|
||
transport_cert_url: Optional[str] = Field(default=None, alias="transportCertUrl", description="运输证URL,逗号分隔")
|
||
legal_use_cert_url: Optional[str] = Field(default=None, alias="legalUseCertUrl", description="合法使用证明URL,逗号分隔")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanProductCertsUpdate(BaseModel):
|
||
products: list[PlanProductCertItem] = Field(..., description="危化品证书更新列表")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveProductItem(BaseModel):
|
||
id: str = Field(..., description="计划产品明细ID pl_plan_product.id")
|
||
actual_qty: Optional[Decimal] = Field(default=None, alias="actualQty", ge=0, description="实际数量")
|
||
inbound_qty: Optional[Decimal] = Field(default=None, alias="inboundQty", ge=0, description="入库数量")
|
||
inbound_price: Optional[Decimal] = Field(default=None, alias="inboundPrice", ge=0, description="入库价")
|
||
outbound_qty: Optional[Decimal] = Field(default=None, alias="outboundQty", ge=0, description="出库数量")
|
||
outbound_price: Optional[Decimal] = Field(default=None, alias="outboundPrice", ge=0, description="出库价")
|
||
brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(default=None, alias="brandQtys")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveStopProductItem(BaseModel):
|
||
id: str = Field(..., description="段产品行ID(pl_plan_load_product / pl_plan_delivery_product)")
|
||
inbound_qty: Optional[Decimal] = Field(default=None, alias="inboundQty", ge=0, description="入库数量(装货段)")
|
||
inbound_price: Optional[Decimal] = Field(default=None, alias="inboundPrice", ge=0, description="入库单价(装货段)")
|
||
outbound_qty: Optional[Decimal] = Field(default=None, alias="outboundQty", ge=0, description="出库数量(送货段)")
|
||
outbound_price: Optional[Decimal] = Field(default=None, alias="outboundPrice", ge=0, description="出库单价(送货段)")
|
||
brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(
|
||
default=None, alias="brandQtys", description="本段归档品牌数量(与归档弹窗填写一致)"
|
||
)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveLoadBlock(BaseModel):
|
||
stop_id: str = Field(alias="stopId", description="装货段ID")
|
||
products: list[PlanArchiveStopProductItem] = Field(default_factory=list)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveDeliveryBlock(BaseModel):
|
||
stop_id: str = Field(alias="stopId", description="送货段ID")
|
||
products: list[PlanArchiveStopProductItem] = Field(default_factory=list)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveFreightBlock(BaseModel):
|
||
has_freight: int = Field(default=1, alias="hasFreight", description="是否有运输费用 0-否 1-是")
|
||
freight_remark: Optional[str] = Field(default=None, max_length=512, alias="freightRemark", description="运费备注")
|
||
distance_km: Optional[Decimal] = Field(default=None, alias="distanceKm")
|
||
freight_type: str = Field(default="by_weight", alias="freightType")
|
||
freight_price: Optional[Decimal] = Field(default=None, alias="freightPrice")
|
||
freight_amount: Optional[Decimal] = Field(default=None, alias="freightAmount")
|
||
transport_qty: Optional[Decimal] = Field(default=None, alias="transportQty")
|
||
has_invoice: Optional[int] = Field(default=None, alias="hasInvoice")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveStopFeedbackAttachmentItem(BaseModel):
|
||
stop_type: str = Field(alias="stopType", description="停靠段类型 load/delivery")
|
||
stop_id: str = Field(alias="stopId", description="停靠段ID")
|
||
attachment_urls: list[str] = Field(default_factory=list, alias="attachmentUrls", description="附件URLs")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveFeedbacksBlock(BaseModel):
|
||
driver_stops: list[PlanArchiveStopFeedbackAttachmentItem] = Field(
|
||
default_factory=list, alias="driverStops", description="司机按段回传附件"
|
||
)
|
||
site_stops: list[PlanArchiveStopFeedbackAttachmentItem] = Field(
|
||
default_factory=list, alias="siteStops", description="现场按段回传附件"
|
||
)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanArchiveBody(BaseModel):
|
||
products: list[PlanArchiveProductItem] = Field(default_factory=list)
|
||
load: Optional[PlanArchiveLoadBlock] = None
|
||
deliveries: Optional[list[PlanArchiveDeliveryBlock]] = None
|
||
freight: Optional[PlanArchiveFreightBlock] = None
|
||
feedbacks: Optional[PlanArchiveFeedbacksBlock] = None
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanDriverFeedbackProductItem(BaseModel):
|
||
plan_product_id: str = Field(alias="planProductId")
|
||
actual_qty: Decimal = Field(alias="actualQty", ge=0)
|
||
brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(default=None, alias="brandQtys")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanDriverFeedbackUpdateBody(BaseModel):
|
||
feedback_remark: Optional[str] = Field(default=None, alias="feedbackRemark", max_length=500, description="回传备注")
|
||
feedback_images: Optional[list[str]] = Field(default=None, alias="feedbackImages", description="回传附件URLs")
|
||
feedback_time: OptionalPlanDateTime = Field(default=None, alias="feedbackTime", description="回传时间")
|
||
products: Optional[list[PlanDriverFeedbackProductItem]] = None
|
||
feedback_qty: Optional[Decimal] = Field(default=None, alias="feedbackQty", ge=0, description="回传数量")
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanSiteFeedbackUpdateBody(BaseModel):
|
||
site_feedback_remark: Optional[str] = Field(
|
||
default=None, alias="siteFeedbackRemark", max_length=500, description="现场回执备注"
|
||
)
|
||
site_feedback_images: Optional[list[str]] = Field(
|
||
default=None, alias="siteFeedbackImages", description="现场回执附件URLs"
|
||
)
|
||
site_feedback_time: OptionalPlanDateTime = Field(
|
||
default=None, alias="siteFeedbackTime", description="现场回执保存时间"
|
||
)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanListResponse(BaseModel):
|
||
total: int
|
||
list: list[PlanWithProducts]
|
||
|
||
|
||
class PlanForceFollowStatusBody(BaseModel):
|
||
"""运维纠错:强制改跟进状态(仅改字段,不联动业务)。"""
|
||
|
||
follow_status: str = Field(
|
||
...,
|
||
alias="followStatus",
|
||
description="跟进状态 wait_notice/pending_arrange/arranged/completed/cancelled",
|
||
)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanSideContractsUpdateBody(BaseModel):
|
||
contracts: list[PlanContractLink] = Field(default_factory=list)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanStopContractsUpdateBody(BaseModel):
|
||
stop_type: str = Field(..., alias="stopType", description="load | delivery")
|
||
stop_id: str = Field(..., alias="stopId", description="装货/送货段ID")
|
||
contracts: list[PlanContractLink] = Field(default_factory=list)
|
||
model_config = {"populate_by_name": True}
|
||
|
||
|
||
class PlanContractGapRead(BaseModel):
|
||
id: str
|
||
plan_no: str = Field(..., alias="planNo")
|
||
plan_date: Optional[date] = Field(None, alias="planDate")
|
||
plan_type: Optional[str] = Field(None, alias="planType")
|
||
follow_status: str = Field(..., alias="followStatus")
|
||
supplier_id: Optional[str] = Field(None, alias="supplierId")
|
||
supplier_name: Optional[str] = Field(None, alias="supplierName")
|
||
customer_id: Optional[str] = Field(None, alias="customerId")
|
||
customer_name: Optional[str] = Field(None, alias="customerName")
|
||
gap_sides: list[str] = Field(default_factory=list, alias="gapSides")
|
||
supplier_contract_exempt: bool = Field(..., alias="supplierContractExempt")
|
||
demander_contract_exempt: bool = Field(..., alias="demanderContractExempt")
|
||
supplier_contracts: list[PlanContractLink] = Field(default_factory=list, alias="supplierContracts")
|
||
demander_contracts: list[PlanContractLink] = Field(default_factory=list, alias="demanderContracts")
|
||
products: list[PlanProductRead] = Field(default_factory=list, description="产品明细")
|
||
load: Optional["PlanLoadRead"] = Field(default=None, description="装货段")
|
||
deliveries: list["PlanDeliveryRead"] = Field(default_factory=list, alias="deliveries", description="送货段列表")
|
||
model_config = {"populate_by_name": True, "from_attributes": True}
|
||
|
||
|
||
class PlanContractGapListResponse(BaseModel):
|
||
total: int
|
||
list: list[PlanContractGapRead]
|