提交
This commit is contained in:
1 parent
6d9354a194
commit
aff71d296b
2759 files changed
+860425
-13211
No files matched your search
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
Reference in new issue
Block a user