"""运输计划管理 - 基础数据 Schema(承运商、车辆、人员、证书)""" from datetime import date, datetime from decimal import Decimal from typing import Optional from pydantic import BaseModel, Field from app.services.pl_carrier_service import ( carrier_agreement_expired, carrier_qualification_complete, ) # ============================================================================= # 承运商 Schema # ============================================================================= class CarrierBase(BaseModel): carrier_name: str = Field(..., max_length=200, alias="carrierName", 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="联系电话") status: str = Field(default="1", max_length=1, description="合作状态 1-合作中 0-已终止") remark: Optional[str] = Field(default=None, description="备注") class CarrierQualificationBase(BaseModel): business_license_urls: Optional[str] = Field( default=None, max_length=2000, alias="businessLicenseUrls", description="营业执照附件URL" ) road_transport_license_urls: Optional[str] = Field( default=None, max_length=2000, alias="roadTransportLicenseUrls", description="道路经营许可证附件URL", ) transport_agreement_urls: Optional[str] = Field( default=None, max_length=2000, alias="transportAgreementUrls", description="运输协议附件URL" ) transport_agreement_expire_date: Optional[date] = Field( default=None, alias="transportAgreementExpireDate", description="运输协议有效期" ) class CarrierCreate(CarrierBase, CarrierQualificationBase): model_config = {"populate_by_name": True} class CarrierUpdate(BaseModel): carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName") contact_name: Optional[str] = Field(default=None, max_length=100, alias="contactName") contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone") status: Optional[str] = Field(default=None, max_length=1) remark: Optional[str] = None business_license_urls: Optional[str] = Field( default=None, max_length=2000, alias="businessLicenseUrls" ) road_transport_license_urls: Optional[str] = Field( default=None, max_length=2000, alias="roadTransportLicenseUrls" ) transport_agreement_urls: Optional[str] = Field( default=None, max_length=2000, alias="transportAgreementUrls" ) transport_agreement_expire_date: Optional[date] = Field( default=None, alias="transportAgreementExpireDate" ) model_config = {"populate_by_name": True} class CarrierAuditReject(BaseModel): remark: Optional[str] = Field(default=None, max_length=512, description="驳回原因") model_config = {"populate_by_name": True} class CarrierRead(CarrierBase, CarrierQualificationBase): id: str audit_status: str = Field(default="pending", alias="auditStatus") audit_by: Optional[str] = Field(default=None, alias="auditBy") audit_time: Optional[datetime] = Field(default=None, alias="auditTime") audit_remark: Optional[str] = Field(default=None, alias="auditRemark") qualification_complete: bool = Field(default=False, alias="qualificationComplete") transport_agreement_expired: bool = Field(default=False, alias="transportAgreementExpired") 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") model_config = {"from_attributes": True, "populate_by_name": True} def carrier_to_read(carrier, today: Optional[date] = None) -> CarrierRead: complete = carrier_qualification_complete(carrier) expired = carrier_agreement_expired(carrier, today) if complete else False return CarrierRead( id=carrier.id, carrier_name=carrier.carrier_name, contact_name=carrier.contact_name, contact_phone=carrier.contact_phone, status=carrier.status, remark=carrier.remark, business_license_urls=carrier.business_license_urls, road_transport_license_urls=carrier.road_transport_license_urls, transport_agreement_urls=carrier.transport_agreement_urls, transport_agreement_expire_date=carrier.transport_agreement_expire_date, audit_status=carrier.audit_status or "pending", audit_by=carrier.audit_by, audit_time=carrier.audit_time, audit_remark=carrier.audit_remark, qualification_complete=complete, transport_agreement_expired=expired, tenant_no=carrier.tenant_no, order_num=carrier.order_num, create_by=carrier.create_by, create_dept_id=carrier.create_dept_id, create_time=carrier.create_time, update_by=carrier.update_by, update_time=carrier.update_time, del_flag=carrier.del_flag, ) # ============================================================================= # 子表行 Schema # ============================================================================= class VehiclePersonnelRow(BaseModel): personnel_id: Optional[str] = Field(default=None, alias="personnelId") personnel_name: str = Field(..., alias="personnelName") phone_no: Optional[str] = Field(default=None, alias="phoneNo") age: Optional[int] = None role: str = Field(..., description="driver|escort") is_default: int = Field(default=0, alias="isDefault") remark: Optional[str] = None model_config = {"populate_by_name": True} class CertRow(BaseModel): cert_id: Optional[str] = Field(default=None, alias="certId") vehicle_part: Optional[str] = Field( default=None, alias="vehiclePart", description="车辆部位 head-主车 trailer-挂车,人员证书为空", ) cert_name: str = Field(..., alias="certName") cert_no: Optional[str] = Field(default=None, alias="certNo") expire_date: Optional[date] = Field(default=None, alias="expireDate") remind_days: int = Field(default=30, alias="remindDays") attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls") model_config = {"populate_by_name": True} class CertRead(CertRow): id: str owner_type: str = Field(alias="ownerType") owner_id: str = Field(alias="ownerId") status: str tenant_no: str = Field(alias="tenantNo") create_time: datetime = Field(alias="createTime") update_time: datetime = Field(alias="updateTime") model_config = {"from_attributes": True, "populate_by_name": True} # ============================================================================= # 车辆 Schema # ============================================================================= class VehicleBase(BaseModel): vehicle_type: str = Field(..., alias="vehicleType", description="车辆类型 self-自有 carrier-承运商") carrier_id: Optional[str] = Field(default=None, alias="carrierId", description="承运商ID") carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName", description="承运商名称") license_plate: str = Field(..., max_length=20, alias="licensePlate", description="车牌号") trailer_no: Optional[str] = Field(default=None, max_length=20, alias="trailerNo", description="挂车号") default_driver_id: Optional[str] = Field(default=None, alias="defaultDriverId", description="默认司机ID(只读兼容)") default_driver_name: Optional[str] = Field(default=None, max_length=100, alias="defaultDriverName", description="默认司机姓名(只读兼容)") load_capacity: Optional[Decimal] = Field(default=None, alias="loadCapacity", description="载重(吨)") vehicle_status: str = Field(default="available", max_length=20, alias="vehicleStatus", description="车辆状态") photo_urls: Optional[str] = Field( default=None, max_length=2000, alias="photoUrls", description="车辆照片URL,逗号分隔(默认1张)" ) remark: Optional[str] = None class VehicleCreate(VehicleBase): trailer_no: str = Field(..., min_length=1, max_length=20, alias="trailerNo", description="挂车号") personnel_list: list[VehiclePersonnelRow] = Field(default_factory=list, alias="personnelList") cert_list: list[CertRow] = Field(default_factory=list, alias="certList") model_config = {"populate_by_name": True} class VehicleUpdate(BaseModel): vehicle_type: Optional[str] = Field(default=None, max_length=20, alias="vehicleType") carrier_id: Optional[str] = Field(default=None, alias="carrierId") carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName") license_plate: Optional[str] = Field(default=None, max_length=20, alias="licensePlate") trailer_no: Optional[str] = Field(default=None, min_length=1, max_length=20, alias="trailerNo") load_capacity: Optional[Decimal] = Field(default=None, alias="loadCapacity") vehicle_status: Optional[str] = Field(default=None, max_length=20, alias="vehicleStatus") photo_urls: Optional[str] = Field( default=None, max_length=2000, alias="photoUrls", description="车辆照片URL,逗号分隔(默认1张)" ) remark: Optional[str] = None personnel_list: Optional[list[VehiclePersonnelRow]] = Field(default=None, alias="personnelList") cert_list: Optional[list[CertRow]] = Field(default=None, alias="certList") model_config = {"populate_by_name": True} class VehicleRead(VehicleBase): id: str tenant_no: str = Field(alias="tenantNo") order_num: Optional[int] = Field(default=None, alias="orderNum") personnel_summary: Optional[str] = Field(default=None, alias="personnelSummary") cert_list: Optional[list[CertRead]] = Field(default=None, alias="certList") 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") model_config = {"from_attributes": True, "populate_by_name": True} class VehicleDetailRead(VehicleRead): personnel_list: list[VehiclePersonnelRow] = Field(default_factory=list, alias="personnelList") cert_list: list[CertRead] = Field(default_factory=list, alias="certList") model_config = {"from_attributes": True, "populate_by_name": True} # ============================================================================= # 人员 Schema # ============================================================================= class PersonnelBase(BaseModel): personnel_name: str = Field(..., max_length=100, alias="personnelName", description="姓名") phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo", description="手机号") age: Optional[int] = Field(default=None, description="年龄") id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo", description="身份证号") personnel_type: str = Field( ..., max_length=20, alias="personnelType", description="人员类型 driver/escort/both", ) personnel_affiliation: str = Field( default="self", max_length=20, alias="personnelAffiliation", description="人员归属 self-山峡 carrier-非山峡", ) status: str = Field(default="1", max_length=1, description="状态 1-在职 0-停用") photo_urls: Optional[str] = Field( default=None, max_length=2000, alias="photoUrls", description="人员照片URL,逗号分隔(默认1张)" ) remark: Optional[str] = None class PersonnelCreate(PersonnelBase): cert_list: list[CertRow] = Field(default_factory=list, alias="certList") model_config = {"populate_by_name": True} class PersonnelUpdate(BaseModel): personnel_name: Optional[str] = Field(default=None, max_length=100, alias="personnelName") phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo") age: Optional[int] = None id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo") personnel_type: Optional[str] = Field(default=None, max_length=20, alias="personnelType") personnel_affiliation: Optional[str] = Field( default=None, max_length=20, alias="personnelAffiliation" ) status: Optional[str] = Field(default=None, max_length=1) photo_urls: Optional[str] = Field( default=None, max_length=2000, alias="photoUrls", description="人员照片URL,逗号分隔(默认1张)" ) remark: Optional[str] = None cert_list: Optional[list[CertRow]] = Field(default=None, alias="certList") model_config = {"populate_by_name": True} class PersonnelRead(PersonnelBase): id: str tenant_no: str = Field(alias="tenantNo") order_num: Optional[int] = Field(default=None, alias="orderNum") nearest_expire_date: Optional[date] = Field(default=None, alias="nearestExpireDate") cert_list: Optional[list[CertRead]] = Field(default=None, alias="certList") 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") model_config = {"from_attributes": True, "populate_by_name": True} class PersonnelDetailRead(PersonnelRead): cert_list: list[CertRead] = Field(default_factory=list, alias="certList") model_config = {"from_attributes": True, "populate_by_name": True} # ============================================================================= # 司机 Schema(兼容旧 API) # ============================================================================= class DriverBase(BaseModel): driver_name: str = Field(..., max_length=100, alias="driverName", description="姓名") phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo", description="手机号") id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo", description="身份证号") driving_license_no: Optional[str] = Field(default=None, max_length=32, alias="drivingLicenseNo", description="驾驶证号") driving_license_expire: Optional[date] = Field(default=None, alias="drivingLicenseExpire", description="驾驶证有效期") vehicle_license_no: Optional[str] = Field(default=None, max_length=32, alias="vehicleLicenseNo", description="行驶证号") vehicle_license_expire: Optional[date] = Field(default=None, alias="vehicleLicenseExpire", description="行驶证有效期") hazmat_cert_no: Optional[str] = Field(default=None, max_length=32, alias="hazmatCertNo", description="危化品证号") hazmat_cert_expire: Optional[date] = Field(default=None, alias="hazmatCertExpire", description="危化品证有效期") remark: Optional[str] = None class DriverCreate(DriverBase): model_config = {"populate_by_name": True} class DriverUpdate(BaseModel): driver_name: Optional[str] = Field(default=None, max_length=100, alias="driverName") phone_no: Optional[str] = Field(default=None, max_length=32, alias="phoneNo") id_card_no: Optional[str] = Field(default=None, max_length=32, alias="idCardNo") driving_license_no: Optional[str] = Field(default=None, max_length=32, alias="drivingLicenseNo") driving_license_expire: Optional[date] = Field(default=None, alias="drivingLicenseExpire") vehicle_license_no: Optional[str] = Field(default=None, max_length=32, alias="vehicleLicenseNo") vehicle_license_expire: Optional[date] = Field(default=None, alias="vehicleLicenseExpire") hazmat_cert_no: Optional[str] = Field(default=None, max_length=32, alias="hazmatCertNo") hazmat_cert_expire: Optional[date] = Field(default=None, alias="hazmatCertExpire") remark: Optional[str] = None model_config = {"populate_by_name": True} class DriverRead(DriverBase): id: str 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") model_config = {"from_attributes": True, "populate_by_name": True} class DriverListResponse(BaseModel): total: int list: list[DriverRead] # ============================================================================= # 车辆-司机关联 Schema(兼容) # ============================================================================= class VehicleDriverCreate(BaseModel): vehicle_id: str = Field(..., alias="vehicleId", description="车辆ID") driver_id: str = Field(..., alias="driverId", description="司机ID") is_default: int = Field(default=0, alias="isDefault", description="是否默认 0-否 1-是") model_config = {"populate_by_name": True} class VehicleDriverRead(BaseModel): id: str vehicle_id: str = Field(alias="vehicleId") driver_id: str = Field(alias="driverId") is_default: int = Field(alias="isDefault") tenant_no: str = Field(alias="tenantNo") create_time: datetime = Field(alias="createTime") model_config = {"from_attributes": True, "populate_by_name": True} class CarrierListResponse(BaseModel): total: int list: list[CarrierRead] class VehicleListResponse(BaseModel): total: int list: list[VehicleRead] class PersonnelListResponse(BaseModel): total: int list: list[PersonnelRead]