fix(inventory): enforce warehouse transfer reservation ownership
This commit is contained in:
+11
@@ -14,6 +14,17 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DispatchCandidateMapperContractTest {
|
||||
@Test
|
||||
void historicalTransferCreationModeDoesNotGrantReservationOwnership() throws Exception {
|
||||
for (Method method : List.of(
|
||||
DispatchCandidateMapper.class.getMethod("available", String.class, List.class, String.class, String.class, String.class, String.class),
|
||||
DispatchCandidateMapper.class.getMethod("lockSelected", String.class, List.class))) {
|
||||
String sql = String.join(" ", method.getAnnotation(Select.class).value());
|
||||
assertTrue(sql.contains("AND NOT EXISTS (SELECT 1 FROM ll_warehouse_transfer_inventory_allocation ta"));
|
||||
org.junit.jupiter.api.Assertions.assertFalse(sql.contains("ip.creation_mode='WAREHOUSE_TRANSFER' OR NOT EXISTS (SELECT 1 FROM ll_warehouse_transfer_inventory_allocation"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void availableAndLockedPalletsApplyTheSameActiveRelabelBoundary() throws Exception {
|
||||
try (var connection = DriverManager.getConnection("jdbc:h2:mem:dispatch-relabel-boundary;MODE=MySQL")) {
|
||||
|
||||
+37
@@ -74,6 +74,43 @@ class InventoryCommandServiceTest {
|
||||
lenient().when(commands.lockInboundTransitBalances(any(), any(), any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void rejectsDispatchAndRelabelAgainstTransferReservationEvenWithForgedBusinessKey() {
|
||||
var balance = balance(200L, 1);
|
||||
var position = new InventoryPosition(); position.setId(300L); position.setBalanceId(200L);
|
||||
position.setAvailablePackages(1L);
|
||||
when(commands.lockPosition("100", 300L)).thenReturn(position);
|
||||
when(commands.lockBalance("100", 200L)).thenReturn(balance);
|
||||
lenient().when(commands.transferReservation("100", 300L))
|
||||
.thenReturn(new InventoryCommandMapper.TransferReservation(900L, 1));
|
||||
var request = new InventoryCommandRequests.Move(300L, 1, "WT-FORGED", "WT-FORGED",
|
||||
"WAREHOUSE_TRANSFER_OUTBOUND:WT-FORGED", "WAREHOUSE_TRANSFER:WT-FORGED:ALLOCATE", null);
|
||||
assertThrows(ResponseStatusException.class, () -> service.allocate("100", "7", request));
|
||||
assertThrows(ResponseStatusException.class, () -> service.hold("100", "7", request));
|
||||
assertEquals(1L, position.getAvailablePackages());
|
||||
verify(positions, never()).updateById(any(InventoryPosition.class));
|
||||
verify(ledgers, never()).insert(any(InventoryLedger.class));
|
||||
}
|
||||
@Test
|
||||
void onlyExactInternalTransferOwnerCanConsumeReservation() {
|
||||
var balance = balance(200L, 1);
|
||||
var position = position(300L, 200L, 1);
|
||||
when(commands.lockPosition("100", 300L)).thenReturn(position);
|
||||
when(commands.lockBalance("100", 200L)).thenReturn(balance);
|
||||
when(commands.transferReservation("100", 300L)).thenReturn(new InventoryCommandMapper.TransferReservation(900L, 1));
|
||||
var allocate = new InventoryCommandRequests.Move(300L, 1, "WT-1", "WT-1", "EVENT", "ALLOCATE-1", null);
|
||||
assertThrows(ResponseStatusException.class, () -> service.allocateWarehouseTransfer("100", "7", 901L, allocate, null));
|
||||
service.allocateWarehouseTransfer("100", "7", 900L, allocate, null);
|
||||
var pick = new InventoryCommandRequests.Move(300L, 1, "WT-1", "WT-1", "EVENT", "PICK-1", null);
|
||||
service.pickWarehouseTransfer("100", "7", 900L, pick, null);
|
||||
var ship = new InventoryCommandRequests.Move(300L, 1, "WT-1", "WT-1", "EVENT", "SHIP-1", null);
|
||||
service.shipWarehouseTransfer("100", "7", 900L, ship, null);
|
||||
assertEquals(0L, position.getAvailablePackages());
|
||||
assertEquals(0L, position.getPickedPackages());
|
||||
verify(ledgers, times(3)).insert(any(InventoryLedger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockInUpdatesBalancePositionAndLedgerInOneTransaction() throws Exception {
|
||||
assertTrue(InventoryCommandService.class.getMethod("stockIn", String.class, String.class,
|
||||
|
||||
+35
-1
@@ -59,6 +59,7 @@ class PdaPalletOperationServiceTest {
|
||||
assertThrows(ResponseStatusException.class, () -> ReflectionTestUtils.invokeMethod(service, "requireUnallocatedRelabel", app));
|
||||
}
|
||||
@Mock private PdaPalletOperationMapper mapper;
|
||||
@Mock private com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper inventoryCommands;
|
||||
@Mock private InventoryPositionMapper positions;
|
||||
@Mock private InventoryBalanceMapper balances;
|
||||
@Mock private InventoryLedgerMapper ledgers;
|
||||
@@ -75,6 +76,7 @@ class PdaPalletOperationServiceTest {
|
||||
@BeforeEach void setUp() {
|
||||
service = new PdaPalletOperationService();
|
||||
ReflectionTestUtils.setField(service, "mapper", mapper);
|
||||
ReflectionTestUtils.setField(service, "inventoryCommands", inventoryCommands);
|
||||
ReflectionTestUtils.setField(service, "positions", positions);
|
||||
ReflectionTestUtils.setField(service, "balances", balances);
|
||||
ReflectionTestUtils.setField(service, "ledgers", ledgers);
|
||||
@@ -187,6 +189,35 @@ class PdaPalletOperationServiceTest {
|
||||
assertTrue(result.printableHtml().contains("RL-1"));
|
||||
}
|
||||
|
||||
@Test void reservedSourceIsRejectedBeforeAnyRemoteTransition() {
|
||||
var source = new PalletPhysicalGateway.PalletReference(20L, "SOURCE", 88L, 1020L,
|
||||
"OCCUPIED", null, 1);
|
||||
var target = new PalletPhysicalGateway.PalletReference(10L, "TARGET", 88L, 1010L,
|
||||
"IDLE", null, 2);
|
||||
when(physical.resolve("SOURCE")).thenReturn(source);
|
||||
when(physical.resolve("TARGET")).thenReturn(target);
|
||||
when(physical.resolveLocation(88L, target.locationId())).thenReturn(
|
||||
new PalletPhysicalGateway.LocationReference(target.locationId(), "LOC-1010", "STORAGE", "ACTIVE"));
|
||||
var row = row(100L, 20L, 1L, 5L);
|
||||
when(mapper.content("88", 20L)).thenReturn(List.of(row));
|
||||
when(mapper.content("88", 10L)).thenReturn(List.of());
|
||||
when(mapper.palletPackages("88", 20L)).thenReturn(5L);
|
||||
when(mapper.palletPackages("88", 10L)).thenReturn(0L);
|
||||
when(mapper.lockPosition("88", 100L)).thenReturn(row);
|
||||
when(inventoryCommands.transferReservation(row.getCustomerId(), 100L)).thenReturn(
|
||||
new com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper.TransferReservation(900L, 5));
|
||||
AtomicReference<PdaPalletOperation> operation = new AtomicReference<>();
|
||||
doAnswer(call -> { operation.set(call.getArgument(0)); return 1; }).when(mapper).insert(any(PdaPalletOperation.class));
|
||||
when(mapper.selectById(anyLong())).thenAnswer(call -> operation.get());
|
||||
var request = new PalletOperationRequests.Assemble("reserved", new PalletOperationRequests.PalletExpectation("TARGET", 2),
|
||||
List.of(new PalletOperationRequests.PalletExpectation("SOURCE", 1)),
|
||||
List.of(new PalletOperationRequests.MoveLine(100L, 5L, 1L)), "assemble");
|
||||
assertThrows(ResponseStatusException.class, () -> service.assemble(identity(), request));
|
||||
verify(physical, never()).apply(anyLong(), any(), anyString(), anyLong(), anyString(), any());
|
||||
verify(positions, never()).updateById(any(InventoryPosition.class));
|
||||
verifyNoInteractions(ledgers);
|
||||
}
|
||||
|
||||
@Test void localConcurrencyFailureCompensatesEveryAppliedPhysicalTransitionInReverseSafeForm() {
|
||||
var source = new PalletPhysicalGateway.PalletReference(20L, "SOURCE", 88L, 1020L,
|
||||
"OCCUPIED", null, 1);
|
||||
@@ -203,7 +234,7 @@ class PdaPalletOperationServiceTest {
|
||||
when(mapper.content("88", 10L)).thenReturn(List.of());
|
||||
when(mapper.palletPackages("88", 20L)).thenReturn(5L);
|
||||
when(mapper.palletPackages("88", 10L)).thenReturn(0L);
|
||||
when(mapper.lockPosition("88", 100L)).thenReturn(stale);
|
||||
when(mapper.lockPosition("88", 100L)).thenReturn(planned, stale);
|
||||
|
||||
AtomicReference<PdaPalletOperation> operation = new AtomicReference<>();
|
||||
doAnswer(invocation -> { operation.set(invocation.getArgument(0)); return 1; })
|
||||
@@ -304,6 +335,9 @@ class PdaPalletOperationServiceTest {
|
||||
doAnswer(invocation -> { target.set(invocation.getArgument(0)); return 1; }).when(positions)
|
||||
.insert(any(InventoryPosition.class));
|
||||
PalletInventoryRow held = row(100L, 20L, 3L, 0L); held.setPalletNo("SOURCE"); held.setHoldPackages(2L);
|
||||
when(inventoryCommands.relabelTransfer(application.getCustomerId(), application.getId())).thenReturn(900L);
|
||||
when(inventoryCommands.transferReservation(application.getCustomerId(), 100L)).thenReturn(
|
||||
new com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper.TransferReservation(900L, 2));
|
||||
when(mapper.content("88", 20L)).thenReturn(List.of(held));
|
||||
when(mapper.content("88", 10L)).thenReturn(List.of());
|
||||
when(mapper.lockPosition("88", 100L)).thenReturn(held);
|
||||
|
||||
+1
-1
@@ -828,7 +828,7 @@ class RelabelApplicationServiceTest {
|
||||
assertEquals("SETTLE_PENDING", app.getWalletStatus()); assertEquals(new BigDecimal("0.01"), app.getChargeAmount());
|
||||
assertEquals("CNY", app.getChargeCurrency());
|
||||
verify(inventoryCommands, never()).release(any(), any(), any());
|
||||
verify(inventoryCommands, never()).releaseTransferRelabel(any(), any(), any(), any());
|
||||
verify(inventoryCommands, never()).releaseTransferRelabel(any(), any(), any(), any(), any());
|
||||
verify(transferRelabelCompletion, never()).complete(any(), any(), any(), any(), any());
|
||||
verify(histories, times(1)).insert(any(RelabelStatusHistory.class));
|
||||
}
|
||||
|
||||
+49
-3
@@ -86,6 +86,50 @@ class WarehouseTransferRelabelPersistenceTest {
|
||||
session.getMapper(WarehouseTransferRelabelBindingMapper.class).insert(binding);
|
||||
}
|
||||
void position(long id,long pallet,long available,long hold,String batch){jdbc.update("INSERT INTO ll_inventory_position(id,customer_id,balance_id,overseas_warehouse_id,destination_warehouse_code,pallet_id,pallet_no,batch_no,available_packages,hold_packages,allocated_packages,picked_packages,receiving_packages,version) VALUES (?,'7',60,'9','ABE4',?,?,?, ?,?,0,0,0,0)",id,pallet,"P-"+pallet,batch,available,hold);}
|
||||
@Test void linkedRelabelCreationAndReleaseUseRealTransferOwnerAndInventory() {
|
||||
transfer.setRelabelNo("RL-OWN");
|
||||
application.setRelabelNo("RL-OWN"); application.setLastlegOrderId(10L);
|
||||
session.getMapper(WarehouseTransferOrderMapper.class).insert(transfer);
|
||||
var relabel=new com.baiyue.lastleg.relabel.service.RelabelApplicationService();
|
||||
ReflectionTestUtils.setField(relabel,"warehouseTransfers",session.getMapper(WarehouseTransferOrderMapper.class));
|
||||
ReflectionTestUtils.setField(relabel,"inventoryCommands",inventory);
|
||||
var hold=new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(101L,1,"RL-OWN",null,
|
||||
"RELABEL:RL-OWN","LINKED-HOLD",null);
|
||||
tx.executeWithoutResult(status->ReflectionTestUtils.invokeMethod(relabel,"holdRelabelInventory",
|
||||
"7","operator",hold,10L,null));
|
||||
assertEquals(1L,jdbc.queryForObject("SELECT hold_packages FROM ll_inventory_position WHERE id=101",Long.class));
|
||||
var release=new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(101L,1,"RL-OWN",null,
|
||||
"RELABEL:RL-OWN","LINKED-RELEASE",null);
|
||||
tx.executeWithoutResult(status->ReflectionTestUtils.invokeMethod(relabel,"releaseRelabelInventory",
|
||||
"7","operator",release,application));
|
||||
assertEquals(0L,jdbc.queryForObject("SELECT hold_packages FROM ll_inventory_position WHERE id=101",Long.class));
|
||||
var retry=new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(101L,1,"RL-OWN",null,
|
||||
"RELABEL:RL-OWN","LINKED-RETRY",null);
|
||||
tx.executeWithoutResult(status->ReflectionTestUtils.invokeMethod(relabel,"holdRelabelInventory",
|
||||
"7","operator",retry,null,application));
|
||||
assertEquals(1L,jdbc.queryForObject("SELECT hold_packages FROM ll_inventory_position WHERE id=101",Long.class));
|
||||
var independent=new com.baiyue.lastleg.relabel.entity.RelabelApplication();
|
||||
independent.setId(999L); independent.setLastlegOrderId(999L); independent.setRelabelNo("RL-OTHER");
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,
|
||||
()->tx.executeWithoutResult(status->ReflectionTestUtils.invokeMethod(relabel,"holdRelabelInventory",
|
||||
"7","operator",new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(101L,1,
|
||||
"RL-OTHER",null,"RELABEL:RL-OTHER","OTHER-HOLD",null),null,independent)));
|
||||
}
|
||||
|
||||
@Test void realReservationBlocksNormalConsumersAndWrongInternalOwner() {
|
||||
var request=new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(101L,1,"WT-ORIGINAL","WT-ORIGINAL",
|
||||
"WAREHOUSE_TRANSFER_OUTBOUND:WT-ORIGINAL","WAREHOUSE_TRANSFER:FORGED:ALLOCATE",null);
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,
|
||||
()->tx.executeWithoutResult(status->inventory.allocate("7","operator",request)));
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,
|
||||
()->tx.executeWithoutResult(status->inventory.hold("7","operator",request)));
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,
|
||||
()->tx.executeWithoutResult(status->inventory.allocateWarehouseTransfer("7","operator",2L,request,null)));
|
||||
assertEquals(2L,jdbc.queryForObject("SELECT available_packages FROM ll_inventory_position WHERE id=101",Long.class));
|
||||
assertEquals("RESERVED",jdbc.queryForObject("SELECT status FROM ll_warehouse_transfer_inventory_allocation WHERE id=13",String.class));
|
||||
assertEquals(0L,jdbc.queryForObject("SELECT COUNT(*) FROM ll_inventory_ledger",Long.class));
|
||||
}
|
||||
|
||||
@Test void confirmsMovedCartonIntoNewSkuAndPreservesOtherHoldWithRealCommandsAndMappers() {
|
||||
tx.executeWithoutResult(s->completion.complete(transfer,application,List.of(item),"operator",LocalDateTime.now()));
|
||||
assertEquals(4L,jdbc.queryForObject("SELECT SUM(available_packages+hold_packages+allocated_packages+picked_packages) FROM ll_inventory_position",Long.class));
|
||||
@@ -121,12 +165,14 @@ class WarehouseTransferRelabelPersistenceTest {
|
||||
var allocate=new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(actual.getInventoryPositionId(),1,"WT-ORIGINAL","WT-ORIGINAL","WAREHOUSE_TRANSFER_OUTBOUND:WT-ORIGINAL","WAREHOUSE_TRANSFER:WT-ORIGINAL:ALLOCATE","");
|
||||
var pick=new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(actual.getInventoryPositionId(),1,"WT-ORIGINAL","WT-ORIGINAL","WAREHOUSE_TRANSFER_OUTBOUND:WT-ORIGINAL","WAREHOUSE_TRANSFER:WT-ORIGINAL:PICK","");
|
||||
var ship=new com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move(actual.getInventoryPositionId(),1,"WT-ORIGINAL","WT-ORIGINAL","WAREHOUSE_TRANSFER_OUTBOUND:WT-ORIGINAL","WAREHOUSE_TRANSFER:WT-ORIGINAL:SHIP","");
|
||||
tx.executeWithoutResult(s->{inventory.allocateTransferSnapshot("7","operator",allocate,actual.getSkuCode());inventory.pickTransferSnapshot("7","operator",pick,actual.getSkuCode());inventory.shipTransferSnapshot("7","operator",ship,actual.getSkuCode());});
|
||||
tx.executeWithoutResult(s->{assertTrue(inventory.allocateTransferSnapshot("7","operator",allocate,"NEW").replayed());assertTrue(inventory.pickTransferSnapshot("7","operator",pick,"NEW").replayed());assertTrue(inventory.shipTransferSnapshot("7","operator",ship,"NEW").replayed());});
|
||||
tx.executeWithoutResult(s->{inventory.allocateWarehouseTransfer("7","operator",1L,allocate,actual.getSkuCode());inventory.pickWarehouseTransfer("7","operator",1L,pick,actual.getSkuCode());inventory.shipWarehouseTransfer("7","operator",1L,ship,actual.getSkuCode());});
|
||||
tx.executeWithoutResult(s->{assertTrue(inventory.allocateWarehouseTransfer("7","operator",1L,allocate,"NEW").replayed());assertTrue(inventory.pickWarehouseTransfer("7","operator",1L,pick,"NEW").replayed());assertTrue(inventory.shipWarehouseTransfer("7","operator",1L,ship,"NEW").replayed());});
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,
|
||||
()->tx.executeWithoutResult(status->inventory.allocateWarehouseTransfer("7","operator",2L,allocate,"NEW")));
|
||||
assertEquals(3L,jdbc.queryForObject("SELECT SUM(available_packages+hold_packages+allocated_packages+picked_packages) FROM ll_inventory_position",Long.class));
|
||||
assertEquals(1L,jdbc.queryForObject("SELECT COUNT(*) FROM ll_inventory_ledger WHERE business_type='SHIP' AND sku_code='NEW'",Long.class));
|
||||
assertEquals(0L,jdbc.queryForObject("SELECT COUNT(*) FROM ll_inventory_ledger WHERE business_type IN ('SHIP','PICK','ALLOCATE') AND sku_code<>'NEW'",Long.class));
|
||||
assertThrows(RuntimeException.class,()->tx.executeWithoutResult(s->inventory.shipTransferSnapshot("7","operator",ship,"OLD")));
|
||||
assertThrows(RuntimeException.class,()->tx.executeWithoutResult(s->inventory.shipWarehouseTransfer("7","operator",1L,ship,"OLD")));
|
||||
assertEquals(1L,jdbc.queryForObject("SELECT hold_packages FROM ll_inventory_position WHERE id=99",Long.class));
|
||||
}
|
||||
@Test void cartonOnlyConfirmationKeepsSamePositionAndRetainsOriginalBindingHistory(){
|
||||
|
||||
+5
-5
@@ -793,9 +793,9 @@ class WarehouseTransferServiceTest {
|
||||
var ordered = inOrder(inventory);
|
||||
ArgumentCaptor<com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move> move =
|
||||
ArgumentCaptor.forClass(com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.Move.class);
|
||||
ordered.verify(inventory).allocate(eq("TENANT-A"), eq("USER-1"), move.capture());
|
||||
ordered.verify(inventory).pick(eq("TENANT-A"), eq("USER-1"), move.capture());
|
||||
ordered.verify(inventory).ship(eq("TENANT-A"), eq("USER-1"), move.capture());
|
||||
ordered.verify(inventory).allocateWarehouseTransfer(eq("TENANT-A"), eq("USER-1"), anyLong(), move.capture(), org.mockito.ArgumentMatchers.isNull());
|
||||
ordered.verify(inventory).pickWarehouseTransfer(eq("TENANT-A"), eq("USER-1"), anyLong(), move.capture(), org.mockito.ArgumentMatchers.isNull());
|
||||
ordered.verify(inventory).shipWarehouseTransfer(eq("TENANT-A"), eq("USER-1"), anyLong(), move.capture(), org.mockito.ArgumentMatchers.isNull());
|
||||
assertTrue(move.getAllValues().stream().allMatch(value -> value.positionId() == 501L && value.packages() == 5));
|
||||
assertEquals(WarehouseTransferService.OUTBOUNDED, transfer.getStatus());
|
||||
assertEquals("SHIPPED", allocation.getStatus());
|
||||
@@ -958,7 +958,7 @@ class WarehouseTransferServiceTest {
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
service.outbound("TENANT-A","WTR-1","USER-1",new WarehouseTransferRequests.Outbound("OUT-NEW","departed"));
|
||||
service.outbound("TENANT-A","WTR-1","USER-1",new WarehouseTransferRequests.Outbound("OUT-NEW","departed"));
|
||||
verify(inventory).shipTransferSnapshot(eq("TENANT-A"),eq("USER-1"),any(),eq("NEW"));
|
||||
verify(inventory).shipWarehouseTransfer(eq("TENANT-A"),eq("USER-1"),anyLong(),any(),eq("NEW"));
|
||||
verify(inventory,never()).ship(anyString(),anyString(),any());assertEquals("OUTBOUNDED",app.getFulfillmentStatus());
|
||||
var mixed=balance(601,101,"IN-1");mixed.setSkuCode("MIXED");when(inventoryCommands.lockBalance("TENANT-A",601L)).thenReturn(mixed);
|
||||
when(inventory.stockIn(eq("TENANT-A"),eq("USER-1"),any())).thenReturn(new InventoryCommandResponse(1L,"STOCK_IN",801L,901L,5,0,0,0,false,false));
|
||||
@@ -984,7 +984,7 @@ class WarehouseTransferServiceTest {
|
||||
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(inventoryAllocations.lockTransfer("TENANT-A", 21L)).thenReturn(List.of(allocation));
|
||||
doThrow(new ResponseStatusException(org.springframework.http.HttpStatus.CONFLICT, "insufficient"))
|
||||
.when(inventory).pick(eq("TENANT-A"), eq("USER-1"), any());
|
||||
.when(inventory).pickWarehouseTransfer(eq("TENANT-A"), eq("USER-1"), anyLong(), any(), org.mockito.ArgumentMatchers.isNull());
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.outbound("TENANT-A", "WTR-1", "USER-1",
|
||||
new WarehouseTransferRequests.Outbound("OUT-1", null)));
|
||||
|
||||
Reference in New Issue
Block a user