From afb38f3c3f7ad997b7b62e6a158023f8b4bf95c0 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 14:00:54 +0800 Subject: [PATCH] fix(dispatch): expose PDA departure gate --- .../delivery/mapper/OutboundOrderMapper.java | 2 + .../pda/dto/DispatchPdaResponses.java | 2 +- .../service/DispatchPdaWorkflowService.java | 18 ++++++++- .../openapi/overseas-pda-operations-v1.yaml | 1 + .../DispatchPdaWorkflowServiceTest.java | 37 +++++++++++++++++++ 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java index 9bd7af835..370db140b 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java @@ -14,6 +14,8 @@ public interface OutboundOrderMapper extends BaseMapper { OutboundOrder findGlobalByNo(@Param("outboundNo") String outboundNo); @Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") OutboundOrder findByIdempotency(@Param("customerId") String customerId, @Param("key") String key); + @Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1") + OutboundOrder findByDispatch(@Param("customerId") String customerId, @Param("dispatchOrderId") Long dispatchOrderId); @Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1 FOR UPDATE") OutboundOrder lockByDispatch(@Param("customerId") String customerId, @Param("dispatchOrderId") Long dispatchOrderId); @Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND outbound_no=#{outboundNo} LIMIT 1 FOR UPDATE") diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/dto/DispatchPdaResponses.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/dto/DispatchPdaResponses.java index e259a89b8..ec1d7cd4b 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/dto/DispatchPdaResponses.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/dto/DispatchPdaResponses.java @@ -5,7 +5,7 @@ import java.util.List; public final class DispatchPdaResponses { private DispatchPdaResponses() {} public record Task(Long id, String taskNo, String dispatchNo, String vehiclePlate, String warehouseId, String status, - List items) {} + boolean canDepart, String departBlockReason, List items) {} public record Item(Long dispatchItemId, String palletNo, String destinationWarehouseCode, String destinationWarehouseName, String fbaWarehouseCode, String warehouseRouteLabel, int scheduledPackages, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java index 2ecbd0cbb..dfa090572 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java @@ -242,7 +242,23 @@ public class DispatchPdaWorkflowService { new DispatchPdaResponses.Position(p.getId(), p.getBalanceId(), n(p.getAvailablePackages()), n(p.getAllocatedPackages()), n(p.getPickedPackages()))).toList())); } - return new DispatchPdaResponses.Task(task.getId(), task.getTaskNo(), order.getDispatchNo(), order.getVehiclePlate(), task.getWarehouseId(), task.getStatus(), values); + OutboundOrder outbound = outboundOrders.findByDispatch(customerId, order.getId()); + String departBlockReason = departBlockReason(order, task, source, outbound); + return new DispatchPdaResponses.Task(task.getId(), task.getTaskNo(), order.getDispatchNo(), order.getVehiclePlate(), + task.getWarehouseId(), task.getStatus(), departBlockReason == null, departBlockReason, values); + } + + private String departBlockReason(DispatchOrder order, DispatchPdaTask task, List source, + OutboundOrder outbound) { + if ("DEPARTED".equals(order.getStatus())) return "该排车已发车,不可重复提交。"; + if (trim(order.getVehiclePlate()) == null) return "请先在平台维护车辆信息。"; + if (!"COMPLETED".equals(task.getStatus()) || !"READY_TO_DEPART".equals(order.getStatus()) + || !allPicked(order.getCustomerId(), task.getId(), source)) { + return "请先完成排车拣货并核平全部箱数。"; + } + if (outbound == null) return "请先在平台完成“转派送”,生成待出库单后才能发车。"; + if (!"WAIT_OUTBOUND".equals(outbound.getStatus())) return "关联出库单不是待出库状态,暂不可发车。"; + return null; } private boolean allPicked(String customerId, Long taskId, List source) { diff --git a/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml b/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml index 346f4ebf8..bac54bd45 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml @@ -15,6 +15,7 @@ paths: /api/pda/v1/last-leg/dispatches/{dispatchNo}: get: summary: 查询绑定仓排车任务详情 + description: 返回 canDepart 与 departBlockReason;PDA 必须以服务端门禁决定是否开放发车,不得仅按本地拣货数量推断。 parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }] responses: { '200': { description: 排车托盘、目的仓与库存位置详情 }, '404': { description: 当前绑定仓无此排车任务 } } /api/pda/v1/last-leg/dispatches/{dispatchNo}/items/{itemId}/pick: diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java index 7341c6269..6f34e38ae 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java @@ -107,6 +107,43 @@ class DispatchPdaWorkflowServiceTest { verify(tasks, never()).findByDispatch(any(), any()); } + @Test + void completedTaskExposesTransferDeliveryGateBeforeDeparture() { + DispatchOrder order = order("100", "DSP-1", "WH-1", "READY_TO_DEPART"); + order.setVehiclePlate("CA-100"); + DispatchPdaTask task = task(11L, "100", "WH-1", "COMPLETED"); + DispatchItem item = item(21L, "100", 6); + when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order); + when(tasks.findByDispatch("100", 1L)).thenReturn(task); + when(items.findByDispatchOrder("100", 1L)).thenReturn(List.of(item)); + when(picks.totalItem("100", 11L, 21L)).thenReturn(6L); + + DispatchPdaResponses.Task result = service.task("WH-1", "DSP-1"); + + assertFalse(result.canDepart()); + assertEquals("请先在平台完成“转派送”,生成待出库单后才能发车。", result.departBlockReason()); + } + + @Test + void completedTaskExposesDepartureWhenPendingOutboundExists() { + DispatchOrder order = order("100", "DSP-1", "WH-1", "READY_TO_DEPART"); + order.setVehiclePlate("CA-100"); + DispatchPdaTask task = task(11L, "100", "WH-1", "COMPLETED"); + DispatchItem item = item(21L, "100", 6); + OutboundOrder outbound = new OutboundOrder(); + outbound.setStatus("WAIT_OUTBOUND"); + when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order); + when(tasks.findByDispatch("100", 1L)).thenReturn(task); + when(items.findByDispatchOrder("100", 1L)).thenReturn(List.of(item)); + when(picks.totalItem("100", 11L, 21L)).thenReturn(6L); + when(outboundOrders.findByDispatch("100", 1L)).thenReturn(outbound); + + DispatchPdaResponses.Task result = service.task("WH-1", "DSP-1"); + + assertTrue(result.canDepart()); + assertNull(result.departBlockReason()); + } + @Test void exactPickRetryReturnsPersistedResultAfterTaskAutomaticallyCompleted() throws Exception { DispatchOrder order = order("100", "DSP-1", "WH-1", "READY_TO_DEPART");