fix: keep dropship pallets atomic in smart loading

This commit is contained in:
2026-09-14 14:35:29 +08:00
parent de9516d19e
commit 1d258ab27b
3 changed files with 88 additions and 6 deletions
@@ -227,8 +227,8 @@ class SmartLoadingServiceTest {
@Test
void pagesEachAvailableDropshipPalletAsAnIndependentCandidate() {
Candidate dropship = withChannel(candidate(9L, 10L, 20L, 30L, 12),
"EXX_TRUCK", "EXX海卡");
Candidate dropship = withStatus(withChannel(candidate(9L, 10L, 20L, 30L, 12),
"EXX_TRUCK", "EXX海卡"), "LOADING", true);
InboundCandidateQuery query = new InboundCandidateQuery(
List.of("EXX"), null, null, "EXXHWK2609140003", null, null,
null, null, List.of(CandidateOrderStatus.WAIT_SPLIT), 1, 20);
@@ -255,6 +255,9 @@ class SmartLoadingServiceTest {
assertThat(result.records()).allSatisfy(candidate -> {
assertThat(candidate.unitType()).isEqualTo("SEALED_PALLET");
assertThat(candidate.indivisible()).isTrue();
assertThat(candidate.locked()).isFalse();
assertThat(candidate.canAssign()).isTrue();
assertThat(candidate.blockReason()).isNull();
});
}
@@ -769,6 +772,53 @@ class SmartLoadingServiceTest {
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
}
@Test
void recommendAssignsDropshipCandidateThroughWholePalletCommand() {
Candidate dropship = candidate(9L, 10L, 20L, 30L, 10);
AtomicInteger version = new AtomicInteger();
List<LoadingPlanItemEntity> saved = new ArrayList<>();
InboundOrderEntity inbound = inbound(9L, 10L);
inbound.setFulfillmentType("ONE_CLICK_DROPSHIP");
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.candidates(null)).thenReturn(List.of(dropship));
when(repository.inbound(9L, 10L)).thenReturn(inbound);
when(repository.activeItemsByPallet(801L)).thenReturn(List.of());
when(repository.activePalletItem(1L, 801L, 20L)).thenReturn(null);
when(dropshipPallets.palletCandidates(10L)).thenReturn(List.of(
new PalletCandidate(801L, "DP-260914-0021", "SEALED_PALLET", true,
10, 1, new BigDecimal("20"), new BigDecimal("5"))));
when(dropshipPallets.sealedPalletLines(801L)).thenReturn(List.of(
palletLine(801L, "DP-260914-0021", 10)));
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.recommend(1L,
new RecommendRequest(0, "recommend-dropship-pallet"), 7L, "u");
assertThat(result.version()).isEqualTo(1);
assertThat(saved).singleElement().satisfies(item -> {
assertThat(item.getUnitType()).isEqualTo("SEALED_PALLET");
assertThat(item.getPalletId()).isEqualTo(801L);
assertThat(item.getPalletNo()).isEqualTo("DP-260914-0021");
assertThat(item.getPackageCount()).isEqualTo(10);
assertThat(item.getIndivisible()).isTrue();
});
verify(repository, never()).candidate(anyLong(), anyLong(), anyLong());
}
@Test
void recommendRechecksCurrentCapacitySkipsStaleGroupAndContinuesWithLaterCandidate() {
Candidate ordinary = candidate(9L, 10L, 20L, 30L, 4);
@@ -1742,6 +1792,21 @@ class SmartLoadingServiceTest {
value.orderStatusSnapshot(), value.expectedArrivalAt());
}
private static DropshipPalletizationMapper.PalletLine palletLine(long palletId, String palletNo,
int cartonCount) {
DropshipPalletizationMapper.PalletLine value = new DropshipPalletizationMapper.PalletLine();
value.setPalletId(palletId); value.setPalletNo(palletNo); value.setPalletStatus("SEALED");
value.setAccountId(9L); value.setInboundId(10L); value.setInboundDetailId(20L); value.setOrderId(30L);
value.setOrderNo("O30"); value.setInboundCode("I10"); value.setWarehouseId(100L);
value.setChannelCode("MATSON_CLX_TRUCK"); value.setChannelName("美森正班海卡");
value.setDestinationWarehouseId(200L); value.setDestinationWarehouseCode("FBA1");
value.setDestinationWarehouseName("FBA仓"); value.setCustomerName("客户"); value.setCargoName("货物");
value.setCartonCount(cartonCount); value.setActualSkuQuantity(cartonCount);
value.setUnitGrossWeightKg(new BigDecimal("2")); value.setUnitNetWeightKg(new BigDecimal("1.5"));
value.setUnitVolumeCbm(new BigDecimal("0.5"));
return value;
}
private static Candidate withWarehouse(Candidate value, long warehouseId) {
return new Candidate(value.accountId(), value.inboundId(), value.inboundDetailId(), value.orderId(),
value.orderNo(), value.inboundCode(), warehouseId, value.channelCode(), value.channelName(),