Merge remote-tracking branch 'origin/test' into test

# Conflicts:
#	deploy/sql/README.md
#	deploy/sql/海外区域统一增量-MySQL8.sql
This commit is contained in:
2026-10-16 15:44:59 +08:00
61 changed files with 1246 additions and 125 deletions
@@ -0,0 +1,42 @@
package com.baiyue.lastleg.inventory.mapper;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
class InventoryCartonOwnershipTest {
@Test void realSqlRequiresMatchingCustomerWarehouseAndInboundForEachCarton() {
var ds = new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "");
var db = new JdbcTemplate(ds);
db.execute("CREATE TABLE ll_inbound_plan_carton(order_id BIGINT,customer_id VARCHAR,carton_mark VARCHAR)");
db.execute("CREATE TABLE ll_order(id BIGINT,customer_id VARCHAR,inbound_code VARCHAR,overseas_warehouse_id VARCHAR)");
db.execute("CREATE TABLE ll_inbound_order(id BIGINT,customer_id VARCHAR,inbound_code VARCHAR)");
db.execute("CREATE TABLE ll_warehouse_transfer_order(id BIGINT,customer_id VARCHAR,inbound_order_id BIGINT,operating_warehouse_id VARCHAR)");
db.execute("CREATE TABLE ll_warehouse_transfer_carton(transfer_order_id BIGINT,customer_id VARCHAR,carton_mark VARCHAR,customer_carton_mark VARCHAR,status VARCHAR)");
db.update("INSERT INTO ll_inbound_order VALUES(1,'C1','IN1'),(2,'C1','IN2')");
db.update("INSERT INTO ll_order VALUES(10,'C1','IN1','WH1'),(20,'C1','IN2','WH1')");
db.update("INSERT INTO ll_inbound_plan_carton VALUES(10,'C1','BOX1'),(20,'C1','OTHER-ORDER')");
db.update("INSERT INTO ll_warehouse_transfer_order VALUES(30,'C1',1,'WH1'),(40,'C2',1,'WH1')");
db.update("INSERT INTO ll_warehouse_transfer_carton VALUES(30,'C1','TRANSFER-BOX','CUSTOMER-BOX','COUNTED'),(30,'C1','UNCHECKED','CUSTOMER-UNCHECKED','NOT_RECEIVED'),(40,'C2','OTHER-CUSTOMER',NULL,'COUNTED')");
var config = new Configuration(new Environment("test", new JdbcTransactionFactory(), ds));
config.addMapper(InventoryBalanceMapper.class);
try (var session = new SqlSessionFactoryBuilder().build(config).openSession()) {
var mapper = session.getMapper(InventoryBalanceMapper.class);
for (String mark : new String[]{"BOX1", "TRANSFER-BOX", "CUSTOMER-BOX"}) {
assertTrue(mapper.cartonBelongsToInbound("C1", "WH1", 1L, mark));
assertFalse(mapper.cartonBelongsToInbound("C1", "WH2", 1L, mark));
assertFalse(mapper.cartonBelongsToInbound("C2", "WH1", 1L, mark));
assertFalse(mapper.cartonBelongsToInbound("C1", "WH1", 2L, mark));
}
for (String mark : new String[]{"MISSING", "OTHER-ORDER", "OTHER-CUSTOMER", "UNCHECKED"})
assertFalse(mapper.cartonBelongsToInbound("C1", "WH1", 1L, mark));
}
}
}
@@ -75,6 +75,7 @@ class PdaPalletOperationServiceTest {
@BeforeEach void setUp() {
service = new PdaPalletOperationService();
ReflectionTestUtils.setField(service, "cartonMeasurements", org.mockito.Mockito.mock(com.baiyue.lastleg.relabel.service.RelabelCartonMeasurementService.class));
ReflectionTestUtils.setField(service, "mapper", mapper);
ReflectionTestUtils.setField(service, "inventoryCommands", inventoryCommands);
ReflectionTestUtils.setField(service, "positions", positions);
@@ -212,9 +212,13 @@ class RelabelApplicationServiceTest {
private RelabelApplicationService service;
private com.baiyue.lastleg.service.CompactBusinessNumberService compactNumbers;
@BeforeEach
void setUp() {
compactNumbers = org.mockito.Mockito.mock(com.baiyue.lastleg.service.CompactBusinessNumberService.class);
service = new RelabelApplicationService();
ReflectionTestUtils.setField(service, "compactNumbers", compactNumbers);
org.mockito.Mockito.lenient().when(compactNumbers.next(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyString())).thenAnswer(call -> call.getArgument(0) + "-CS-261016102030");
ReflectionTestUtils.setField(service, "bindingGuard", bindingGuard);
ReflectionTestUtils.setField(service, "returnPackingTraces", org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnPackingTraceMapper.class));
ReflectionTestUtils.setField(service, "skuConversions", org.mockito.Mockito.mock(com.baiyue.lastleg.relabel.service.ReturnSkuConversionService.class));
@@ -251,11 +255,21 @@ class RelabelApplicationServiceTest {
balance.setOverseasWarehouseId("WH-1"); balance.setInboundOrderId(301L); balance.setInboundOrderNo("IN-1");
balance.setInboundCode("CODE-1"); balance.setSkuCode("SKU-A"); balance.setProductNameZh("Chinese name");
lenient().when(inventoryBalances.selectById(any(Long.class))).thenReturn(balance);
lenient().when(inventoryBalances.cartonBelongsToInbound(anyString(), anyString(), anyLong(), anyString())).thenReturn(true);
ReflectionTestUtils.setField(service, "feeSources", feeSources);
lenient().when(feeSources.publishApproved(any(), any(), any())).thenReturn(new OverseasFeeSource());
}
@Test
void rejectsCartonOutsideSelectedInboundBeforeCreatingOrHoldingInventory() {
when(inventoryBalances.cartonBelongsToInbound(eq("10001"), eq("WH-1"), eq(301L), anyString())).thenReturn(false);
var error = assertThrows(ResponseStatusException.class, () -> service.create("10001", "operator-1", request("bad-carton", 1, 1)));
assertEquals("当前箱子不存在或不属于当前订单", error.getReason());
verify(applications, never()).insert(any(RelabelApplication.class));
verify(inventoryCommands, never()).hold(any(), any(), any());
}
@Test
void createBuildsPendingRelabelTailOrderItemsAndBothHistories() {
RelabelRequests.Create request = request("key-1", 10, 6);
@@ -286,7 +300,7 @@ class RelabelApplicationServiceTest {
assertEquals("PENDING_REVIEW", tailOrder.getValue().getCurrentStatus());
assertEquals("RELABEL:PENDING_REVIEW", tailOrder.getValue().getSubOrderStatus());
assertEquals("CUSTOMER", tailOrder.getValue().getSourceRegion());
assertTrue(tailOrder.getValue().getOrderNo().matches("RL-WH-\\d{8}-\\d{6}"));
assertTrue(tailOrder.getValue().getOrderNo().matches("TB-CS-[0-9]{12}"));
assertNull(tailOrder.getValue().getInboundCode());
assertEquals(application.getValue().getRelabelNo() + ":WH-1", tailOrder.getValue().getSourceInboundId());
assertEquals(tailOrder.getValue().getRelabelOrderNo(), application.getValue().getRelabelNo());
@@ -0,0 +1,89 @@
package com.baiyue.lastleg.relabel.service;
import com.baiyue.lastleg.relabel.entity.*;
import com.baiyue.lastleg.relabel.mapper.*;
import com.baiyue.lastleg.relabel.pda.entity.PdaRelabelHistory;
import com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelHistoryMapper;
import com.baiyue.lastleg.palletops.dto.PalletOperationRequests.MeasuredCartonBinding;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.*;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
class RelabelCartonMeasurementServiceTest {
final RelabelCartonMeasurementService boxes = new RelabelCartonMeasurementService();
final RelabelPalletMeasurementService pallets = new RelabelPalletMeasurementService();
final RelabelPalletMeasurementMapper mapper = mock(RelabelPalletMeasurementMapper.class);
final PdaRelabelHistoryMapper histories = mock(PdaRelabelHistoryMapper.class);
final RelabelItemMapper items = mock(RelabelItemMapper.class);
final ObjectMapper json = new ObjectMapper();
final RelabelApplication app = new RelabelApplication();
final List<PdaRelabelHistory> records = new ArrayList<>();
@BeforeEach void setup() {
app.setId(1L); app.setCustomerId("7"); app.setRelabelNo("RL-1"); app.setOverseasWarehouseId("92");
for (Object service : List.of(boxes,pallets)) {
ReflectionTestUtils.setField(service,"mapper",mapper);
ReflectionTestUtils.setField(service,"histories",histories);
ReflectionTestUtils.setField(service,"json",json);
}
ReflectionTestUtils.setField(pallets,"items",items);
ReflectionTestUtils.setField(pallets,"cartonMeasurements",boxes);
when(mapper.transfer("7","RL-1")).thenReturn(10L);
when(histories.selectList(any())).thenAnswer(i -> List.copyOf(records));
when(mapper.originals("7",10L)).thenReturn(List.of());
when(items.selectList(any())).thenReturn(List.of());
when(mapper.cartons("7",10L)).thenReturn(List.of(box("BIG",100,60,50,50,"2.5"),box("SMALL1",100,50,40,40,"3.6"),box("SMALL2",100,50,40,40,"3.6")));
}
RelabelPalletMeasurementMapper.Carton box(String mark,long pallet,int l,int w,int h,String kg) {
return new RelabelPalletMeasurementMapper.Carton((long)mark.hashCode(),mark,null,pallet,"P"+pallet,
BigDecimal.valueOf(l),BigDecimal.valueOf(w),BigDecimal.valueOf(h),new BigDecimal(kg));
}
RelabelItem item(String mark) { var i=new RelabelItem(); i.setCartonMark(mark); i.setNotifiedPackageCount(1); return i; }
void save(List<RelabelCartonMeasurementService.Move> moves) throws Exception {
var h=new PdaRelabelHistory(); h.setAction("PALLET_BIND"); h.setDetailJson(json.writeValueAsString(Map.of("measuredCartons",moves))); records.add(h);
}
void stock(long source,long target,long third) {
when(mapper.stock("7","92")).thenReturn(List.of(new RelabelPalletMeasurementMapper.Stock(1L,100L,"P100",source,1),
new RelabelPalletMeasurementMapper.Stock(2L,200L,"P200",target,1),new RelabelPalletMeasurementMapper.Stock(3L,300L,"P300",third,1)));
}
void totals(long pallet,String cbm,String kg) {
var p=pallets.snapshots(app).stream().filter(row -> row.palletId()==pallet).findFirst().orElseThrow();
assertTrue(p.cartonAggregated()); assertNull(p.lengthCm());
assertEquals(0,new BigDecimal(cbm).compareTo(p.volumeCbm())); assertEquals(0,new BigDecimal(kg).compareTo(p.grossWeightKg()));
}
@Test void movingMixedBoxSubtractsExactMetricsAndRebindingMovesSameBoxOnce() throws Exception {
stock(3,0,0); totals(100,"0.31","9.7");
save(boxes.prepare(app,List.of(item("CUSTOMER")),100L,200L,"P200",List.of(new MeasuredCartonBinding("CUSTOMER","SMALL1"))));
stock(2,1,0); totals(100,"0.23","6.1"); totals(200,"0.08","3.6");
assertDoesNotThrow(() -> pallets.requireMeasured(app));
var rebind=boxes.prepare(app,List.of(item("CUSTOMER")),200L,300L,"P300",null);
assertEquals("SMALL1",rebind.get(0).sourceCartonMark()); save(rebind); save(rebind);
stock(2,0,1); totals(100,"0.23","6.1"); totals(300,"0.08","3.6");
assertEquals(3,boxes.current(app).size()); assertDoesNotThrow(() -> pallets.requireMeasured(app));
}
@Test void equalSpecificationsNeedNoExtraBoxSelection() throws Exception {
when(mapper.cartons("7",10L)).thenReturn(List.of(box("A",100,60,50,50,"2.5"),box("B",100,60,50,50,"2.50")));
save(boxes.prepare(app,List.of(item("CUSTOMER")),100L,200L,"P200",null));
stock(1,1,0); totals(100,"0.15","2.5"); totals(200,"0.15","2.5");
}
@Test void ambiguousForeignDuplicateAndAlreadyUsedBoxesAreRejected() throws Exception {
assertThrows(ResponseStatusException.class,()->boxes.prepare(app,List.of(item("X")),100L,200L,"P200",null));
assertThrows(ResponseStatusException.class,()->boxes.prepare(app,List.of(item("X")),100L,200L,"P200",List.of(new MeasuredCartonBinding("X","FOREIGN"))));
assertThrows(ResponseStatusException.class,()->boxes.prepare(app,List.of(item("X"),item("Y")),100L,200L,"P200",List.of(new MeasuredCartonBinding("X","BIG"),new MeasuredCartonBinding("Y","BIG"))));
save(boxes.prepare(app,List.of(item("X")),100L,200L,"P200",List.of(new MeasuredCartonBinding("X","BIG"))));
assertThrows(ResponseStatusException.class,()->boxes.prepare(app,List.of(item("Y")),200L,300L,"P300",List.of(new MeasuredCartonBinding("Y","BIG"))));
assertThrows(ResponseStatusException.class,()->boxes.prepare(app,List.of(item("X")),200L,300L,"P300",List.of(new MeasuredCartonBinding("X","SMALL1"))));
}
@Test void missingBoxMeasurementsOrUntrackedContentsCannotPassCompletion() {
when(mapper.cartons("7",10L)).thenReturn(List.of(box("A",100,0,50,50,"2.5")));
assertThrows(ResponseStatusException.class,()->boxes.prepare(app,List.of(item("A")),100L,200L,"P200",null));
stock(1,0,0); assertThrows(ResponseStatusException.class,()->pallets.requireMeasured(app));
when(mapper.cartons("7",10L)).thenReturn(List.of(box("A",100,60,50,50,"2.5")));
stock(2,0,0); assertThrows(ResponseStatusException.class,()->pallets.requireMeasured(app));
}
}
@@ -0,0 +1,44 @@
package com.baiyue.lastleg.relabel.service;
import com.baiyue.lastleg.relabel.mapper.RelabelPalletMeasurementMapper;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.*;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.util.UUID;
import static org.assertj.core.api.Assertions.*;
class RelabelPalletMeasurementMapperTest {
@Test void realSqlGroupsMeasuredCartonsAndKeepsCustomerTransferAndPalletBoundaries() {
var ds = new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "");
var db = new JdbcTemplate(ds);
db.execute("CREATE TABLE ll_warehouse_transfer_pallet(id BIGINT,customer_id VARCHAR,transfer_order_id BIGINT,warehouse_pallet_id BIGINT,status VARCHAR)");
db.execute("CREATE TABLE ll_warehouse_transfer_carton(customer_id VARCHAR,transfer_order_id BIGINT,transfer_pallet_id BIGINT,status VARCHAR,length_cm DECIMAL(12,2),width_cm DECIMAL(12,2),height_cm DECIMAL(12,2),gross_weight_kg DECIMAL(12,3))");
db.update("INSERT INTO ll_warehouse_transfer_pallet VALUES(1,'7',10,100,'COMPLETED'),(2,'8',10,200,'COMPLETED'),(3,'7',20,300,'COMPLETED'),(4,'7',10,400,'BOUND')");
db.update("INSERT INTO ll_warehouse_transfer_carton VALUES('7',10,1,'COUNTED',60,50,50,2.5),('7',10,1,'COUNTED',60,50,50,2.5),('7',10,1,'COUNTED',50,40,40,3.6),('7',10,1,'NOT_RECEIVED',60,50,50,2.5),('8',10,2,'COUNTED',60,50,50,2.5),('7',20,3,'COUNTED',60,50,50,2.5),('7',10,4,'COUNTED',60,50,50,2.5),('7',10,2,'COUNTED',60,50,50,2.5)");
db.execute("ALTER TABLE ll_warehouse_transfer_carton ADD id BIGINT GENERATED BY DEFAULT AS IDENTITY");
db.execute("ALTER TABLE ll_warehouse_transfer_carton ADD carton_mark VARCHAR DEFAULT 'BOX'");
db.execute("ALTER TABLE ll_warehouse_transfer_carton ADD customer_carton_mark VARCHAR DEFAULT 'CUSTOMER'");
db.execute("ALTER TABLE ll_warehouse_transfer_pallet ADD pallet_no VARCHAR DEFAULT 'P100'");
var configuration = new Configuration(new Environment("test", new JdbcTransactionFactory(), ds));
configuration.addMapper(RelabelPalletMeasurementMapper.class);
try (var session = new SqlSessionFactoryBuilder().build(configuration).openSession()) {
var rows = session.getMapper(RelabelPalletMeasurementMapper.class).cartonSpecs("7", 10L);
assertThat(rows).hasSize(2).allSatisfy(row -> assertThat(row.palletId()).isEqualTo(100L));
assertThat(rows.get(0).lengthCm()).isEqualByComparingTo("50");
assertThat(rows.get(0).cartons()).isEqualTo(1);
assertThat(rows.get(1).lengthCm()).isEqualByComparingTo("60");
assertThat(rows.get(1).grossWeightKg()).isEqualByComparingTo("2.5");
assertThat(rows.get(1).cartons()).isEqualTo(2);
var boxes = session.getMapper(RelabelPalletMeasurementMapper.class).cartons("7", 10L);
assertThat(boxes).hasSize(3).allSatisfy(box -> {
assertThat(box.palletId()).isEqualTo(100L);
assertThat(box.palletCode()).isEqualTo("P100");
assertThat(box.cartonMark()).isEqualTo("BOX");
assertThat(box.customerCartonMark()).isEqualTo("CUSTOMER");
});
}
}
}
@@ -29,6 +29,7 @@ class RelabelPalletMeasurementServiceTest {
ReflectionTestUtils.setField(service,"mapper",mapper); ReflectionTestUtils.setField(service,"applications",apps);
ReflectionTestUtils.setField(service,"items",items); ReflectionTestUtils.setField(service,"histories",histories);
ReflectionTestUtils.setField(service,"tasks",tasks); ReflectionTestUtils.setField(service,"json",json);
ReflectionTestUtils.setField(service,"cartonMeasurements",mock(RelabelCartonMeasurementService.class));
app.setId(1L);app.setCustomerId("7");app.setRelabelNo("RL-1");app.setOverseasWarehouseId("92");
app.setStatus("READY_FOR_OUTBOUND");app.setFulfillmentStatus("WAIT_PRE_DISPATCH");
when(apps.findAnyByRelabelNo("RL-1")).thenReturn(app);when(apps.lockAnyByRelabelNo("RL-1")).thenReturn(app);
@@ -49,6 +50,45 @@ class RelabelPalletMeasurementServiceTest {
var p = service.snapshots(app).get(0);assertTrue(p.inherited()); assertEquals(new BigDecimal("80"),p.grossWeightKg());
assertEquals(BigDecimal.ONE,p.volumeCbm());assertDoesNotThrow(() -> service.requireMeasured(app));
}
@Test void cartonMeasuredSourceShowsRealBoxSpecsWithoutInventingPalletDimensions() {
cartonMeasuredSource();
var p = service.snapshots(app).get(0);
assertTrue(p.inherited()); assertNull(p.lengthCm()); assertNull(p.widthCm()); assertNull(p.heightCm());
assertEquals(1, p.sourceCartonSpecs().size());
assertEquals(new BigDecimal("60"), p.sourceCartonSpecs().get(0).lengthCm());
assertEquals(2, p.sourceCartonSpecs().get(0).cartons());
assertEquals(new BigDecimal("5.0"), p.grossWeightKg());
assertEquals(new BigDecimal("0.3"), p.volumeCbm());
assertDoesNotThrow(() -> service.requireMeasured(app));
}
@Test void incompleteOrMismatchedSourceCartonsCannotBypassMeasurement() {
cartonMeasuredSource();
for (var spec : List.of(
new RelabelPalletMeasurementMapper.CartonSpec(100L,new BigDecimal("60"),new BigDecimal("50"),new BigDecimal("50"),new BigDecimal("2.5"),1),
new RelabelPalletMeasurementMapper.CartonSpec(100L,new BigDecimal("61"),new BigDecimal("50"),new BigDecimal("50"),new BigDecimal("2.5"),2),
new RelabelPalletMeasurementMapper.CartonSpec(100L,new BigDecimal("60"),new BigDecimal("50"),new BigDecimal("50"),new BigDecimal("3"),2),
new RelabelPalletMeasurementMapper.CartonSpec(100L,BigDecimal.ZERO,new BigDecimal("50"),new BigDecimal("50"),new BigDecimal("2.5"),2))) {
when(mapper.cartonSpecs("7",10L)).thenReturn(List.of(spec));
assertThrows(ResponseStatusException.class, () -> service.requireMeasured(app));
}
when(mapper.cartonSpecs("7",10L)).thenReturn(List.of());
assertThrows(ResponseStatusException.class, () -> service.requireMeasured(app));
}
@Test void changedCartonMeasuredPalletMustBeRemeasuredInsteadOfReusingOldBoxSpecs() {
cartonMeasuredSource();
when(mapper.movements("7",100L)).thenReturn(1);
var p = service.snapshots(app).get(0);
assertFalse(p.inherited()); assertTrue(p.sourceCartonSpecs().isEmpty());
assertNull(p.volumeCbm());
assertThrows(ResponseStatusException.class, () -> service.requireMeasured(app));
}
private void cartonMeasuredSource() {
when(mapper.originals("7",10L)).thenReturn(List.of(new RelabelPalletMeasurementMapper.Original(100L,"OLD",2,
BigDecimal.ZERO,BigDecimal.ZERO,BigDecimal.ZERO,new BigDecimal("5.0"),new BigDecimal("0.3"))));
when(mapper.stock("7","92")).thenReturn(List.of(stock(1,100,"OLD",2,1)));
when(mapper.cartonSpecs("7",10L)).thenReturn(List.of(new RelabelPalletMeasurementMapper.CartonSpec(100L,
new BigDecimal("60"),new BigDecimal("50"),new BigDecimal("50"),new BigDecimal("2.5"),2)));
}
@Test void partialSplitRequiresBothNewPalletAndOriginalRemainderMeasurements() throws Exception {
split(); var values = service.snapshots(app);assertEquals(2,values.size());
assertTrue(values.stream().allMatch(p -> p.volumeCbm()==null && p.grossWeightKg()==null));
@@ -184,7 +184,7 @@ class ReturnInventoryServiceTest {
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("RP");
assertThat(source.getCartonMark()).startsWith("RP-").hasSizeLessThanOrEqualTo(16);
assertThat(source.getSourceUnitType()).isEqualTo("PARCEL");
verifyNoInteractions(numbers);
assertThat(source.getLabelStatus()).isEqualTo("CONFIRMED");
@@ -305,7 +305,7 @@ class ReturnInventoryServiceTest {
assertThat(created).hasSize(2);
assertThat(created).extracting(ReturnInventoryCarton::getCartonMark)
.allMatch(mark -> mark.matches("RC[A-F0-9]{16}#2-[12]"))
.allMatch(mark -> mark.matches("RC[0-9A-Z]{1,13}#2-[12]"))
.doesNotHaveDuplicates();
assertThat(created.get(0).getCartonMark()).endsWith("#2-1");
assertThat(created.get(1).getCartonMark()).endsWith("#2-2");
@@ -538,7 +538,7 @@ class ReturnInventoryServiceTest {
String a = ReflectionTestUtils.invokeMethod(service, "shortReturnMark", "RT-ORDER", 123L, "SAMEPREFIX-A", 2L, 1L);
String b = ReflectionTestUtils.invokeMethod(service, "shortReturnMark", "RT-ORDER", 123L, "SAMEPREFIX-B", 2L, 1L);
String second = ReflectionTestUtils.invokeMethod(service, "shortReturnMark", "RT-ORDER", 123L, "SAMEPREFIX-A", 2L, 2L);
assertThat(a).isNotEqualTo(b).matches("RC[A-F0-9]{16}#2-1");
assertThat(a).isNotEqualTo(b).matches("RC[0-9A-Z]{1,13}#2-1");
assertThat(second).isEqualTo(a.substring(0, a.lastIndexOf('-') + 1) + "2");
}
@@ -121,9 +121,13 @@ class ReturnReceiptServiceTest {
ReturnReceiptService service;
PdaWarehouseIdentity identity;
private com.baiyue.lastleg.service.CompactBusinessNumberService compactNumbers;
@BeforeEach
void setUp() {
compactNumbers = org.mockito.Mockito.mock(com.baiyue.lastleg.service.CompactBusinessNumberService.class);
service = new ReturnReceiptService();
ReflectionTestUtils.setField(service, "compactNumbers", compactNumbers);
org.mockito.Mockito.lenient().when(compactNumbers.next(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyString())).thenAnswer(call -> call.getArgument(0) + "-CS-261016102030");
ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "orderItems", orderItems);
ReflectionTestUtils.setField(service, "receipts", receipts);
@@ -243,7 +247,7 @@ class ReturnReceiptServiceTest {
when(orderItems.lockOrder(1L)).thenReturn(List.of(item(11L, "SKU-A", 50, 50), item(12L, "SKU-B", 40, 40)));
when(fbaWarehouseValidator.required("200", "XLX6", null)).thenReturn(
new FbaWarehouseValidator.VerifiedFbaWarehouse(200L, "XLX6", "FBA XLX6", "US", "CA"));
when(numbers.next(eq("RELABEL"), eq("10"), anyLong())).thenReturn("RL-MULTI");
when(compactNumbers.next(eq("TT"), eq("customer-1"))).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();
@@ -294,7 +298,7 @@ class ReturnReceiptServiceTest {
when(orderItems.lockOrder(1L)).thenReturn(List.of(item(11L, "SKU-A", 100, 100)));
when(fbaWarehouseValidator.required("200", "XLX6", null)).thenReturn(
new FbaWarehouseValidator.VerifiedFbaWarehouse(200L, "XLX6", "FBA XLX6", "US", "CA"));
when(numbers.next(eq("RELABEL"), eq("10"), anyLong())).thenReturn("RL-SECOND");
when(compactNumbers.next(eq("TT"), eq("customer-1"))).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();
@@ -349,7 +353,7 @@ class ReturnReceiptServiceTest {
void createForecastPersistsCompleteTailOrderSourceAndReturnsItsBusinessNumber() {
AtomicReference<ReturnOrder> saved = new AtomicReference<>();
when(orders.findKey("customer-1", "create-key")).thenReturn(null);
when(numbers.next(eq("RETURN"), eq("10"), anyLong())).thenReturn("RT-1");
when(compactNumbers.next(eq("TT"), eq("customer-1"))).thenReturn("RT-1");
when(orders.insert(any(ReturnOrder.class))).thenAnswer(invocation -> { saved.set(invocation.getArgument(0)); return 1; });
when(orders.findNo("customer-1", "RT-1")).thenAnswer(invocation -> saved.get());
when(orderItems.listOrder(anyLong())).thenReturn(List.of());
@@ -0,0 +1,41 @@
package com.baiyue.lastleg.service;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import java.time.Instant;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class CompactBusinessNumberServiceTest {
@Test void usesCustomerInitialsAndShanghaiSecondsWithoutSequenceSuffix() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
var customers = mock(ReturnOrderReferenceResolver.class);
when(customers.customerName("42")).thenReturn("柏悦物流");
when(jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class))
.thenAnswer(call -> Instant.now().getEpochSecond());
String number = new CompactBusinessNumberService(jdbc, customers).next("TB", "42");
assertTrue(number.matches("TB-BYWL-[0-9]{12}"), number);
assertEquals("AB", CompactBusinessNumberService.initials("Alpha Beta"));
}
@Test void usesDatabaseLogicalSecondAcrossInstancesAndClockRollback() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
var first = new CompactBusinessNumberService(jdbc, null);
var second = new CompactBusinessNumberService(jdbc, null);
Instant now = Instant.parse("2026-10-16T02:30:00Z");
when(jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class))
.thenReturn(now.getEpochSecond(), now.getEpochSecond() + 1, now.getEpochSecond() + 2);
assertEquals("ZY-ABC-261016103000", first.allocate("ZY", "ABC", now));
assertEquals("ZY-ABC-261016103001", second.allocate("ZY", "ABC", now));
assertEquals("ZY-ABC-261016103002", first.allocate("ZY", "ABC", now.minusSeconds(60)));
}
@Test void physicalLabelKeepsEveryIdBitAndIsCompact() {
long id = 2087379674555461633L;
String mark = LastlegOrderNoGenerator.compactCartonMark(id);
assertTrue(mark.length() <= 16);
assertEquals(id, Long.parseLong(mark.substring(3), 36));
assertNotEquals(mark, LastlegOrderNoGenerator.compactCartonMark(id + 1_000_000));
assertEquals(mark, new LastlegOrderNoGenerator().next("RETURN_CARTON", "92", id));
}
}
@@ -0,0 +1,48 @@
package com.baiyue.lastleg.transfer.service;
import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests;
import com.baiyue.lastleg.transfer.mapper.InboundSnapshotMapper;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.*;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
class TransferInventorySourceTest {
WarehouseTransferRequests.Create request(String inbound, String destination, String method, LocalDateTime arrival) {
return new WarehouseTransferRequests.Create(inbound,List.of(),"WH",destination,"ADDRESS",1,BigDecimal.ZERO,
method,arrival,null,null,null,null,false,"KEY",List.of(new WarehouseTransferRequests.Item("SKU","Product",1,null)));
}
@Test void inventoryDoesNotRequireRepeatedArrivalButExternalCargoStillDoes() {
assertTrue(request("IN","ONT8",null,null).isInboundModeValid());
assertFalse(request("IN",null,null,null).isInboundModeValid());
assertFalse(request(null,null,null,null).isInboundModeValid());
assertTrue(request(null,null,"CUSTOMER_DELIVERY",LocalDateTime.now()).isInboundModeValid());
}
@Test void originalBoxesIncludeBothSourcesButNeverAnotherCustomerWarehouseOrInbound() {
var ds = new DriverManagerDataSource("jdbc:h2:mem:"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1","sa","");
var db=new JdbcTemplate(ds);
db.execute("CREATE TABLE ll_order(id BIGINT,customer_id VARCHAR,inbound_code VARCHAR,overseas_warehouse_id VARCHAR)");
db.execute("CREATE TABLE ll_inbound_plan_carton(customer_id VARCHAR,order_id BIGINT,carton_mark VARCHAR,sku_code VARCHAR,destination_warehouse_code VARCHAR,destination_warehouse_name VARCHAR)");
db.execute("CREATE TABLE ll_warehouse_transfer_order(id BIGINT,customer_id VARCHAR,lastleg_order_id BIGINT,operating_warehouse_id VARCHAR,destination_warehouse_code VARCHAR,destination_warehouse_name VARCHAR)");
db.execute("CREATE TABLE ll_warehouse_transfer_item(id BIGINT,customer_id VARCHAR,transfer_order_id BIGINT,sku_code VARCHAR,product_name_zh VARCHAR)");
db.execute("CREATE TABLE ll_warehouse_transfer_carton(customer_id VARCHAR,transfer_order_id BIGINT,transfer_item_id BIGINT,carton_mark VARCHAR,counted_quantity BIGINT,sku_items_json VARCHAR,status VARCHAR)");
db.update("INSERT INTO ll_order VALUES(1,'A','IN','WH'),(2,'B','IN','WH'),(3,'A','IN','OTHER'),(4,'A','OTHER','WH')");
db.update("INSERT INTO ll_inbound_plan_carton VALUES('A',1,'FIRST','S','ONT8','Amazon'),('B',2,'FOREIGN','S','ONT8','Amazon'),('A',3,'WRONG-WH','S','ONT8','Amazon'),('A',4,'WRONG-IN','S','ONT8','Amazon'),('B',1,'FORGED','S','ONT8','Amazon')");
db.update("INSERT INTO ll_warehouse_transfer_order VALUES(10,'A',1,'WH','ONT8','Amazon')");
db.update("INSERT INTO ll_warehouse_transfer_item VALUES(11,'A',10,'SKU','Product')");
db.update("INSERT INTO ll_warehouse_transfer_carton VALUES('A',10,11,'MEASURED',20,NULL,'COUNTED'),('A',10,11,'EXPECTED',NULL,NULL,'EXPECTED'),('B',10,11,'FOREIGN2',20,NULL,'COUNTED')");
var config=new Configuration(new Environment("test",new JdbcTransactionFactory(),ds)); config.addMapper(InboundSnapshotMapper.class);
try(var session=new SqlSessionFactoryBuilder().build(config).openSession()) {
var result=session.getMapper(InboundSnapshotMapper.class).sourceCartons("A","WH","IN");
assertEquals(List.of("FIRST","MEASURED"),result.stream().map(InboundSnapshotMapper.SourceCarton::cartonMark).toList());
assertEquals("ONT8",result.get(0).destinationWarehouseCode());
assertNull(result.get(0).quantity()); assertEquals(20L,result.get(1).quantity());
}
}
}
@@ -0,0 +1,60 @@
package com.baiyue.lastleg.transfer.service;
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton;
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem;
import com.baiyue.lastleg.returnreceipt.entity.ReturnOrder;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper;
import com.baiyue.lastleg.transfer.repository.TransferReturnSourceRepository;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class TransferReturnSourceTest {
/** 来源查询应排除占用和未清点箱,并保留整箱内的全部 SKU。 */
@Test
void returnsOnlyAvailableConfirmedCartonsWithContents() {
var orders = mock(ReturnOrderMapper.class);
var cartons = mock(ReturnInventoryCartonMapper.class);
var items = mock(ReturnInventoryCartonItemMapper.class);
var repository = new TransferReturnSourceRepository();
ReflectionTestUtils.setField(repository, "orders", orders);
ReflectionTestUtils.setField(repository, "cartons", cartons);
ReflectionTestUtils.setField(repository, "items", items);
var service = new WarehouseTransferService();
ReflectionTestUtils.setField(service, "returnSources", repository);
var order = new ReturnOrder();
order.setOverseasWarehouseId("WH-1");
when(orders.findNo("C-1", "RET-1")).thenReturn(order);
var available = carton(1L, "AVAILABLE", "CONFIRMED");
when(cartons.currentReturnCartons("C-1", "WH-1", "RET-1")).thenReturn(List.of(
available, carton(2L, "OUTBOUND_PENDING", "CONFIRMED"), carton(3L, "AVAILABLE", "PENDING")));
var sku = new ReturnInventoryCartonItem();
sku.setCartonId(1L); sku.setSkuCode("SKU-1"); sku.setQuantity(3L);
when(items.listCartons(List.of(1L))).thenReturn(List.of(sku));
var result = service.sourceCartons("C-1", "WH-1", "RETURN_ORDER:RET-1");
assertEquals(1, result.size());
assertEquals("BOX-1", result.get(0).cartonMark());
assertEquals("SKU-1", result.get(0).items().get(0).skuCode());
assertEquals(3L, result.get(0).items().get(0).quantity());
assertThrows(ResponseStatusException.class,
() -> service.sourceCartons("C-1", "OTHER-WH", "RETURN_ORDER:RET-1"));
verify(cartons, never()).currentReturnCartons("C-1", "OTHER-WH", "RET-1");
}
/** 构造不同库存状态的退货箱以验证来源筛选边界。 */
private static ReturnInventoryCarton carton(long id, String status, String countStatus) {
var carton = new ReturnInventoryCarton();
carton.setId(id); carton.setCartonMark("BOX-" + id); carton.setTotalQuantity(3L);
carton.setStatus(status); carton.setCountStatus(countStatus);
return carton;
}
}
@@ -203,9 +203,13 @@ class WarehouseTransferServiceTest {
@Mock WarehouseTransferReceiptCoordinator receiptCoordinator;
@Mock com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory customerDirectory;
private com.baiyue.lastleg.service.CompactBusinessNumberService compactNumbers;
@BeforeEach
void setUp() {
compactNumbers = org.mockito.Mockito.mock(com.baiyue.lastleg.service.CompactBusinessNumberService.class);
service = new WarehouseTransferService();
ReflectionTestUtils.setField(service, "compactNumbers", compactNumbers);
org.mockito.Mockito.lenient().when(compactNumbers.next(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyString())).thenAnswer(call -> call.getArgument(0) + "-CS-261016102030");
ReflectionTestUtils.setField(service, "customerDirectory", customerDirectory);
lenient().when(customerDirectory.find("TENANT-A")).thenReturn(
new com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory.Customer("TENANT-A", "C-A", "Customer A", null, null));
@@ -416,7 +420,7 @@ class WarehouseTransferServiceTest {
assertEquals("Customer A", tailOrder.getValue().getCustomerName());
assertEquals(WarehouseTransferService.DRAFT, tailOrder.getValue().getCurrentStatus());
assertEquals("WAREHOUSE_TRANSFER:DRAFT;RELABEL:PENDING_BINDING", tailOrder.getValue().getSubOrderStatus());
assertTrue(tailOrder.getValue().getOrderNo().matches("WT-[0-9A-Z]{25}"));
assertTrue(tailOrder.getValue().getOrderNo().matches("ZY-CS-[0-9]{12}"));
assertNull(tailOrder.getValue().getTransferWarehouseId());
verify(sourceOrders).insert(any(WarehouseTransferSourceOrder.class));
verify(relabelApplications, never()).insert(any(RelabelApplication.class));
@@ -461,10 +465,47 @@ class WarehouseTransferServiceTest {
verifyNoInteractions(transfers);
}
@Test
void transferRelabelRejectsUnknownAndForeignOrderCartonsBeforePersistence() {
var request = new WarehouseTransferRequests.Create(
"IN-1", List.of(11L), "WH-1", null, "FBA", null, "ONT8", null,
null, null, null, null, null, null, null, 1,
BigDecimal.ZERO, null, null, null, null, null, null, null, true,
LocalDateTime.of(2026, 10, 16, 10, 0), "Contact", "123", null, "TRUCK",
"carton-ownership", List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 1, null)),
List.of(new WarehouseTransferRequests.RelabelItem("CARTON_LABEL", List.of("FOREIGN-BOX"), "SKU-1", null, 1, "FBA-1", "ONT8", null)));
for (var cartons : List.of(List.<InboundSnapshotMapper.SourceCarton>of(),
List.of(new InboundSnapshotMapper.SourceCarton("CURRENT-BOX", "SKU-1", null, null, null, null, null)))) {
when(inboundSnapshots.sourceCartons("TENANT-A", "WH-1", "IN-1")).thenReturn(cartons);
var error = assertThrows(ResponseStatusException.class, () -> service.create("TENANT-A", "USER-1", request));
assertEquals("当前箱子不存在或不属于当前订单", error.getReason());
}
when(inboundSnapshots.sourceCartons("TENANT-A", "WH-1", "IN-1"))
.thenReturn(List.of(new InboundSnapshotMapper.SourceCarton("FOREIGN-BOX", "SKU-1", null, null, null, null, null)));
org.junit.jupiter.api.Assertions.assertDoesNotThrow(() ->
ReflectionTestUtils.invokeMethod(service, "validateRelabelRequest", "TENANT-A", request));
verifyNoInteractions(transfers);
}
@Test
void submittingOldDraftRechecksRelabelCartonOwnership() {
var transfer = transfer(WarehouseTransferService.DRAFT);
transfer.setOperatingWarehouseId("WH-1");
transfer.setInboundCode("IN-1");
transfer.setNeedRelabel(true);
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
var binding = new com.baiyue.lastleg.transfer.entity.WarehouseTransferRelabelBinding();
binding.setCustomerCartonMark("FOREIGN-BOX");
when(relabelBindings.listOrder("TENANT-A", transfer.getId())).thenReturn(List.of(binding));
var error = assertThrows(ResponseStatusException.class, () -> service.submit("TENANT-A", "WTR-1", "USER-1"));
assertEquals("当前箱子不存在或不属于当前订单", error.getReason());
verify(transfers, never()).updateById(any(WarehouseTransferOrder.class));
}
@ParameterizedTest
@NullSource
@ValueSource(strings = {"NONE"})
void transferWithoutDestinationCanBeCreatedAndSubmitted(String destinationType) {
void inventoryTransferRequiresKnownDestination(String destinationType) {
WarehouseTransferRequests.Create request = new WarehouseTransferRequests.Create(
"IN-1", List.of(11L), "WH-1", null, destinationType, null, null, null,
null, null, null, null, "123 Pickup Road", null, null, 5,
@@ -473,53 +514,23 @@ class WarehouseTransferServiceTest {
null, null, null, null, null, "key-no-fba",
List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 5, "MARK-1")), null);
when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L);
when(orders.selectList(any())).thenReturn(List.of(historicalOrder()));
WarehouseTransferOrder draft = transfer(WarehouseTransferService.DRAFT);
draft.setFbaWarehouseCode(null);
when(transfers.findNo(eq("TENANT-A"), anyString())).thenReturn(draft);
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(draft);
when(orders.selectById(draft.getLastlegOrderId())).thenReturn(tailOrder());
when(transferItems.selectList(any())).thenReturn(List.of());
when(sourceOrders.selectList(any())).thenReturn(List.of());
service.create("TENANT-A", "USER-1", request);
var result = service.submit("TENANT-A", "WTR-1", "USER-1");
ArgumentCaptor<WarehouseTransferOrder> created = ArgumentCaptor.forClass(WarehouseTransferOrder.class);
verify(transfers).insert(created.capture());
assertNull(created.getValue().getDestinationType());
assertNull(created.getValue().getFbaWarehouseCode());
assertEquals(WarehouseTransferService.PENDING_REVIEW, result.status());
verify(relabelApplications, never()).insert(any(RelabelApplication.class));
var error = assertThrows(ResponseStatusException.class, () -> service.create("TENANT-A","USER-1",request));
assertEquals(400,error.getStatusCode().value());
verifyNoInteractions(transfers,orders);
}
@Test
void merchantCanClearDestinationWhenEditingDraft() {
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.DRAFT);
transfer.setDestinationType("FBA");
transfer.setDestinationWarehouseCode("ONT8");
LastlegOrder order = tailOrder();
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
when(orders.selectById(transfer.getLastlegOrderId())).thenReturn(order);
when(transferItems.selectList(any())).thenReturn(List.of());
when(sourceOrders.selectList(any())).thenReturn(List.of());
void inventoryDraftCannotClearDestination() {
var transfer = transfer(WarehouseTransferService.DRAFT);
when(transfers.lockNo("TENANT-A","WTR-1")).thenReturn(transfer);
WarehouseTransferRequests.Create request = new WarehouseTransferRequests.Create(
"IN-1", List.of(11L), "WH-1", null, "NONE", null, null, null,
null, null, null, null, "123 Pickup Road", null, null, 5,
new BigDecimal("0.2500"), "WAREHOUSE_PICKUP", LocalDateTime.of(2026, 9, 12, 10, 0),
null, null, null, null, null, false, null, null, null, null, null, "edit-no-fba",
List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 5, "MARK-1")), null);
var result = service.updateDraft("TENANT-A", "WTR-1", "USER-1", request);
assertEquals(WarehouseTransferService.DRAFT, result.status());
assertNull(transfer.getDestinationType());
assertNull(transfer.getDestinationWarehouseCode());
assertNull(transfer.getFbaWarehouseCode());
assertNull(order.getFbaWarehouseCode());
verify(transfers).updateById(transfer);
assertThrows(ResponseStatusException.class, () -> service.updateDraft("TENANT-A","WTR-1","USER-1",request));
verify(transfers, never()).updateById(any(WarehouseTransferOrder.class));
}
@Test
@@ -544,7 +555,8 @@ class WarehouseTransferServiceTest {
assertEquals("ONT8", transfer.getValue().getFbaWarehouseCode());
assertNull(transfer.getValue().getFbaShipmentNo());
assertEquals("ONT8", tailOrder.getValue().getFbaWarehouseCode());
assertEquals("WAREHOUSE_PICKUP", transfer.getValue().getDeliveryMethod());
assertEquals("INVENTORY_TRANSFER", transfer.getValue().getDeliveryMethod());
assertNull(transfer.getValue().getEstimatedArrivalAt());
}
@Test
@@ -620,7 +632,7 @@ class WarehouseTransferServiceTest {
assertEquals(List.of(1, 2, 3, 4, 5), cartons.getAllValues().stream()
.map(WarehouseTransferCarton::getCartonIndex).toList());
assertTrue(cartons.getAllValues().stream().allMatch(value -> value.getCartonMark()
.matches("WT-[0-9A-Z]{25}#5-[1-5]")));
.matches("BX-[0-9A-Z]+")));
when(transfers.findKey("TENANT-A", request.idempotencyKey())).thenReturn(transfer.getValue());
clearInvocations(customerDirectory, orders, inboundOrders);
service.create("TENANT-A", "USER-1", request);