feat(pl): optional escort selection on transport arrange
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
3dc71788d4
commit
ca3062b0e2
10 files changed
+339
-10
No files matched your search
@@ -439,6 +439,7 @@ def _apply_transport_arrange_fields(
|
||||
payload: TransportArrange,
|
||||
vehicle_info: dict,
|
||||
driver_info: Optional[dict],
|
||||
escort_info: Optional[dict],
|
||||
carrier_name: Optional[str],
|
||||
start_point: Optional[str],
|
||||
end_point: Optional[str],
|
||||
@@ -450,6 +451,9 @@ def _apply_transport_arrange_fields(
|
||||
transport.driver_id = driver_info["id"] if driver_info else None
|
||||
transport.driver_name = driver_info["name"] if driver_info else None
|
||||
transport.driver_phone = driver_info["phone"] if driver_info else None
|
||||
transport.escort_id = escort_info["id"] if escort_info else None
|
||||
transport.escort_name = escort_info["name"] if escort_info else None
|
||||
transport.escort_phone = escort_info["phone"] if escort_info else None
|
||||
transport.carrier_id = payload.carrier_id or vehicle_info.get("carrier_id")
|
||||
transport.carrier_name = carrier_name
|
||||
transport.transport_medium = payload.transport_medium
|
||||
@@ -653,6 +657,9 @@ def create_transport(
|
||||
driver_id=payload.driver_id,
|
||||
driver_name=payload.driver_name,
|
||||
driver_phone=payload.driver_phone,
|
||||
escort_id=payload.escort_id,
|
||||
escort_name=payload.escort_name,
|
||||
escort_phone=payload.escort_phone,
|
||||
carrier_id=payload.carrier_id,
|
||||
carrier_name=payload.carrier_name,
|
||||
transport_medium=payload.transport_medium,
|
||||
@@ -696,12 +703,16 @@ def create_transport(
|
||||
plan.license_plate = transport.license_plate
|
||||
plan.driver_id = transport.driver_id
|
||||
plan.driver_name = transport.driver_name
|
||||
plan.escort_id = transport.escort_id
|
||||
plan.escort_name = transport.escort_name
|
||||
plan.escort_phone = transport.escort_phone
|
||||
if transport.vehicle_id:
|
||||
snapshot = build_transport_resource_snapshot(
|
||||
db,
|
||||
tenant_no,
|
||||
transport.vehicle_id,
|
||||
selected_driver_id=transport.driver_id,
|
||||
selected_escort_id=transport.escort_id,
|
||||
)
|
||||
apply_resource_snapshot_json(
|
||||
plan=plan, transport=transport, snapshot=snapshot
|
||||
@@ -766,6 +777,13 @@ def arrange_transport(
|
||||
if not driver_info:
|
||||
raise HTTPException(status_code=400, detail="司机不存在")
|
||||
|
||||
# 获取押运员信息(可选)
|
||||
escort_info = None
|
||||
if payload.escort_id:
|
||||
escort_info = _resolve_driver_info(db, tenant_no, payload.escort_id)
|
||||
if not escort_info:
|
||||
raise HTTPException(status_code=400, detail="押运员不存在")
|
||||
|
||||
# 检查产品属性(易制毒/易制爆需要证件,以 CRM 产品主数据为准)
|
||||
products = db.query(PlPlanProduct).filter(
|
||||
PlPlanProduct.plan_id == plan_id,
|
||||
@@ -811,6 +829,7 @@ def arrange_transport(
|
||||
payload=payload,
|
||||
vehicle_info=vehicle_info,
|
||||
driver_info=driver_info,
|
||||
escort_info=escort_info,
|
||||
carrier_name=carrier_name,
|
||||
start_point=start_point,
|
||||
end_point=end_point,
|
||||
@@ -835,6 +854,7 @@ def arrange_transport(
|
||||
payload=payload,
|
||||
vehicle_info=vehicle_info,
|
||||
driver_info=driver_info,
|
||||
escort_info=escort_info,
|
||||
carrier_name=carrier_name,
|
||||
start_point=start_point,
|
||||
end_point=end_point,
|
||||
@@ -858,12 +878,16 @@ def arrange_transport(
|
||||
plan.license_plate = transport.license_plate
|
||||
plan.driver_id = transport.driver_id
|
||||
plan.driver_name = transport.driver_name
|
||||
plan.escort_id = transport.escort_id
|
||||
plan.escort_name = transport.escort_name
|
||||
plan.escort_phone = transport.escort_phone
|
||||
if transport.vehicle_id:
|
||||
snapshot = build_transport_resource_snapshot(
|
||||
db,
|
||||
tenant_no,
|
||||
transport.vehicle_id,
|
||||
selected_driver_id=transport.driver_id,
|
||||
selected_escort_id=transport.escort_id,
|
||||
)
|
||||
apply_resource_snapshot_json(plan=plan, transport=transport, snapshot=snapshot)
|
||||
plan.transport_status = "arranged"
|
||||
|
||||
@@ -287,6 +287,7 @@ class PlanCreate(PlanBase):
|
||||
class PlanTransportCreateBlock(BaseModel):
|
||||
vehicle_id: str = Field(..., alias="vehicleId", description="车辆ID")
|
||||
driver_id: Optional[str] = Field(default=None, alias="driverId", description="司机/人员ID")
|
||||
escort_id: Optional[str] = Field(default=None, alias="escortId", description="押运员人员ID(可选)")
|
||||
carrier_id: Optional[str] = Field(default=None, alias="carrierId", description="承运商ID")
|
||||
distance_km: Optional[Decimal] = Field(default=None, alias="distanceKm", description="运输公里数")
|
||||
freight_type: str = Field(default="by_weight", alias="freightType", description="运费计算方式")
|
||||
@@ -384,6 +385,9 @@ class PlanRead(PlanBase, PlanResourceSnapshotMixin):
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
driver_id: Optional[str] = Field(default=None, alias="driverId")
|
||||
driver_name: Optional[str] = Field(default=None, alias="driverName")
|
||||
escort_id: Optional[str] = Field(default=None, alias="escortId")
|
||||
escort_name: Optional[str] = Field(default=None, alias="escortName")
|
||||
escort_phone: Optional[str] = Field(default=None, alias="escortPhone")
|
||||
personnel_summary: Optional[str] = Field(default=None, alias="personnelSummary")
|
||||
transport_id: Optional[str] = Field(default=None, alias="transportId")
|
||||
transport_no: Optional[str] = Field(default=None, alias="transportNo")
|
||||
|
||||
@@ -21,6 +21,9 @@ class TransportBase(BaseModel):
|
||||
driver_id: Optional[str] = Field(default=None, alias="driverId", description="司机ID")
|
||||
driver_name: Optional[str] = Field(default=None, max_length=100, alias="driverName", description="司机姓名")
|
||||
driver_phone: Optional[str] = Field(default=None, max_length=32, alias="driverPhone", description="司机电话")
|
||||
escort_id: Optional[str] = Field(default=None, alias="escortId", description="押运员人员ID")
|
||||
escort_name: Optional[str] = Field(default=None, max_length=100, alias="escortName", description="押运员姓名")
|
||||
escort_phone: Optional[str] = Field(default=None, max_length=32, alias="escortPhone", description="押运员电话")
|
||||
carrier_id: Optional[str] = Field(default=None, alias="carrierId", description="承运商ID")
|
||||
carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName", description="承运商名称")
|
||||
transport_medium: Optional[str] = Field(default=None, max_length=100, alias="transportMedium", description="运输介质")
|
||||
@@ -54,6 +57,7 @@ class TransportStopDibangOption(BaseModel):
|
||||
class TransportArrange(BaseModel):
|
||||
vehicle_id: str = Field(..., alias="vehicleId", description="车辆ID")
|
||||
driver_id: Optional[str] = Field(default=None, alias="driverId", description="司机ID(可选)")
|
||||
escort_id: Optional[str] = Field(default=None, alias="escortId", description="押运员人员ID(可选)")
|
||||
carrier_id: Optional[str] = Field(default=None, alias="carrierId", description="承运商ID")
|
||||
transport_medium: Optional[str] = Field(default=None, alias="transportMedium", description="运输介质")
|
||||
pickup_address: Optional[str] = Field(default=None, alias="pickupAddress", description="取货地址")
|
||||
@@ -128,6 +132,9 @@ class TransportUpdate(BaseModel):
|
||||
driver_id: Optional[str] = Field(default=None, alias="driverId")
|
||||
driver_name: Optional[str] = Field(default=None, max_length=100, alias="driverName")
|
||||
driver_phone: Optional[str] = Field(default=None, max_length=32, alias="driverPhone")
|
||||
escort_id: Optional[str] = Field(default=None, alias="escortId")
|
||||
escort_name: Optional[str] = Field(default=None, max_length=100, alias="escortName")
|
||||
escort_phone: Optional[str] = Field(default=None, max_length=32, alias="escortPhone")
|
||||
carrier_id: Optional[str] = Field(default=None, alias="carrierId")
|
||||
carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName")
|
||||
transport_medium: Optional[str] = Field(default=None, max_length=100, alias="transportMedium")
|
||||
|
||||
@@ -225,28 +225,32 @@ def _apply_vehicle_snapshot_to_plan_transport(
|
||||
transport: Optional[PlTransport],
|
||||
vehicle: PlVehicle,
|
||||
driver_id: Optional[str],
|
||||
escort_id: Optional[str] = None,
|
||||
) -> None:
|
||||
snapshot = build_transport_resource_snapshot(
|
||||
db,
|
||||
tenant_no,
|
||||
vehicle.id,
|
||||
selected_driver_id=driver_id,
|
||||
selected_escort_id=escort_id,
|
||||
)
|
||||
apply_resource_snapshot_json(plan=plan, transport=transport, snapshot=snapshot)
|
||||
|
||||
|
||||
def _resolve_driver(
|
||||
def _resolve_personnel_by_vehicle_role(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
vehicle_id: str,
|
||||
driver_id: Optional[str],
|
||||
personnel_id: Optional[str],
|
||||
*,
|
||||
role: str,
|
||||
) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
||||
"""解析司机:显式选择优先,否则取车辆关联司机;均无则允许为空。"""
|
||||
if driver_id:
|
||||
"""解析驾押人员:显式选择优先,否则取车辆关联该角色;均无则允许为空。"""
|
||||
if personnel_id:
|
||||
personnel = (
|
||||
db.query(PlPersonnel)
|
||||
.filter(
|
||||
PlPersonnel.id == driver_id,
|
||||
PlPersonnel.id == personnel_id,
|
||||
PlPersonnel.tenant_no == tenant_no,
|
||||
PlPersonnel.del_flag == 0,
|
||||
)
|
||||
@@ -261,7 +265,7 @@ def _resolve_driver(
|
||||
PlVehiclePersonnel.vehicle_id == vehicle_id,
|
||||
PlVehiclePersonnel.tenant_no == tenant_no,
|
||||
PlVehiclePersonnel.del_flag == 0,
|
||||
PlVehiclePersonnel.role == "driver",
|
||||
PlVehiclePersonnel.role == role,
|
||||
)
|
||||
.order_by(PlVehiclePersonnel.is_default.desc(), PlVehiclePersonnel.order_num.asc())
|
||||
.first()
|
||||
@@ -281,6 +285,30 @@ def _resolve_driver(
|
||||
return None, None, None
|
||||
|
||||
|
||||
def _resolve_driver(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
vehicle_id: str,
|
||||
driver_id: Optional[str],
|
||||
) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
||||
"""解析司机:显式选择优先,否则取车辆关联司机;均无则允许为空。"""
|
||||
return _resolve_personnel_by_vehicle_role(
|
||||
db, tenant_no, vehicle_id, driver_id, role="driver"
|
||||
)
|
||||
|
||||
|
||||
def _resolve_escort(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
vehicle_id: str,
|
||||
escort_id: Optional[str],
|
||||
) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
||||
"""解析押运员:显式选择优先,否则取车辆关联押运;均无则允许为空。"""
|
||||
return _resolve_personnel_by_vehicle_role(
|
||||
db, tenant_no, vehicle_id, escort_id, role="escort"
|
||||
)
|
||||
|
||||
|
||||
def _calc_freight_amount(
|
||||
freight_type: str,
|
||||
products: list[PlPlanProduct],
|
||||
@@ -732,6 +760,9 @@ def _resolve_transport_context(
|
||||
Optional[str],
|
||||
Optional[str],
|
||||
Optional[str],
|
||||
Optional[str],
|
||||
Optional[str],
|
||||
Optional[str],
|
||||
Decimal,
|
||||
Optional[Decimal],
|
||||
]:
|
||||
@@ -750,6 +781,9 @@ def _resolve_transport_context(
|
||||
driver_id, driver_name, driver_phone = _resolve_driver(
|
||||
db, tenant_no, transport_payload.vehicle_id, transport_payload.driver_id
|
||||
)
|
||||
escort_id, escort_name, escort_phone = _resolve_escort(
|
||||
db, tenant_no, transport_payload.vehicle_id, transport_payload.escort_id
|
||||
)
|
||||
carrier_id = transport_payload.carrier_id or vehicle.carrier_id
|
||||
carrier_name = vehicle.carrier_name
|
||||
if carrier_id:
|
||||
@@ -777,6 +811,9 @@ def _resolve_transport_context(
|
||||
driver_id,
|
||||
driver_name,
|
||||
driver_phone,
|
||||
escort_id,
|
||||
escort_name,
|
||||
escort_phone,
|
||||
carrier_id,
|
||||
carrier_name,
|
||||
freight_amount,
|
||||
@@ -791,6 +828,9 @@ def _apply_payload_to_transport_row(
|
||||
driver_id: Optional[str],
|
||||
driver_name: Optional[str],
|
||||
driver_phone: Optional[str],
|
||||
escort_id: Optional[str],
|
||||
escort_name: Optional[str],
|
||||
escort_phone: Optional[str],
|
||||
carrier_id: Optional[str],
|
||||
carrier_name: Optional[str],
|
||||
plan: PlPlan,
|
||||
@@ -804,6 +844,9 @@ def _apply_payload_to_transport_row(
|
||||
transport.driver_id = driver_id
|
||||
transport.driver_name = driver_name
|
||||
transport.driver_phone = driver_phone
|
||||
transport.escort_id = escort_id
|
||||
transport.escort_name = escort_name
|
||||
transport.escort_phone = escort_phone
|
||||
transport.carrier_id = carrier_id
|
||||
transport.carrier_name = carrier_name
|
||||
transport.start_point = plan.pickup_address
|
||||
@@ -837,6 +880,9 @@ def upsert_draft_transport_for_plan(
|
||||
driver_id,
|
||||
driver_name,
|
||||
driver_phone,
|
||||
escort_id,
|
||||
escort_name,
|
||||
escort_phone,
|
||||
carrier_id,
|
||||
carrier_name,
|
||||
freight_amount,
|
||||
@@ -868,6 +914,9 @@ def upsert_draft_transport_for_plan(
|
||||
driver_id=driver_id,
|
||||
driver_name=driver_name,
|
||||
driver_phone=driver_phone,
|
||||
escort_id=escort_id,
|
||||
escort_name=escort_name,
|
||||
escort_phone=escort_phone,
|
||||
carrier_id=carrier_id,
|
||||
carrier_name=carrier_name,
|
||||
plan=plan,
|
||||
@@ -910,6 +959,9 @@ def upsert_draft_transport_for_plan(
|
||||
driver_id=driver_id,
|
||||
driver_name=driver_name,
|
||||
driver_phone=driver_phone,
|
||||
escort_id=escort_id,
|
||||
escort_name=escort_name,
|
||||
escort_phone=escort_phone,
|
||||
carrier_id=carrier_id,
|
||||
carrier_name=carrier_name,
|
||||
plan=plan,
|
||||
@@ -923,6 +975,9 @@ def upsert_draft_transport_for_plan(
|
||||
plan.license_plate = vehicle.license_plate
|
||||
plan.driver_id = driver_id
|
||||
plan.driver_name = driver_name
|
||||
plan.escort_id = escort_id
|
||||
plan.escort_name = escort_name
|
||||
plan.escort_phone = escort_phone
|
||||
_apply_vehicle_snapshot_to_plan_transport(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
@@ -930,6 +985,7 @@ def upsert_draft_transport_for_plan(
|
||||
transport=transport,
|
||||
vehicle=vehicle,
|
||||
driver_id=driver_id,
|
||||
escort_id=escort_id,
|
||||
)
|
||||
plan.freight_amount = freight_amount or 0
|
||||
if plan.transport_id:
|
||||
@@ -979,6 +1035,9 @@ def clear_draft_transport_for_plan(
|
||||
plan.license_plate = None
|
||||
plan.driver_id = None
|
||||
plan.driver_name = None
|
||||
plan.escort_id = None
|
||||
plan.escort_name = None
|
||||
plan.escort_phone = None
|
||||
clear_resource_snapshot(plan=plan, transport=transport)
|
||||
plan.freight_amount = 0
|
||||
if plan.transport_status != "arranged":
|
||||
@@ -1057,6 +1116,9 @@ def create_transport_for_plan(
|
||||
driver_id, driver_name, driver_phone = _resolve_driver(
|
||||
db, tenant_no, transport_payload.vehicle_id, transport_payload.driver_id
|
||||
)
|
||||
escort_id, escort_name, escort_phone = _resolve_escort(
|
||||
db, tenant_no, transport_payload.vehicle_id, transport_payload.escort_id
|
||||
)
|
||||
|
||||
carrier_name = vehicle.carrier_name
|
||||
carrier_id = transport_payload.carrier_id or vehicle.carrier_id
|
||||
@@ -1091,6 +1153,9 @@ def create_transport_for_plan(
|
||||
driver_id=driver_id,
|
||||
driver_name=driver_name,
|
||||
driver_phone=driver_phone,
|
||||
escort_id=escort_id,
|
||||
escort_name=escort_name,
|
||||
escort_phone=escort_phone,
|
||||
carrier_id=carrier_id,
|
||||
carrier_name=carrier_name,
|
||||
start_point=plan.pickup_address,
|
||||
@@ -1127,6 +1192,9 @@ def create_transport_for_plan(
|
||||
plan.license_plate = vehicle.license_plate
|
||||
plan.driver_id = driver_id
|
||||
plan.driver_name = driver_name
|
||||
plan.escort_id = escort_id
|
||||
plan.escort_name = escort_name
|
||||
plan.escort_phone = escort_phone
|
||||
_apply_vehicle_snapshot_to_plan_transport(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
@@ -1134,6 +1202,7 @@ def create_transport_for_plan(
|
||||
transport=transport,
|
||||
vehicle=vehicle,
|
||||
driver_id=driver_id,
|
||||
escort_id=escort_id,
|
||||
)
|
||||
plan.transport_status = "arranged"
|
||||
plan.follow_status = "arranged"
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""运输计划解析押运员:车辆未关联押运时允许为空。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from app.services.pl_plan_service import _resolve_escort
|
||||
|
||||
|
||||
def test_resolve_escort_without_vehicle_link_returns_none():
|
||||
db = MagicMock()
|
||||
db.query.return_value.filter.return_value.order_by.return_value.first.return_value = None
|
||||
|
||||
result = _resolve_escort(db, "tenant-1", "vehicle-1", None)
|
||||
|
||||
assert result == (None, None, None)
|
||||
|
||||
|
||||
def test_resolve_escort_with_explicit_id_returns_personnel():
|
||||
db = MagicMock()
|
||||
personnel = MagicMock()
|
||||
personnel.id = "escort-1"
|
||||
personnel.personnel_name = "王押"
|
||||
personnel.phone_no = "13900000001"
|
||||
db.query.return_value.filter.return_value.first.return_value = personnel
|
||||
|
||||
result = _resolve_escort(db, "tenant-1", "vehicle-1", "escort-1")
|
||||
|
||||
assert result == ("escort-1", "王押", "13900000001")
|
||||
@@ -43,6 +43,7 @@ def test_apply_transport_arrange_fields_without_driver():
|
||||
payload=payload,
|
||||
vehicle_info={"license_plate": "粤A12345", "carrier_id": None},
|
||||
driver_info=None,
|
||||
escort_info=None,
|
||||
carrier_name=None,
|
||||
start_point="A",
|
||||
end_point="B",
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""安排运输:押运员可选。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.api.pl.transport import _apply_transport_arrange_fields
|
||||
from app.schemas.pl.plan import PlanTransportCreateBlock
|
||||
from app.schemas.pl.transport import TransportArrange
|
||||
|
||||
|
||||
def test_transport_arrange_schema_allows_escort_id():
|
||||
payload = TransportArrange.model_validate(
|
||||
{"vehicleId": "veh-1", "escortId": "escort-1"}
|
||||
)
|
||||
assert payload.escort_id == "escort-1"
|
||||
assert payload.driver_id is None
|
||||
|
||||
|
||||
def test_plan_transport_block_allows_escort_id():
|
||||
block = PlanTransportCreateBlock.model_validate(
|
||||
{"vehicleId": "veh-1", "escortId": "escort-9"}
|
||||
)
|
||||
assert block.escort_id == "escort-9"
|
||||
|
||||
|
||||
def test_apply_transport_arrange_fields_sets_escort():
|
||||
transport = SimpleNamespace(
|
||||
vehicle_id=None,
|
||||
license_plate=None,
|
||||
driver_id=None,
|
||||
driver_name=None,
|
||||
driver_phone=None,
|
||||
escort_id=None,
|
||||
escort_name=None,
|
||||
escort_phone=None,
|
||||
carrier_id=None,
|
||||
carrier_name=None,
|
||||
transport_medium=None,
|
||||
start_point=None,
|
||||
end_point=None,
|
||||
distance_km=None,
|
||||
unit=None,
|
||||
freight_price=None,
|
||||
freight_amount=0,
|
||||
freight_type="by_weight",
|
||||
has_invoice=0,
|
||||
remark=None,
|
||||
update_by=None,
|
||||
)
|
||||
payload = TransportArrange.model_validate(
|
||||
{"vehicleId": "veh-1", "freightType": "by_weight", "escortId": "e1"}
|
||||
)
|
||||
|
||||
_apply_transport_arrange_fields(
|
||||
transport,
|
||||
payload=payload,
|
||||
vehicle_info={"license_plate": "粤A12345", "carrier_id": None},
|
||||
driver_info=None,
|
||||
escort_info={"id": "e1", "name": "李押", "phone": "13900000000"},
|
||||
carrier_name=None,
|
||||
start_point="A",
|
||||
end_point="B",
|
||||
freight_amount=0,
|
||||
user_id="u1",
|
||||
)
|
||||
|
||||
assert transport.escort_id == "e1"
|
||||
assert transport.escort_name == "李押"
|
||||
assert transport.escort_phone == "13900000000"
|
||||
|
||||
|
||||
def test_apply_transport_arrange_fields_clears_escort():
|
||||
transport = SimpleNamespace(
|
||||
vehicle_id="veh-old",
|
||||
license_plate="旧牌",
|
||||
driver_id=None,
|
||||
driver_name=None,
|
||||
driver_phone=None,
|
||||
escort_id="old-e",
|
||||
escort_name="旧押",
|
||||
escort_phone="1",
|
||||
carrier_id=None,
|
||||
carrier_name=None,
|
||||
transport_medium=None,
|
||||
start_point=None,
|
||||
end_point=None,
|
||||
distance_km=None,
|
||||
unit=None,
|
||||
freight_price=None,
|
||||
freight_amount=0,
|
||||
freight_type="by_weight",
|
||||
has_invoice=0,
|
||||
remark=None,
|
||||
update_by=None,
|
||||
)
|
||||
payload = TransportArrange.model_validate(
|
||||
{"vehicleId": "veh-1", "freightType": "by_weight"}
|
||||
)
|
||||
|
||||
_apply_transport_arrange_fields(
|
||||
transport,
|
||||
payload=payload,
|
||||
vehicle_info={"license_plate": "粤A12345", "carrier_id": None},
|
||||
driver_info=None,
|
||||
escort_info=None,
|
||||
carrier_name=None,
|
||||
start_point="A",
|
||||
end_point="B",
|
||||
freight_amount=0,
|
||||
user_id="u1",
|
||||
)
|
||||
|
||||
assert transport.escort_id is None
|
||||
assert transport.escort_name is None
|
||||
assert transport.escort_phone is None
|
||||
@@ -194,6 +194,8 @@ export interface PersonnelQuery {
|
||||
personnelAffiliation?: string
|
||||
/** driver | escort;不传则不过滤类型 */
|
||||
personnelType?: string
|
||||
/** 逗号分隔多类型,如 escort,both */
|
||||
personnelTypeIn?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
@@ -578,6 +580,9 @@ export interface Plan {
|
||||
trailerNo?: string
|
||||
driverId?: string
|
||||
driverName?: string
|
||||
escortId?: string
|
||||
escortName?: string
|
||||
escortPhone?: string
|
||||
personnelSummary?: string
|
||||
resourceSnapshot?: TransportResourceSnapshot
|
||||
transportId?: string
|
||||
@@ -658,6 +663,7 @@ export interface PlanListResponse {
|
||||
export interface PlanTransportCreateBlock {
|
||||
vehicleId: string
|
||||
driverId?: string
|
||||
escortId?: string
|
||||
carrierId?: string
|
||||
distanceKm?: number
|
||||
freightType: string
|
||||
@@ -1053,6 +1059,9 @@ export interface Transport {
|
||||
driverId?: string
|
||||
driverName?: string
|
||||
driverPhone?: string
|
||||
escortId?: string
|
||||
escortName?: string
|
||||
escortPhone?: string
|
||||
resourceSnapshot?: TransportResourceSnapshot
|
||||
carrierId?: string
|
||||
carrierName?: string
|
||||
@@ -1090,6 +1099,7 @@ export interface TransportStopDibangOption {
|
||||
export interface TransportArrange {
|
||||
vehicleId: string
|
||||
driverId?: string
|
||||
escortId?: string
|
||||
carrierId?: string
|
||||
transportMedium?: string
|
||||
pickupAddress?: string
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
label-width="100px"
|
||||
class="pl-dialog-form"
|
||||
>
|
||||
<div class="pl-plan-transport-row pl-plan-transport-row--three">
|
||||
<div class="pl-plan-transport-row pl-plan-transport-row--four">
|
||||
<el-form-item label="车牌号" class="pl-plan-transport-row__vehicle">
|
||||
<div class="pl-plan-vehicle-with-library">
|
||||
<el-select
|
||||
@@ -136,6 +136,26 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择押运员" class="pl-plan-transport-row__escort">
|
||||
<el-select
|
||||
v-model="selectedEscortId"
|
||||
placeholder="请选择押运员(可选)"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
:remote-method="searchEscorts"
|
||||
:loading="escortSelectLoading"
|
||||
:disabled="!formData.vehicleId"
|
||||
@focus="onEscortSelectFocus"
|
||||
>
|
||||
<el-option
|
||||
v-for="d in escortList"
|
||||
:key="d.id"
|
||||
:label="formatDriverOptionLabel(d)"
|
||||
:value="d.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="运输公司" class="pl-plan-transport-row__carrier">
|
||||
<div class="pl-plan-carrier-with-library">
|
||||
<el-select
|
||||
@@ -509,6 +529,7 @@
|
||||
const deliveryForms = ref<PlanDeliveryForm[]>([createEmptyDeliveryForm(1)])
|
||||
const loadedPlanSnapshot = ref<Plan | null>(null)
|
||||
const selectedDriverId = ref('')
|
||||
const selectedEscortId = ref('')
|
||||
|
||||
/** 已安排:禁止仅「保存」,须走保存并安排/调整运输,避免计划与地磅等联动字段脱节 */
|
||||
const showSaveButton = computed(() => {
|
||||
@@ -664,6 +685,16 @@
|
||||
getSelectedId: () => selectedDriverId.value,
|
||||
listParams: { status: '1', personnelType: 'driver' }
|
||||
})
|
||||
const {
|
||||
list: escortList,
|
||||
loading: escortSelectLoading,
|
||||
search: searchEscorts,
|
||||
onFocus: onEscortSelectFocus,
|
||||
ensureById: ensureSelectedEscortOption
|
||||
} = usePlRemotePersonnelOptions({
|
||||
getSelectedId: () => selectedEscortId.value,
|
||||
listParams: { status: '1', personnelTypeIn: 'escort,both' }
|
||||
})
|
||||
const vehicleBundle = ref<VehicleViewBundle | null>(null)
|
||||
|
||||
const formData = reactive({
|
||||
@@ -1509,6 +1540,7 @@
|
||||
return {
|
||||
vehicleId: formData.vehicleId,
|
||||
driverId: selectedDriverId.value || undefined,
|
||||
escortId: selectedEscortId.value || undefined,
|
||||
carrierId: formData.carrierId || undefined,
|
||||
distanceKm: formData.distanceKm ?? undefined,
|
||||
freightType: formData.freightType,
|
||||
@@ -1580,6 +1612,7 @@
|
||||
freightAmount?: number
|
||||
carrierId?: string
|
||||
driverId?: string
|
||||
escortId?: string
|
||||
}
|
||||
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
|
||||
formData.freightType = transport.freightType || 'by_weight'
|
||||
@@ -1589,6 +1622,9 @@
|
||||
if (transport.driverId) {
|
||||
selectedDriverId.value = transport.driverId
|
||||
}
|
||||
if (transport.escortId) {
|
||||
selectedEscortId.value = transport.escortId
|
||||
}
|
||||
freightCalc.freightPrice.value =
|
||||
transport.freightPrice != null ? Number(transport.freightPrice) : null
|
||||
freightCalc.freightAmount.value =
|
||||
@@ -1660,6 +1696,7 @@
|
||||
plan,
|
||||
vehicleId: formData.vehicleId,
|
||||
driverId: selectedDriverId.value,
|
||||
escortId: selectedEscortId.value,
|
||||
carrierId: formData.carrierId,
|
||||
loadForm: loadForm.value,
|
||||
deliveryForms: deliveryForms.value,
|
||||
@@ -1689,6 +1726,7 @@
|
||||
loadedPlanSnapshot.value = null
|
||||
vehicleBundle.value = null
|
||||
selectedDriverId.value = ''
|
||||
selectedEscortId.value = ''
|
||||
const d = todayYmd()
|
||||
Object.assign(formData, {
|
||||
createDate: d,
|
||||
@@ -1794,11 +1832,17 @@
|
||||
if (data.driverId) {
|
||||
selectedDriverId.value = data.driverId
|
||||
}
|
||||
if (data.escortId) {
|
||||
selectedEscortId.value = data.escortId
|
||||
}
|
||||
freightCalc.resetFreightCalc()
|
||||
await restoreFreightFromTransport(data)
|
||||
if (selectedDriverId.value) {
|
||||
await ensureSelectedDriverOption(selectedDriverId.value)
|
||||
}
|
||||
if (selectedEscortId.value) {
|
||||
await ensureSelectedEscortOption(selectedEscortId.value)
|
||||
}
|
||||
if (formData.carrierId) {
|
||||
await ensureSelectedCarrierOption(formData.carrierId)
|
||||
}
|
||||
@@ -1865,6 +1909,7 @@
|
||||
formData.vehicleId = ''
|
||||
formData.carrierId = ''
|
||||
selectedDriverId.value = ''
|
||||
selectedEscortId.value = ''
|
||||
vehicleBundle.value = null
|
||||
reapplyStopDibangByVehiclePlate('')
|
||||
freightCalc.resetFreightCalc()
|
||||
@@ -1886,7 +1931,13 @@
|
||||
}
|
||||
|
||||
async function loadMasterData() {
|
||||
await Promise.all([searchVehicles(''), searchCarriers(''), searchDrivers(''), loadDefaultCustomerOptions()])
|
||||
await Promise.all([
|
||||
searchVehicles(''),
|
||||
searchCarriers(''),
|
||||
searchDrivers(''),
|
||||
searchEscorts(''),
|
||||
loadDefaultCustomerOptions()
|
||||
])
|
||||
}
|
||||
|
||||
function resolveDefaultDriver(personnelList: VehiclePersonnelRow[]) {
|
||||
@@ -1895,6 +1946,12 @@
|
||||
selectedDriverId.value = defaultDriver?.personnelId || ''
|
||||
}
|
||||
|
||||
function resolveDefaultEscort(personnelList: VehiclePersonnelRow[]) {
|
||||
const escorts = personnelList.filter((item) => item.role === 'escort' && item.personnelId)
|
||||
const defaultEscort = escorts.find((item) => item.isDefault === 1) || escorts[0]
|
||||
selectedEscortId.value = defaultEscort?.personnelId || ''
|
||||
}
|
||||
|
||||
async function handleVehicleChange(
|
||||
vehicleId: string,
|
||||
opts?: { syncStopDibang?: boolean }
|
||||
@@ -1902,6 +1959,7 @@
|
||||
const syncStopDibang = opts?.syncStopDibang !== false
|
||||
vehicleBundle.value = null
|
||||
selectedDriverId.value = ''
|
||||
selectedEscortId.value = ''
|
||||
if (!vehicleId) {
|
||||
formData.carrierId = ''
|
||||
if (syncStopDibang) reapplyStopDibangByVehiclePlate('')
|
||||
@@ -1922,9 +1980,13 @@
|
||||
formData.carrierId = vehicleBundle.value.detail.carrierId
|
||||
}
|
||||
resolveDefaultDriver(vehicleBundle.value.personnelList || [])
|
||||
resolveDefaultEscort(vehicleBundle.value.personnelList || [])
|
||||
if (selectedDriverId.value) {
|
||||
await ensureSelectedDriverOption(selectedDriverId.value)
|
||||
}
|
||||
if (selectedEscortId.value) {
|
||||
await ensureSelectedEscortOption(selectedEscortId.value)
|
||||
}
|
||||
} finally {
|
||||
vehicleLoading.value = false
|
||||
}
|
||||
@@ -2306,14 +2368,21 @@
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.pl-plan-transport-row--four {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(180px, 240px) minmax(180px, 240px) minmax(240px, 1fr);
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.pl-plan-transport-row__vehicle {
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.pl-plan-transport-row__driver {
|
||||
.pl-plan-transport-row__driver,
|
||||
.pl-plan-transport-row__escort {
|
||||
min-width: 0;
|
||||
max-width: 340px;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.pl-plan-transport-row__carrier {
|
||||
|
||||
@@ -99,6 +99,7 @@ export interface BuildArrangePayloadInput {
|
||||
plan: Plan
|
||||
vehicleId: string
|
||||
driverId: string
|
||||
escortId: string
|
||||
carrierId: string
|
||||
loadForm: PlanLoadForm
|
||||
deliveryForms: PlanDeliveryForm[]
|
||||
@@ -128,6 +129,7 @@ export function buildArrangePayloadFromPlanForm(input: BuildArrangePayloadInput)
|
||||
return {
|
||||
vehicleId: input.vehicleId,
|
||||
driverId: input.driverId || undefined,
|
||||
escortId: input.escortId || undefined,
|
||||
carrierId: input.carrierId || undefined,
|
||||
pickupAddress: input.loadForm.address || undefined,
|
||||
pickupLng: input.loadForm.lng,
|
||||
|
||||
Reference in new issue
Block a user