fix: tolerate stale pallets in smart recommendation
This commit is contained in:
@@ -221,18 +221,20 @@ public interface DropshipPalletizationMapper {
|
||||
SELECT p.id pallet_id,p.pallet_no,p.pallet_status,c.tenant_id account_id,c.inbound_id,o.order_id,o.order_no,
|
||||
o.inbound_code,o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,
|
||||
o.destination_code destination_warehouse_code,o.destination_name destination_warehouse_name,o.customer_name,
|
||||
s.inbound_detail_id,s.sku_code,MAX(s.cargo_name) cargo_name,COUNT(DISTINCT c.id) carton_count,
|
||||
SUM(s.actual_quantity) actual_sku_quantity,
|
||||
COALESCE(s.inbound_detail_id,c.inbound_detail_id) inbound_detail_id,
|
||||
COALESCE(s.sku_code,c.sku_code) sku_code,MAX(COALESCE(s.cargo_name,c.cargo_name)) cargo_name,
|
||||
COUNT(DISTINCT c.id) carton_count,SUM(COALESCE(s.actual_quantity,c.actual_sku_quantity,0)) actual_sku_quantity,
|
||||
COALESCE(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0),0) unit_gross_weight_kg,
|
||||
COALESCE(o.actual_net_weight_kg/NULLIF(o.total_package_count,0),0) unit_net_weight_kg,
|
||||
COALESCE(o.actual_volume_cbm/NULLIF(o.total_package_count,0),0) unit_volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
JOIN fl_dropship_pallet_carton_sku s ON s.carton_id=c.id
|
||||
LEFT JOIN fl_dropship_pallet_carton_sku s ON s.carton_id=c.id
|
||||
JOIN fl_inbound_order o ON o.id=c.inbound_id AND o.tenant_id=c.tenant_id WHERE p.id=#{palletId}
|
||||
GROUP BY p.id,p.pallet_no,p.pallet_status,c.tenant_id,c.inbound_id,o.order_id,o.order_no,o.inbound_code,
|
||||
o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,o.destination_code,o.destination_name,
|
||||
o.customer_name,s.inbound_detail_id,s.sku_code,o.actual_gross_weight_kg,o.actual_net_weight_kg,o.actual_volume_cbm,o.total_package_count
|
||||
ORDER BY s.inbound_detail_id
|
||||
o.customer_name,COALESCE(s.inbound_detail_id,c.inbound_detail_id),COALESCE(s.sku_code,c.sku_code),
|
||||
o.actual_gross_weight_kg,o.actual_net_weight_kg,o.actual_volume_cbm,o.total_package_count
|
||||
ORDER BY inbound_detail_id
|
||||
""") List<PalletLine> sealedPalletLines(@Param("palletId") long palletId);
|
||||
|
||||
@Getter @Setter class TaskRow { private Long id,tenantId,inboundId,warehouseId,assigneeId; private String inboundCode,customerName,warehouseName,taskStatus,assigneeName,processStatus,fulfillmentType; private Integer totalCartons,countedCartons,allocatedCartons,discrepancyCartons,expectedPalletCount,version; }
|
||||
|
||||
@@ -931,6 +931,12 @@ public class SmartLoadingService {
|
||||
if ("SEALED_PALLET".equals(candidate.unitType())) {
|
||||
if (candidate.palletId() == null || !visitedPallets.add(candidate.palletId())
|
||||
|| !Boolean.TRUE.equals(candidate.canAssign())) continue;
|
||||
List<com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletLine> currentPalletLines =
|
||||
dropshipPallets.sealedPalletLines(candidate.palletId());
|
||||
if (currentPalletLines.isEmpty()
|
||||
|| currentPalletLines.stream().anyMatch(line -> !"SEALED".equals(line.getPalletStatus()))) {
|
||||
continue;
|
||||
}
|
||||
int fitting = fittingBoxes(booking, repository.items(planId), candidate.availablePackageCount(),
|
||||
candidate.averageUnitVolumeCbm(), candidate.unitGrossWeightKg());
|
||||
if (fitting != candidate.availablePackageCount()) continue;
|
||||
|
||||
@@ -773,8 +773,8 @@ class SmartLoadingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void recommendAssignsDropshipCandidateThroughWholePalletCommand() {
|
||||
Candidate dropship = candidate(9L, 10L, 20L, 30L, 10);
|
||||
void recommendAssignsMultipleDropshipCandidatesThroughWholePalletCommands() {
|
||||
Candidate dropship = candidate(9L, 10L, 20L, 30L, 20);
|
||||
AtomicInteger version = new AtomicInteger();
|
||||
List<LoadingPlanItemEntity> saved = new ArrayList<>();
|
||||
InboundOrderEntity inbound = inbound(9L, 10L);
|
||||
@@ -785,13 +785,17 @@ class SmartLoadingServiceTest {
|
||||
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(repository.activeItemsByPallet(anyLong())).thenReturn(List.of());
|
||||
when(repository.activePalletItem(eq(1L), anyLong(), eq(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")),
|
||||
new PalletCandidate(802L, "DP-260914-0022", "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(dropshipPallets.sealedPalletLines(802L)).thenReturn(List.of(
|
||||
palletLine(802L, "DP-260914-0022", 10)));
|
||||
when(repository.touch(eq(1L), anyInt(), eq(7L), eq("u"))).thenAnswer(invocation -> {
|
||||
int expected = invocation.getArgument(1);
|
||||
if (expected != version.get()) return 0;
|
||||
@@ -808,17 +812,39 @@ class SmartLoadingServiceTest {
|
||||
Plan result = service.recommend(1L,
|
||||
new RecommendRequest(0, "recommend-dropship-pallet"), 7L, "u");
|
||||
|
||||
assertThat(result.version()).isEqualTo(1);
|
||||
assertThat(saved).singleElement().satisfies(item -> {
|
||||
assertThat(result.version()).isEqualTo(2);
|
||||
assertThat(saved).hasSize(2).allSatisfy(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();
|
||||
});
|
||||
assertThat(saved).extracting(LoadingPlanItemEntity::getPalletId,
|
||||
LoadingPlanItemEntity::getPalletNo).containsExactly(
|
||||
org.assertj.core.groups.Tuple.tuple(801L, "DP-260914-0021"),
|
||||
org.assertj.core.groups.Tuple.tuple(802L, "DP-260914-0022"));
|
||||
verify(repository, never()).candidate(anyLong(), anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void recommendSkipsStalePalletCandidateInsteadOfFailingTheWholeRecommendation() {
|
||||
Candidate dropship = candidate(9L, 10L, 20L, 30L, 10);
|
||||
when(repository.plan(1L)).thenReturn(plan("EDITING", 0));
|
||||
when(repository.planByBooking(99L)).thenReturn(plan("EDITING", 0));
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of());
|
||||
when(repository.candidates(null)).thenReturn(List.of(dropship));
|
||||
when(dropshipPallets.palletCandidates(10L)).thenReturn(List.of(
|
||||
new PalletCandidate(801L, "DP-STALE", "SEALED_PALLET", true,
|
||||
10, 1, new BigDecimal("20"), new BigDecimal("5"))));
|
||||
when(dropshipPallets.sealedPalletLines(801L)).thenReturn(List.of());
|
||||
|
||||
Plan result = service.recommend(1L,
|
||||
new RecommendRequest(0, "recommend-stale-pallet"), 7L, "u");
|
||||
|
||||
assertThat(result.items()).isEmpty();
|
||||
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void recommendRechecksCurrentCapacitySkipsStaleGroupAndContinuesWithLaterCandidate() {
|
||||
Candidate ordinary = candidate(9L, 10L, 20L, 30L, 4);
|
||||
|
||||
Reference in New Issue
Block a user