From 5c331f173d42a421e07bbc175f29065de13e3f32 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Mon, 24 Aug 2026 17:58:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inboundplan/pda/dto/PdaResponses.java | 4 +- .../service/InboundPdaWorkflowService.java | 52 +++++++++++++------ .../InboundPdaWorkflowServiceTest.java | 28 +++++----- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/PdaResponses.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/PdaResponses.java index 20fb0f557..268374c33 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/PdaResponses.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/PdaResponses.java @@ -12,7 +12,7 @@ public final class PdaResponses { List unloadPhotoFileIds, LocalDateTime unloadPhotoUploadedAt, LocalDateTime estimatedEmptyReturnAt, int expectedPackageCount, int pickedPackageCount, String orderType, boolean wholePalletInbound, - List dropshipPallets) { + List dropshipPallets, String inboundMode) { public Task(long taskId, String taskNo, String planNo, String status, LocalDateTime pushedAt, LocalDateTime startedAt, LocalDateTime completedAt, List unloadPhotoFileIds, LocalDateTime unloadPhotoUploadedAt, @@ -20,7 +20,7 @@ public final class PdaResponses { int pickedPackageCount) { this(taskId, taskNo, planNo, status, pushedAt, startedAt, completedAt, unloadPhotoFileIds, unloadPhotoUploadedAt, estimatedEmptyReturnAt, - expectedPackageCount, pickedPackageCount, null, false, List.of()); + expectedPackageCount, pickedPackageCount, null, false, List.of(), null); } public Task(long taskId, String taskNo, String planNo, String status, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java index 228017b3a..92d3be40d 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java @@ -146,7 +146,7 @@ public class InboundPdaWorkflowService { public PdaResponses.Operation count(String customerId, String planNo, long allocationId, PdaRequests.PackageOperation request) { InboundPlan plan = requirePlan(customerId, planNo); - requireLegacyCounting(plan); + requireCountMode(plan); InboundPdaOperation replay = replay(customerId, "COUNT", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Operation.class); InboundPdaTask task = requireActiveTask(customerId, plan); @@ -174,7 +174,7 @@ public class InboundPdaWorkflowService { public PdaResponses.Operation pick(String customerId, String planNo, long allocationId, PdaRequests.PackageOperation request) { InboundPlan plan = requirePlan(customerId, planNo); - requireLegacyCounting(plan); + requireCountMode(plan); InboundPdaOperation replay = replay(customerId, "PICK", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Operation.class); InboundPdaTask task = requireActiveTask(customerId, plan); @@ -250,6 +250,7 @@ public class InboundPdaWorkflowService { @Transactional public PdaResponses.Carton scanCarton(String customerId, String planNo, PdaRequests.ScanCarton request) { InboundPlan plan = requirePlan(customerId, planNo); + requireCartonScanMode(plan); InboundPdaOperation replay = replay(customerId, "SCAN_CARTON", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Carton.class); InboundPdaTask task = requireActiveTask(customerId, plan); @@ -271,22 +272,12 @@ public class InboundPdaWorkflowService { : requireLine(plan.getId(), carton.getLineId()); if (line == null) throw notFound("Carton mark does not belong to cargo in this inbound plan"); int requestedCount = request.packageCount() == null ? 1 : request.packageCount(); - if (requestedCount < 1) throw conflict("Package count must be positive"); + if (requestedCount != 1) throw conflict("Overseas warehouse carton scanning accepts exactly one carton per scan"); int alreadyScanned = (int) repository.scannedCartons(plan.getId(), line.getId()); if (alreadyScanned + requestedCount > value(line.getRequiredPackageCount())) { throw conflict("This inbound plan line has reached its required carton count"); } - List cartonsToScan = new ArrayList<>(); - cartonsToScan.add(carton); - if (requestedCount > 1) { - repository.cartons(plan.getId()).stream() - .filter(candidate -> !Objects.equals(candidate.getId(), carton.getId())) - .filter(candidate -> Objects.equals(candidate.getLineId(), line.getId())) - .filter(candidate -> "UNASSIGNED".equals(candidate.getStatus()) || "EXPECTED".equals(candidate.getStatus())) - .filter(candidate -> sameWarehouse(candidate, pallet)) - .limit(requestedCount - 1L).forEach(cartonsToScan::add); - if (cartonsToScan.size() != requestedCount) throw conflict("Not enough unscanned cartons for the requested quantity"); - } + List cartonsToScan = List.of(carton); InboundPlanPalletAllocation allocation = repository.lockAllocation(plan.getId(), line.getId(), pallet.getId()); if (allocation == null) { allocation = new InboundPlanPalletAllocation(); allocation.setId(IdWorker.getId()); allocation.setPlanId(plan.getId()); @@ -323,6 +314,7 @@ public class InboundPdaWorkflowService { @Transactional public PdaResponses.Carton removeCarton(String customerId, String planNo, PdaRequests.RemoveCarton request) { InboundPlan plan = requirePlan(customerId, planNo); + requireCartonScanMode(plan); InboundPdaOperation replay = replay(customerId, "REMOVE_CARTON", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Carton.class); InboundPdaTask task = requireActiveTask(customerId, plan); @@ -460,7 +452,7 @@ public class InboundPdaWorkflowService { } } List cartons = repository.cartons(plan.getId()); - if (!isDropship(plan) && !cartons.isEmpty() + if (mode(plan) == InboundMode.OWN_OVERSEAS && !cartons.isEmpty() && cartons.stream().anyMatch(carton -> !"ON_PALLET".equals(carton.getStatus()))) { throw conflict("All expected carton marks must be scanned onto a pallet before stock-in"); } @@ -676,6 +668,17 @@ public class InboundPdaWorkflowService { repository.update(plan); } + private void requireCountMode(InboundPlan plan) { + InboundMode mode = mode(plan); + if (mode == InboundMode.DROPSHIP) { + throw conflict("One-click dropship inbound must be picked by complete source pallet"); + } + if (mode == InboundMode.OWN_OVERSEAS) { + throw conflict("Overseas warehouse inbound must be operated by one-carton scanning"); + } + } + + /** Backward-compatible guard for pallet merge, which remains a legacy quantity workflow. */ private void requireLegacyCounting(InboundPlan plan) { if (isDropship(plan)) { throw conflict("One-click dropship inbound must be picked by complete source pallet"); @@ -685,6 +688,14 @@ public class InboundPdaWorkflowService { } } + private void requireCartonScanMode(InboundPlan plan) { + if (mode(plan) != InboundMode.OWN_OVERSEAS) { + throw conflict(mode(plan) == InboundMode.FBA + ? "FBA inbound must be operated by package quantity, not carton scanning" + : "One-click dropship inbound must be operated by complete source pallet"); + } + } + private boolean isNotAssigned(InboundPlanPallet pallet) { return pallet.getWarehousePalletId() == null || pallet.getWarehouseLocationId() == null || pallet.getWarehouseResourceLockId() == null @@ -768,7 +779,7 @@ public class InboundPdaWorkflowService { return new PdaResponses.Task(task.getId(), task.getTaskNo(), planNo, task.getStatus(), task.getPushedAt(), task.getStartedAt(), task.getCompletedAt(), unloadPhotoFileIds(task), task.getUnloadPhotoUploadedAt(), task.getEstimatedEmptyReturnAt(), expected, picked, - orderType, wholePalletInbound, dropshipPallets); + orderType, wholePalletInbound, dropshipPallets, mode(plan).name()); } private PdaResponses.Task task(InboundPdaTask task, String planNo) { @@ -801,6 +812,15 @@ public class InboundPdaWorkflowService { return order != null && "ONE_CLICK_DROPSHIP".equals(order.getOrderType()); } + private InboundMode mode(InboundPlan plan) { + if (isDropship(plan)) return InboundMode.DROPSHIP; + List lines = repository.lines(plan.getId()); + return lines.stream().anyMatch(line -> line.getFbaWarehouseCode() != null + && !line.getFbaWarehouseCode().isBlank()) ? InboundMode.FBA : InboundMode.OWN_OVERSEAS; + } + + private enum InboundMode { DROPSHIP, OWN_OVERSEAS, FBA } + private List unloadPhotoFileIds(InboundPdaTask task) { if (task.getUnloadPhotoFileIds() == null || task.getUnloadPhotoFileIds().isBlank()) return List.of(); try { diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java index 210cf2dba..023378307 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java @@ -167,27 +167,17 @@ class InboundPdaWorkflowServiceTest { } @Test - void cartonScanCanAutoSelectDestinationPalletAndBindAQuantity() { + void cartonScanRejectsBatchQuantityAndRequiresOneRealCartonPerScan() { InboundPlanCarton first = carton("BOX-BATCH-1", "UNASSIGNED", targetLine.getId(), "WH-A"); InboundPlanCarton second = carton("BOX-BATCH-2", "UNASSIGNED", targetLine.getId(), "WH-A"); second.setId(42L); activeTask(); when(repository.lockCarton("100", "BOX-BATCH-1")).thenReturn(first); - when(repository.pallets(plan.getId())).thenReturn(List.of(targetPallet)); - when(repository.lockPallet(plan.getId(), targetPallet.getId())).thenReturn(targetPallet); - when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine); - when(repository.cartons(plan.getId())).thenReturn(List.of(first, second)); - when(repository.lockAllocation(plan.getId(), targetLine.getId(), targetPallet.getId())).thenReturn(target); - var result = service.scanCarton("100", "LIP1", - new PdaRequests.ScanCarton("BOX-BATCH-1", null, 2, "scan-batch", "7", "worker")); - - assertEquals(targetPallet.getId(), result.planPalletId()); - assertEquals("PLT-21", result.palletNo()); - assertEquals(2, result.scannedPackageCount()); - assertEquals("ON_PALLET", first.getStatus()); - assertEquals("ON_PALLET", second.getStatus()); - assertEquals(2, target.getPickedPackageCount()); + assertThrows(ResponseStatusException.class, () -> service.scanCarton("100", "LIP1", + new PdaRequests.ScanCarton("BOX-BATCH-1", null, 2, "scan-batch", "7", "worker"))); + assertEquals("UNASSIGNED", first.getStatus()); + assertEquals("UNASSIGNED", second.getStatus()); } @Test @@ -226,7 +216,9 @@ class InboundPdaWorkflowServiceTest { @Test void countMustPrecedePickAndPickedBoxesDriveActualVolume() { + targetLine.setFbaWarehouseCode("ONT8"); activeTask(); + when(repository.lines(plan.getId())).thenReturn(List.of(targetLine)); target.setCountedPackageCount(0); when(repository.lockAllocation(plan.getId(), target.getId())).thenReturn(target); when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine); @@ -249,7 +241,9 @@ class InboundPdaWorkflowServiceTest { @Test void overPickReturnsConfirmationWithoutChangingAllocationOrPallet() { + targetLine.setFbaWarehouseCode("ONT8"); activeTask(); + when(repository.lines(plan.getId())).thenReturn(List.of(targetLine)); when(repository.lockAllocation(plan.getId(), target.getId())).thenReturn(target); when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine); candidateQueries(); @@ -546,6 +540,8 @@ class InboundPdaWorkflowServiceTest { @Test void rejectsReuseOfIdempotencyKeyWithDifferentActionOrPayload() { + targetLine.setFbaWarehouseCode("ONT8"); + when(repository.lines(plan.getId())).thenReturn(List.of(targetLine)); InboundPdaOperation existing = new InboundPdaOperation(); existing.setAction("COUNT"); existing.setRequestHash("different"); @@ -557,7 +553,9 @@ class InboundPdaWorkflowServiceTest { @Test void completedTaskStillReplaysAnAlreadyPersistedPick() { + targetLine.setFbaWarehouseCode("ONT8"); activeTask(); + when(repository.lines(plan.getId())).thenReturn(List.of(targetLine)); when(repository.lockAllocation(plan.getId(), target.getId())).thenReturn(target); when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine); when(repository.lockPallet(plan.getId(), targetPallet.getId())).thenReturn(targetPallet);