diff --git a/firstleg/src/main/java/com/baiyue/firstleg/loading/service/LoadingDeliveryService.java b/firstleg/src/main/java/com/baiyue/firstleg/loading/service/LoadingDeliveryService.java index e2703301b..52467a049 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/loading/service/LoadingDeliveryService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/loading/service/LoadingDeliveryService.java @@ -97,13 +97,19 @@ public class LoadingDeliveryService { @Transactional public LoadingActionResponse completeLoading(long tenantId, long inboundId, long operatorId, String operatorName, String key, CompleteLoadingRequest request) { + return completeLoading(tenantId, inboundId, inboundId, operatorId, operatorName, key, request); + } + + private LoadingActionResponse completeLoading(long tenantId, long inboundId, long evidenceInboundId, + long operatorId, String operatorName, String key, + CompleteLoadingRequest request) { String hash = hash(request); LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, COMPLETE_LOADING, key, hash); if (replay != null) return replay; Snapshot inbound = requireInbound(tenantId, inboundId); CompletionTarget target = repository.completionTarget(tenantId, inboundId); if (target != null) { - return completeFulfillmentSlice(tenantId, inboundId, operatorId, operatorName, key, request, target); + return completeFulfillmentSlice(tenantId, evidenceInboundId, operatorId, operatorName, key, request, target); } if ("LOADED".equals(inbound.processStatus()) || "DELIVERED".equals(inbound.processStatus())) { loadingModelBridge.requireCompleted(tenantId, inboundId); @@ -114,7 +120,7 @@ public class LoadingDeliveryService { loadingModelBridge.requireCompleted(tenantId, inboundId); completionGate.requireReleasedBooking(tenantId, inboundId); requireCompletedPicking(tenantId, inboundId); - requireCompletionEvidence(tenantId, inboundId); + requireCompletionEvidence(tenantId, evidenceInboundId); if (repository.completeLoading(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) throw concurrent(); addHistory(tenantId, inboundId, "LOADING", "LOADED", COMPLETE_LOADING, request.remark(), operatorId, operatorName); return complete(tenantId, inboundId, operatorId, COMPLETE_LOADING, key, @@ -129,13 +135,19 @@ public class LoadingDeliveryService { public LoadingActionResponse completeAfterPdaSettlement(long tenantId, long inboundId, long operatorId, String operatorName, String key, String remark) { + return completeAfterPdaSettlement(tenantId, inboundId, inboundId, operatorId, operatorName, key, remark); + } + + public LoadingActionResponse completeAfterPdaSettlement(long tenantId, long inboundId, + long evidenceInboundId, long operatorId, + String operatorName, String key, String remark) { CompletionTarget target = repository.completionTarget(tenantId, inboundId); long completionInboundId = target == null ? inboundId : target.completionInboundId(); Snapshot current = repository.findInboundForUpdate(tenantId, completionInboundId); if (current == null || current.version() == null) { throw new BizException(ResultCode.CONFLICT, "PDA结算后的实装履约分片不存在,请刷新后重试"); } - return completeLoading(tenantId, inboundId, operatorId, operatorName, key, + return completeLoading(tenantId, inboundId, evidenceInboundId, operatorId, operatorName, key, new CompleteLoadingRequest(current.version(), remark)); } @@ -144,13 +156,17 @@ public class LoadingDeliveryService { * every external/business hard gate must pass before the PDA task is mutated. */ public void requirePdaCompletionReady(long tenantId, long inboundId) { + requirePdaCompletionReady(tenantId, inboundId, inboundId); + } + + public void requirePdaCompletionReady(long tenantId, long inboundId, long evidenceInboundId) { Snapshot inbound = requireInbound(tenantId, inboundId); if (!"LOADING".equals(inbound.processStatus())) { throw new BizException(ResultCode.CONFLICT, "PDA确认装柜完成前订单必须处于LOADING"); } loadingModelBridge.requireStarted(tenantId, inboundId); completionGate.requireReleasedBooking(tenantId, inboundId); - requireCompletionEvidence(tenantId, inboundId); + requireCompletionEvidence(tenantId, evidenceInboundId); } private LoadingActionResponse completeFulfillmentSlice(long tenantId, long evidenceInboundId, @@ -167,7 +183,7 @@ public class LoadingDeliveryService { loadingModelBridge.requireCompleted(tenantId, target.sourceInboundId()); completionGate.requireReleasedBooking(tenantId, target.sourceInboundId()); requireCompletedPicking(tenantId, target.sourceInboundId()); - requireCompletionEvidence(tenantId, target.sourceInboundId()); + requireCompletionEvidence(tenantId, evidenceInboundId); if (repository.completeLoadedChild(tenantId, target.sourceInboundId(), operatorId, request.expectedVersion()) != 1) throw concurrent(); repository.aggregateRootLoaded(tenantId, target.rootInboundId(), operatorId); diff --git a/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPickItemMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPickItemMapper.java index ea943e0b9..d57e01168 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPickItemMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPickItemMapper.java @@ -107,10 +107,12 @@ public interface LoadingPickItemMapper extends BaseMapper } @Select(""" - SELECT DISTINCT plan_item.account_id tenantId,plan_item.inbound_id inboundId + SELECT plan_item.account_id tenantId,plan_item.inbound_id inboundId FROM fl_loading_pick_item pick_item JOIN fl_loading_plan_item plan_item ON plan_item.id=pick_item.loading_plan_item_id WHERE pick_item.pick_task_id=#{taskId} + GROUP BY plan_item.account_id,plan_item.inbound_id + ORDER BY MIN(pick_item.id) """) java.util.List selectInboundOwners(@Param("taskId") long taskId); diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java index 6fda57d36..4faf88928 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java @@ -450,16 +450,20 @@ public class LoadingPickService { throw conflict("存在尚未处理的PDA拣货明细,不能确认装柜完成"); } if (repository.review(taskId) != null) throw conflict("PDA任务已经确认装柜完成"); - List owners = repository.inboundOwners(taskId).stream() + List discoveredOwners = repository.inboundOwners(taskId).stream() .filter(owner -> owner.tenantId() != null && owner.inboundId() != null) + .toList(); + if (discoveredOwners.isEmpty()) throw conflict("PDA任务未关联有效入库单"); + LoadingPickItemMapper.InboundOwner evidenceOwner = discoveredOwners.get(0); + List owners = discoveredOwners.stream() .sorted(Comparator.comparing(LoadingPickItemMapper.InboundOwner::tenantId) .thenComparing(LoadingPickItemMapper.InboundOwner::inboundId)) .toList(); - if (owners.isEmpty()) throw conflict("PDA任务未关联有效入库单"); for (LoadingPickItemMapper.InboundOwner owner : owners) { LoadingInboundMapper.Snapshot snapshot = repository.inboundForUpdate(owner.tenantId(), owner.inboundId()); if (snapshot == null) throw conflict("PDA任务关联入库单不存在"); - loadingDeliveryService.requirePdaCompletionReady(owner.tenantId(), owner.inboundId()); + loadingDeliveryService.requirePdaCompletionReady(owner.tenantId(), owner.inboundId(), + evidenceOwner.inboundId()); } settlementService.settle(taskId, operatorId, operatorName); if (!repository.allPlanItemsPicked(task.getLoadingPlanId())) { @@ -472,8 +476,9 @@ public class LoadingPickService { loadingListService.finishFromPda(task.getLoadingPlanId(), taskId, operatorId, operatorName); repository.syncInboundProjections(taskId, operatorId, operatorName); for (LoadingPickItemMapper.InboundOwner owner : owners) { - loadingDeliveryService.completeAfterPdaSettlement(owner.tenantId(), owner.inboundId(), operatorId, - operatorName, "PDA-" + operationId + '-' + owner.inboundId(), remark); + loadingDeliveryService.completeAfterPdaSettlement(owner.tenantId(), owner.inboundId(), + evidenceOwner.inboundId(), operatorId, operatorName, + "PDA-" + operationId + '-' + owner.inboundId(), remark); } loadingListService.publishCompletedEvent(task.getLoadingPlanId()); completeTruckAfterCommit(task.getContainerBookingId(), taskId, operatorId, operatorName); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java index dbaefc6b3..6279bf6bd 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java @@ -508,9 +508,12 @@ class LoadingPickServiceTest { when(repository.allPlanItemsPicked(10L)).thenReturn(true); when(repository.approve(20L, 9L, "operator", "checked")).thenReturn(1); var owner = new LoadingPickItemMapper.InboundOwner(101L, 50L); - when(repository.inboundOwners(20L)).thenReturn(List.of(owner)); + var incrementalOwner = new LoadingPickItemMapper.InboundOwner(101L, 60L); + when(repository.inboundOwners(20L)).thenReturn(List.of(owner, incrementalOwner)); when(repository.inboundForUpdate(101L, 50L)).thenReturn( new LoadingInboundMapper.Snapshot(50L, 101L, "LOADING", 7)); + when(repository.inboundForUpdate(101L, 60L)).thenReturn( + new LoadingInboundMapper.Snapshot(60L, 101L, "LOADING", 3)); service.complete(20L, new CompleteLoadingRequest(0, "complete-1", "checked"), 9L, "operator"); service.complete(20L, new CompleteLoadingRequest(0, "complete-1", "checked"), 9L, "operator"); @@ -519,14 +522,17 @@ class LoadingPickServiceTest { verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator"); verify(settlementService).settle(20L, 9L, "operator"); verify(repository).syncInboundProjections(20L, 9L, "operator"); - verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L); + verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L, 50L); + verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L, 50L); verify(truckAppointmentService).completeLoadingFromPda(99L, 20L, 9L, "operator"); - verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator", + verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 50L, 9L, "operator", "PDA-complete-1-50", "checked"); + verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 60L, 50L, 9L, "operator", + "PDA-complete-1-60", "checked"); verify(loadingListService).publishCompletedEvent(10L); InOrder finalization = inOrder(loadingListService, loadingDeliveryService); finalization.verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator"); - finalization.verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator", + finalization.verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 50L, 9L, "operator", "PDA-complete-1-50", "checked"); finalization.verify(loadingListService).publishCompletedEvent(10L); } @@ -556,7 +562,7 @@ class LoadingPickServiceTest { verify(settlementService).settle(20L, 9L, "operator"); verify(repository).approve(20L, 9L, "operator", "checked"); verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator"); - verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator", + verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 50L, 9L, "operator", "PDA-complete-not-blocked-50", "checked"); verify(loadingListService).publishCompletedEvent(10L); } @@ -574,7 +580,7 @@ class LoadingPickServiceTest { new LoadingInboundMapper.Snapshot(50L, 101L, "LOADING", 7)); doThrow(new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT, "入库单并发修改")) - .when(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator", + .when(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 50L, 9L, "operator", "PDA-complete-finalize-fails-50", "checked"); assertThatThrownBy(() -> service.complete(20L,