fix: keep dropship pallets atomic in smart loading
This commit is contained in:
@@ -273,15 +273,19 @@ public class SmartLoadingService {
|
||||
String goods = details.stream().map(Candidate::goodsName).filter(StringUtils::hasText)
|
||||
.map(String::trim).distinct().collect(java.util.stream.Collectors.joining("、"));
|
||||
List<PalletCandidate> pallets=dropshipPallets.palletCandidates(first.inboundId());
|
||||
if(!pallets.isEmpty()) return pallets.stream().map(pallet -> new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
if(!pallets.isEmpty()) return pallets.stream().map(pallet -> {
|
||||
String palletBlock = StringUtils.hasText(first.blockReason()) ? first.blockReason()
|
||||
: pallet.cartonCount() == null || pallet.cartonCount() <= 0 ? "托盘没有可加入的箱数" : null;
|
||||
return new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||
first.customerName(), goods, pallet.cartonCount(),
|
||||
pallet.cartonCount()==null||pallet.cartonCount()==0?null:pallet.weightKg().divide(BigDecimal.valueOf(pallet.cartonCount()),6,RoundingMode.HALF_UP),
|
||||
first.unitNetWeightKg(), pallet.cartonCount()==null||pallet.cartonCount()==0?null:pallet.volumeCbm().divide(BigDecimal.valueOf(pallet.cartonCount()),6,RoundingMode.HALF_UP),
|
||||
pallet.volumeCbm(),"SEALED_PALLET",pallet.palletId(),pallet.palletNo(),true,
|
||||
first.coLoadGroupId(), first.groupNo(), first.memberCount(),first.groupTotalBoxes(), first.locked(), canAssign(first), capabilityBlock(first),
|
||||
first.orderStatusSnapshot(),first.expectedArrivalAt(),List.of())).toList();
|
||||
first.coLoadGroupId(), first.groupNo(), first.memberCount(),first.groupTotalBoxes(), false, !StringUtils.hasText(palletBlock), palletBlock,
|
||||
first.orderStatusSnapshot(),first.expectedArrivalAt(),List.of());
|
||||
}).toList();
|
||||
return List.of(new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||
@@ -903,6 +907,7 @@ public class SmartLoadingService {
|
||||
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||
Set<Long> visitedGroups = new HashSet<>();
|
||||
Set<Long> visitedPallets = new HashSet<>();
|
||||
for (InboundCandidate candidate : recommendationCandidates(booking)) {
|
||||
if (candidate.coLoadGroupId() != null) {
|
||||
if (!visitedGroups.add(candidate.coLoadGroupId()) || StringUtils.hasText(candidate.blockReason())) {
|
||||
@@ -923,6 +928,18 @@ public class SmartLoadingService {
|
||||
}
|
||||
if (StringUtils.hasText(booking.getDestinationWarehouseCode())
|
||||
&& !Objects.equals(booking.getDestinationWarehouseCode(), candidate.destinationWarehouseCode())) continue;
|
||||
if ("SEALED_PALLET".equals(candidate.unitType())) {
|
||||
if (candidate.palletId() == null || !visitedPallets.add(candidate.palletId())
|
||||
|| !Boolean.TRUE.equals(candidate.canAssign())) continue;
|
||||
int fitting = fittingBoxes(booking, repository.items(planId), candidate.availablePackageCount(),
|
||||
candidate.averageUnitVolumeCbm(), candidate.unitGrossWeightKg());
|
||||
if (fitting != candidate.availablePackageCount()) continue;
|
||||
plan = repository.plan(planId);
|
||||
assignPallet(planId, new AssignPalletRequest(candidate.palletId(), plan.getVersion(),
|
||||
"REC-PAL-" + sha256(operation + '|' + candidate.palletId()).substring(0, 48)),
|
||||
operatorId, operatorName);
|
||||
continue;
|
||||
}
|
||||
int count = fittingBoxes(booking, repository.items(planId), candidate.availablePackageCount(),
|
||||
candidate.averageUnitVolumeCbm(), candidate.unitGrossWeightKg());
|
||||
if (count > 0) {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user