From e6ee9cb442cd006fc1dd98b12166612bad252f5f Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 29 Aug 2026 08:25:42 +0800 Subject: [PATCH] fix(firstleg): recover multi-customer PDA completion --- .../service/LoadingDeliveryService.java | 49 ++++++----- .../firstleg/service/LoadingPickService.java | 31 +++++-- .../service/LoadingDeliveryServiceTest.java | 64 +++++++++++++- .../service/LoadingPickServiceTest.java | 83 ++++++++++++++++--- 4 files changed, 185 insertions(+), 42 deletions(-) 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 43321e6da..30610cb9a 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 @@ -110,17 +110,20 @@ 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); + return completeLoading(tenantId, inboundId, tenantId, inboundId, + operatorId, operatorName, key, request); } - private LoadingActionResponse completeLoading(long tenantId, long inboundId, long evidenceInboundId, + private LoadingActionResponse completeLoading(long tenantId, long inboundId, + long evidenceTenantId, long evidenceInboundId, long operatorId, String operatorName, String key, CompleteLoadingRequest request) { - return completeLoading(tenantId, inboundId, evidenceInboundId, operatorId, operatorName, - key, request, false); + return completeLoading(tenantId, inboundId, evidenceTenantId, evidenceInboundId, + operatorId, operatorName, key, request, false); } - private LoadingActionResponse completeLoading(long tenantId, long inboundId, long evidenceInboundId, + private LoadingActionResponse completeLoading(long tenantId, long inboundId, + long evidenceTenantId, long evidenceInboundId, long operatorId, String operatorName, String key, CompleteLoadingRequest request, boolean acceptedPdaCompletion) { @@ -131,8 +134,8 @@ public class LoadingDeliveryService { Snapshot inbound = requireInbound(tenantId, inboundId); CompletionTarget target = repository.completionTarget(tenantId, inboundId); if (target != null) { - return completeFulfillmentSlice(tenantId, evidenceInboundId, operatorId, operatorName, - key, request, target, acceptedPdaCompletion); + return completeFulfillmentSlice(tenantId, inboundId, evidenceTenantId, evidenceInboundId, + operatorId, operatorName, key, request, target, acceptedPdaCompletion); } if ("LOADED".equals(inbound.processStatus()) || "DELIVERED".equals(inbound.processStatus())) { requireLoadingPlanGate(tenantId, inboundId, acceptedPdaCompletion); @@ -143,7 +146,7 @@ public class LoadingDeliveryService { requireLoadingPlanGate(tenantId, inboundId, acceptedPdaCompletion); completionGate.requireReleasedBooking(tenantId, inboundId); requirePickingGate(tenantId, inboundId, acceptedPdaCompletion); - requireCompletionEvidence(tenantId, evidenceInboundId); + requireCompletionEvidence(evidenceTenantId, 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, @@ -159,12 +162,14 @@ 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); + return completeAfterPdaSettlement(tenantId, inboundId, tenantId, inboundId, + operatorId, operatorName, key, remark); } @Transactional public LoadingActionResponse completeAfterPdaSettlement(long tenantId, long inboundId, - long evidenceInboundId, long operatorId, + long evidenceTenantId, long evidenceInboundId, + long operatorId, String operatorName, String key, String remark) { CompletionTarget target = repository.completionTarget(tenantId, inboundId); long completionInboundId = target == null ? inboundId : target.completionInboundId(); @@ -172,8 +177,8 @@ public class LoadingDeliveryService { if (current == null || current.version() == null) { throw new BizException(ResultCode.CONFLICT, "PDA结算后的实装履约分片不存在,请刷新后重试"); } - return completeLoading(tenantId, inboundId, evidenceInboundId, operatorId, operatorName, key, - new CompleteLoadingRequest(current.version(), remark), true); + return completeLoading(tenantId, inboundId, evidenceTenantId, evidenceInboundId, + operatorId, operatorName, key, new CompleteLoadingRequest(current.version(), remark), true); } /** @@ -181,27 +186,29 @@ 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); + requirePdaCompletionReady(tenantId, inboundId, tenantId, inboundId); } - public void requirePdaCompletionReady(long tenantId, long inboundId, long evidenceInboundId) { + public void requirePdaCompletionReady(long tenantId, long inboundId, + long evidenceTenantId, 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, evidenceInboundId); + requireCompletionEvidence(evidenceTenantId, evidenceInboundId); } - private LoadingActionResponse completeFulfillmentSlice(long tenantId, long evidenceInboundId, + private LoadingActionResponse completeFulfillmentSlice(long tenantId, long inboundId, + long evidenceTenantId, long evidenceInboundId, long operatorId, String operatorName, String key, CompleteLoadingRequest request, CompletionTarget target, boolean acceptedPdaCompletion) { if ("LOADED".equals(target.loadedChildStatus())) { - return complete(tenantId, evidenceInboundId, operatorId, COMPLETE_LOADING, key, - new LoadingActionResponse("LOADED", evidenceResponse(tenantId, evidenceInboundId))); + return complete(tenantId, inboundId, operatorId, COMPLETE_LOADING, key, + new LoadingActionResponse("LOADED", evidenceResponse(tenantId, inboundId))); } if (!"LOADING".equals(target.loadedChildStatus())) { throw new BizException(ResultCode.CONFLICT, "实装履约分片不在装柜中"); @@ -209,14 +216,14 @@ public class LoadingDeliveryService { requireLoadingPlanGate(tenantId, target.sourceInboundId(), acceptedPdaCompletion); completionGate.requireReleasedBooking(tenantId, target.sourceInboundId()); requirePickingGate(tenantId, target.sourceInboundId(), acceptedPdaCompletion); - requireCompletionEvidence(tenantId, evidenceInboundId); + requireCompletionEvidence(evidenceTenantId, evidenceInboundId); if (repository.completeLoadedChild(tenantId, target.sourceInboundId(), operatorId, request.expectedVersion()) != 1) throw concurrent(); repository.aggregateRootLoaded(tenantId, target.rootInboundId(), operatorId); addHistory(tenantId, target.completionInboundId(), "LOADING", "LOADED", COMPLETE_LOADING, request.remark(), operatorId, operatorName); - return complete(tenantId, evidenceInboundId, operatorId, COMPLETE_LOADING, key, - new LoadingActionResponse("LOADED", evidenceResponse(tenantId, evidenceInboundId))); + return complete(tenantId, inboundId, operatorId, COMPLETE_LOADING, key, + new LoadingActionResponse("LOADED", evidenceResponse(tenantId, inboundId))); } public EvidenceListResponse evidence(long tenantId, long inboundId) { 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 20da87435..526d82296 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java @@ -512,17 +512,32 @@ public class LoadingPickService { .thenComparing(LoadingPickItemMapper.InboundOwner::inboundId)) .toList(); if (owners.isEmpty()) throw new CompletionProcessingException("OWNER_DISCOVERY", conflict("PDA任务未关联有效入库单")); - long evidenceInboundId = owners.get(0).inboundId(); + LoadingPickItemMapper.InboundOwner evidenceOwner = owners.get(0); + + // Phase 1 is read-only: every owner must pass its business gates against the same cabinet evidence owner + // before any warehouse resource or inbound status can be changed. This prevents a later customer gate from + // leaving earlier customers partially completed. for (int index = 0; index < owners.size(); index++) { requireCompletionLease(command.getId(), worker); LoadingPickItemMapper.InboundOwner owner = owners.get(index); String checkpoint = completionCommands.checkpoint(command.getId(), owner.tenantId(), owner.inboundId()); - if (!checkpointAtLeast(checkpoint, "READY")) { + if (!checkpointAtLeast(checkpoint, "SETTLED")) { runCompletionStage("OWNER_READY", () -> loadingDeliveryService.requirePdaCompletionReady( - owner.tenantId(), owner.inboundId(), evidenceInboundId)); - requireCompletionLease(command.getId(), worker); - completionCommands.checkpoint(command.getId(), owner.tenantId(), owner.inboundId(), index + 1, "READY"); + owner.tenantId(), owner.inboundId(), evidenceOwner.tenantId(), evidenceOwner.inboundId())); + if (!checkpointAtLeast(checkpoint, "READY")) { + requireCompletionLease(command.getId(), worker); + completionCommands.checkpoint(command.getId(), owner.tenantId(), owner.inboundId(), + index + 1, "READY"); + } } + } + + // Phase 2 is recoverable by per-owner checkpoints and stable idempotency keys. It starts only after the + // complete cabinet passed phase 1, while prior deployments' completed checkpoints remain safely resumable. + for (int index = 0; index < owners.size(); index++) { + requireCompletionLease(command.getId(), worker); + LoadingPickItemMapper.InboundOwner owner = owners.get(index); + String checkpoint = completionCommands.checkpoint(command.getId(), owner.tenantId(), owner.inboundId()); if (!checkpointAtLeast(checkpoint, "SETTLED")) { runCompletionStage("WAREHOUSE_SETTLEMENT", () -> settlementService.settleOwner( taskId, owner.inboundId(), command.getOperatorId(), command.getOperatorName())); @@ -531,9 +546,9 @@ public class LoadingPickService { } if (!checkpointAtLeast(checkpoint, "INBOUND_COMPLETED")) { runCompletionStage("INBOUND_COMPLETE", () -> loadingDeliveryService.completeAfterPdaSettlement( - owner.tenantId(), owner.inboundId(), evidenceInboundId, command.getOperatorId(), - command.getOperatorName(), "PDA-" + command.getOperationId() + '-' + owner.inboundId(), - command.getRemark())); + owner.tenantId(), owner.inboundId(), evidenceOwner.tenantId(), evidenceOwner.inboundId(), + command.getOperatorId(), command.getOperatorName(), + "PDA-" + command.getOperationId() + '-' + owner.inboundId(), command.getRemark())); requireCompletionLease(command.getId(), worker); completionCommands.checkpoint(command.getId(), owner.tenantId(), owner.inboundId(), index + 1, "INBOUND_COMPLETED"); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingDeliveryServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingDeliveryServiceTest.java index f4e7fe2a1..b9c167b46 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingDeliveryServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingDeliveryServiceTest.java @@ -253,10 +253,72 @@ class LoadingDeliveryServiceTest { long.class, long.class, long.class, String.class, String.class, String.class) .getAnnotation(Transactional.class)).isNotNull(); assertThat(LoadingDeliveryService.class.getMethod("completeAfterPdaSettlement", - long.class, long.class, long.class, long.class, String.class, String.class, String.class) + long.class, long.class, long.class, long.class, long.class, + String.class, String.class, String.class) .getAnnotation(Transactional.class)).isNotNull(); } + @Test + void pdaPreflightUsesCabinetEvidenceOwnerWithoutCrossCustomerLookup() { + when(repository.findInbound(20, 200)).thenReturn(new Snapshot(200L, 20L, "LOADING", 3)); + when(repository.listEvidence(10, 100)).thenReturn(List.of( + evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null), + evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null), + evidence("SEAL", 5L, "LOCK-001"))); + when(files.validate(eq("token"), eq(10L), eq(100L), anyLong())).thenAnswer(invocation -> { + long fileId = invocation.getArgument(3); + String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId; + return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata( + fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png")); + }); + + service.requirePdaCompletionReady(20, 200, 10, 100); + + verify(loadingModelBridge).requireStarted(20, 200); + verify(completionGate).requireReleasedBooking(20, 200); + verify(repository).listEvidence(10, 100); + verify(repository, never()).listEvidence(20, 100); + verify(files, times(5)).validate(eq("token"), eq(10L), eq(100L), anyLong()); + } + + @Test + void pdaFinalizeKeepsOperationOnBusinessOwnerWhileUsingCabinetEvidenceOwner() { + when(repository.claimOperation(eq(20L), eq(200L), eq(7L), eq("COMPLETE_LOADING"), + eq("pda-multi-customer"), anyString())).thenReturn(true); + when(repository.findInboundForUpdate(20, 200)).thenReturn(new Snapshot(200L, 20L, "LOADING", 7)); + when(repository.findInbound(20, 200)).thenReturn(new Snapshot(200L, 20L, "LOADING", 7)); + LoadingPickItemMapper.InboundProgress progress = mock(LoadingPickItemMapper.InboundProgress.class); + when(progress.getExpectedCount()).thenReturn(1); + when(progress.getPickedCount()).thenReturn(1); + when(progress.getTaskCount()).thenReturn(1); + when(progress.getCompletedTaskCount()).thenReturn(0); + when(progress.getUnresolvedCount()).thenReturn(0); + when(loadingPicks.inboundProgress(20, 200)).thenReturn(progress); + when(repository.listEvidence(10, 100)).thenReturn(List.of( + evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null), + evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null), + evidence("SEAL", 5L, "LOCK-001"))); + when(files.validate(eq("token"), eq(10L), eq(100L), anyLong())).thenAnswer(invocation -> { + long fileId = invocation.getArgument(3); + String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId; + return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata( + fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png")); + }); + when(repository.completeLoading(20, 200, 7, 7)).thenReturn(1); + + LoadingActionResponse result = service.completeAfterPdaSettlement( + 20, 200, 10, 100, 7, "admin", "pda-multi-customer", "完成"); + + assertThat(result.mainStatus()).isEqualTo("LOADED"); + verify(repository).completeLoading(20, 200, 7, 7); + verify(repository).completeOperation(eq(20L), eq(200L), eq(7L), + eq("COMPLETE_LOADING"), eq("pda-multi-customer"), anyString()); + verify(repository, never()).completeOperation(eq(20L), eq(100L), anyLong(), + anyString(), anyString(), anyString()); + verify(repository).listEvidence(10, 100); + verify(repository, never()).listEvidence(20, 100); + } + @Test void pdaFinalizeLocksAndUsesLoadedSliceVersionAfterPartialSettlement() { readyForPdaFinalize(); 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 d1b0c4258..587375316 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java @@ -567,17 +567,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, 50L); - verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L, 50L); + verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L, 101L, 50L); + verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L, 101L, 50L); verify(truckAppointmentService).completeLoadingFromPda(99L, 20L, 9L, "operator"); - verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 50L, 9L, "operator", + verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator", "PDA-complete-1-50", "checked"); - verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 60L, 50L, 9L, "operator", + verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 60L, 101L, 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, 50L, 9L, "operator", + finalization.verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator", "PDA-complete-1-50", "checked"); finalization.verify(loadingListService).publishCompletedEvent(10L); } @@ -608,7 +608,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, 50L, 9L, "operator", + verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator", "PDA-complete-not-blocked-50", "checked"); verify(loadingListService).publishCompletedEvent(10L); } @@ -627,7 +627,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, 50L, 9L, "operator", + .when(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator", "PDA-complete-finalize-fails-50", "checked"); assertThatThrownBy(() -> service.complete(20L, @@ -687,7 +687,7 @@ class LoadingPickServiceTest { verify(loadingListService, never()).finishFromPda(anyLong(), anyLong(), anyLong(), anyString()); verify(loadingListService, never()).publishCompletedEvent(anyLong()); verify(loadingDeliveryService, never()).completeAfterPdaSettlement(anyLong(), anyLong(), anyLong(), - anyLong(), anyString(), anyString(), anyString()); + anyLong(), anyLong(), anyString(), anyString(), anyString()); } @Test @@ -827,6 +827,65 @@ class LoadingPickServiceTest { verify(completionCommands).complete(900L, "worker-1"); } + @Test + void workerUsesOneStableCabinetEvidenceOwnerAcrossTwoCustomers() { + LoadingPickTaskEntity completing = task(); + completing.setTaskStatus("COMPLETING"); + when(repository.task(20L)).thenReturn(completing); + when(repository.inboundOwners(20L)).thenReturn(List.of( + new LoadingPickItemMapper.InboundOwner(202L, 80L), + new LoadingPickItemMapper.InboundOwner(101L, 60L), + new LoadingPickItemMapper.InboundOwner(202L, 70L), + new LoadingPickItemMapper.InboundOwner(101L, 50L))); + when(repository.allPlanItemsPicked(10L)).thenReturn(true); + when(repository.approve(20L, 9L, "operator", "checked")).thenReturn(1); + when(repository.completeAccepted(20L, 9L, "operator")).thenReturn(1); + when(completionCommands.complete(903L, "worker-multi")).thenReturn(true); + + service.processCompletion(completionCommand(903L, "async-multi"), "worker-multi"); + + verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L, 101L, 50L); + verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L, 101L, 50L); + verify(loadingDeliveryService).requirePdaCompletionReady(202L, 70L, 101L, 50L); + verify(loadingDeliveryService).requirePdaCompletionReady(202L, 80L, 101L, 50L); + verify(loadingDeliveryService).completeAfterPdaSettlement( + 202L, 70L, 101L, 50L, 9L, "operator", "PDA-async-multi-70", "checked"); + verify(settlementService, times(4)).settleOwner(eq(20L), anyLong(), eq(9L), eq("operator")); + verify(repository, times(1)).insert(argThat((LoadingPickOperationEntity operation) -> + "COMPLETE_LOADING".equals(operation.getActionType()) + && "async-multi".equals(operation.getOperationId()))); + verify(loadingListService, times(1)).publishCompletedEvent(10L); + verify(completionCommands).complete(903L, "worker-multi"); + } + + @Test + void secondCustomerOwnerReadyFailureLeavesEveryOwnerUnsettledAndUncompleted() { + LoadingPickTaskEntity completing = task(); + completing.setTaskStatus("COMPLETING"); + when(repository.task(20L)).thenReturn(completing); + when(repository.inboundOwners(20L)).thenReturn(List.of( + new LoadingPickItemMapper.InboundOwner(101L, 50L), + new LoadingPickItemMapper.InboundOwner(202L, 70L))); + doThrow(new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT, "第二客户门禁未就绪")) + .when(loadingDeliveryService).requirePdaCompletionReady(202L, 70L, 101L, 50L); + + assertThatThrownBy(() -> service.processCompletion( + completionCommand(904L, "async-owner-ready-fails"), "worker-failure")) + .isInstanceOf(LoadingPickService.CompletionProcessingException.class) + .satisfies(error -> assertThat(((LoadingPickService.CompletionProcessingException) error).stage()) + .isEqualTo("OWNER_READY")); + + verify(settlementService, never()).settleOwner(anyLong(), anyLong(), anyLong(), anyString()); + verify(loadingDeliveryService, never()).completeAfterPdaSettlement(anyLong(), anyLong(), anyLong(), + anyLong(), anyLong(), anyString(), anyString(), anyString()); + verify(completionCommands).checkpoint(904L, 101L, 50L, 1, "READY"); + verify(completionCommands, never()).checkpoint(eq(904L), anyLong(), anyLong(), anyInt(), + eq("SETTLED")); + verify(repository, never()).insert(any(LoadingPickOperationEntity.class)); + verify(loadingListService, never()).finishFromPda(anyLong(), anyLong(), anyLong(), anyString()); + verify(loadingListService, never()).publishCompletedEvent(anyLong()); + } + @Test void workerRestartResumesFromOwnerCheckpointsWithoutRepeatingSettlements() { LoadingPickTaskEntity completing = task(); @@ -834,9 +893,9 @@ class LoadingPickServiceTest { when(repository.task(20L)).thenReturn(completing); when(repository.inboundOwners(20L)).thenReturn(List.of( new LoadingPickItemMapper.InboundOwner(101L, 50L), - new LoadingPickItemMapper.InboundOwner(101L, 60L))); + new LoadingPickItemMapper.InboundOwner(202L, 60L))); when(completionCommands.checkpoint(901L, 101L, 50L)).thenReturn("INBOUND_COMPLETED"); - when(completionCommands.checkpoint(901L, 101L, 60L)).thenReturn("SETTLED"); + when(completionCommands.checkpoint(901L, 202L, 60L)).thenReturn("SETTLED"); when(repository.allPlanItemsPicked(10L)).thenReturn(true); when(repository.approve(20L, 9L, "operator", "checked")).thenReturn(1); when(repository.completeAccepted(20L, 9L, "operator")).thenReturn(1); @@ -846,8 +905,8 @@ class LoadingPickServiceTest { verify(settlementService, never()).settleOwner(anyLong(), anyLong(), anyLong(), anyString()); verify(loadingDeliveryService, never()).completeAfterPdaSettlement(eq(101L), eq(50L), anyLong(), - anyLong(), anyString(), anyString(), anyString()); - verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 60L, 50L, 9L, "operator", + anyLong(), anyLong(), anyString(), anyString(), anyString()); + verify(loadingDeliveryService).completeAfterPdaSettlement(202L, 60L, 101L, 50L, 9L, "operator", "PDA-async-resume-60", "checked"); verify(loadingListService).publishCompletedEvent(10L); verify(repository).insert(org.mockito.ArgumentMatchers.argThat(