diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java index 543e821a1..7d56977ec 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java @@ -79,14 +79,20 @@ public class ContainerService { public ContainerBookingEntity linkOceanBooking(long id, long operatorId, String operatorName, OceanBookingLinkRequest request) { ContainerBookingEntity container = required(id); requireVersion(container, request.expectedVersion()); requireFirstLinkMutable(container); if (container.getOceanBookingId() != null) throw conflict("模拟柜已关联真实订舱柜"); + String existingContainerNo = trim(container.getContainerNo()); + String requestedContainerNo = trim(request.containerNo()); + String containerNo = requestedContainerNo == null + ? existingContainerNo + : Iso6346ContainerNumber.require(requestedContainerNo); + if (existingContainerNo != null && !existingContainerNo.equals(containerNo)) { + throw conflict("输入的真实集装箱号与当前柜次已录入柜号不一致,请先在预装运输信息中纠正"); + } OceanBookingEntity booking = oceanBookings.find(request.oceanBookingId()); if (booking == null || !"RELEASED".equals(booking.getStatus())) throw conflict("只能关联已发布订舱"); OceanBookingContainerRequirementEntity requirement = oceanBookings.requirement(request.oceanBookingId(), request.requirementId()); if (requirement == null) throw new BizException(ResultCode.NOT_FOUND, "订舱箱型需求不存在"); long allocated = oceanBookings.activeLinkedCount(request.oceanBookingId(), request.requirementId()); if (allocated >= requirement.getQuantity()) throw conflict("该订舱箱型需求已达到数量上限"); - String requestedContainerNo = trim(request.containerNo()); - String containerNo = requestedContainerNo == null ? null : Iso6346ContainerNumber.require(requestedContainerNo); if (containerNo != null && repository.containerNoExists(containerNo, id)) throw conflict("集装箱号已存在"); if (!java.util.Objects.equals(container.getWarehouseId(), booking.getWarehouseId()) || !java.util.Objects.equals(container.getChannelGroupCode(), booking.getChannelCode()) @@ -134,7 +140,6 @@ public class ContainerService { private void requireFirstLinkMutable(ContainerBookingEntity container) { SmartLoadingStatus value = status(container); if ((value != SmartLoadingStatus.WAIT_LOADING && value != SmartLoadingStatus.EDITING) - || trim(container.getContainerNo()) != null || repository.hasPickTask(container.getId()) || repository.hasEvidence(container.getId()) || repository.hasDocuments(container.getId())) throw conflict("已进入PDA、证据、单证或不可逆装柜阶段,不能关联订舱"); } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java index 36662a340..5be2b2072 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java @@ -252,6 +252,39 @@ class ContainerServiceTest { verify(oceanBookings).insert(any(OceanBookingContainerAllocationEntity.class)); } + @Test + @DisplayName("预装已录入真实柜号时关联订舱保留原柜号") + void linksReleasedBookingWhilePreservingExistingRealContainerNumber() { + ContainerBookingEntity container = stubLinkContext(); + container.setContainerNo("MSCU6639870"); + when(repository.find(11L)).thenReturn(container, container); + when(repository.linkReleasedBooking(eq(11L), eq(21L), eq(31L), eq("MSCU6639870"), + any(OceanBookingEntity.class), eq(2), eq(9L), eq("admin"))).thenReturn(1); + + ContainerBookingEntity result = service.linkOceanBooking(11L, 9L, "admin", + new OceanBookingLinkRequest(21L, 31L, null, 2)); + + assertThat(result.getContainerNo()).isEqualTo("MSCU6639870"); + verify(repository).linkReleasedBooking(eq(11L), eq(21L), eq(31L), eq("MSCU6639870"), + any(OceanBookingEntity.class), eq(2), eq(9L), eq("admin")); + verify(oceanBookings).insert(any(OceanBookingContainerAllocationEntity.class)); + } + + @Test + @DisplayName("关联订舱时拒绝用不同柜号覆盖预装已录入柜号") + void rejectsDifferentRealContainerNumberWhenLinkingReleasedBooking() { + ContainerBookingEntity container = stubLinkContext(); + container.setContainerNo("MSCU6639870"); + + assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin", + new OceanBookingLinkRequest(21L, 31L, "TSTU1503306", 2))) + .isInstanceOf(BizException.class).hasMessageContaining("与当前柜次已录入柜号不一致"); + + verify(repository, never()).linkReleasedBooking(anyLong(), anyLong(), anyLong(), anyString(), + any(), anyInt(), anyLong(), anyString()); + verifyNoInteractions(oceanBookings); + } + @Test @DisplayName("提柜后按柜次版本原子补录真实柜号") void assignsRealContainerNumberAfterPickup() { @@ -436,7 +469,7 @@ class ContainerServiceTest { any(), anyInt(), anyLong(), anyString()); } - private void stubLinkContext() { + private ContainerBookingEntity stubLinkContext() { ContainerBookingEntity container = booking("WAIT_LOADING", 2); container.setContainerNo(null); container.setWarehouseId(101L); container.setChannelGroupCode("MATSON_CLX"); container.setChannelCode(null); @@ -450,6 +483,7 @@ class ContainerServiceTest { when(oceanBookings.find(21L)).thenReturn(oceanBooking); when(oceanBookings.requirement(21L, 31L)).thenReturn(requirement); when(oceanBookings.activeLinkedCount(21L, 31L)).thenReturn(0L); + return container; } @Test