This commit is contained in:
zlwu committed 2026-07-20 10:50:26 +08:00
1 parent 6d9354a194
commit aff71d296b
2759 files changed
+860425 -13211

No files matched your search

+5
View File
@@ -3,6 +3,7 @@ from fastapi import APIRouter
from app.api.pl import plan, transport, freight, inventory, menu_badges, ocr
from app.api.pl import base_data, vehicle_recharge, vehicle_maintenance, vehicle_urea, transport_address
from app.api.pl import io_order
pl_router = APIRouter(prefix="/transport", tags=["运输计划管理"])
@@ -18,6 +19,10 @@ pl_router.include_router(ocr.router)
# 计划管理
pl_router.include_router(plan.router)
# 入库单 / 出库单
pl_router.include_router(io_order.inbound_router)
pl_router.include_router(io_order.outbound_router)
# 运输管理
pl_router.include_router(transport.router)
+46 -1
View File
@@ -47,9 +47,15 @@ from app.schemas.pl.plan import (
PlanUpdate,
PlanWithProducts,
)
from app.schemas.pl.transport import PlanArrangeInfoRead, TransportInventoryLinkRead, TransportSiteLinkRead
from app.schemas.pl.transport import (
PlanArrangeInfoRead,
TransportInventoryLinkRead,
TransportIoOrderBrief,
TransportSiteLinkRead,
)
from app.services.pl_dibang_bridge_service import revoke_regeneratable_dibang_for_transport
from app.services.pl_inventory_link_read import build_transport_inventory_link_read
from app.services.pl_io_order_service import get_active_io_order_for_plan, void_io_orders_for_plan
from app.services.pl_site_link_read import build_transport_site_link_read_from_row
from app.services.pl_transport_site_service import revoke_site_links_for_transport
from app.services.pl_plan_service import (
@@ -730,6 +736,12 @@ def update_plan(
if plan.follow_status == "cancelled":
raise HTTPException(status_code=400, detail="已作废的计划不能修改")
if plan.transport_status == "arranged" and plan.plan_type in ("inbound", "outbound"):
raise HTTPException(
status_code=400,
detail="已安排的入库/出库计划不可修改,请作废后新建计划",
)
# 更新主表字段
update_data = payload.model_dump(exclude_unset=True, exclude={"products", "transport"})
for key, value in update_data.items():
@@ -1009,11 +1021,40 @@ def get_plan_arrange_info(
)
)
io_order_brief = None
active_io = get_active_io_order_for_plan(db, tenant_no, plan)
if active_io:
kind, header = active_io
from app.services.pl_io_order_service import build_io_order_site_url
token = (header.site_access_token or "").strip()
io_order_brief = TransportIoOrderBrief(
kind=kind,
order_id=header.id,
order_no=header.order_no,
site_access_token=token or None,
site_url=build_io_order_site_url(kind, token) if token else None,
status=header.status,
)
if not site_records and token and header.status != "voided":
site_records = [
TransportSiteLinkRead(
plan_product_id="",
product_name=header.order_no,
site_link_id=header.id,
plan_no=plan.plan_no or "",
business_type="入库" if kind == "inbound" else "出库",
site_access_token=token,
site_url=build_io_order_site_url(kind, token),
)
]
return PlanArrangeInfoRead(
driver_url=driver_url,
license_plate=transport.license_plate or plan.license_plate,
site_records=site_records,
inventory_records=inventory_records,
io_order=io_order_brief,
)
@@ -1067,6 +1108,10 @@ def cancel_plan(
transport.transport_status = "cancelled"
transport.update_by = user.id
void_io_orders_for_plan(
db, tenant_no=tenant_no, plan_id=plan.id, user_id=user.id
)
plan.follow_status = "cancelled"
plan.transport_status = "pending"
plan.update_by = user.id
+49 -29
View File
@@ -36,6 +36,7 @@ from app.schemas.pl.transport import (
TransportCreate,
TransportFeedback,
TransportInventoryLinkRead,
TransportIoOrderBrief,
TransportSiteLinkRead,
TransportListQuery,
TransportListResponse,
@@ -46,10 +47,9 @@ from app.services.pl_dibang_bridge_service import (
sync_dibang_for_transport,
)
from app.services.pl_inventory_link_read import build_transport_inventory_link_read
from app.services.pl_site_link_read import build_transport_site_link_read
from app.services.pl_transport_site_service import (
revoke_site_links_for_transport,
sync_site_links_for_transport,
from app.services.pl_io_order_service import (
apply_dibang_prefill_to_order,
create_io_order_for_transport,
)
from app.services.pl_plan_service import (
is_ton_product_unit,
@@ -224,11 +224,14 @@ def _rotate_driver_access_token(transport: PlTransport) -> str:
def _is_rearrange(plan: PlPlan, existing_transport: Optional[PlTransport]) -> bool:
if plan.transport_status == "arranged":
return True
if existing_transport and existing_transport.transport_status != "cancelled":
return True
return False
"""仅计划已正式安排才算「调整运输」。
创建/编辑表单预选车辆会留下 draft 运输单,计划仍为 pending;
若把非 cancelled 运输单都当成 rearrange,入库/出库会被误拦截。
existing_transport 保留入参以兼容调用方;草稿复用由后续分支处理。
"""
_ = existing_transport
return plan.transport_status == "arranged"
def _transport_has_db_links(db: Session, tenant_no: str, transport_id: str) -> bool:
@@ -327,6 +330,7 @@ def _build_arrange_result(
transport: PlTransport,
site_records: list[TransportSiteLinkRead],
inventory_records: list[TransportInventoryLinkRead],
io_order: Optional[TransportIoOrderBrief] = None,
) -> TransportArrangeResult:
token = transport.driver_access_token or ""
result = TransportArrangeResult.model_validate(transport)
@@ -334,6 +338,7 @@ def _build_arrange_result(
result.driver_url = _build_driver_url(token) if token else None
result.site_records = site_records
result.inventory_records = inventory_records
result.io_order = io_order
return result
@@ -511,6 +516,13 @@ def arrange_transport(
is_rearrange = _is_rearrange(plan, existing_transport)
# 入库/出库:已安排后完全冻结,仅允许作废后新建计划再安排
if is_rearrange and plan.plan_type in ("inbound", "outbound"):
raise HTTPException(
status_code=400,
detail="已安排的入库/出库计划不可调整运输,请作废后新建计划再安排",
)
if is_rearrange and existing_transport and existing_transport.vehicle_id:
_assert_vehicle_carrier_eligible(
db,
@@ -644,36 +656,41 @@ def arrange_transport(
site_records: list[TransportSiteLinkRead] = []
inventory_records: list[TransportInventoryLinkRead] = []
io_order_brief: Optional[TransportIoOrderBrief] = None
if plan.plan_type in ("inbound", "outbound"):
if is_rearrange and existing_transport:
revoke_site_links_for_transport(
db,
tenant_no=tenant_no,
transport_id=transport.id,
user_id=user.id,
)
try:
site_link_results = sync_site_links_for_transport(
io_result = create_io_order_for_transport(
db,
tenant_no=tenant_no,
plan=plan,
products=products,
transport=transport,
user_id=user.id,
dept_id=user.dept_id,
dept_id=user.dept_id or "",
after_dibang=False,
)
site_records = [
build_transport_site_link_read(
db,
plan_product_id=item.plan_product_id,
product_name=item.product_name,
site_link_id=item.site_link_id,
plan_no=item.plan_no,
business_type=item.business_type,
if io_result:
io_order_brief = TransportIoOrderBrief(
kind=io_result.kind,
order_id=io_result.order_id,
order_no=io_result.order_no,
site_access_token=io_result.site_access_token,
site_url=io_result.site_url,
status="in_progress",
)
for item in site_link_results
]
# 兼容旧前端:合成单条 siteRecords 供复制现场链接
site_records = [
TransportSiteLinkRead(
plan_product_id="",
product_name=io_result.order_no,
site_link_id=io_result.order_id,
plan_no=plan.plan_no or "",
business_type="入库" if io_result.kind == "inbound" else "出库",
site_access_token=io_result.site_access_token,
site_url=io_result.site_url,
)
]
except ValueError as exc:
db.rollback()
raise HTTPException(status_code=400, detail=str(exc)) from exc
@@ -714,10 +731,13 @@ def arrange_transport(
except ValueError as exc:
db.rollback()
raise HTTPException(status_code=400, detail=str(exc)) from exc
apply_dibang_prefill_to_order(
db, tenant_no=tenant_no, plan=plan, transport=transport
)
db.commit()
db.refresh(transport)
return _build_arrange_result(transport, site_records, inventory_records)
return _build_arrange_result(transport, site_records, inventory_records, io_order_brief)
# =============================================================================