feat: close preload confirmation and loading flow

This commit is contained in:
2026-08-15 17:04:00 +08:00
parent 5b8e7d6989
commit a35794e918
34 changed files with 1194 additions and 512 deletions
@@ -19,6 +19,8 @@ import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper;
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
import com.baiyue.firstleg.mapper.InboundStatusHistoryMapper;
import com.baiyue.firstleg.mapper.LoadingPickItemMapper;
import com.baiyue.firstleg.repository.LoadingPickRepository;
@@ -38,6 +40,7 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -55,6 +58,8 @@ class LoadingPickServiceTest {
@MockBean
private OceanBookingRepository oceanBookingRepository;
@MockBean
private LoadingModelBridgeService loadingModelBridge;
@MockBean
private LoadingListService loadingListService;
@MockBean
private InboundStatusHistoryMapper inboundHistories;
@@ -77,6 +82,8 @@ class LoadingPickServiceTest {
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
when(smartLoadingRepository.planByBooking(40L)).thenReturn(plan);
when(smartLoadingRepository.plan(10L)).thenReturn(plan);
when(loadingModelBridge.byLoadingPlan(10L)).thenReturn(List.of(
new Link(1L, 1L, 50L, 2L, "CONFIRMED", 1, 10L, 40L, "MSCU-001", "EDITING", 1, 1, null)));
when(oceanBookingRepository.allocationByContainer(40L)).thenReturn(allocation);
when(oceanBookingRepository.find(90L)).thenReturn(ocean);
when(repository.taskByPlan(10L)).thenReturn(null);
@@ -102,6 +109,8 @@ class LoadingPickServiceTest {
var ocean = new OceanBookingEntity(); ocean.setId(90L); ocean.setStatus("CONFIRMED");
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
when(smartLoadingRepository.planByBooking(40L)).thenReturn(plan);
when(loadingModelBridge.byLoadingPlan(10L)).thenReturn(List.of(
new Link(1L, 1L, 50L, 2L, "CONFIRMED", 1, 10L, 40L, "MSCU-001", "EDITING", 1, 1, null)));
when(oceanBookingRepository.allocationByContainer(40L)).thenReturn(allocation);
when(oceanBookingRepository.find(90L)).thenReturn(ocean);
@@ -110,6 +119,93 @@ class LoadingPickServiceTest {
verify(repository, never()).insert(any(LoadingPickTaskEntity.class));
}
@Test
void doesNotOpenTaskBeforeEveryLinkedPreloadIsConfirmed() {
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
booking.setId(40L); booking.setContainerNo("MSCU-001");
var plan = new com.baiyue.firstleg.entity.LoadingPlanEntity();
plan.setId(10L); plan.setContainerBookingId(40L); plan.setPlanStatus("EDITING");
plan.setPreloadPushedAt(java.time.LocalDateTime.now());
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
when(smartLoadingRepository.planByBooking(40L)).thenReturn(plan);
when(loadingModelBridge.byLoadingPlan(10L)).thenReturn(List.of(
new Link(1L, 1L, 50L, 2L, "PENDING", 1, 10L, 40L, "MSCU-001", "EDITING", 1, 1, null)));
service.beginIfReady(40L, 7L, "admin");
verify(repository, never()).insert(any(LoadingPickTaskEntity.class));
verify(oceanBookingRepository, never()).allocationByContainer(40L);
}
@Test
void planConfirmationGateRequiresRealContainerAndReleasedBooking() {
Link link = new Link(1L, 1L, 50L, 2L, "PENDING", 1,
10L, 40L, "MSCU-001", "EDITING", 3, 2, null);
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
booking.setId(40L); booking.setContainerNo("MSCU-001");
var allocation = new OceanBookingContainerAllocationEntity();
allocation.setOceanBookingId(90L);
var ocean = new OceanBookingEntity();
ocean.setId(90L); ocean.setStatus("RELEASED");
when(loadingModelBridge.requireAllocatedLinksForConfirmation(10L)).thenReturn(List.of(link));
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
when(oceanBookingRepository.allocationByContainer(40L)).thenReturn(allocation);
when(oceanBookingRepository.find(90L)).thenReturn(ocean);
assertThat(service.requirePreloadConfirmationReady(10L)).containsExactly(link);
}
@Test
void planConfirmationGateRejectsMissingRealContainerNumber() {
Link link = new Link(1L, 1L, 50L, 2L, "PENDING", 1,
10L, 40L, null, "EDITING", 3, 2, null);
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
booking.setId(40L);
when(loadingModelBridge.requireAllocatedLinksForConfirmation(10L)).thenReturn(List.of(link));
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
assertThatThrownBy(() -> service.requirePreloadConfirmationReady(10L))
.isInstanceOf(BizException.class).hasMessageContaining("真实集装箱号");
verify(oceanBookingRepository, never()).allocationByContainer(anyLong());
}
@Test
void newKeyCannotConfirmAnAlreadyConfirmedPlanAgain() {
Link confirmed = new Link(1L, 1L, 50L, 2L, "CONFIRMED", 2,
10L, 40L, "MSCU-001", "EDITING", 4, 2, null);
when(loadingModelBridge.requireAllocatedLinksForConfirmation(10L))
.thenReturn(List.of(confirmed));
assertThatThrownBy(() -> service.requirePreloadConfirmationReady(10L))
.isInstanceOf(BizException.class).hasMessageContaining("已确认");
verify(smartLoadingRepository, never()).booking(anyLong());
}
@Test
void readinessCreatesAtMostOneTask() {
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
booking.setId(40L); booking.setContainerNo("MSCU-001");
var plan = new com.baiyue.firstleg.entity.LoadingPlanEntity();
plan.setId(10L); plan.setContainerBookingId(40L); plan.setPlanStatus("EDITING");
plan.setPreloadPushedAt(java.time.LocalDateTime.now());
var allocation = new OceanBookingContainerAllocationEntity(); allocation.setOceanBookingId(90L);
var ocean = new OceanBookingEntity(); ocean.setId(90L); ocean.setStatus("RELEASED");
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
when(smartLoadingRepository.planByBooking(40L)).thenReturn(plan);
when(loadingModelBridge.byLoadingPlan(10L)).thenReturn(List.of(
new Link(1L, 1L, 50L, 2L, "CONFIRMED", 1, 10L, 40L, "MSCU-001", "EDITING", 1, 1, null)));
when(oceanBookingRepository.allocationByContainer(40L)).thenReturn(allocation);
when(oceanBookingRepository.find(90L)).thenReturn(ocean);
when(repository.taskByPlan(10L)).thenReturn(new LoadingPickTaskEntity());
service.beginIfReady(40L, 7L, "admin");
verify(repository, never()).insert(any(LoadingPickTaskEntity.class));
verify(smartLoadingRepository, never()).startPicking(
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(),
org.mockito.ArgumentMatchers.anyString());
}
@Test
void stableCabinetAndPlanQueriesReturnSameTaskWithCurrentContainerNumber() {
LoadingPickTaskEntity task = task();