base_data.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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.services.pl_carrier_service import (
  7. carrier_agreement_expired,
  8. carrier_qualification_complete,
  9. )
  10. # =============================================================================
  11. # 承运商 Schema
  12. # =============================================================================
  13. class CarrierBase(BaseModel):
  14. carrier_name: str = Field(..., max_length=200, alias="carrierName", description="承运商名称")
  15. contact_name: Optional[str] = Field(default=None, max_length=100, alias="contactName", description="联系人")
  16. contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone", description="联系电话")
  17. status: str = Field(default="1", max_length=1, description="合作状态 1-合作中 0-已终止")
  18. remark: Optional[str] = Field(default=None, description="备注")
  19. class CarrierQualificationBase(BaseModel):
  20. business_license_urls: Optional[str] = Field(
  21. default=None, max_length=2000, alias="businessLicenseUrls", description="营业执照附件URL"
  22. )
  23. road_transport_license_urls: Optional[str] = Field(
  24. default=None,
  25. max_length=2000,
  26. alias="roadTransportLicenseUrls",
  27. description="道路经营许可证附件URL",
  28. )
  29. transport_agreement_urls: Optional[str] = Field(
  30. default=None, max_length=2000, alias="transportAgreementUrls", description="运输协议附件URL"
  31. )
  32. transport_agreement_expire_date: Optional[date] = Field(
  33. default=None, alias="transportAgreementExpireDate", description="运输协议有效期"
  34. )
  35. class CarrierCreate(CarrierBase, CarrierQualificationBase):
  36. model_config = {"populate_by_name": True}
  37. class CarrierUpdate(BaseModel):
  38. carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName")
  39. contact_name: Optional[str] = Field(default=None, max_length=100, alias="contactName")
  40. contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone")
  41. status: Optional[str] = Field(default=None, max_length=1)
  42. remark: Optional[str] = None
  43. business_license_urls: Optional[str] = Field(
  44. default=None, max_length=2000, alias="businessLicenseUrls"
  45. )
  46. road_transport_license_urls: Optional[str] = Field(
  47. default=None, max_length=2000, alias="roadTransportLicenseUrls"
  48. )
  49. transport_agreement_urls: Optional[str] = Field(
  50. default=None, max_length=2000, alias="transportAgreementUrls"
  51. )
  52. transport_agreement_expire_date: Optional[date] = Field(
  53. default=None, alias="transportAgreementExpireDate"
  54. )
  55. model_config = {"populate_by_name": True}
  56. class CarrierAuditReject(BaseModel):
  57. remark: Optional[str] = Field(default=None, max_length=512, description="驳回原因")
  58. model_config = {"populate_by_name": True}
  59. class CarrierRead(CarrierBase, CarrierQualificationBase):
  60. id: str
  61. audit_status: str = Field(default="pending", alias="auditStatus")
  62. audit_by: Optional[str] = Field(default=None, alias="auditBy")
  63. audit_time: Optional[datetime] = Field(default=None, alias="auditTime")
  64. audit_remark: Optional[str] = Field(default=None, alias="auditRemark")
  65. qualification_complete: bool = Field(default=False, alias="qualificationComplete")
  66. transport_agreement_expired: bool = Field(default=False, alias="transportAgreementExpired")
  67. tenant_no: str = Field(alias="tenantNo")
  68. order_num: Optional[int] = Field(default=None, alias="orderNum")
  69. create_by: Optional[str] = Field(default=None, alias="createBy")
  70. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  71. create_time: datetime = Field(alias="createTime")
  72. update_by: Optional[str] = Field(default=None, alias="updateBy")
  73. update_time: datetime = Field(alias="updateTime")
  74. del_flag: int = Field(default=0, alias="delFlag")
  75. model_config = {"from_attributes": True, "populate_by_name": True}
  76. def carrier_to_read(carrier, today: Optional[date] = None) -> CarrierRead:
  77. complete = carrier_qualification_complete(carrier)
  78. expired = carrier_agreement_expired(carrier, today) if complete else False
  79. return CarrierRead(
  80. id=carrier.id,
  81. carrier_name=carrier.carrier_name,
  82. contact_name=carrier.contact_name,
  83. contact_phone=carrier.contact_phone,
  84. status=carrier.status,
  85. remark=carrier.remark,
  86. business_license_urls=carrier.business_license_urls,
  87. road_transport_license_urls=carrier.road_transport_license_urls,
  88. transport_agreement_urls=carrier.transport_agreement_urls,
  89. transport_agreement_expire_date=carrier.transport_agreement_expire_date,
  90. audit_status=carrier.audit_status or "pending",
  91. audit_by=carrier.audit_by,
  92. audit_time=carrier.audit_time,
  93. audit_remark=carrier.audit_remark,
  94. qualification_complete=complete,
  95. transport_agreement_expired=expired,
  96. tenant_no=carrier.tenant_no,
  97. order_num=carrier.order_num,
  98. create_by=carrier.create_by,
  99. create_dept_id=carrier.create_dept_id,
  100. create_time=carrier.create_time,
  101. update_by=carrier.update_by,
  102. update_time=carrier.update_time,
  103. del_flag=carrier.del_flag,
  104. )
  105. # =============================================================================
  106. # 子表行 Schema
  107. # =============================================================================
  108. class VehiclePersonnelRow(BaseModel):
  109. personnel_id: Optional[str] = Field(default=None, alias="personnelId")
  110. personnel_name: str = Field(..., alias="personnelName")
  111. phone_no: Optional[str] = Field(default=None, alias="phoneNo")
  112. age: Optional[int] = None
  113. role: str = Field(..., description="driver|escort")
  114. is_default: int = Field(default=0, alias="isDefault")
  115. remark: Optional[str] = None
  116. model_config = {"populate_by_name": True}
  117. class CertRow(BaseModel):
  118. cert_id: Optional[str] = Field(default=None, alias="certId")
  119. vehicle_part: Optional[str] = Field(
  120. default=None,
  121. alias="vehiclePart",
  122. description="车辆部位 head-主车 trailer-挂车,人员证书为空",
  123. )
  124. cert_name: str = Field(..., alias="certName")
  125. cert_no: Optional[str] = Field(default=None, alias="certNo")
  126. expire_date: Optional[date] = Field(default=None, alias="expireDate")
  127. remind_days: int = Field(default=30, alias="remindDays")
  128. attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
  129. model_config = {"populate_by_name": True}
  130. class CertRead(CertRow):
  131. id: str
  132. owner_type: str = Field(alias="ownerType")
  133. owner_id: str = Field(alias="ownerId")
  134. status: str
  135. tenant_no: str = Field(alias="tenantNo")
  136. create_time: datetime = Field(alias="createTime")
  137. update_time: datetime = Field(alias="updateTime")
  138. model_config = {"from_attributes": True, "populate_by_name": True}
  139. # =============================================================================
  140. # 车辆 Schema
  141. # =============================================================================
  142. class VehicleBase(BaseModel):
  143. vehicle_type: str = Field(..., alias="vehicleType", description="车辆类型 self-自有 carrier-承运商")
  144. carrier_id: Optional[str] = Field(default=None, alias="carrierId", description="承运商ID")
  145. carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName", description="承运商名称")
  146. license_plate: str = Field(..., max_length=20, alias="licensePlate", description="车牌号")
  147. trailer_no: Optional[str] = Field(default=None, max_length=20, alias="trailerNo", description="挂车号")
  148. default_driver_id: Optional[str] = Field(default=None, alias="defaultDriverId", description="默认司机ID(只读兼容)")
  149. default_driver_name: Optional[str] = Field(default=None, max_length=100, alias="defaultDriverName", description="默认司机姓名(只读兼容)")
  150. load_capacity: Optional[Decimal] = Field(default=None, alias="loadCapacity", description="载重(吨)")
  151. vehicle_status: str = Field(default="available", max_length=20, alias="vehicleStatus", description="车辆状态")
  152. photo_urls: Optional[str] = Field(
  153. default=None, max_length=2000, alias="photoUrls", description="车辆照片URL,逗号分隔(默认1张)"
  154. )
  155. remark: Optional[str] = None
  156. class VehicleCreate(VehicleBase):
  157. personnel_list: list[VehiclePersonnelRow] = Field(default_factory=list, alias="personnelList")
  158. cert_list: list[CertRow] = Field(default_factory=list, alias="certList")
  159. model_config = {"populate_by_name": True}
  160. class VehicleUpdate(BaseModel):
  161. vehicle_type: Optional[str] = Field(default=None, max_length=20, alias="vehicleType")
  162. carrier_id: Optional[str] = Field(default=None, alias="carrierId")
  163. carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName")
  164. license_plate: Optional[str] = Field(default=None, max_length=20, alias="licensePlate")
  165. trailer_no: Optional[str] = Field(default=None, max_length=20, alias="trailerNo")
  166. load_capacity: Optional[Decimal] = Field(default=None, alias="loadCapacity")
  167. vehicle_status: Optional[str] = Field(default=None, max_length=20, alias="vehicleStatus")
  168. photo_urls: Optional[str] = Field(
  169. default=None, max_length=2000, alias="photoUrls", description="车辆照片URL,逗号分隔(默认1张)"
  170. )
  171. remark: Optional[str] = None
  172. personnel_list: Optional[list[VehiclePersonnelRow]] = Field(default=None, alias="personnelList")
  173. cert_list: Optional[list[CertRow]] = Field(default=None, alias="certList")
  174. model_config = {"populate_by_name": True}
  175. class VehicleRead(VehicleBase):
  176. id: str
  177. tenant_no: str = Field(alias="tenantNo")
  178. order_num: Optional[int] = Field(default=None, alias="orderNum")
  179. personnel_summary: Optional[str] = Field(default=None, alias="personnelSummary")
  180. cert_list: Optional[list[CertRead]] = Field(default=None, alias="certList")
  181. create_by: Optional[str] = Field(default=None, alias="createBy")
  182. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  183. create_time: datetime = Field(alias="createTime")
  184. update_by: Optional[str] = Field(default=None, alias="updateBy")
  185. update_time: datetime = Field(alias="updateTime")
  186. del_flag: int = Field(default=0, alias="delFlag")
  187. model_config = {"from_attributes": True, "populate_by_name": True}
  188. class VehicleDetailRead(VehicleRead):
  189. personnel_list: list[VehiclePersonnelRow] = Field(default_factory=list, alias="personnelList")
  190. cert_list: list[CertRead] = Field(default_factory=list, alias="certList")
  191. model_config = {"from_attributes": True, "populate_by_name": True}
  192. # =============================================================================
  193. # 人员 Schema
  194. # =============================================================================
  195. class PersonnelBase(BaseModel):
  196. personnel_name: str = Field(..., max_length=100, alias="personnelName", description="姓名")
  197. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo", description="手机号")
  198. age: Optional[int] = Field(default=None, description="年龄")
  199. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo", description="身份证号")
  200. personnel_type: str = Field(
  201. ...,
  202. max_length=20,
  203. alias="personnelType",
  204. description="人员类型 driver/escort/both",
  205. )
  206. personnel_affiliation: str = Field(
  207. default="self",
  208. max_length=20,
  209. alias="personnelAffiliation",
  210. description="人员归属 self-山峡 carrier-非山峡",
  211. )
  212. status: str = Field(default="1", max_length=1, description="状态 1-在职 0-停用")
  213. photo_urls: Optional[str] = Field(
  214. default=None, max_length=2000, alias="photoUrls", description="人员照片URL,逗号分隔(默认1张)"
  215. )
  216. remark: Optional[str] = None
  217. class PersonnelCreate(PersonnelBase):
  218. cert_list: list[CertRow] = Field(default_factory=list, alias="certList")
  219. model_config = {"populate_by_name": True}
  220. class PersonnelUpdate(BaseModel):
  221. personnel_name: Optional[str] = Field(default=None, max_length=100, alias="personnelName")
  222. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo")
  223. age: Optional[int] = None
  224. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo")
  225. personnel_type: Optional[str] = Field(default=None, max_length=20, alias="personnelType")
  226. personnel_affiliation: Optional[str] = Field(
  227. default=None, max_length=20, alias="personnelAffiliation"
  228. )
  229. status: Optional[str] = Field(default=None, max_length=1)
  230. photo_urls: Optional[str] = Field(
  231. default=None, max_length=2000, alias="photoUrls", description="人员照片URL,逗号分隔(默认1张)"
  232. )
  233. remark: Optional[str] = None
  234. cert_list: Optional[list[CertRow]] = Field(default=None, alias="certList")
  235. model_config = {"populate_by_name": True}
  236. class PersonnelRead(PersonnelBase):
  237. id: str
  238. tenant_no: str = Field(alias="tenantNo")
  239. order_num: Optional[int] = Field(default=None, alias="orderNum")
  240. nearest_expire_date: Optional[date] = Field(default=None, alias="nearestExpireDate")
  241. cert_list: Optional[list[CertRead]] = Field(default=None, alias="certList")
  242. create_by: Optional[str] = Field(default=None, alias="createBy")
  243. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  244. create_time: datetime = Field(alias="createTime")
  245. update_by: Optional[str] = Field(default=None, alias="updateBy")
  246. update_time: datetime = Field(alias="updateTime")
  247. del_flag: int = Field(default=0, alias="delFlag")
  248. model_config = {"from_attributes": True, "populate_by_name": True}
  249. class PersonnelDetailRead(PersonnelRead):
  250. cert_list: list[CertRead] = Field(default_factory=list, alias="certList")
  251. model_config = {"from_attributes": True, "populate_by_name": True}
  252. # =============================================================================
  253. # 司机 Schema(兼容旧 API)
  254. # =============================================================================
  255. class DriverBase(BaseModel):
  256. driver_name: str = Field(..., max_length=100, alias="driverName", description="姓名")
  257. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo", description="手机号")
  258. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo", description="身份证号")
  259. driving_license_no: Optional[str] = Field(default=None, max_length=32, alias="drivingLicenseNo", description="驾驶证号")
  260. driving_license_expire: Optional[date] = Field(default=None, alias="drivingLicenseExpire", description="驾驶证有效期")
  261. vehicle_license_no: Optional[str] = Field(default=None, max_length=32, alias="vehicleLicenseNo", description="行驶证号")
  262. vehicle_license_expire: Optional[date] = Field(default=None, alias="vehicleLicenseExpire", description="行驶证有效期")
  263. hazmat_cert_no: Optional[str] = Field(default=None, max_length=32, alias="hazmatCertNo", description="危化品证号")
  264. hazmat_cert_expire: Optional[date] = Field(default=None, alias="hazmatCertExpire", description="危化品证有效期")
  265. remark: Optional[str] = None
  266. class DriverCreate(DriverBase):
  267. model_config = {"populate_by_name": True}
  268. class DriverUpdate(BaseModel):
  269. driver_name: Optional[str] = Field(default=None, max_length=100, alias="driverName")
  270. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo")
  271. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo")
  272. driving_license_no: Optional[str] = Field(default=None, max_length=32, alias="drivingLicenseNo")
  273. driving_license_expire: Optional[date] = Field(default=None, alias="drivingLicenseExpire")
  274. vehicle_license_no: Optional[str] = Field(default=None, max_length=32, alias="vehicleLicenseNo")
  275. vehicle_license_expire: Optional[date] = Field(default=None, alias="vehicleLicenseExpire")
  276. hazmat_cert_no: Optional[str] = Field(default=None, max_length=32, alias="hazmatCertNo")
  277. hazmat_cert_expire: Optional[date] = Field(default=None, alias="hazmatCertExpire")
  278. remark: Optional[str] = None
  279. model_config = {"populate_by_name": True}
  280. class DriverRead(DriverBase):
  281. id: str
  282. tenant_no: str = Field(alias="tenantNo")
  283. order_num: Optional[int] = Field(default=None, alias="orderNum")
  284. create_by: Optional[str] = Field(default=None, alias="createBy")
  285. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  286. create_time: datetime = Field(alias="createTime")
  287. update_by: Optional[str] = Field(default=None, alias="updateBy")
  288. update_time: datetime = Field(alias="updateTime")
  289. del_flag: int = Field(default=0, alias="delFlag")
  290. model_config = {"from_attributes": True, "populate_by_name": True}
  291. class DriverListResponse(BaseModel):
  292. total: int
  293. list: list[DriverRead]
  294. # =============================================================================
  295. # 车辆-司机关联 Schema(兼容)
  296. # =============================================================================
  297. class VehicleDriverCreate(BaseModel):
  298. vehicle_id: str = Field(..., alias="vehicleId", description="车辆ID")
  299. driver_id: str = Field(..., alias="driverId", description="司机ID")
  300. is_default: int = Field(default=0, alias="isDefault", description="是否默认 0-否 1-是")
  301. model_config = {"populate_by_name": True}
  302. class VehicleDriverRead(BaseModel):
  303. id: str
  304. vehicle_id: str = Field(alias="vehicleId")
  305. driver_id: str = Field(alias="driverId")
  306. is_default: int = Field(alias="isDefault")
  307. tenant_no: str = Field(alias="tenantNo")
  308. create_time: datetime = Field(alias="createTime")
  309. model_config = {"from_attributes": True, "populate_by_name": True}
  310. class CarrierListResponse(BaseModel):
  311. total: int
  312. list: list[CarrierRead]
  313. class VehicleListResponse(BaseModel):
  314. total: int
  315. list: list[VehicleRead]
  316. class PersonnelListResponse(BaseModel):
  317. total: int
  318. list: list[PersonnelRead]