This commit is contained in:
2026-09-16 17:49:20 +08:00
parent 392f5b7386
commit 8488d4d989
10 changed files with 148 additions and 60 deletions
@@ -74,7 +74,7 @@ class LoadingListRepositoryTest {
}
@Test
void missingContainerNumberDoesNotBlockPreloadConfirmationOrPdaReadiness() {
void missingContainerNumberBlocksPreloadConfirmationAndPdaReadiness() {
AggregateRow row = new AggregateRow();
row.setPlanId(41L);
row.setContainerBookingId(61L);
@@ -90,12 +90,12 @@ class LoadingListRepositoryTest {
var pending = repository.preloadDetail(row).summary().readiness();
assertThat(pending.containerNoReady()).isFalse();
assertThat(pending.canConfirm()).isTrue();
assertThat(pending.blockingReasons()).doesNotContain("CONTAINER_NO_MISSING");
assertThat(pending.canConfirm()).isFalse();
assertThat(pending.blockingReasons()).contains("CONTAINER_NO_MISSING");
row.setConfirmedPreloadCount(2L);
var confirmed = repository.preloadDetail(row).summary().readiness();
assertThat(confirmed.readyForPda()).isTrue();
assertThat(confirmed.readyForPda()).isFalse();
}
@Test
@@ -158,30 +158,15 @@ class LoadingPickServiceTest {
}
@Test
void opensTaskAfterReleasedBookingWithoutWaitingForRealContainerNumber() {
void doesNotOpenTaskWithoutRealContainerNumber() {
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
booking.setId(40L);
var plan = new com.baiyue.firstleg.entity.LoadingPlanEntity();
plan.setId(10L); plan.setContainerBookingId(40L); plan.setPlanStatus("EDITING");
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(smartLoadingRepository.plan(10L)).thenReturn(plan);
when(loadingModelBridge.byLoadingPlan(10L)).thenReturn(List.of(
new Link(1L, 1L, 50L, 2L, "CONFIRMED", 1, 10L, 40L, null, "EDITING", 1, 1, null)));
when(oceanBookingRepository.allocationByContainer(40L)).thenReturn(allocation);
when(oceanBookingRepository.find(90L)).thenReturn(ocean);
when(repository.taskByPlan(10L)).thenReturn(null);
when(smartLoadingRepository.items(10L)).thenReturn(List.of());
doAnswer(invocation -> {
invocation.getArgument(0, LoadingPickTaskEntity.class).setId(20L);
return null;
}).when(repository).insert(any(LoadingPickTaskEntity.class));
service.beginIfReady(40L, 7L, "admin");
verify(repository).insert(any(LoadingPickTaskEntity.class));
verify(repository, never()).insert(any(LoadingPickTaskEntity.class));
verify(smartLoadingRepository, never()).planByBooking(40L);
}
@Test
@@ -222,11 +207,11 @@ class LoadingPickServiceTest {
}
@Test
void planConfirmationGateRequiresReleasedBookingButNotRealContainerNumber() {
void planConfirmationGateRequiresReleasedBookingAndRealContainerNumber() {
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.setId(40L); booking.setContainerNo("MSCU-001");
var allocation = new OceanBookingContainerAllocationEntity();
allocation.setOceanBookingId(90L);
var ocean = new OceanBookingEntity();
@@ -240,21 +225,17 @@ class LoadingPickServiceTest {
}
@Test
void planConfirmationGateAllowsMissingRealContainerNumber() {
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);
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);
assertThatThrownBy(() -> service.requirePreloadConfirmationReady(10L))
.isInstanceOf(BizException.class).hasMessageContaining("真实集装箱号");
verify(oceanBookingRepository, never()).allocationByContainer(40L);
}
@Test
@@ -9,6 +9,7 @@ import com.baiyue.firstleg.client.PendingReviewOrderClient;
import com.baiyue.firstleg.dto.SmartLoadingDtos.AssignRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.AssignInboundRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.AssignPendingReviewRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.AssignPalletRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.AdjustRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate;
import com.baiyue.firstleg.dto.SmartLoadingDtos.CandidateOrderStatus;
@@ -62,6 +63,7 @@ import java.util.List;
import java.util.Map;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
import static org.assertj.core.api.Assertions.assertThat;
@@ -76,6 +78,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -119,6 +122,11 @@ class SmartLoadingServiceTest {
when(fbaPricingService.recalculateUnlockedContainerCustomerFees(anyLong(), anyLong(), anyString()))
.thenReturn(new FbaPricingService.ContainerFeeRecalculationResult(
FbaPricingService.ContainerFeeRecalculationStatus.NOT_READY, 0));
when(dropshipPallets.physicalMetricsByPalletIds(any())).thenAnswer(invocation -> {
Collection<Long> palletIds = invocation.getArgument(0);
return palletIds.stream().map(palletId -> new DropshipPalletizationMapper.PalletMetrics(
palletId, 10, new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("5"))).toList();
});
}
@Test
@@ -181,6 +189,9 @@ class SmartLoadingServiceTest {
secondMember.setCustomerName("客户"); secondMember.setAvailablePackageCount(5);
when(dropshipPallets.palletInboundMembersByPalletIds(List.of(801L)))
.thenReturn(List.of(firstMember,secondMember));
doReturn(List.of(new DropshipPalletizationMapper.PalletMetrics(
801L, 11, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)))
.when(dropshipPallets).physicalMetricsByPalletIds(any());
Plan result=service.detail(1L);
@@ -887,6 +898,53 @@ class SmartLoadingServiceTest {
eq(99L), eq(9L), anyString());
}
@Test
void multiSkuPalletKeepsSkuLinesButCountsPhysicalCartonsAndVolumeOnce() {
AtomicInteger version = new AtomicInteger();
List<LoadingPlanItemEntity> saved = new ArrayList<>();
DropshipPalletizationMapper.PalletLine first = palletLine(801L, "DP-260916-0009", 20);
DropshipPalletizationMapper.PalletLine second = palletLine(801L, "DP-260916-0009", 20);
second.setInboundDetailId(21L);
when(repository.plan(1L)).thenAnswer(invocation -> plan("EDITING", version.get()));
when(repository.planByBooking(99L)).thenAnswer(invocation -> plan("EDITING", version.get()));
when(repository.booking(99L)).thenReturn(booking());
when(repository.items(1L)).thenAnswer(invocation -> List.copyOf(saved));
when(repository.activeItemsByPallet(801L)).thenReturn(List.of());
when(repository.activePalletItem(eq(1L), eq(801L), anyLong())).thenReturn(null);
when(repository.inbound(9L, 10L)).thenReturn(inbound(9L, 10L));
when(dropshipPallets.sealedPalletLines(801L)).thenReturn(List.of(first, second));
doReturn(List.of(
new DropshipPalletizationMapper.PalletMetrics(801L, 20, new BigDecimal("197"),
new BigDecimal("180"), new BigDecimal("3.365"))))
.when(dropshipPallets).physicalMetricsByPalletIds(any());
when(repository.touch(eq(1L), anyInt(), eq(7L), eq("u"))).thenAnswer(invocation -> {
int expected = invocation.getArgument(1);
if (expected != version.get()) return 0;
version.incrementAndGet();
return 1;
});
doAnswer(invocation -> {
LoadingPlanItemEntity item = invocation.getArgument(0);
item.setId(800L + saved.size());
saved.add(item);
return null;
}).when(repository).insert(any(LoadingPlanItemEntity.class));
Plan result = service.assignPallet(1L,
new AssignPalletRequest(801L, 0, "assign-multi-sku-pallet"), 7L, "u");
assertThat(saved).hasSize(2);
assertThat(saved).extracting(LoadingPlanItemEntity::getInboundDetailId)
.containsExactly(20L, 21L);
assertThat(saved).extracting(LoadingPlanItemEntity::getVolumeCbm)
.containsExactly(new BigDecimal("3.365000"), new BigDecimal("0.000000"));
assertThat(result.items()).singleElement().satisfies(item -> {
assertThat(item.getPackageCount()).isEqualTo(20);
assertThat(item.getGrossWeightKg()).isEqualByComparingTo("197");
assertThat(item.getVolumeCbm()).isEqualByComparingTo("3.365");
});
}
@Test
void recommendSkipsStalePalletCandidateInsteadOfFailingTheWholeRecommendation() {
Candidate dropship = candidate(9L, 10L, 20L, 30L, 10);