421 lines
19 KiB
Python
421 lines
19 KiB
Python
"""运输计划管理 - 基础数据 Schema(承运商、车辆、人员、证书)"""
|
||
from datetime import date, datetime
|
||
from decimal import Decimal
|
||
from typing import Optional
|
||
|
||
from pydantic import BaseModel, Field, model_validator
|
||
|
||
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):
|
||
business_license_urls: str = Field(..., max_length=2000, alias="businessLicenseUrls")
|
||
road_transport_license_urls: str = Field(..., max_length=2000, alias="roadTransportLicenseUrls")
|
||
transport_agreement_urls: str = Field(..., max_length=2000, alias="transportAgreementUrls")
|
||
transport_agreement_expire_date: Optional[date] = Field(
|
||
default=None, alias="transportAgreementExpireDate"
|
||
)
|
||
|
||
model_config = {"populate_by_name": True}
|
||
|
||
@model_validator(mode="after")
|
||
def validate_qualification(self) -> "CarrierCreate":
|
||
if not self.business_license_urls or not str(self.business_license_urls).strip():
|
||
raise ValueError("请上传营业执照")
|
||
if not self.road_transport_license_urls or not str(self.road_transport_license_urls).strip():
|
||
raise ValueError("请上传道路经营许可证")
|
||
if not self.transport_agreement_urls or not str(self.transport_agreement_urls).strip():
|
||
raise ValueError("请上传运输协议")
|
||
return self
|
||
|
||
|
||
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}
|
||
|
||
@model_validator(mode="after")
|
||
def validate_qualification_patch(self) -> "CarrierUpdate":
|
||
fields = (
|
||
self.business_license_urls,
|
||
self.road_transport_license_urls,
|
||
self.transport_agreement_urls,
|
||
self.transport_agreement_expire_date,
|
||
)
|
||
if not any(item is not None for item in fields):
|
||
return self
|
||
if self.business_license_urls is not None and not str(self.business_license_urls).strip():
|
||
raise ValueError("请上传营业执照")
|
||
if (
|
||
self.road_transport_license_urls is not None
|
||
and not str(self.road_transport_license_urls).strip()
|
||
):
|
||
raise ValueError("请上传道路经营许可证")
|
||
if self.transport_agreement_urls is not None and not str(self.transport_agreement_urls).strip():
|
||
raise ValueError("请上传运输协议")
|
||
return self
|
||
|
||
|
||
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="车辆状态")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class VehicleCreate(VehicleBase):
|
||
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, 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")
|
||
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-停用")
|
||
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)
|
||
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]
|