Files
shanxia2.0/backend/app/schemas/pl/base_data.py
T
2026-07-12 17:55:11 +08:00

421 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""运输计划管理 - 基础数据 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]