From febe265c50aa9168cc643e80d66078a00e690714 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 14 Sep 2026 15:01:05 +0800 Subject: [PATCH] fix: tolerate stale pallets in smart recommendation --- .../mapper/DropshipPalletizationMapper.java | 12 +++--- .../firstleg/service/SmartLoadingService.java | 6 +++ .../service/SmartLoadingServiceTest.java | 42 +++++++++++++++---- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/mapper/DropshipPalletizationMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/mapper/DropshipPalletizationMapper.java index f639861df..b591cf946 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/mapper/DropshipPalletizationMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/mapper/DropshipPalletizationMapper.java @@ -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 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; } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/SmartLoadingService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/SmartLoadingService.java index f66c5909e..1fdd914e2 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/SmartLoadingService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/SmartLoadingService.java @@ -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 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; diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/SmartLoadingServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/SmartLoadingServiceTest.java index 72432f40d..341c5a47b 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/SmartLoadingServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/SmartLoadingServiceTest.java @@ -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 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);