贴标申请,仓储转运
This commit is contained in:
+40
-92
@@ -67,6 +67,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -209,105 +210,52 @@ class DeliveryTransferServiceTest {
|
||||
|
||||
var candidates = service.returnOutboundCandidates("TENANT-A", "WH-1");
|
||||
|
||||
assertEquals(List.of("RC-PAL"), candidates.stream().map(DeliveryResponses.ReturnOutboundCandidate::cartonMark).toList());
|
||||
assertEquals(List.of("RC-PAL", "RC-PARCEL"), candidates.stream().map(DeliveryResponses.ReturnOutboundCandidate::cartonMark).toList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
void directPartialReturnOutboundRequiresPackingBeforeAnyInventoryLock() {
|
||||
for (var request : List.of(
|
||||
new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1", List.of("RP-1"), null, "partial-sku", "SKU-A", "BOX", 1L),
|
||||
new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1", List.of("BOX-1"), null, "partial-box", null, "BOX", null,
|
||||
List.of(new DeliveryRequests.ReturnSkuOutboundItem("BOX-1", "SKU-A", 1L))))) {
|
||||
var error = assertThrows(ResponseStatusException.class, () -> service.createReturnOutbound("TENANT-A", "U-1", request));
|
||||
assertTrue(error.getReason().contains("先合箱"));
|
||||
}
|
||||
verify(returnCartons, never()).lockMark(anyString());
|
||||
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleSkuReturnOutboundReservesParcelAndCreatesDelivery() {
|
||||
ReturnInventoryCarton parcel = returnCarton("RC-PARCEL", null);
|
||||
parcel.setId(901L); parcel.setSourceUnitType("PARCEL"); parcel.setTotalQuantity(3L);
|
||||
ReturnInventoryCartonItem line = new ReturnInventoryCartonItem();
|
||||
line.setSkuCode("SKU-A"); line.setQuantity(3L);
|
||||
when(returnCartons.lockMark("RC-PARCEL")).thenReturn(parcel);
|
||||
when(returnCartonItems.lockCarton(901L)).thenReturn(List.of(line));
|
||||
|
||||
service.createReturnOutbound("TENANT-A", "U-1",
|
||||
new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1",
|
||||
List.of("RC-PARCEL"), null, "sku-return-1", "SKU-A"));
|
||||
|
||||
ArgumentCaptor<OutboundPalletItem> item = ArgumentCaptor.forClass(OutboundPalletItem.class);
|
||||
ArgumentCaptor<DeliveryOrder> delivery = ArgumentCaptor.forClass(DeliveryOrder.class);
|
||||
verify(outboundPallets).insert(item.capture());
|
||||
verify(deliveryOrders).insert(delivery.capture());
|
||||
assertEquals("SKU-A", item.getValue().getSkuCode());
|
||||
assertEquals("RC-PARCEL", item.getValue().getCartonMarks());
|
||||
void wholeMultiSkuParcelCreatesBarcodePickWithoutSkuQuantity() {
|
||||
var parcel = returnCarton("RP-1", null); parcel.setId(901L); parcel.setSourceUnitType("PARCEL"); parcel.setTotalQuantity(5L);
|
||||
var a = new ReturnInventoryCartonItem(); a.setSkuCode("SKU-A"); a.setQuantity(3L);
|
||||
var b = new ReturnInventoryCartonItem(); b.setSkuCode("SKU-B"); b.setQuantity(2L);
|
||||
when(returnCartons.lockMark("RP-1")).thenReturn(parcel);
|
||||
when(returnCartonItems.lockCarton(901L)).thenReturn(List.of(a,b));
|
||||
service.createReturnOutbound("TENANT-A", "U-1", new DeliveryRequests.ReturnOutbound("receiver","WH-1","RT-1",List.of("RP-1"),null,"whole-package",null,"BOX"));
|
||||
var capture=ArgumentCaptor.forClass(OutboundPalletItem.class); verify(outboundPallets).insert(capture.capture());
|
||||
assertEquals("RP-1", capture.getValue().getCartonMarks());
|
||||
assertNull(capture.getValue().getSkuCode()); assertNull(capture.getValue().getReturnSkuQuantity());
|
||||
assertEquals("OUTBOUND_PENDING", parcel.getStatus());
|
||||
assertEquals("WAIT_DELIVERY", delivery.getValue().getStatus());
|
||||
assertEquals(1, delivery.getValue().getPackageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialSkuOutboundDistributesSixPiecesAcrossTwoFivePieceSources() {
|
||||
var first = returnCarton("RC-1", null); first.setId(901L); first.setTotalQuantity(5L);
|
||||
var second = returnCarton("RC-2", null); second.setId(902L); second.setTotalQuantity(5L);
|
||||
first.setSourceUnitType("PARCEL"); second.setSourceUnitType("PARCEL");
|
||||
when(returnCartons.lockMark("RC-1")).thenReturn(first); when(returnCartons.lockMark("RC-2")).thenReturn(second);
|
||||
var line = new ReturnInventoryCartonItem(); line.setSkuCode("SKU-A"); line.setQuantity(5L);
|
||||
when(returnCartonItems.lockCarton(901L)).thenReturn(List.of(line));
|
||||
when(returnCartonItems.lockCarton(902L)).thenReturn(List.of(line));
|
||||
service.createReturnOutbound("TENANT-A", "U-1", new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1",
|
||||
List.of("RC-1", "RC-2"), null, "partial-six", "SKU-A", "BOX", 6L));
|
||||
var capture = ArgumentCaptor.forClass(OutboundPalletItem.class);
|
||||
verify(outboundPallets, org.mockito.Mockito.times(2)).insert(capture.capture());
|
||||
assertEquals(List.of(5L, 1L), capture.getAllValues().stream().map(OutboundPalletItem::getReturnSkuQuantity).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleSkuReturnOutboundRejectsPackedSingleSkuWithoutReservingInventory() {
|
||||
ReturnInventoryCarton carton = returnCarton("RC-BOX", "PAL-1");
|
||||
carton.setSourceUnitType("BOX"); carton.setTotalQuantity(5L);
|
||||
when(returnCartons.lockMark("RC-BOX")).thenReturn(carton);
|
||||
var error = assertThrows(ResponseStatusException.class, () -> service.createReturnOutbound("TENANT-A", "U-1",
|
||||
new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1", List.of("RC-BOX"), null, "packed-sku", "SKU-A")));
|
||||
assertTrue(error.getReason().contains("单SKU出库只允许散件库存"));
|
||||
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
verify(returnCartons, never()).updateById(any(ReturnInventoryCarton.class));
|
||||
assertEquals("AVAILABLE", carton.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleLooseSkusCreateOneOutboundWithExactPerSourceQuantities() {
|
||||
var first = returnCarton("SP-A", null); first.setId(901L); first.setSourceUnitType("PARCEL"); first.setTotalQuantity(5L);
|
||||
var second = returnCarton("SP-B", null); second.setId(902L); second.setSourceUnitType("PARCEL"); second.setTotalQuantity(8L);
|
||||
when(returnCartons.lockMark("SP-A")).thenReturn(first); when(returnCartons.lockMark("SP-B")).thenReturn(second);
|
||||
var a = new ReturnInventoryCartonItem(); a.setSkuCode("SKU-A"); a.setQuantity(5L);
|
||||
var b = new ReturnInventoryCartonItem(); b.setSkuCode("SKU-B"); b.setQuantity(8L);
|
||||
when(returnCartonItems.lockCarton(901L)).thenReturn(List.of(a)); when(returnCartonItems.lockCarton(902L)).thenReturn(List.of(b));
|
||||
service.createReturnOutbound("TENANT-A", "U-1", new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1",
|
||||
List.of("SP-A", "SP-B"), null, "multi-loose", null, "BOX", null,
|
||||
List.of(new DeliveryRequests.ReturnSkuOutboundItem("SP-A", "SKU-A", 2L), new DeliveryRequests.ReturnSkuOutboundItem("SP-B", "SKU-B", 3L))));
|
||||
var capture = ArgumentCaptor.forClass(OutboundPalletItem.class);
|
||||
verify(outboundPallets, org.mockito.Mockito.times(2)).insert(capture.capture());
|
||||
assertEquals(List.of("SKU-A", "SKU-B"), capture.getAllValues().stream().map(OutboundPalletItem::getSkuCode).toList());
|
||||
assertEquals(List.of(2L, 3L), capture.getAllValues().stream().map(OutboundPalletItem::getReturnSkuQuantity).toList());
|
||||
verify(outboundOrders).insert(any(OutboundOrder.class)); verify(deliveryOrders).insert(any(DeliveryOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void looseItemQuantityCannotExceedItsOwnSource() {
|
||||
var first = returnCarton("SP-A", null); first.setId(901L); first.setSourceUnitType("PARCEL"); first.setTotalQuantity(1L);
|
||||
when(returnCartons.lockMark("SP-A")).thenReturn(first);
|
||||
var a = new ReturnInventoryCartonItem(); a.setSkuCode("SKU-A"); a.setQuantity(1L);
|
||||
when(returnCartonItems.lockCarton(901L)).thenReturn(List.of(a));
|
||||
var error = assertThrows(ResponseStatusException.class, () -> service.createReturnOutbound("TENANT-A", "U-1", new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1",
|
||||
List.of("SP-A"), null, "over-source", null, "BOX", null, List.of(new DeliveryRequests.ReturnSkuOutboundItem("SP-A", "SKU-A", 2L)))));
|
||||
assertTrue(error.getReason().contains("超过")); verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleSkuReturnOutboundRejectsMixedCarton() {
|
||||
ReturnInventoryCarton mixed = returnCarton("RC-MIXED", null);
|
||||
mixed.setSourceUnitType("PARCEL");
|
||||
mixed.setId(901L); mixed.setTotalQuantity(5L);
|
||||
ReturnInventoryCartonItem skuA = new ReturnInventoryCartonItem(); skuA.setSkuCode("SKU-A"); skuA.setQuantity(3L);
|
||||
ReturnInventoryCartonItem skuB = new ReturnInventoryCartonItem(); skuB.setSkuCode("SKU-B"); skuB.setQuantity(2L);
|
||||
when(returnCartons.lockMark("RC-MIXED")).thenReturn(mixed);
|
||||
when(returnCartonItems.lockCarton(901L)).thenReturn(List.of(skuA, skuB));
|
||||
|
||||
assertEquals(409, assertThrows(ResponseStatusException.class, () -> service.createReturnOutbound("TENANT-A", "U-1",
|
||||
new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1",
|
||||
List.of("RC-MIXED"), null, "sku-return-2", "SKU-A"))).getStatusCode().value());
|
||||
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
void newlyReceivedParcelCanOutboundBeforeItsLabelIsConfirmed() {
|
||||
var parcel = returnCarton("RP-1", null); parcel.setLabelStatus("WAIT_LABEL");
|
||||
when(returnCartons.lockMark("RP-1")).thenReturn(parcel);
|
||||
var line = new ReturnInventoryCartonItem(); line.setSkuCode("SKU-A"); line.setQuantity(3L);
|
||||
when(returnCartonItems.lockCarton(anyLong())).thenReturn(List.of(line));
|
||||
service.createReturnOutbound("TENANT-A", "U-1",
|
||||
new DeliveryRequests.ReturnOutbound("receiver", "WH-1", "RT-1", List.of("RP-1"), null, "waiting-label"));
|
||||
verify(outboundOrders).insert(any(OutboundOrder.class));
|
||||
assertEquals("OUTBOUND_PENDING", parcel.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+4
-18
@@ -71,27 +71,13 @@ class ReturnOutboundClosureTest {
|
||||
verify(outbounds, times(1)).insert(any(OutboundOrder.class));
|
||||
}
|
||||
|
||||
@Test void singleSkuPackedBoxIsExcludedFromLooseOutbound() {
|
||||
assertThatThrownBy(() -> service.createReturnOutbound("C1", "USER", command("SKU1")))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("散件库存");
|
||||
verify(items, never()).insert(any(OutboundPalletItem.class));
|
||||
assertThat(carton.getStatus()).isEqualTo("AVAILABLE");
|
||||
}
|
||||
|
||||
@Test void singleSkuCannotConsumeMixedSkuCarton() {
|
||||
ReturnInventoryCartonItem other = new ReturnInventoryCartonItem(); other.setSkuCode("SKU2"); other.setQuantity(1L);
|
||||
var first = contents.lockCarton(10L).get(0);
|
||||
when(contents.lockCarton(10L)).thenReturn(List.of(first, other));
|
||||
assertThatThrownBy(() -> service.createReturnOutbound("C1", "USER", command("SKU1")))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("SKU");
|
||||
verify(outbounds, never()).insert(any(OutboundOrder.class));
|
||||
}
|
||||
|
||||
@Test void singleSkuParcelReservesAndKeepsScanSku() {
|
||||
|
||||
@Test void wholeParcelUsesItsPhysicalBarcode() {
|
||||
carton.setSourceUnitType("PARCEL");
|
||||
service.createReturnOutbound("C1", "USER", command("SKU1"));
|
||||
verify(items).insert(argThat((OutboundPalletItem i) -> "SKU1".equals(i.getSkuCode())));
|
||||
assertThat(carton.getStatus()).isEqualTo("OUTBOUND_PENDING");
|
||||
service.createReturnOutbound("C1", "USER", command(null));
|
||||
verify(items).insert(argThat((OutboundPalletItem i) -> i.getSkuCode() == null && i.getReturnSkuQuantity() == null && "RC#1-1".equals(i.getCartonMarks())));
|
||||
}
|
||||
|
||||
@Test void refusesPackingReservedQuantity() {
|
||||
|
||||
-23
@@ -123,29 +123,6 @@ class ReturnOutboundPdaPersistenceTest {
|
||||
assertEquals(1, after.items().stream().flatMap(item -> item.positions().stream()).mapToLong(row -> row.availablePackages()).sum());
|
||||
}
|
||||
|
||||
@Test void multipleLooseSkusPersistPickAndDeductTheirOwnPieceQuantitiesWithReplayProtection() {
|
||||
jdbc.update("UPDATE ll_return_inventory_carton SET source_unit_type='PARCEL' WHERE id IN (1,2)");
|
||||
jdbc.update("UPDATE ll_return_inventory_carton_item SET sku_code='SKU-B' WHERE id=2");
|
||||
jdbc.update("UPDATE ll_return_inventory_balance SET total_quantity=5,available_quantity=5,carton_quantity=1 WHERE id=1");
|
||||
jdbc.update("INSERT INTO ll_return_inventory_balance(id,customer_id,overseas_warehouse_id,sku_code,total_quantity,available_quantity,carton_quantity) VALUES(10,'C1','WH-1','SKU-B',5,5,1)");
|
||||
var task = service.createReturnOutbound("C1", "operator", new DeliveryRequests.ReturnOutbound("recipient", "WH-1", "RT-1",
|
||||
List.of("BOX-A", "BOX-B"), null, "multi-sku", null, "BOX", null,
|
||||
List.of(new DeliveryRequests.ReturnSkuOutboundItem("BOX-A", "SKU-A", 2L), new DeliveryRequests.ReturnSkuOutboundItem("BOX-B", "SKU-B", 3L))));
|
||||
var detail = service.pdaTaskDetail("WH-1", task.taskNo());
|
||||
assertEquals(2, detail.items().size());
|
||||
for (var item : detail.items()) {
|
||||
String sku = item.skuCode(); long quantity = "SKU-A".equals(sku) ? 2 : 3;
|
||||
var request = new DeliveryRequests.PdaPick(item.outboundPalletItemId(), null, 1L, "pick-" + sku, List.of(), sku, quantity);
|
||||
service.pdaPick("WH-1", "operator", task.taskNo(), request);
|
||||
service.pdaPick("WH-1", "operator", task.taskNo(), request);
|
||||
}
|
||||
assertEquals(3L, jdbc.queryForObject("SELECT available_quantity FROM ll_return_inventory_balance WHERE id=1", Long.class));
|
||||
assertEquals(2L, jdbc.queryForObject("SELECT available_quantity FROM ll_return_inventory_balance WHERE id=10", Long.class));
|
||||
assertEquals(5L, jdbc.queryForObject("SELECT SUM(quantity) FROM ll_return_inventory_outbound_movement", Long.class));
|
||||
assertEquals(2, jdbc.queryForObject("SELECT COUNT(*) FROM ll_outbound_pda_pick", Integer.class));
|
||||
assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM ll_outbound_order", Integer.class));
|
||||
assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM ll_delivery_order", Integer.class));
|
||||
}
|
||||
@Test void wholeReturnPalletRejectsPartialScansAndChangedContentsThenShipsAtomically() {
|
||||
jdbc.update("UPDATE ll_return_inventory_carton SET pallet_code='PAL-1' WHERE id IN (1,2)");
|
||||
var task = service.createReturnOutbound("C1", "operator", new DeliveryRequests.ReturnOutbound(
|
||||
|
||||
+35
-8
@@ -308,7 +308,7 @@ class PdaRelabelServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnSkuTotalCompletesOneSkuOnceAndAllocatesAcrossSourcePalletItems() {
|
||||
void returnSkuTotalRejectsAutomaticAllocationAcrossSourceCartons() {
|
||||
application.setStatus("PROCESSING"); application.setNotifiedPackageCount(100);
|
||||
item.setOperationType("RETURN_BULK_SKU_LABEL"); item.setSkuCode("sku-fba100");
|
||||
item.setNotifiedPackageCount(50); item.setCartonMark("PALLET-A");
|
||||
@@ -319,15 +319,42 @@ class PdaRelabelServiceTest {
|
||||
when(tasks.lock("100", 1L)).thenReturn(task);
|
||||
when(items.selectList(any())).thenReturn(List.of(item, second));
|
||||
|
||||
var result = service.completeReturnSkuTotal("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.BulkSkuTotal("sku-fba100", 100, 0, "sku-total-1"));
|
||||
assertThrows(ResponseStatusException.class, () -> service.completeReturnSkuTotal("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.BulkSkuTotal("sku-fba100", 100, 0, "sku-total-1")));
|
||||
assertTrue(persistedFacts.isEmpty());
|
||||
}
|
||||
|
||||
assertEquals(100, result.relabelledPackages());
|
||||
assertEquals(2, persistedFacts.size());
|
||||
assertEquals(List.of(50, 50), persistedFacts.stream().map(PdaRelabelUnitFact::getQuantity).toList());
|
||||
assertEquals(List.of(10L, 11L), persistedFacts.stream().map(PdaRelabelUnitFact::getItemId).toList());
|
||||
@Test void returnSkuScanRequiresPhysicalSourceNewSkuAndApprovedQuantity() {
|
||||
application.setStatus("PROCESSING"); application.setDeliveryMethod("EXPRESS");
|
||||
item.setOperationType("SKU_LABEL"); item.setReturnCartonId(20L); item.setCartonMark("PARCEL-A");
|
||||
item.setNotifiedPackageCount(1);
|
||||
item.setSkuRelabelItemsJson("[{\"skuCode\":\"OLD\",\"newSkuCode\":\"NEW\",\"quantity\":3}]");
|
||||
var cartons = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
||||
ReflectionTestUtils.setField(service, "returnCartons", cartons);
|
||||
var carton = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton();
|
||||
carton.setId(20L); carton.setCountStatus("CONFIRMED"); carton.setTotalQuantity(10L);
|
||||
when(cartons.selectById(20L)).thenReturn(carton);
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(task("PROCESSING", 0));
|
||||
when(items.selectOne(any())).thenReturn(item);
|
||||
assertThrows(ResponseStatusException.class, () -> service.scanSku("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.SkuScan(10L, 0, "OLD", 0, "wrong-source", "PARCEL-B", "NEW", 3)));
|
||||
assertThrows(ResponseStatusException.class, () -> service.scanSku("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.SkuScan(10L, 0, "OLD", 0, "wrong-quantity", "PARCEL-A", "NEW", 2)));
|
||||
assertTrue(persistedFacts.isEmpty());
|
||||
var result = service.scanSku("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.SkuScan(10L, 0, "OLD", 0, "good", "PARCEL-A", "NEW", 3));
|
||||
assertEquals(1, result.relabelledPackages()); assertEquals("EXPRESS", result.deliveryMethod());
|
||||
assertEquals(false, result.requiresPalletBinding()); assertEquals(10, result.items().get(0).returnQuantity());
|
||||
var second = item(); second.setId(11L); second.setReturnCartonId(20L); second.setCartonMark("PARCEL-A");
|
||||
second.setOperationType("SKU_LABEL"); second.setNotifiedPackageCount(1);
|
||||
second.setSkuRelabelItemsJson(item.getSkuRelabelItemsJson());
|
||||
when(items.selectList(any())).thenReturn(List.of(item, second));
|
||||
when(items.selectOne(any())).thenReturn(second);
|
||||
var again = service.scanSku("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.SkuScan(11L, 0, "OLD", 1, "same-merged-box", "PARCEL-A", "NEW", 3));
|
||||
assertEquals(2, again.relabelledPackages());
|
||||
assertTrue(persistedFacts.stream().allMatch(fact -> fact.getOldLabel() == null));
|
||||
assertEquals(1, result.version());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -452,7 +452,7 @@ class RelabelApplicationServiceTest {
|
||||
ReturnOrder returnOrder = new ReturnOrder();
|
||||
returnOrder.setId(41L); returnOrder.setCustomerId("10001");
|
||||
returnOrder.setRelabelApplicationId(application.getId()); returnOrder.setRelabelStatus("PROCESSING");
|
||||
when(returnOrders.selectOne(any())).thenReturn(returnOrder);
|
||||
when(returnOrders.selectList(any())).thenReturn(List.of(returnOrder));
|
||||
|
||||
service.changeStatus("10001", "FBR-REVIEW", "operator-2",
|
||||
new RelabelRequests.ChangeStatus(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, "submitted"));
|
||||
|
||||
+21
@@ -16,6 +16,27 @@ import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RelabelBindingGuardTest {
|
||||
@Test void partialReturnSkuMustBeIsolatedAndLooseFullCartonOnlyRequiresPalletForTruck() {
|
||||
var guard = new RelabelBindingGuard();
|
||||
var items = mock(RelabelItemMapper.class); var histories = mock(PdaRelabelHistoryMapper.class);
|
||||
var lines = mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
||||
var cartons = mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
||||
ReflectionTestUtils.setField(guard, "items", items); ReflectionTestUtils.setField(guard, "histories", histories);
|
||||
ReflectionTestUtils.setField(guard, "returnLines", lines); ReflectionTestUtils.setField(guard, "returnCartons", cartons);
|
||||
ReflectionTestUtils.setField(guard, "objectMapper", new ObjectMapper());
|
||||
var app = new RelabelApplication(); app.setId(1L); app.setCustomerId("2");
|
||||
var item = new RelabelItem(); item.setReturnCartonId(3L); item.setCartonMark("PARCEL");
|
||||
item.setOperationType("SKU_LABEL"); item.setNotifiedPackageCount(1);
|
||||
item.setSkuRelabelItemsJson("[{\"skuCode\":\"A\",\"newSkuCode\":\"NEW-A\",\"quantity\":3}]");
|
||||
when(items.selectList(any())).thenReturn(List.of(item)); when(histories.selectList(any())).thenReturn(List.of());
|
||||
var stock = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem(); stock.setSkuCode("A"); stock.setQuantity(10L);
|
||||
when(lines.lockCarton(3L)).thenReturn(List.of(stock));
|
||||
assertThrows(ResponseStatusException.class, () -> guard.requireBound(app));
|
||||
stock.setQuantity(3L);
|
||||
assertDoesNotThrow(() -> guard.requireBound(app));
|
||||
app.setDeliveryMethod("EXPRESS"); assertDoesNotThrow(() -> guard.requireBound(app));
|
||||
app.setDeliveryMethod("TRUCK"); assertThrows(ResponseStatusException.class, () -> guard.requireBound(app));
|
||||
}
|
||||
@Test void physicalBindingMustMatchLatestTargetAndCannotSurviveRework() {
|
||||
var guard = new RelabelBindingGuard();
|
||||
var items = mock(RelabelItemMapper.class);
|
||||
|
||||
+10
-4
@@ -36,9 +36,9 @@ class RelabelReturnCartonCandidateTest {
|
||||
ReturnInventoryCartonItem cartonItem = new ReturnInventoryCartonItem();
|
||||
cartonItem.setCartonId(9001L); cartonItem.setSkuCode("SKU-RETURN");
|
||||
cartonItem.setProductNameZh("退货商品"); cartonItem.setQuantity(12L);
|
||||
when(cartons.findAvailable("10001", "WH-1")).thenReturn(List.of(carton));
|
||||
when(cartons.findRelabelCandidates("10001", "WH-1")).thenReturn(List.of(carton));
|
||||
when(cartonItems.listCarton(9001L)).thenReturn(List.of(cartonItem));
|
||||
when(cartons.relabelledQuantity(9001L, "SKU-RETURN")).thenReturn(1L);
|
||||
when(cartons.totalRelabelledQuantity(9001L)).thenReturn(1L);
|
||||
|
||||
List<RelabelResponses.InventoryCandidate> result = service.candidates("10001", "WH-1", "RC-WH");
|
||||
|
||||
@@ -46,8 +46,14 @@ class RelabelReturnCartonCandidateTest {
|
||||
assertEquals("RETURN_CARTON", result.get(0).sourceType());
|
||||
assertEquals(9001L, result.get(0).returnCartonId());
|
||||
assertEquals("RC-WH-0001", result.get(0).cartonMark());
|
||||
assertEquals(11, result.get(0).availablePackages());
|
||||
assertEquals(12, result.get(0).availablePackages());
|
||||
assertEquals("SKU-RETURN", result.get(0).skuItems().get(0).skuCode());
|
||||
assertEquals(11, result.get(0).skuItems().get(0).quantity());
|
||||
assertEquals(12, result.get(0).skuItems().get(0).quantity());
|
||||
assertEquals(1, result.get(0).relabelledQuantity());
|
||||
assertEquals("PARTIALLY_COMPLETED", result.get(0).relabelStatus());
|
||||
cartonItem.setPackingReservedQuantity(4L);
|
||||
result = service.candidates("10001", "WH-1", "RC-WH");
|
||||
assertEquals(8, result.get(0).availablePackages());
|
||||
assertEquals(1, result.get(0).relabelledQuantity(), "合箱预占不能冒充实际换标完成量");
|
||||
}
|
||||
}
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package com.baiyue.lastleg.relabel.service;
|
||||
|
||||
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
|
||||
import com.baiyue.lastleg.relabel.entity.*;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.dto.*;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.*;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.*;
|
||||
import com.baiyue.lastleg.returnreceipt.service.ReturnInventoryService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
|
||||
class ReturnRelabelPackingServiceTest {
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.ValueSource(booleans = {false, true})
|
||||
void skuPackingMovesCompletionToIndependentCartonAndKeepsOriginalSourceMarks(boolean inheritedCompletion) {
|
||||
var service = new ReturnRelabelPackingService();
|
||||
var cartons = mock(ReturnInventoryCartonMapper.class);
|
||||
var items = mock(RelabelItemMapper.class);
|
||||
var inventory = mock(ReturnInventoryService.class);
|
||||
ReflectionTestUtils.setField(service, "cartons", cartons);
|
||||
ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
ReflectionTestUtils.setField(service, "json", new ObjectMapper());
|
||||
var app = new RelabelApplication(); app.setId(1L); app.setCustomerId("2"); app.setRelabelNo("RL1");
|
||||
var first = item(10L, 20L, "A"); var second = item(11L, 21L, "B");
|
||||
var a = carton(20L, "A"); var b = carton(21L, "B"); var target = carton(22L, "NEW");
|
||||
var history = mock(com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelHistoryMapper.class);
|
||||
ReflectionTestUtils.setField(service, "histories", history);
|
||||
if (inheritedCompletion) {
|
||||
var prior = item(9L, 20L, "A"); prior.setApplicationId(99L);
|
||||
prior.setSkuRelabelItemsJson("[{\"skuCode\":\"SKU\",\"quantity\":6}]");
|
||||
when(items.priorCompletedReturnItems("2", 20L, 1L)).thenReturn(List.of(prior));
|
||||
var facts = mock(com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelUnitFactMapper.class);
|
||||
var fact = new com.baiyue.lastleg.relabel.pda.entity.PdaRelabelUnitFact(); fact.setItemId(9L); fact.setQuantity(1);
|
||||
when(facts.selectList(any())).thenReturn(List.of(fact));
|
||||
ReflectionTestUtils.setField(service, "facts", facts);
|
||||
var physical = mock(ReturnInventoryCartonItemMapper.class);
|
||||
var line = new ReturnInventoryCartonItem(); line.setSkuCode("SKU"); line.setQuantity(6L);
|
||||
when(physical.lockCarton(20L)).thenReturn(List.of(line));
|
||||
ReflectionTestUtils.setField(service, "cartonItems", physical);
|
||||
ReflectionTestUtils.setField(service, "completionLedger", mock(ReturnRelabelCompletionLedgerMapper.class));
|
||||
var tasks = mock(com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelTaskMapper.class);
|
||||
var task = new com.baiyue.lastleg.relabel.pda.entity.PdaRelabelTask(); task.setId(100L);
|
||||
when(tasks.find("2", 1L)).thenReturn(task); ReflectionTestUtils.setField(service, "tasks", tasks);
|
||||
}
|
||||
when(cartons.lockMark("A")).thenReturn(a); when(cartons.lockMark("B")).thenReturn(b);
|
||||
when(cartons.lockMark("NEW")).thenReturn(target);
|
||||
when(inventory.mergeBoxes(any(), any())).thenAnswer(call -> {
|
||||
var request = (ReturnInventoryRequests.BoxMerge)call.getArgument(1);
|
||||
assertEquals(List.of("A", "B"), request.sourceCartonMarks());
|
||||
assertNull(request.targetCartonMark());
|
||||
assertEquals(List.of(new ReturnInventoryRequests.SourceDeduction("A", "SKU", 3),
|
||||
new ReturnInventoryRequests.SourceDeduction("B", "SKU", 3)), request.sourceDeductions());
|
||||
return new ReturnInventoryResponses.MergeResult("NEW", "SKU", 6, null);
|
||||
});
|
||||
var result = service.pack(new PdaWarehouseIdentity("op", "picker", 42L), app, List.of(first, second),
|
||||
new ReturnInventoryRequests.BoxMerge(List.of("A", "B"), null, "key", null,
|
||||
BigDecimal.TEN, BigDecimal.TEN, BigDecimal.TEN, BigDecimal.ONE));
|
||||
assertEquals("NEW", result.cartonMark());
|
||||
assertEquals(22L, first.getReturnCartonId()); assertEquals(22L, second.getReturnCartonId());
|
||||
assertEquals("A", first.getSourceCartonMark()); assertEquals("B", second.getSourceCartonMark());
|
||||
assertEquals("RELABEL_PENDING", target.getStatus());
|
||||
verify(cartons).clearRelabel(20L); verify(cartons).clearRelabel(21L);
|
||||
verify(items).updateById(first); verify(items).updateById(second);
|
||||
if (inheritedCompletion) {
|
||||
var saved = org.mockito.ArgumentCaptor.forClass(com.baiyue.lastleg.relabel.pda.entity.PdaRelabelHistory.class);
|
||||
verify(history).insert(saved.capture());
|
||||
assertEquals("RETURN_COMPLETION_TRANSFER", saved.getValue().getAction());
|
||||
assertTrue(saved.getValue().getDetailJson().contains("\"sourceItemIds\":[\"9\"]"));
|
||||
assertTrue(saved.getValue().getDetailJson().contains("\"quantity\":3"));
|
||||
assertTrue(saved.getValue().getDetailJson().contains("\"targetCartonMark\":\"NEW\""));
|
||||
} else verifyNoInteractions(history);
|
||||
}
|
||||
|
||||
@Test void wrongSourceAndDifferentDestinationCannotEnterInventoryMutation() {
|
||||
var service = new ReturnRelabelPackingService();
|
||||
var inventory = mock(ReturnInventoryService.class);
|
||||
ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
var app = new RelabelApplication();
|
||||
var a = item(10L, 20L, "A"); var b = item(11L, 21L, "B"); b.setDestinationFbaWarehouseCode("OTHER");
|
||||
assertThrows(ResponseStatusException.class, () -> service.pack(new PdaWarehouseIdentity("op", "name", 42L), app,
|
||||
List.of(a), new ReturnInventoryRequests.BoxMerge(List.of("OTHER"), null, "wrong")));
|
||||
assertThrows(ResponseStatusException.class, () -> service.pack(new PdaWarehouseIdentity("op", "name", 42L), app,
|
||||
List.of(a, b), new ReturnInventoryRequests.BoxMerge(List.of("A", "B"), null, "dest")));
|
||||
verifyNoInteractions(inventory);
|
||||
}
|
||||
private RelabelItem item(long id, long cartonId, String mark) {
|
||||
var item = new RelabelItem(); item.setId(id); item.setReturnCartonId(cartonId);
|
||||
item.setCartonMark(mark); item.setSourceCartonMark(mark); item.setOperationType("SKU_LABEL");
|
||||
item.setNotifiedPackageCount(1); item.setDestinationFbaWarehouseCode("FBA");
|
||||
item.setSkuRelabelItemsJson("[{\"skuCode\":\"SKU\",\"newSkuCode\":\"NEW-SKU\",\"quantity\":3}]");
|
||||
return item;
|
||||
}
|
||||
private ReturnInventoryCarton carton(long id, String mark) {
|
||||
var carton = new ReturnInventoryCarton(); carton.setId(id); carton.setCartonMark(mark);
|
||||
carton.setCustomerId("2"); carton.setRelabelApplicationId(1L); carton.setStatus("RELABEL_PENDING");
|
||||
return carton;
|
||||
}
|
||||
}
|
||||
+68
-10
@@ -127,7 +127,7 @@ class ReturnInventoryServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingReceiptDetailsDoNotExposeProvisionalAveragesAndParcelsHaveNoBoxes() {
|
||||
void pendingReceiptDetailsDoNotExposeProvisionalAveragesForPhysicalUnits() {
|
||||
var receipt = new ReturnReceipt(); receipt.setId(21L); receipt.setCustomerId("C1"); receipt.setReceiptNo("RR-1"); receipt.setUnitType("PALLET");
|
||||
var box = new ReturnInventoryCarton(); box.setCartonMark("RC#2-1"); box.setCountStatus("PENDING"); box.setTotalQuantity(5L);
|
||||
when(cartons.listReceipt(21L)).thenReturn(List.of(box));
|
||||
@@ -135,7 +135,7 @@ class ReturnInventoryServiceTest {
|
||||
assertThat(detail.get(0).countStatus()).isEqualTo("PENDING");
|
||||
assertThat(detail.get(0).items()).isEmpty();
|
||||
receipt.setUnitType("PARCEL");
|
||||
assertThat(service.receiptContents(receipt)).isEmpty();
|
||||
assertThat(service.receiptContents(receipt)).hasSize(1);
|
||||
verifyNoInteractions(cartonItems);
|
||||
}
|
||||
|
||||
@@ -150,13 +150,14 @@ class ReturnInventoryServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void parcelSigningStoresLooseSkuSourceWithoutGeneratingCartonNumberOrIncreasingBoxCount() {
|
||||
void parcelSigningCreatesOneLabelledPhysicalUnitWithoutIncreasingBoxCount() {
|
||||
var receipt = new ReturnReceipt(); receipt.setId(21L); receipt.setUnitType("PARCEL"); receipt.setUnitCode("PK-1");
|
||||
var source = service.createReceiptCarton(completedOrder(), receipt, null,
|
||||
List.of(new ReturnInventoryService.ReceiptLine("A", "商品A", 3)));
|
||||
assertThat(source.getCartonMark()).startsWith("SP");
|
||||
assertThat(source.getCartonMark()).startsWith("RP");
|
||||
assertThat(source.getSourceUnitType()).isEqualTo("PARCEL");
|
||||
verifyNoInteractions(numbers, operations);
|
||||
verifyNoInteractions(numbers);
|
||||
assertThat(source.getLabelStatus()).isEqualTo("WAIT_LABEL");
|
||||
verify(balances).adjust(anyLong(), anyString(), anyString(), eq("A"), eq("商品A"), eq(3L), eq(3L), eq(0L));
|
||||
}
|
||||
|
||||
@@ -227,7 +228,7 @@ class ReturnInventoryServiceTest {
|
||||
void cartonPageCountsPhysicalCartonsAndKeepsTheAuthenticatedCustomerScope() {
|
||||
var query = new ReturnInventoryRequests.Query(2, 1, "42", "SKU", null, "AVAILABLE", "RT-1");
|
||||
ReturnInventoryCarton carton = new ReturnInventoryCarton();
|
||||
carton.setCartonMark("RC-SECOND");
|
||||
carton.setId(31L); carton.setCartonMark("RC-SECOND");
|
||||
carton.setSkuTypes(2);
|
||||
carton.setTotalQuantity(5L);
|
||||
when(cartons.merchantCount("10001", query)).thenReturn(2L);
|
||||
@@ -369,6 +370,32 @@ class ReturnInventoryServiceTest {
|
||||
verify(balances).adjustCartonQuantity("10001", "42", "SKU-A", 1L);
|
||||
}
|
||||
|
||||
@Test void completedLabelQuantityCannotBeGuessedWhenSplittingAStoredCarton() {
|
||||
prepareConcurrentSource();
|
||||
when(cartonItems.lockCarton(31L)).thenReturn(List.of(item(41L, "SKU-A", 2L)));
|
||||
when(cartons.relabelledQuantity(31L, "SKU-A")).thenReturn(1L);
|
||||
var error = assertThrows(ResponseStatusException.class, () -> service.merge(
|
||||
new PdaWarehouseIdentity("operator-2", "name", 42L), mergeRequest(1)));
|
||||
assertThat(error.getReason()).contains("不能自动分配来源", "独立新箱");
|
||||
verify(cartonItems, never()).updateById(any(ReturnInventoryCartonItem.class));
|
||||
verify(cartons, never()).insert(any(ReturnInventoryCarton.class));
|
||||
}
|
||||
|
||||
@Test void wholeCartonMergeCarriesItsCompletedRelabelFactsToTheTarget() {
|
||||
var source = prepareConcurrentSource();
|
||||
when(cartonItems.lockCarton(31L)).thenReturn(List.of(item(41L, "SKU-A", 2L)));
|
||||
when(cartons.relabelledQuantity(31L, "SKU-A")).thenReturn(2L);
|
||||
when(numbers.next(anyString(), anyString(), anyLong())).thenReturn("RC-TARGET");
|
||||
when(balances.adjustCartonQuantity(anyString(), anyString(), anyString(), anyLong())).thenReturn(1);
|
||||
var relabel = org.mockito.Mockito.mock(com.baiyue.lastleg.relabel.mapper.RelabelItemMapper.class);
|
||||
ReflectionTestUtils.setField(service, "relabelItems", relabel);
|
||||
service.merge(new PdaWarehouseIdentity("operator-2", "name", 42L), mergeRequest(2));
|
||||
var target = ArgumentCaptor.forClass(ReturnInventoryCarton.class);
|
||||
verify(cartons).insert(target.capture());
|
||||
verify(relabel).moveCompletedReturnCarton("10001", 31L, target.getValue().getId(), "RC-TARGET");
|
||||
assertThat(source.getTotalQuantity()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameKeyInsertRaceReplaysCommittedResultAndLabelUsingOnlyCurrentReads() {
|
||||
var request = mergeRequest(1);
|
||||
@@ -468,11 +495,11 @@ class ReturnInventoryServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void planCreationLocksExactSkuAndCancelReleasesOnlyItsReservation() {
|
||||
void planCreationLocksPhysicalSourceAndCancelReleasesReservation() {
|
||||
var source = new ReturnInventoryCarton(); source.setId(31L); source.setCartonMark("RC-SOURCE");
|
||||
source.setCustomerId("10001"); source.setOverseasWarehouseId("42"); source.setSourceReturnNo("RT-1");
|
||||
source.setStatus("AVAILABLE"); source.setSourceUnitType("PARCEL");
|
||||
var line = item(41L, "SKU-A", 5); line.setPackingReservedQuantity(1L);
|
||||
var line = item(41L, "SKU-A", 5); line.setPackingReservedQuantity(0L);
|
||||
when(orders.lockNo("RT-1")).thenReturn(completedOrder());
|
||||
when(cartons.packingAvailable("10001", "42")).thenReturn(List.of(source));
|
||||
when(cartons.lockMark("RC-SOURCE")).thenReturn(source);
|
||||
@@ -483,12 +510,14 @@ class ReturnInventoryServiceTest {
|
||||
var request = new ReturnInventoryRequests.PackingPlanCreate(List.of("RT-1"), "SKU",
|
||||
List.of(new ReturnInventoryRequests.PlanBox(List.of(new ReturnInventoryRequests.PlanItem("SKU-A", 2)), null, null, null, null)), null, "plan-key");
|
||||
var created = service.createPackingPlan("platform", request);
|
||||
assertThat(created.status()).isEqualTo("PENDING_REVIEW"); assertThat(line.getPackingReservedQuantity()).isEqualTo(3);
|
||||
assertThat(created.status()).isEqualTo("PENDING_REVIEW"); assertThat(line.getPackingReservedQuantity()).isEqualTo(2);
|
||||
assertThat(source.getStatus()).isEqualTo("PACKING_RESERVED");
|
||||
var captor = ArgumentCaptor.forClass(com.baiyue.lastleg.returnreceipt.entity.ReturnPackingPlan.class);
|
||||
verify(packingPlans).insert(captor.capture());
|
||||
when(packingPlans.lockNo("PLAN-1")).thenReturn(captor.getValue());
|
||||
assertThat(service.cancelPackingPlan("PLAN-1").status()).isEqualTo("CANCELLED");
|
||||
assertThat(line.getPackingReservedQuantity()).isEqualTo(1);
|
||||
assertThat(line.getPackingReservedQuantity()).isEqualTo(0);
|
||||
assertThat(source.getStatus()).isEqualTo("AVAILABLE");
|
||||
verify(balances).adjust(anyLong(), org.mockito.ArgumentMatchers.eq("10001"), org.mockito.ArgumentMatchers.eq("42"), org.mockito.ArgumentMatchers.eq("SKU-A"), org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.eq(0L), org.mockito.ArgumentMatchers.eq(2L), org.mockito.ArgumentMatchers.eq(0L));
|
||||
}
|
||||
|
||||
@@ -502,6 +531,35 @@ class ReturnInventoryServiceTest {
|
||||
verifyNoInteractions(cartonItems, balances);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialPhysicalBoxCannotBeMergedIntoAnExistingTarget() {
|
||||
var source = new ReturnInventoryCarton(); source.setId(31L); source.setCartonMark("RC-SOURCE");
|
||||
source.setCustomerId("10001"); source.setOverseasWarehouseId("42"); source.setSourceReturnNo("RT-1");
|
||||
source.setStatus("AVAILABLE"); source.setSourceUnitType("BOX");
|
||||
when(cartons.lockMark("RC-SOURCE")).thenReturn(source);
|
||||
when(cartonItems.lockCarton(31L)).thenReturn(List.of(item(41L, "SKU-A", 5)));
|
||||
var request = new ReturnInventoryRequests.BoxMerge(List.of("RC-SOURCE"), "RC-TARGET", "partial-existing",
|
||||
List.of(new ReturnInventoryRequests.SourceDeduction("RC-SOURCE", "SKU-A", 1)), null, null, null, null);
|
||||
var error = assertThrows(ResponseStatusException.class, () -> service.mergeBoxes(new PdaWarehouseIdentity("operator-1", "作业员", 42L), request));
|
||||
assertThat(error.getReason()).contains("独立新箱");
|
||||
verify(cartonItems, never()).updateById(any(ReturnInventoryCartonItem.class));
|
||||
verify(cartons, never()).insert(any(ReturnInventoryCarton.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmingPhysicalLabelIsIdempotentAndBoundToWarehouse() {
|
||||
var source = new ReturnInventoryCarton(); source.setId(31L); source.setCartonMark("RC-SOURCE");
|
||||
source.setCustomerId("10001"); source.setOverseasWarehouseId("42"); source.setSourceReturnNo("RT-1");
|
||||
source.setStatus("AVAILABLE"); source.setLabelStatus("WAIT_LABEL");
|
||||
when(cartons.lockMark("RC-SOURCE")).thenReturn(source);
|
||||
when(cartonItems.listCarton(31L)).thenReturn(List.of(item(41L, "SKU-A", 2)));
|
||||
var identity = new PdaWarehouseIdentity("operator-1", "作业员", 42L);
|
||||
service.confirmLabel(identity, "RC-SOURCE"); service.confirmLabel(identity, "RC-SOURCE");
|
||||
assertThat(source.getLabelStatus()).isEqualTo("CONFIRMED");
|
||||
verify(cartons, org.mockito.Mockito.times(1)).updateById(source);
|
||||
assertThrows(ResponseStatusException.class, () -> service.confirmLabel(new PdaWarehouseIdentity("operator-2", "其他仓", 43L), "RC-SOURCE"));
|
||||
}
|
||||
|
||||
private ReturnInventoryCarton prepareConcurrentSource() {
|
||||
ReturnReceipt receipt = new ReturnReceipt(); receipt.setId(21L);
|
||||
ReturnInventoryCarton snapshot = new ReturnInventoryCarton();
|
||||
|
||||
+15
-11
@@ -227,10 +227,13 @@ class ReturnReceiptServiceTest {
|
||||
when(numbers.next(eq("RELABEL"), eq("10"), anyLong())).thenReturn("RL-MULTI");
|
||||
when(lastlegOrders.selectById(99L)).thenReturn(tail);
|
||||
when(cartons.findAvailable("customer-1", "10")).thenReturn(List.of(first, second));
|
||||
when(inventory.matchesReturnSource(any(), eq("RT-MULTI"))).thenCallRealMethod();
|
||||
when(cartons.lockId(101L)).thenReturn(first);
|
||||
when(cartons.lockId(102L)).thenReturn(second);
|
||||
when(cartonItems.listCarton(101L)).thenReturn(List.of(returnCartonItem("SKU-A", 50)));
|
||||
when(cartonItems.listCarton(102L)).thenReturn(List.of(returnCartonItem("SKU-B", 40)));
|
||||
when(cartonItems.lockCarton(101L)).thenReturn(List.of(returnCartonItem("SKU-A", 50)));
|
||||
when(cartonItems.lockCarton(102L)).thenReturn(List.of(returnCartonItem("SKU-B", 40)));
|
||||
|
||||
var result = service.createRelabel("customer-1", "merchant-1", "RT-MULTI",
|
||||
new ReturnOrderRequests.RelabelCreate(List.of(
|
||||
@@ -263,7 +266,7 @@ class ReturnReceiptServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondRelabelCannotAllocateAlreadyLabelledQuantity() {
|
||||
void completedReturnCartonCanStartAnotherRelabelRound() {
|
||||
ReturnOrder order = order("RT-MULTI", "10", "COMPLETED", 100, 100);
|
||||
order.setLastlegOrderId(99L); order.setRelabelNo("RL-PREVIOUS");
|
||||
LastlegOrder tail = new LastlegOrder(); tail.setId(99L);
|
||||
@@ -275,16 +278,17 @@ class ReturnReceiptServiceTest {
|
||||
when(numbers.next(eq("RELABEL"), eq("10"), anyLong())).thenReturn("RL-SECOND");
|
||||
when(lastlegOrders.selectById(99L)).thenReturn(tail);
|
||||
when(cartons.findAvailable("customer-1", "10")).thenReturn(List.of(carton));
|
||||
when(inventory.matchesReturnSource(any(), eq("RT-MULTI"))).thenCallRealMethod();
|
||||
when(cartonItems.listCarton(101L)).thenReturn(List.of(returnCartonItem("SKU-A", 100)));
|
||||
when(cartons.relabelledQuantity(101L, "SKU-A")).thenReturn(100L);
|
||||
var error = assertThrows(ResponseStatusException.class, () -> service.createRelabel(
|
||||
when(cartons.lockId(101L)).thenReturn(carton);
|
||||
when(cartonItems.lockCarton(101L)).thenReturn(List.of(returnCartonItem("SKU-A", 100)));
|
||||
var result = service.createRelabel(
|
||||
"customer-1", "merchant-1", "RT-MULTI", new ReturnOrderRequests.RelabelCreate(
|
||||
List.of(new ReturnOrderRequests.RelabelItem("SKU-A", 1)),
|
||||
"SKU_TOTAL", "200", "XLX6", "second-key", "second attempt")));
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
assertTrue(error.getReason().contains("可用实收数量不足"));
|
||||
verify(relabelItems, never()).insert(any(RelabelItem.class));
|
||||
verify(cartons, never()).updateById(any(ReturnInventoryCarton.class));
|
||||
"SKU_TOTAL", "200", "XLX6", "second-key", "second attempt"));
|
||||
assertEquals("RL-SECOND", result.relabelNo());
|
||||
verify(relabelItems).insert(any(RelabelItem.class));
|
||||
verify(cartons).updateById(carton);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -444,7 +448,7 @@ class ReturnReceiptServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void parcelWithoutTrackingGeneratesParcelCodeAndNeverReturnsBoxLabelsOnReplay() {
|
||||
void parcelWithoutTrackingKeepsPhysicalMarkOnReplay() {
|
||||
ReturnOrder order = order("RT-1", "10", ReturnReceiptService.PENDING_RECEIPT, 3, 0);
|
||||
ReturnOrderItem line = item(11L, "SKU-A", 3, 0);
|
||||
stubNewReceipt(order, List.of(line), "SP-SOURCE");
|
||||
@@ -455,7 +459,7 @@ class ReturnReceiptServiceTest {
|
||||
var capture = ArgumentCaptor.forClass(ReturnReceipt.class);
|
||||
verify(receipts).insert(capture.capture());
|
||||
assertThat(result.unitCode()).startsWith("PK");
|
||||
assertThat(result.cartonMark()).isNull();
|
||||
assertThat(result.cartonMark()).isEqualTo("SP-SOURCE");
|
||||
assertThat(result.printableHtml()).isNull();
|
||||
assertThat(result.labels()).isEmpty();
|
||||
assertThat(capture.getValue().getTrackingNo()).isNull();
|
||||
@@ -463,7 +467,7 @@ class ReturnReceiptServiceTest {
|
||||
var replay = service.confirm(identity, request);
|
||||
assertThat(replay.unitCode()).isEqualTo(result.unitCode());
|
||||
assertThat(replay.labels()).isEmpty();
|
||||
verify(inventory, never()).label(anyString(), anyString());
|
||||
verify(inventory, times(2)).label("customer-1", "SP-SOURCE");
|
||||
verify(inventory, times(1)).createReceiptCarton(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user