Merge branch 'dev-lyhfive' of http://git.buggoglobal.com/java-project/baiyue-firstleg into dev
This commit is contained in:
+111
@@ -12,6 +12,8 @@ import com.baiyue.lastleg.inboundplan.pda.entity.InboundPdaTask;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.UnloadPhotoFileGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.repository.PdaWorkflowRepository;
|
||||
import com.baiyue.lastleg.dropship.service.DropshipOrderService;
|
||||
import com.baiyue.lastleg.dropship.model.DropshipPalletSnapshot;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -51,6 +53,7 @@ class InboundPdaWorkflowServiceTest {
|
||||
@Mock InboundUnloadCompletedOutboxService unloadCompletedOutbox;
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock UnloadPhotoFileGateway unloadPhotoFiles;
|
||||
@Mock DropshipOrderService dropshipOrders;
|
||||
private InboundPdaWorkflowService service;
|
||||
|
||||
private InboundPlan plan;
|
||||
@@ -73,6 +76,7 @@ class InboundPdaWorkflowServiceTest {
|
||||
ReflectionTestUtils.setField(service, "unloadCompletedOutbox", unloadCompletedOutbox);
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "unloadPhotoFiles", unloadPhotoFiles);
|
||||
ReflectionTestUtils.setField(service, "dropshipOrders", dropshipOrders);
|
||||
ReflectionTestUtils.setField(service, "sourceObjectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
service.initializeObjectMapper();
|
||||
plan = plan();
|
||||
@@ -189,6 +193,20 @@ class InboundPdaWorkflowServiceTest {
|
||||
assertEquals(3, targetPallet.getActualPackageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
|
||||
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
|
||||
void cartonScanRejectsDuplicateAndWrongDestination() {
|
||||
InboundPlanCarton repeated = carton("BOX-002", "ON_PALLET", targetLine.getId(), "WH-A");
|
||||
@@ -225,7 +243,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);
|
||||
@@ -248,7 +268,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();
|
||||
@@ -459,8 +481,95 @@ class InboundPdaWorkflowServiceTest {
|
||||
verify(repository, never()).update(task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialCompleteStocksOnlyActualPickedPackagesAndKeepsPartialStatus() {
|
||||
target.setPickedPackageCount(2);
|
||||
activeTask();
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
when(repository.allocations(plan.getId())).thenReturn(List.of(target));
|
||||
when(repository.pallets(plan.getId())).thenReturn(List.of(targetPallet));
|
||||
when(repository.cartons(plan.getId())).thenReturn(List.of());
|
||||
|
||||
var result = service.partialComplete("100", "LIP1",
|
||||
new PdaRequests.ForceComplete("partial-1", "7", "worker"));
|
||||
|
||||
assertEquals("PARTIAL_INBOUND", result.status());
|
||||
assertEquals(5, result.expectedPackageCount());
|
||||
assertEquals(2, result.pickedPackageCount());
|
||||
assertEquals("PARTIAL_INBOUND", plan.getStatus());
|
||||
verify(stockReceipts).stockIn(eq("100"), eq(plan), eq(task), eq(List.of(targetLine)),
|
||||
eq(List.of(target)), eq(List.of(targetPallet)), any(), eq(true));
|
||||
verify(unloadCompletedOutbox).publish(eq(plan), eq(task), eq(List.of(101L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialCompleteRequiresUnloadPhotosBeforeStockIn() {
|
||||
target.setPickedPackageCount(2);
|
||||
task.setUnloadPhotoFileIds(null);
|
||||
task.setUnloadPhotoUploadedAt(null);
|
||||
task.setEstimatedEmptyReturnAt(null);
|
||||
activeTask();
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.partialComplete("100", "LIP1",
|
||||
new PdaRequests.ForceComplete("partial-no-photo", "7", "worker")));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(stockReceipts, never()).stockIn(any(), any(), any(), any(), any(), any(), any(), eq(true));
|
||||
verify(unloadCompletedOutbox, never()).publish(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropshipTaskIsMarkedAndWholePalletPickReceivesThePlannedPalletAtOnce() {
|
||||
plan.setSourceOrderId(101L);
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setId(101L); order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(repository.order(101L)).thenReturn(order);
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(
|
||||
new DropshipPalletSnapshot("PLT-21", 3,
|
||||
List.of(new DropshipPalletSnapshot.Sku("SKU-A", "Product A", 3, 30)))));
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
when(repository.allocations(plan.getId())).thenReturn(List.of(target));
|
||||
when(repository.pallets(plan.getId())).thenReturn(List.of(targetPallet));
|
||||
when(repository.task("100", plan.getId())).thenReturn(task);
|
||||
activeTask();
|
||||
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);
|
||||
|
||||
var taskResult = service.task("100", "LIP1");
|
||||
var pickResult = service.pickWholePallet("100", "LIP1", target.getId(),
|
||||
new PdaRequests.WholePalletOperation("whole-1", "7", "worker"));
|
||||
|
||||
assertEquals("ONE_CLICK_DROPSHIP", taskResult.orderType());
|
||||
assertTrue(taskResult.wholePalletInbound());
|
||||
assertEquals("SKU-A", taskResult.dropshipPallets().get(0).skus().get(0).skuCode());
|
||||
assertEquals(targetPallet.getId(), taskResult.dropshipPallets().get(0).planPalletId());
|
||||
assertEquals(target.getId(), taskResult.dropshipPallets().get(0).allocationId());
|
||||
assertEquals("WHOLE_PALLET_PICKED", pickResult.result());
|
||||
assertEquals(3, target.getPickedPackageCount());
|
||||
assertEquals(3, targetPallet.getActualPackageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropshipPlanRejectsOrdinaryPackagePicking() {
|
||||
plan.setSourceOrderId(101L);
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setId(101L); order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(repository.order(101L)).thenReturn(order);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.pick("100", "LIP1", target.getId(),
|
||||
new PdaRequests.PackageOperation(1, "wrong-mode", "7", "worker")));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(repository, never()).lockAllocation(plan.getId(), target.getId());
|
||||
}
|
||||
|
||||
@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");
|
||||
@@ -472,7 +581,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);
|
||||
|
||||
+56
@@ -19,6 +19,7 @@ import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.dropship.service.DropshipOrderService;
|
||||
import com.baiyue.lastleg.dropship.model.DropshipPalletSnapshot;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -157,6 +158,21 @@ class InboundStockReceiptServiceTest {
|
||||
assertEquals(5L, requests.getAllValues().stream().mapToLong(InventoryCommandRequests.StockIn::packages).sum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stocksInToHeldInventoryWhenInboundOrderIsDetained() {
|
||||
Fixture fixture = fixture(5, 5, 5, 10);
|
||||
fixture.order.setDetained(true);
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(inventory.stockInHeld(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
|
||||
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 5, 0, 0, 0, false, false));
|
||||
|
||||
service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
|
||||
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now());
|
||||
|
||||
verify(inventory).stockInHeld(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class));
|
||||
verify(inventory, never()).stockIn(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void existingTaskReceiptMakesStockInReplayANoOp() {
|
||||
Fixture fixture = fixture(10, 5, 5, 10);
|
||||
@@ -200,6 +216,7 @@ class InboundStockReceiptServiceTest {
|
||||
void activatesDropshipAfterGenericStockInReceiptIsCreated() {
|
||||
Fixture fixture = fixture(5, 5, 5, 10);
|
||||
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(dropshipPallet("PLT-21", 5)));
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(inventory.stockIn(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
|
||||
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 5, 0, 0, 0, false, false));
|
||||
@@ -227,6 +244,45 @@ class InboundStockReceiptServiceTest {
|
||||
verify(receipts, never()).insert(any(InboundStockReceipt.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialStockInSynchronizesOrderWithoutActivatingDropship() {
|
||||
Fixture fixture = fixture(5, 2, 2, 10);
|
||||
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(dropshipPallet("PLT-21", 2)));
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(inventory.stockIn(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
|
||||
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 2, 0, 0, 0, false, false));
|
||||
|
||||
service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
|
||||
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now(), true);
|
||||
|
||||
assertEquals("INBOUND_COMPLETED", fixture.order.getCurrentStatus());
|
||||
assertEquals("INBOUND_PLAN:PARTIAL_INBOUND", fixture.order.getSubOrderStatus());
|
||||
verify(orders).updateById(fixture.order);
|
||||
verify(orderHistories).insert(any(LastlegOrderStatusHistory.class));
|
||||
verify(dropshipOrders, never()).activateAfterStockIn(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsPartiallyPickedDropshipSourcePallet() {
|
||||
Fixture fixture = fixture(5, 5, 2, 10);
|
||||
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(dropshipPallet("PLT-21", 5)));
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
|
||||
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now(), true));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(inventory, never()).stockIn(any(), any(), any());
|
||||
}
|
||||
|
||||
private DropshipPalletSnapshot dropshipPallet(String palletNo, long packages) {
|
||||
return new DropshipPalletSnapshot(palletNo, packages,
|
||||
List.of(new DropshipPalletSnapshot.Sku("SKU-1", "Product", packages, packages * 10)));
|
||||
}
|
||||
|
||||
private Fixture fixture(int required, int planned, int picked, int quantity) {
|
||||
InboundPlan plan = new InboundPlan(); plan.setId(1L); plan.setPlanNo("IP-1"); plan.setOverseasWarehouseId(900L);
|
||||
InboundPdaTask task = new InboundPdaTask(); task.setId(2L); task.setOperatorId("operator");
|
||||
|
||||
+14
@@ -502,6 +502,20 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
verifyNoInteractions(workflow, operations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cartonResolutionReturnsInboundAndPalletMergeReminderFromTailLegOrder() {
|
||||
stubCurrent("FBA", "CONT-A");
|
||||
LastlegOrder marked = order("FBA");
|
||||
marked.setPalletMergeFlag("REQUIRED");
|
||||
when(orders.selectById(1L)).thenReturn(marked);
|
||||
|
||||
var resolved = service.resolveCarton("7", new ReceivingContainerRequests.CartonResolve("IN-1#2-1"));
|
||||
|
||||
assertThat(resolved.inboundCode()).isEqualTo("IN-1");
|
||||
assertThat(resolved.palletMergeRequired()).isTrue();
|
||||
assertThat(resolved.palletMergeHint()).contains("合托");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fbaCartonResolutionRejectsCrossContainerAndInvalidPrefixesAsConflicts() {
|
||||
stubCurrent("FBA", "CONT-A");
|
||||
|
||||
Reference in New Issue
Block a user