From e0bb7de0cf0dcee57c6cb8a58aaa54d68c9150ed Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 16:36:21 +0800 Subject: [PATCH] fix: isolate invalid container progress snapshots --- .../lastleg/service/LastlegOrderService.java | 47 ++++++++++++++----- .../service/LastlegOrderServiceTest.java | 35 ++++++++++++-- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java index cc12334bb..b6ff873e0 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java @@ -86,7 +86,7 @@ public class LastlegOrderService { long total = orderMapper.countContainerProgress(query); List records = total == 0 ? List.of() : orderMapper.pageContainerProgress(query, (query.page() - 1) * query.size(), query.size()) - .stream().map(this::requireFrozenContainer).toList(); + .stream().map(this::containerSummaryForRead).toList(); long pages = total == 0 ? 0 : (total + query.size() - 1) / query.size(); return new PageResult<>(records, query.page(), query.size(), total, pages); } @@ -101,7 +101,7 @@ public class LastlegOrderService { if (summary == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "来源柜不存在"); } - return new ContainerPlanProgressDetail(requireFrozenContainer(summary), + return new ContainerPlanProgressDetail(containerSummaryForRead(summary), orderMapper.findContainerProgressItems(bookingId)); } @@ -150,17 +150,42 @@ public class LastlegOrderService { private ContainerPlanProgressSummary requireFrozenContainer(ContainerPlanProgressSummary summary) { if (summary == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "来源柜不存在"); + String issue = containerSnapshotIssue(summary); + if (issue != null) throw new ResponseStatusException(HttpStatus.CONFLICT, issue); + return summary; + } + + private ContainerPlanProgressSummary containerSummaryForRead(ContainerPlanProgressSummary summary) { + String issue = containerSnapshotIssue(summary); + if (issue == null) return summary; + boolean invalidContainer = !hasCanonicalContainerNo(summary.containerNo()); + return new ContainerPlanProgressSummary(summary.sourceContainerBookingId(), summary.containerNo(), + summary.overseasWarehouseId(), summary.overseasWarehouseCode(), summary.overseasWarehouseName(), + summary.assignmentVersion(), invalidContainer ? false : summary.canChangeReceivingWarehouse(), + invalidContainer ? issue : summary.receivingWarehouseLockReason(), summary.totalOrderCount(), + summary.inboundCodeCount(), summary.missingInboundCodeCount(), summary.planCount(), + summary.plannedInboundCodeCount(), summary.pendingPlanCount(), summary.plannedArrivalAt(), + false, issue); + } + + private String containerSnapshotIssue(ContainerPlanProgressSummary summary) { + if (!hasCanonicalContainerNo(summary.containerNo())) { + return "来源柜真实集装箱号缺失、格式无效或存在漂移"; + } + if (!StringUtils.hasText(summary.overseasWarehouseId()) + || !StringUtils.hasText(summary.overseasWarehouseCode()) + || !StringUtils.hasText(summary.overseasWarehouseName()) || summary.assignmentVersion() < 0) { + return "柜内接柜仓业务快照不一致"; + } + return null; + } + + private boolean hasCanonicalContainerNo(String containerNo) { try { - ContainerNumbers.requireCanonical(summary.containerNo()); - if (!StringUtils.hasText(summary.overseasWarehouseId()) - || !StringUtils.hasText(summary.overseasWarehouseCode()) - || !StringUtils.hasText(summary.overseasWarehouseName()) || summary.assignmentVersion() < 0) { - throw new IllegalArgumentException("receiving warehouse assignment drift"); - } - return summary; + ContainerNumbers.requireCanonical(containerNo); + return true; } catch (IllegalArgumentException exception) { - throw new ResponseStatusException(HttpStatus.CONFLICT, - "来源柜真实集装箱号缺失、格式无效或存在漂移", exception); + return false; } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java index e1114fbb4..73a4f0565 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java @@ -30,7 +30,9 @@ import java.time.LocalDateTime; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -319,16 +321,39 @@ class LastlegOrderServiceTest { } @Test - void containerProgressDetailFailsClosedWhenContainerNumberFactHasDrifted() { + void containerProgressDetailKeepsInvalidHistoricalContainerReadableAndBlocked() { var drifted = new ContainerPlanProgressSummary("BOOKING-1", null, 2, 2, 0, 2, 2, 0, LocalDateTime.of(2026, 10, 3, 9, 0), false, "柜号缺失或不一致"); when(orders.findContainerProgress("BOOKING-1")).thenReturn(drifted); + when(orders.findContainerProgressItems("BOOKING-1")).thenReturn(List.of()); - ResponseStatusException error = assertThrows(ResponseStatusException.class, - () -> service.containerProgressDetail("BOOKING-1")); + var result = service.containerProgressDetail("BOOKING-1"); - assertEquals(409, error.getStatusCode().value()); - verify(orders, never()).findContainerProgressItems(any()); + assertFalse(result.summary().allPlansReady()); + assertFalse(result.summary().canChangeReceivingWarehouse()); + assertEquals("来源柜真实集装箱号缺失、格式无效或存在漂移", + result.summary().blockingReason()); + assertEquals(result.summary().blockingReason(), result.summary().receivingWarehouseLockReason()); + verify(orders).findContainerProgressItems("BOOKING-1"); + } + + @Test + void containerProgressPageKeepsMissingWarehouseSnapshotFromBreakingHealthyContainers() { + var query = new LastlegOrderRequests.ContainerProgressQuery(1, 10, null, null, null); + var healthy = summary(false, 3, "存在3票尚未生成有效入库计划"); + var missingWarehouse = new ContainerPlanProgressSummary("BOOKING-2", "MSCU6639870", + null, null, null, 0, true, null, 2, 2, 0, 0, 0, 2, + LocalDateTime.of(2026, 10, 4, 9, 0), false, "存在2票尚未生成有效入库计划"); + when(orders.countContainerProgress(query)).thenReturn(2L); + when(orders.pageContainerProgress(query, 0, 10)).thenReturn(List.of(healthy, missingWarehouse)); + + var result = service.containerProgressPage(query); + + assertEquals(2, result.records().size()); + assertSame(healthy, result.records().get(0)); + assertEquals("柜内接柜仓业务快照不一致", result.records().get(1).blockingReason()); + assertFalse(result.records().get(1).allPlansReady()); + assertTrue(result.records().get(1).canChangeReceivingWarehouse()); } private static ContainerPlanProgressSummary summary(boolean ready, long pending, String reason) {