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 4faf88928..1dcd6ad1a 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java @@ -462,10 +462,12 @@ public class LoadingPickService { 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(), - evidenceOwner.inboundId()); + completionStage(taskId, "完成前业务门禁", () -> + loadingDeliveryService.requirePdaCompletionReady(owner.tenantId(), owner.inboundId(), + evidenceOwner.inboundId())); } - settlementService.settle(taskId, operatorId, operatorName); + completionStage(taskId, "拆单与库位结算", () -> + settlementService.settle(taskId, operatorId, operatorName)); if (!repository.allPlanItemsPicked(task.getLoadingPlanId())) { throw conflict("柜次仍有未完成PDA拣货的箱数"); } @@ -473,18 +475,33 @@ public class LoadingPickService { throw conflict("PDA任务已经复核"); } insertOperation(taskId, operationId, requestHash, "COMPLETE_LOADING", operatorId, operatorName); - loadingListService.finishFromPda(task.getLoadingPlanId(), taskId, operatorId, operatorName); + completionStage(taskId, "装柜计划完成", () -> + loadingListService.finishFromPda(task.getLoadingPlanId(), taskId, operatorId, operatorName)); repository.syncInboundProjections(taskId, operatorId, operatorName); for (LoadingPickItemMapper.InboundOwner owner : owners) { - loadingDeliveryService.completeAfterPdaSettlement(owner.tenantId(), owner.inboundId(), - evidenceOwner.inboundId(), operatorId, operatorName, - "PDA-" + operationId + '-' + owner.inboundId(), remark); + completionStage(taskId, "入库单状态完成", () -> + loadingDeliveryService.completeAfterPdaSettlement(owner.tenantId(), owner.inboundId(), + evidenceOwner.inboundId(), operatorId, operatorName, + "PDA-" + operationId + '-' + owner.inboundId(), remark)); } - loadingListService.publishCompletedEvent(task.getLoadingPlanId()); + completionStage(taskId, "完成事件发布", () -> + loadingListService.publishCompletedEvent(task.getLoadingPlanId())); completeTruckAfterCommit(task.getContainerBookingId(), taskId, operatorId, operatorName); return view(requiredTask(taskId), null); } + private void completionStage(long taskId, String stage, Runnable action) { + try { + action.run(); + } catch (BizException exception) { + throw exception; + } catch (RuntimeException exception) { + log.error("Unexpected PDA loading completion failure taskId={} stage={}", taskId, stage, exception); + throw new BizException(ResultCode.INTERNAL_ERROR, + "装柜完成在【" + stage + "】阶段处理异常,请联系管理员并提供请求编号"); + } + } + private void completeTruckAfterCommit(long containerBookingId, long taskId, long operatorId, String operatorName) { Runnable completion = () -> { 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 6279bf6bd..437c62dcd 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java @@ -591,6 +591,28 @@ class LoadingPickServiceTest { verify(loadingListService, never()).publishCompletedEvent(anyLong()); } + @Test + void translatesUnexpectedSettlementFailureIntoActionableStageMessage() { + LoadingPickTaskEntity task = task(); + when(repository.taskForUpdate(20L)).thenReturn(task); + when(repository.allTaskItemsResolved(20L)).thenReturn(true); + var owner = new LoadingPickItemMapper.InboundOwner(101L, 50L); + when(repository.inboundOwners(20L)).thenReturn(List.of(owner)); + when(repository.inboundForUpdate(101L, 50L)).thenReturn( + new LoadingInboundMapper.Snapshot(50L, 101L, "LOADING", 7)); + doThrow(new IllegalStateException("unexpected database failure")) + .when(settlementService).settle(20L, 9L, "operator"); + + assertThatThrownBy(() -> service.complete(20L, + new CompleteLoadingRequest(0, "complete-stage-error", "checked"), 9L, "operator")) + .isInstanceOf(BizException.class) + .hasMessageContaining("拆单与库位结算") + .hasMessageContaining("请求编号"); + + verify(repository, never()).approve(anyLong(), anyLong(), anyString(), anyString()); + verify(loadingListService, never()).finishFromPda(anyLong(), anyLong(), anyLong(), anyString()); + } + @Test void atomicallySubmitsCompleteBatchAndReplaysSameResult() { LoadingPickTaskEntity task = task();