贴标申请,仓储转运
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(
|
||||
|
||||
Reference in New Issue
Block a user