贴标申请,仓储转运

This commit is contained in:
2026-10-10 16:58:33 +08:00
parent 05302490fb
commit c6471d06ff
6 changed files with 105 additions and 13 deletions
@@ -265,6 +265,35 @@ class DeliveryTransferServiceTest {
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);
@@ -71,10 +71,11 @@ class ReturnOutboundClosureTest {
verify(outbounds, times(1)).insert(any(OutboundOrder.class));
}
@Test void singleSkuPackedBoxKeepsCartonScanAndPieceSnapshot() {
service.createReturnOutbound("C1", "USER", command("SKU1"));
verify(items).insert(argThat((OutboundPalletItem i) -> i.getSkuCode() == null && Long.valueOf(6L).equals(i.getReturnSkuQuantity())));
assertThat(carton.getStatus()).isEqualTo("OUTBOUND_PENDING");
@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() {
@@ -122,6 +122,30 @@ class ReturnOutboundPdaPersistenceTest {
assertEquals(1, after.items().stream().mapToInt(item -> item.pickedPackageCount()).sum());
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(