base_data.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  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. remark: Optional[str] = None
  153. class VehicleCreate(VehicleBase):
  154. personnel_list: list[VehiclePersonnelRow] = Field(default_factory=list, alias="personnelList")
  155. cert_list: list[CertRow] = Field(default_factory=list, alias="certList")
  156. model_config = {"populate_by_name": True}
  157. class VehicleUpdate(BaseModel):
  158. vehicle_type: Optional[str] = Field(default=None, max_length=20, alias="vehicleType")
  159. carrier_id: Optional[str] = Field(default=None, alias="carrierId")
  160. carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName")
  161. license_plate: Optional[str] = Field(default=None, max_length=20, alias="licensePlate")
  162. trailer_no: Optional[str] = Field(default=None, max_length=20, alias="trailerNo")
  163. load_capacity: Optional[Decimal] = Field(default=None, alias="loadCapacity")
  164. vehicle_status: Optional[str] = Field(default=None, max_length=20, alias="vehicleStatus")
  165. remark: Optional[str] = None
  166. personnel_list: Optional[list[VehiclePersonnelRow]] = Field(default=None, alias="personnelList")
  167. cert_list: Optional[list[CertRow]] = Field(default=None, alias="certList")
  168. model_config = {"populate_by_name": True}
  169. class VehicleRead(VehicleBase):
  170. id: str
  171. tenant_no: str = Field(alias="tenantNo")
  172. order_num: Optional[int] = Field(default=None, alias="orderNum")
  173. personnel_summary: Optional[str] = Field(default=None, alias="personnelSummary")
  174. cert_list: Optional[list[CertRead]] = Field(default=None, alias="certList")
  175. create_by: Optional[str] = Field(default=None, alias="createBy")
  176. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  177. create_time: datetime = Field(alias="createTime")
  178. update_by: Optional[str] = Field(default=None, alias="updateBy")
  179. update_time: datetime = Field(alias="updateTime")
  180. del_flag: int = Field(default=0, alias="delFlag")
  181. model_config = {"from_attributes": True, "populate_by_name": True}
  182. class VehicleDetailRead(VehicleRead):
  183. personnel_list: list[VehiclePersonnelRow] = Field(default_factory=list, alias="personnelList")
  184. cert_list: list[CertRead] = Field(default_factory=list, alias="certList")
  185. model_config = {"from_attributes": True, "populate_by_name": True}
  186. # =============================================================================
  187. # 人员 Schema
  188. # =============================================================================
  189. class PersonnelBase(BaseModel):
  190. personnel_name: str = Field(..., max_length=100, alias="personnelName", description="姓名")
  191. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo", description="手机号")
  192. age: Optional[int] = Field(default=None, description="年龄")
  193. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo", description="身份证号")
  194. personnel_type: str = Field(
  195. ...,
  196. max_length=20,
  197. alias="personnelType",
  198. description="人员类型 driver/escort/both",
  199. )
  200. personnel_affiliation: str = Field(
  201. default="self",
  202. max_length=20,
  203. alias="personnelAffiliation",
  204. description="人员归属 self-山峡 carrier-非山峡",
  205. )
  206. status: str = Field(default="1", max_length=1, description="状态 1-在职 0-停用")
  207. remark: Optional[str] = None
  208. class PersonnelCreate(PersonnelBase):
  209. cert_list: list[CertRow] = Field(default_factory=list, alias="certList")
  210. model_config = {"populate_by_name": True}
  211. class PersonnelUpdate(BaseModel):
  212. personnel_name: Optional[str] = Field(default=None, max_length=100, alias="personnelName")
  213. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo")
  214. age: Optional[int] = None
  215. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo")
  216. personnel_type: Optional[str] = Field(default=None, max_length=20, alias="personnelType")
  217. personnel_affiliation: Optional[str] = Field(
  218. default=None, max_length=20, alias="personnelAffiliation"
  219. )
  220. status: Optional[str] = Field(default=None, max_length=1)
  221. remark: Optional[str] = None
  222. cert_list: Optional[list[CertRow]] = Field(default=None, alias="certList")
  223. model_config = {"populate_by_name": True}
  224. class PersonnelRead(PersonnelBase):
  225. id: str
  226. tenant_no: str = Field(alias="tenantNo")
  227. order_num: Optional[int] = Field(default=None, alias="orderNum")
  228. nearest_expire_date: Optional[date] = Field(default=None, alias="nearestExpireDate")
  229. cert_list: Optional[list[CertRead]] = Field(default=None, alias="certList")
  230. create_by: Optional[str] = Field(default=None, alias="createBy")
  231. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  232. create_time: datetime = Field(alias="createTime")
  233. update_by: Optional[str] = Field(default=None, alias="updateBy")
  234. update_time: datetime = Field(alias="updateTime")
  235. del_flag: int = Field(default=0, alias="delFlag")
  236. model_config = {"from_attributes": True, "populate_by_name": True}
  237. class PersonnelDetailRead(PersonnelRead):
  238. cert_list: list[CertRead] = Field(default_factory=list, alias="certList")
  239. model_config = {"from_attributes": True, "populate_by_name": True}
  240. # =============================================================================
  241. # 司机 Schema(兼容旧 API)
  242. # =============================================================================
  243. class DriverBase(BaseModel):
  244. driver_name: str = Field(..., max_length=100, alias="driverName", description="姓名")
  245. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo", description="手机号")
  246. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo", description="身份证号")
  247. driving_license_no: Optional[str] = Field(default=None, max_length=32, alias="drivingLicenseNo", description="驾驶证号")
  248. driving_license_expire: Optional[date] = Field(default=None, alias="drivingLicenseExpire", description="驾驶证有效期")
  249. vehicle_license_no: Optional[str] = Field(default=None, max_length=32, alias="vehicleLicenseNo", description="行驶证号")
  250. vehicle_license_expire: Optional[date] = Field(default=None, alias="vehicleLicenseExpire", description="行驶证有效期")
  251. hazmat_cert_no: Optional[str] = Field(default=None, max_length=32, alias="hazmatCertNo", description="危化品证号")
  252. hazmat_cert_expire: Optional[date] = Field(default=None, alias="hazmatCertExpire", description="危化品证有效期")
  253. remark: Optional[str] = None
  254. class DriverCreate(DriverBase):
  255. model_config = {"populate_by_name": True}
  256. class DriverUpdate(BaseModel):
  257. driver_name: Optional[str] = Field(default=None, max_length=100, alias="driverName")
  258. phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo")
  259. id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo")
  260. driving_license_no: Optional[str] = Field(default=None, max_length=32, alias="drivingLicenseNo")
  261. driving_license_expire: Optional[date] = Field(default=None, alias="drivingLicenseExpire")
  262. vehicle_license_no: Optional[str] = Field(default=None, max_length=32, alias="vehicleLicenseNo")
  263. vehicle_license_expire: Optional[date] = Field(default=None, alias="vehicleLicenseExpire")
  264. hazmat_cert_no: Optional[str] = Field(default=None, max_length=32, alias="hazmatCertNo")
  265. hazmat_cert_expire: Optional[date] = Field(default=None, alias="hazmatCertExpire")
  266. remark: Optional[str] = None
  267. model_config = {"populate_by_name": True}
  268. class DriverRead(DriverBase):
  269. id: str
  270. tenant_no: str = Field(alias="tenantNo")
  271. order_num: Optional[int] = Field(default=None, alias="orderNum")
  272. create_by: Optional[str] = Field(default=None, alias="createBy")
  273. create_dept_id: Optional[str] = Field(default=None, alias="createDeptId")
  274. create_time: datetime = Field(alias="createTime")
  275. update_by: Optional[str] = Field(default=None, alias="updateBy")
  276. update_time: datetime = Field(alias="updateTime")
  277. del_flag: int = Field(default=0, alias="delFlag")
  278. model_config = {"from_attributes": True, "populate_by_name": True}
  279. class DriverListResponse(BaseModel):
  280. total: int
  281. list: list[DriverRead]
  282. # =============================================================================
  283. # 车辆-司机关联 Schema(兼容)
  284. # =============================================================================
  285. class VehicleDriverCreate(BaseModel):
  286. vehicle_id: str = Field(..., alias="vehicleId", description="车辆ID")
  287. driver_id: str = Field(..., alias="driverId", description="司机ID")
  288. is_default: int = Field(default=0, alias="isDefault", description="是否默认 0-否 1-是")
  289. model_config = {"populate_by_name": True}
  290. class VehicleDriverRead(BaseModel):
  291. id: str
  292. vehicle_id: str = Field(alias="vehicleId")
  293. driver_id: str = Field(alias="driverId")
  294. is_default: int = Field(alias="isDefault")
  295. tenant_no: str = Field(alias="tenantNo")
  296. create_time: datetime = Field(alias="createTime")
  297. model_config = {"from_attributes": True, "populate_by_name": True}
  298. class CarrierListResponse(BaseModel):
  299. total: int
  300. list: list[CarrierRead]
  301. class VehicleListResponse(BaseModel):
  302. total: int
  303. list: list[VehicleRead]
  304. class PersonnelListResponse(BaseModel):
  305. total: int
  306. list: list[PersonnelRead]