This commit is contained in:
2026-08-18 16:39:39 +08:00
parent 7fe5b4e31f
commit a9061eca54
43 changed files with 821 additions and 1217 deletions
@@ -8,6 +8,7 @@ import com.baiyue.firstleg.dto.ContainerDtos.FullUpdateRequest;
import com.baiyue.firstleg.dto.ContainerDtos.SaveRequest;
import com.baiyue.firstleg.dto.ContainerDtos.SimulationCreateRequest;
import com.baiyue.firstleg.dto.ContainerDtos.OceanBookingLinkRequest;
import com.baiyue.firstleg.dto.ContainerDtos.OceanBookingUnlinkRequest;
import com.baiyue.firstleg.dto.ContainerDtos.TransportUpdateRequest;
import com.baiyue.firstleg.entity.ContainerBookingEntity;
import com.baiyue.firstleg.entity.LoadingPlanEntity;
@@ -32,12 +33,11 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -282,94 +282,83 @@ class ContainerServiceTest {
@Test
@DisplayName("首次关联允许已有预排货的编辑计划且保持内部柜和计划身份")
void linksConfirmedBookingAfterPreloading() {
void linksReleasedBookingAfterPreloading() {
ContainerBookingEntity container = booking("EDITING", 2);
container.setWarehouseId(101L); container.setChannelCode("SEA-US"); container.setContainerType("40HC");
container.setCapacityCbm(new BigDecimal("76.0000")); container.setMaxWeightKg(new BigDecimal("28000.000"));
ContainerBookingEntity realContainer = realContainer(41L, 21L, 31L);
OceanBookingEntity booking = new OceanBookingEntity();
booking.setId(21L); booking.setStatus("CONFIRMED"); booking.setWarehouseId(101L); booking.setChannelCode("SEA-US");
booking.setId(21L); booking.setStatus("RELEASED"); booking.setWarehouseId(101L); booking.setChannelCode("SEA-US");
OceanBookingContainerRequirementEntity requirement = new OceanBookingContainerRequirementEntity();
requirement.setId(31L); requirement.setQuantity(1); requirement.setContainerType("40HC"); requirement.setCapacityCbm(new BigDecimal("76.0000")); requirement.setMaxWeightKg(new BigDecimal("28000.000"));
OceanBookingContainerAllocationEntity allocation = new OceanBookingContainerAllocationEntity();
allocation.setOceanBookingId(21L); allocation.setRequirementId(31L); allocation.setContainerBookingId(41L);
when(repository.find(11L)).thenReturn(container);
when(repository.findForUpdate(41L)).thenReturn(realContainer);
when(repository.find(11L)).thenReturn(container, container);
when(oceanBookings.find(21L)).thenReturn(booking);
when(oceanBookings.requirement(21L, 31L)).thenReturn(requirement);
when(oceanBookings.allocationByContainer(41L)).thenReturn(allocation);
when(repository.consumeRealSource(41L, 11L, 9L, "admin")).thenReturn(1);
when(repository.mergeRealSource(11L, 41L, "MSCU7654321", 2, 9L, "admin")).thenReturn(1);
when(oceanBookings.reassignAllocation(41L, 11L)).thenReturn(1);
when(oceanBookings.allocations(21L)).thenReturn(List.of());
when(repository.linkReleasedBooking(eq(11L), eq(21L), eq(31L), eq("MSCU7654321"),
same(booking), eq(2), eq(9L), eq("admin"))).thenReturn(1);
ContainerBookingEntity result = service.linkOceanBooking(11L, 9L, "admin", new OceanBookingLinkRequest(41L, 2));
ContainerBookingEntity result = service.linkOceanBooking(11L, 9L, "admin",
new OceanBookingLinkRequest(21L, 31L, "MSCU7654321", 2));
assertThat(result).isSameAs(container);
assertThat(container.getCabinetNo()).isEqualTo("柜-001");
verify(repository, never()).hasActiveLoadingItems(11L);
verify(repository).consumeRealSource(41L, 11L, 9L, "admin");
verify(repository).mergeRealSource(11L, 41L, "MSCU7654321", 2, 9L, "admin");
verify(oceanBookings).reassignAllocation(41L, 11L);
verify(repository).linkReleasedBooking(eq(11L), eq(21L), eq(31L), eq("MSCU7654321"),
same(booking), eq(2), eq(9L), eq("admin"));
verify(oceanBookings).insert(any(OceanBookingContainerAllocationEntity.class));
}
@Test
@DisplayName("首次关联允许消费已放舱的真实订舱柜")
void linksReleasedBooking() {
@DisplayName("未发布订舱不可关联")
void rejectsDraftBooking() {
ContainerBookingEntity container = booking("WAIT_LOADING", 2);
container.setWarehouseId(101L); container.setChannelCode("SEA-US"); container.setContainerType("40HC");
container.setCapacityCbm(new BigDecimal("76.0000")); container.setMaxWeightKg(new BigDecimal("28000.000"));
ContainerBookingEntity realContainer = realContainer(41L, 21L, 31L);
OceanBookingEntity booking = new OceanBookingEntity();
booking.setId(21L); booking.setStatus("RELEASED"); booking.setWarehouseId(101L); booking.setChannelCode("SEA-US");
booking.setId(21L); booking.setStatus("DRAFT");
when(repository.find(11L)).thenReturn(container);
when(oceanBookings.find(21L)).thenReturn(booking);
assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin",
new OceanBookingLinkRequest(21L, 31L, "MSCU7654321", 2)))
.isInstanceOf(BizException.class).hasMessageContaining("已发布");
}
@Test
@DisplayName("订舱箱型需求达到数量上限时拒绝关联")
void rejectsRequirementOverAllocation() {
ContainerBookingEntity container = booking("WAIT_LOADING", 2);
OceanBookingEntity booking = new OceanBookingEntity();
booking.setId(21L); booking.setStatus("RELEASED");
OceanBookingContainerRequirementEntity requirement = new OceanBookingContainerRequirementEntity();
requirement.setId(31L); requirement.setContainerType("40HC");
requirement.setCapacityCbm(new BigDecimal("76.0000")); requirement.setMaxWeightKg(new BigDecimal("28000.000"));
requirement.setId(31L); requirement.setQuantity(1);
OceanBookingContainerAllocationEntity allocation = new OceanBookingContainerAllocationEntity();
allocation.setOceanBookingId(21L); allocation.setRequirementId(31L); allocation.setContainerBookingId(41L);
allocation.setRequirementId(31L);
when(repository.find(11L)).thenReturn(container);
when(repository.findForUpdate(41L)).thenReturn(realContainer);
when(oceanBookings.find(21L)).thenReturn(booking);
when(oceanBookings.requirement(21L, 31L)).thenReturn(requirement);
when(oceanBookings.allocationByContainer(41L)).thenReturn(allocation);
when(repository.consumeRealSource(41L, 11L, 9L, "admin")).thenReturn(1);
when(repository.mergeRealSource(11L, 41L, "MSCU7654321", 2, 9L, "admin")).thenReturn(1);
when(oceanBookings.reassignAllocation(41L, 11L)).thenReturn(1);
when(oceanBookings.allocations(21L)).thenReturn(List.of(allocation));
service.linkOceanBooking(11L, 9L, "admin", new OceanBookingLinkRequest(41L, 2));
verify(oceanBookings).reassignAllocation(41L, 11L);
assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin",
new OceanBookingLinkRequest(21L, 31L, "MSCU7654321", 2)))
.isInstanceOf(BizException.class).hasMessageContaining("数量上限");
verify(repository, never()).linkReleasedBooking(anyLong(), anyLong(), anyLong(), anyString(),
any(), anyInt(), anyLong(), anyString());
}
@Test
@DisplayName("已合并的真实订舱柜不可再次关联")
void rejectsConsumedRealContainer() {
ContainerBookingEntity container = booking("WAIT_LOADING", 2);
ContainerBookingEntity consumed = realContainer(41L, 21L, 31L);
consumed.setRecordStatus("MERGED"); consumed.setMergedIntoContainerBookingId(99L);
when(repository.find(11L)).thenReturn(container);
when(repository.findForUpdate(41L)).thenReturn(consumed);
@DisplayName("完全空的模拟柜直接解绑并删除当前allocation,不依赖MERGED来源柜")
void unlinksEmptySimulationDirectly() {
ContainerBookingEntity linked = booking("WAIT_LOADING", 2);
linked.setOceanBookingId(21L);
ContainerBookingEntity detached = booking("WAIT_LOADING", 3);
when(repository.find(11L)).thenReturn(linked, detached);
when(repository.unlinkReleasedBooking(11L, 2, 9L, "admin")).thenReturn(1);
assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin",
new OceanBookingLinkRequest(41L, 2)))
.isInstanceOf(BizException.class).hasMessageContaining("已被消费");
ContainerBookingEntity result = service.unlinkOceanBooking(11L, 9L, "admin",
new OceanBookingUnlinkRequest(2, "换绑"));
verifyNoInteractions(oceanBookings);
}
@Test
@DisplayName("已有装柜计划的真实订舱柜不可被模拟柜消费")
void rejectsRealContainerWithLoadingPlan() {
ContainerBookingEntity container = booking("WAIT_LOADING", 2);
ContainerBookingEntity realContainer = realContainer(41L, 21L, 31L);
when(repository.find(11L)).thenReturn(container);
when(repository.findForUpdate(41L)).thenReturn(realContainer);
when(repository.hasLoadingPlan(41L)).thenReturn(true);
assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin",
new OceanBookingLinkRequest(41L, 2)))
.isInstanceOf(BizException.class).hasMessageContaining("已有装柜计划");
verifyNoInteractions(oceanBookings);
assertThat(result).isSameAs(detached);
verify(repository).unlinkReleasedBooking(11L, 2, 9L, "admin");
verify(oceanBookings).deleteAllocation(11L);
}
@Test
@@ -379,21 +368,12 @@ class ContainerServiceTest {
when(repository.find(11L)).thenReturn(container);
when(repository.hasEvidence(11L)).thenReturn(true);
assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin", new OceanBookingLinkRequest(41L, 2)))
assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin",
new OceanBookingLinkRequest(21L, 31L, "MSCU7654321", 2)))
.isInstanceOf(BizException.class).hasMessageContaining("证据");
verifyNoInteractions(oceanBookings);
}
private static ContainerBookingEntity realContainer(long id, long oceanBookingId, long requirementId) {
ContainerBookingEntity value = new ContainerBookingEntity();
value.setId(id); value.setContainerNo("MSCU7654321"); value.setCabinetNo("CAB-REAL");
value.setSourceType("OCEAN_BOOKING"); value.setRecordStatus("ACTIVE");
value.setOceanBookingId(oceanBookingId); value.setOceanBookingRequirementId(requirementId);
value.setWarehouseId(101L); value.setChannelCode("SEA-US"); value.setContainerType("40HC");
value.setCapacityCbm(new BigDecimal("76.0000")); value.setMaxWeightKg(new BigDecimal("28000.000"));
return value;
}
private static ApiResponse<WarehouseMasterDataClient.Detail> activeWarehouse(long warehouseId,
String type,
String name) {