plan.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. """运输计划管理 - 计划 Schema"""
  2. from datetime import date, datetime
  3. from decimal import Decimal
  4. from typing import Optional
  5. from pydantic import BaseModel, Field
  6. from app.schemas.pl.datetime_parse import OptionalPlanDateTime
  7. from app.schemas.pl.resource_snapshot import PlanResourceSnapshotMixin
  8. # =============================================================================
  9. # 计划产品明细 Schema
  10. # =============================================================================
  11. class PlanProductBase(BaseModel):
  12. product_id: str = Field(..., alias="productId", description="产品ID")
  13. product_no: str = Field(..., max_length=50, alias="productNo", description="产品编号")
  14. product_name: str = Field(..., max_length=200, alias="productName", description="产品名称")
  15. brand_mode: Optional[str] = Field(default=None, alias="brandMode", description="required/any")
  16. brand_id: Optional[str] = Field(default=None, alias="brandId", description="指定品牌ID")
  17. brand_name: Optional[str] = Field(default=None, alias="brandName", description="品牌名快照")
  18. model: Optional[str] = Field(default=None, max_length=100, description="规格型号")
  19. unit: str = Field(default="吨", max_length=20, description="单位")
  20. category_name: Optional[str] = Field(default=None, max_length=200, alias="categoryName", description="产品分类")
  21. package_spec: Optional[str] = Field(default=None, max_length=200, alias="packageSpec", description="包装规格")
  22. delivery_method: Optional[str] = Field(default=None, max_length=50, alias="deliveryMethod", description="送货方式")
  23. quantity: Optional[Decimal] = Field(default=None, description="合同数量")
  24. actual_qty: Optional[Decimal] = Field(default=None, alias="actualQty", description="实际数量")
  25. tax_price: Optional[Decimal] = Field(default=None, alias="taxPrice", description="含税单价")
  26. tax_amount: Optional[Decimal] = Field(default=None, alias="taxAmount", description="税费")
  27. tax_subtotal: Optional[Decimal] = Field(default=None, alias="taxSubtotal", description="含税总价")
  28. is_explosive: int = Field(default=0, alias="isExplosive", description="是否易制爆")
  29. is_toxic: int = Field(default=0, alias="isToxic", description="是否易制毒")
  30. purchase_cert_url: Optional[str] = Field(
  31. default=None, alias="purchaseCertUrl", description="购买证URL,逗号分隔"
  32. )
  33. transport_cert_url: Optional[str] = Field(
  34. default=None, alias="transportCertUrl", description="运输证URL,逗号分隔"
  35. )
  36. legal_use_cert_url: Optional[str] = Field(
  37. default=None, alias="legalUseCertUrl", description="合法使用证明URL,逗号分隔"
  38. )
  39. inbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="inboundEstimateQty", description="入库预估数")
  40. inbound_qty: Optional[Decimal] = Field(default=None, alias="inboundQty", description="入库数")
  41. inbound_price: Optional[Decimal] = Field(default=None, alias="inboundPrice", description="入库价")
  42. inbound_amount: Optional[Decimal] = Field(default=None, alias="inboundAmount", description="入库金额")
  43. outbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="outboundEstimateQty", description="出库预估数")
  44. outbound_qty: Optional[Decimal] = Field(default=None, alias="outboundQty", description="出库数")
  45. outbound_price: Optional[Decimal] = Field(default=None, alias="outboundPrice", description="出库单价")
  46. outbound_amount: Optional[Decimal] = Field(default=None, alias="outboundAmount", description="出库金额")
  47. remark: Optional[str] = Field(default=None, max_length=512, description="备注")
  48. class PlanProductCreate(PlanProductBase):
  49. model_config = {"populate_by_name": True}
  50. class PlanProductUpdate(BaseModel):
  51. product_id: Optional[str] = Field(default=None, alias="productId")
  52. product_no: Optional[str] = Field(default=None, max_length=50, alias="productNo")
  53. product_name: Optional[str] = Field(default=None, max_length=200, alias="productName")
  54. model: Optional[str] = Field(default=None, max_length=100)
  55. unit: Optional[str] = Field(default=None, max_length=20)
  56. is_explosive: Optional[int] = Field(default=None, alias="isExplosive")
  57. is_toxic: Optional[int] = Field(default=None, alias="isToxic")
  58. purchase_cert_url: Optional[str] = Field(default=None, alias="purchaseCertUrl")
  59. transport_cert_url: Optional[str] = Field(default=None, alias="transportCertUrl")
  60. legal_use_cert_url: Optional[str] = Field(default=None, alias="legalUseCertUrl")
  61. inbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="inboundEstimateQty")
  62. inbound_qty: Optional[Decimal] = Field(default=None, alias="inboundQty")
  63. inbound_price: Optional[Decimal] = Field(default=None, alias="inboundPrice")
  64. inbound_amount: Optional[Decimal] = Field(default=None, alias="inboundAmount")
  65. outbound_estimate_qty: Optional[Decimal] = Field(default=None, alias="outboundEstimateQty")
  66. outbound_qty: Optional[Decimal] = Field(default=None, alias="outboundQty")
  67. outbound_price: Optional[Decimal] = Field(default=None, alias="outboundPrice")
  68. outbound_amount: Optional[Decimal] = Field(default=None, alias="outboundAmount")
  69. model_config = {"populate_by_name": True}
  70. class PlanArchiveBrandQtyItem(BaseModel):
  71. brand_id: str = Field(default="", alias="brandId")
  72. brand_name: Optional[str] = Field(default=None, alias="brandName")
  73. qty: Decimal = Field(..., ge=0)
  74. model_config = {"populate_by_name": True}
  75. class PlanProductRead(PlanProductBase):
  76. id: str
  77. plan_id: str = Field(alias="planId")
  78. tenant_no: str = Field(alias="tenantNo")
  79. create_time: datetime = Field(alias="createTime")
  80. update_time: datetime = Field(alias="updateTime")
  81. brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(
  82. default=None, alias="brandQtys", description="归档品牌数量明细"
  83. )
  84. model_config = {"from_attributes": True, "populate_by_name": True}
  85. # =============================================================================
  86. # 计划主表 Schema
  87. # =============================================================================
  88. class PlanBase(BaseModel):
  89. belongs_company: str = Field(..., alias="belongsCompany", description="归属公司 shanxia-安庆山峡 yundong-安徽云栋")
  90. plan_type: str = Field(
  91. default="",
  92. alias="planType",
  93. description="单据类型 direct-直销单 inbound-入库单 outbound-出库单;未选择可为空",
  94. )
  95. follow_status: str = Field(
  96. default="pending_arrange",
  97. alias="followStatus",
  98. description="跟进状态 wait_notice/pending_arrange/arranged/completed/cancelled",
  99. )
  100. transport_status: str = Field(default="pending", alias="transportStatus", description="运输安排状态")
  101. archive_status: str = Field(default="unarchived", alias="archiveStatus", description="归档状态")
  102. complete_status: str = Field(default="no", alias="completeStatus", description="完成状态")
  103. contract_id: Optional[str] = Field(default=None, alias="contractId", description="CRM合同ID")
  104. contract_no: Optional[str] = Field(default=None, max_length=50, alias="contractNo", description="合同编号")
  105. supplier_id: Optional[str] = Field(default=None, alias="supplierId", description="供应商ID")
  106. supplier_name: Optional[str] = Field(default=None, max_length=200, alias="supplierName", description="供应商名称")
  107. destination_id: Optional[str] = Field(default=None, alias="destinationId", description="目的地ID")
  108. destination_name: Optional[str] = Field(default=None, max_length=200, alias="destinationName", description="目的地名称")
  109. customer_id: Optional[str] = Field(default=None, alias="customerId", description="客户ID")
  110. customer_name: Optional[str] = Field(default=None, max_length=200, alias="customerName", description="客户名称")
  111. contact_name: Optional[str] = Field(default=None, max_length=100, alias="contactName", description="联系人")
  112. contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone", description="联系电话")
  113. party_a: Optional[str] = Field(default=None, max_length=200, alias="partyA", description="甲方")
  114. party_b: Optional[str] = Field(default=None, max_length=200, alias="partyB", description="乙方")
  115. contract_start_date: Optional[date] = Field(default=None, alias="contractStartDate", description="合同开始日期")
  116. contract_end_date: Optional[date] = Field(default=None, alias="contractEndDate", description="合同结束日期")
  117. delivery_method: Optional[str] = Field(default=None, alias="deliveryMethod", description="送货方式")
  118. settlement_method: Optional[str] = Field(default=None, max_length=50, alias="settlementMethod", description="结算方式")
  119. has_freight: int = Field(default=0, alias="hasFreight", description="是否有运输费用")
  120. pickup_address: Optional[str] = Field(default=None, max_length=500, alias="pickupAddress", description="取货地址")
  121. pickup_lng: Optional[Decimal] = Field(default=None, alias="pickupLng", description="取货经度")
  122. pickup_lat: Optional[Decimal] = Field(default=None, alias="pickupLat", description="取货纬度")
  123. pickup_time: OptionalPlanDateTime = Field(default=None, alias="pickupTime", description="取货时间")
  124. pickup_contact: Optional[str] = Field(default=None, max_length=200, alias="pickupContact", description="取货联系方式")
  125. pickup_notes: Optional[str] = Field(default=None, max_length=500, alias="pickupNotes", description="取货注意事项")
  126. destination_address: Optional[str] = Field(default=None, max_length=500, alias="destinationAddress", description="目的地地址")
  127. destination_lng: Optional[Decimal] = Field(default=None, alias="destinationLng", description="送货经度")
  128. destination_lat: Optional[Decimal] = Field(default=None, alias="destinationLat", description="送货纬度")
  129. delivery_time: OptionalPlanDateTime = Field(default=None, alias="deliveryTime", description="送达时间")
  130. delivery_contact: Optional[str] = Field(default=None, max_length=200, alias="deliveryContact", description="送货联系方式")
  131. destination_notes: Optional[str] = Field(default=None, max_length=500, alias="destinationNotes", description="目的地注意事项")
  132. notify_message: Optional[str] = Field(default=None, alias="notifyMessage", description="通知信息")
  133. follow_user_id: Optional[str] = Field(default=None, alias="followUserId", description="跟进人ID")
  134. follow_user_name: Optional[str] = Field(default=None, max_length=100, alias="followUserName", description="跟进人姓名")
  135. remark: Optional[str] = None
  136. class PlanContractLink(BaseModel):
  137. contract_id: str = Field(..., alias="contractId", description="CRM合同ID")
  138. contract_no: Optional[str] = Field(default=None, max_length=50, alias="contractNo", description="合同编号")
  139. model_config = {"populate_by_name": True}
  140. class PlanCreate(PlanBase):
  141. create_date: Optional[date] = Field(
  142. default=None, alias="createDate", description="创建日期;缺省则服务端用当天"
  143. )
  144. plan_date: Optional[date] = Field(default=None, alias="planDate", description="计划日期(非必填)")
  145. products: Optional[list[PlanProductCreate]] = Field(default=None, description="产品明细列表")
  146. supplier_contracts: Optional[list[PlanContractLink]] = Field(
  147. default=None, alias="supplierContracts", description="供方合同列表"
  148. )
  149. demander_contracts: Optional[list[PlanContractLink]] = Field(
  150. default=None, alias="demanderContracts", description="需方合同列表"
  151. )
  152. model_config = {"populate_by_name": True}
  153. class PlanTransportCreateBlock(BaseModel):
  154. vehicle_id: str = Field(..., alias="vehicleId", description="车辆ID")
  155. driver_id: Optional[str] = Field(default=None, alias="driverId", description="司机/人员ID")
  156. carrier_id: Optional[str] = Field(default=None, alias="carrierId", description="承运商ID")
  157. distance_km: Optional[Decimal] = Field(default=None, alias="distanceKm", description="运输公里数")
  158. freight_type: str = Field(default="by_weight", alias="freightType", description="运费计算方式")
  159. transport_qty: Optional[Decimal] = Field(default=None, alias="transportQty", description="运输量")
  160. freight_price: Optional[Decimal] = Field(default=None, alias="freightPrice", description="运输单价")
  161. freight_amount: Optional[Decimal] = Field(default=None, alias="freightAmount", description="运费金额")
  162. has_invoice: Optional[int] = Field(default=None, alias="hasInvoice", description="是否开票(已废弃,由运费结算维护)")
  163. remark: Optional[str] = None
  164. model_config = {"populate_by_name": True}
  165. class PlanUpdate(BaseModel):
  166. create_date: Optional[date] = Field(default=None, alias="createDate")
  167. plan_date: Optional[date] = Field(default=None, alias="planDate")
  168. belongs_company: Optional[str] = Field(default=None, alias="belongsCompany")
  169. plan_type: Optional[str] = Field(default=None, alias="planType")
  170. follow_status: Optional[str] = Field(default=None, alias="followStatus")
  171. transport_status: Optional[str] = Field(default=None, alias="transportStatus")
  172. supplier_id: Optional[str] = Field(default=None, alias="supplierId")
  173. supplier_name: Optional[str] = Field(default=None, max_length=200, alias="supplierName")
  174. destination_id: Optional[str] = Field(default=None, alias="destinationId")
  175. destination_name: Optional[str] = Field(default=None, max_length=200, alias="destinationName")
  176. customer_id: Optional[str] = Field(default=None, alias="customerId")
  177. customer_name: Optional[str] = Field(default=None, max_length=200, alias="customerName")
  178. contact_name: Optional[str] = Field(default=None, max_length=100, alias="contactName")
  179. contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone")
  180. party_a: Optional[str] = Field(default=None, max_length=200, alias="partyA")
  181. party_b: Optional[str] = Field(default=None, max_length=200, alias="partyB")
  182. contract_start_date: Optional[date] = Field(default=None, alias="contractStartDate")
  183. contract_end_date: Optional[date] = Field(default=None, alias="contractEndDate")
  184. delivery_method: Optional[str] = Field(default=None, alias="deliveryMethod")
  185. settlement_method: Optional[str] = Field(default=None, max_length=50, alias="settlementMethod")
  186. has_freight: Optional[int] = Field(default=None, alias="hasFreight")
  187. pickup_address: Optional[str] = Field(default=None, max_length=500, alias="pickupAddress")
  188. pickup_lng: Optional[Decimal] = Field(default=None, alias="pickupLng")
  189. pickup_lat: Optional[Decimal] = Field(default=None, alias="pickupLat")
  190. pickup_time: OptionalPlanDateTime = Field(default=None, alias="pickupTime")
  191. pickup_contact: Optional[str] = Field(default=None, max_length=200, alias="pickupContact")
  192. pickup_notes: Optional[str] = Field(default=None, max_length=500, alias="pickupNotes")
  193. destination_address: Optional[str] = Field(default=None, max_length=500, alias="destinationAddress")
  194. destination_lng: Optional[Decimal] = Field(default=None, alias="destinationLng")
  195. destination_lat: Optional[Decimal] = Field(default=None, alias="destinationLat")
  196. delivery_time: OptionalPlanDateTime = Field(default=None, alias="deliveryTime")
  197. delivery_contact: Optional[str] = Field(default=None, max_length=200, alias="deliveryContact")
  198. destination_notes: Optional[str] = Field(default=None, max_length=500, alias="destinationNotes")
  199. notify_message: Optional[str] = Field(default=None, alias="notifyMessage")
  200. follow_user_id: Optional[str] = Field(default=None, alias="followUserId")
  201. follow_user_name: Optional[str] = Field(default=None, alias="followUserName")
  202. remark: Optional[str] = None
  203. products: Optional[list[PlanProductCreate]] = None
  204. supplier_contracts: Optional[list[PlanContractLink]] = Field(
  205. default=None, alias="supplierContracts", description="供方合同列表"
  206. )
  207. demander_contracts: Optional[list[PlanContractLink]] = Field(
  208. default=None, alias="demanderContracts", description="需方合同列表"
  209. )
  210. transport: Optional[PlanTransportCreateBlock] = Field(default=None, description="运输预选/更新(有车牌时同步车辆与运费)")
  211. model_config = {"populate_by_name": True}
  212. class HazardCertReadyBody(BaseModel):
  213. ready: bool = Field(..., description="true=确认证书齐备 false=取消齐备")
  214. model_config = {"populate_by_name": True}
  215. class PlanRead(PlanBase, PlanResourceSnapshotMixin):
  216. id: str
  217. plan_no: str = Field(alias="planNo")
  218. create_date: date = Field(alias="createDate")
  219. plan_date: Optional[date] = Field(default=None, alias="planDate")
  220. hazard_cert_ready: int = Field(default=0, alias="hazardCertReady", description="危化品证书是否已人工确认齐备")
  221. hazard_cert_ready_by: Optional[str] = Field(default=None, alias="hazardCertReadyBy", description="危化品证书确认人")
  222. hazard_cert_ready_time: Optional[datetime] = Field(default=None, alias="hazardCertReadyTime", description="危化品证书确认时间")
  223. vehicle_id: Optional[str] = Field(default=None, alias="vehicleId")
  224. license_plate: Optional[str] = Field(default=None, alias="licensePlate")
  225. driver_id: Optional[str] = Field(default=None, alias="driverId")
  226. driver_name: Optional[str] = Field(default=None, alias="driverName")
  227. personnel_summary: Optional[str] = Field(default=None, alias="personnelSummary")
  228. transport_id: Optional[str] = Field(default=None, alias="transportId")
  229. transport_no: Optional[str] = Field(default=None, alias="transportNo")
  230. total_amount: Decimal = Field(alias="totalAmount")
  231. freight_amount: Decimal = Field(alias="freightAmount")
  232. tenant_no: str = Field(alias="tenantNo")
  233. order_num: Optional[int] = Field(default=None, alias="orderNum")
  234. create_by: Optional[str] = Field(default=None, alias="createBy")
  235. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  236. create_time: datetime = Field(alias="createTime")
  237. update_by: Optional[str] = Field(default=None, alias="updateBy")
  238. update_time: datetime = Field(alias="updateTime")
  239. del_flag: int = Field(default=0, alias="delFlag")
  240. supplier_contracts: list[PlanContractLink] = Field(
  241. default_factory=list, alias="supplierContracts", description="供方合同列表"
  242. )
  243. demander_contracts: list[PlanContractLink] = Field(
  244. default_factory=list, alias="demanderContracts", description="需方合同列表"
  245. )
  246. model_config = {"from_attributes": True, "populate_by_name": True}
  247. class PlanWithProducts(PlanRead):
  248. products: list[PlanProductRead] = []
  249. class PlanInboundSlipTemplateSave(BaseModel):
  250. slip_template_content: str = Field(..., alias="slipTemplateContent", description="入库单打印模板HTML")
  251. model_config = {"populate_by_name": True}
  252. class PlanDeliverySlipTemplateSave(BaseModel):
  253. delivery_slip_template_content: str = Field(
  254. ..., alias="slipTemplateContent", description="送(提)货单打印模板HTML"
  255. )
  256. model_config = {"populate_by_name": True}
  257. class PlanSubpackSlipTemplateSave(BaseModel):
  258. subpack_slip_template_content: str = Field(
  259. ..., alias="slipTemplateContent", description="分装单打印模板HTML"
  260. )
  261. model_config = {"populate_by_name": True}
  262. # =============================================================================
  263. # 从合同创建计划
  264. # =============================================================================
  265. class CreateFromContractRequest(BaseModel):
  266. contract_id: str = Field(..., alias="contractId", description="CRM合同ID")
  267. create_date: Optional[date] = Field(
  268. default=None, alias="createDate", description="创建日期;缺省当天"
  269. )
  270. plan_date: Optional[date] = Field(default=None, alias="planDate", description="计划日期(非必填)")
  271. follow_user_id: Optional[str] = Field(default=None, alias="followUserId", description="跟进人ID")
  272. follow_user_name: Optional[str] = Field(default=None, max_length=100, alias="followUserName", description="跟进人姓名")
  273. model_config = {"populate_by_name": True}
  274. class PlanCreateFull(PlanCreate):
  275. contract_id: Optional[str] = Field(default=None, alias="contractId", description="旧字段:单一合同ID,已废弃为唯一源")
  276. transport: Optional[PlanTransportCreateBlock] = Field(default=None, description="运输安排(有车牌时创建运输)")
  277. model_config = {"populate_by_name": True}
  278. # =============================================================================
  279. # 计划列表查询
  280. # =============================================================================
  281. class PlanListQuery(BaseModel):
  282. q: Optional[str] = Field(default=None, description="模糊搜索计划号/客户名称/产品名称/车牌号")
  283. plan_no: Optional[str] = Field(default=None, alias="planNo", description="计划编号")
  284. plan_type: Optional[str] = Field(default=None, alias="planType", description="单据类型")
  285. follow_status: Optional[str] = Field(default=None, alias="followStatus", description="跟进状态")
  286. transport_status: Optional[str] = Field(default=None, alias="transportStatus", description="运输状态")
  287. archive_status: Optional[str] = Field(default=None, alias="archiveStatus", description="归档状态")
  288. customer_name: Optional[str] = Field(default=None, alias="customerName", description="客户名称")
  289. product_name: Optional[str] = Field(default=None, alias="productName", description="产品名称")
  290. license_plate: Optional[str] = Field(default=None, alias="licensePlate", description="车牌号")
  291. plan_date_from: Optional[date] = Field(default=None, alias="planDateFrom", description="计划日期开始")
  292. plan_date_to: Optional[date] = Field(default=None, alias="planDateTo", description="计划日期结束")
  293. has_hazard_product: Optional[int] = Field(
  294. default=None, alias="hasHazardProduct", description="1=仅含易制毒/易制爆产品的计划"
  295. )
  296. cert_upload_status: Optional[str] = Field(
  297. default=None,
  298. alias="certUploadStatus",
  299. description="incomplete=至少一张必需危化证书未上传",
  300. )
  301. page: int = Field(default=1, ge=1, description="页码")
  302. page_size: int = Field(default=20, ge=1, le=100, alias="pageSize", description="每页数量")
  303. model_config = {"populate_by_name": True}
  304. class PlanProductCertItem(BaseModel):
  305. id: str = Field(..., description="计划产品明细ID")
  306. purchase_cert_url: Optional[str] = Field(default=None, alias="purchaseCertUrl", description="购买证URL,逗号分隔")
  307. transport_cert_url: Optional[str] = Field(default=None, alias="transportCertUrl", description="运输证URL,逗号分隔")
  308. legal_use_cert_url: Optional[str] = Field(default=None, alias="legalUseCertUrl", description="合法使用证明URL,逗号分隔")
  309. model_config = {"populate_by_name": True}
  310. class PlanProductCertsUpdate(BaseModel):
  311. products: list[PlanProductCertItem] = Field(..., description="危化品证书更新列表")
  312. model_config = {"populate_by_name": True}
  313. class PlanArchiveProductItem(BaseModel):
  314. id: str = Field(..., description="计划产品明细ID pl_plan_product.id")
  315. actual_qty: Decimal = Field(..., alias="actualQty", ge=0, description="实际数量")
  316. brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(default=None, alias="brandQtys")
  317. model_config = {"populate_by_name": True}
  318. class PlanArchiveFreightBlock(BaseModel):
  319. has_freight: int = Field(default=1, alias="hasFreight", description="是否有运输费用 0-否 1-是")
  320. distance_km: Optional[Decimal] = Field(default=None, alias="distanceKm")
  321. freight_type: str = Field(default="by_weight", alias="freightType")
  322. freight_price: Optional[Decimal] = Field(default=None, alias="freightPrice")
  323. freight_amount: Optional[Decimal] = Field(default=None, alias="freightAmount")
  324. transport_qty: Optional[Decimal] = Field(default=None, alias="transportQty")
  325. has_invoice: Optional[int] = Field(default=None, alias="hasInvoice")
  326. model_config = {"populate_by_name": True}
  327. class PlanArchiveBody(BaseModel):
  328. products: list[PlanArchiveProductItem] = Field(..., min_length=1)
  329. freight: Optional[PlanArchiveFreightBlock] = None
  330. model_config = {"populate_by_name": True}
  331. class PlanDriverFeedbackProductItem(BaseModel):
  332. plan_product_id: str = Field(alias="planProductId")
  333. actual_qty: Decimal = Field(alias="actualQty", ge=0)
  334. brand_qtys: Optional[list[PlanArchiveBrandQtyItem]] = Field(default=None, alias="brandQtys")
  335. model_config = {"populate_by_name": True}
  336. class PlanDriverFeedbackUpdateBody(BaseModel):
  337. feedback_remark: Optional[str] = Field(default=None, alias="feedbackRemark", max_length=500, description="回传备注")
  338. feedback_images: Optional[list[str]] = Field(default=None, alias="feedbackImages", description="回传附件URLs")
  339. feedback_time: OptionalPlanDateTime = Field(default=None, alias="feedbackTime", description="回传时间")
  340. products: Optional[list[PlanDriverFeedbackProductItem]] = None
  341. feedback_qty: Optional[Decimal] = Field(default=None, alias="feedbackQty", ge=0, description="回传数量")
  342. model_config = {"populate_by_name": True}
  343. class PlanSiteFeedbackUpdateBody(BaseModel):
  344. site_feedback_remark: Optional[str] = Field(
  345. default=None, alias="siteFeedbackRemark", max_length=500, description="现场回执备注"
  346. )
  347. site_feedback_images: Optional[list[str]] = Field(
  348. default=None, alias="siteFeedbackImages", description="现场回执附件URLs"
  349. )
  350. site_feedback_time: OptionalPlanDateTime = Field(
  351. default=None, alias="siteFeedbackTime", description="现场回执保存时间"
  352. )
  353. model_config = {"populate_by_name": True}
  354. class PlanListResponse(BaseModel):
  355. total: int
  356. list: list[PlanWithProducts]