This commit is contained in:
2026-10-16 11:41:24 +08:00
parent 4e2ea89403
commit 5855529e97
59 changed files with 1077 additions and 110 deletions
@@ -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());
}
}
}
@@ -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,45 @@ 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.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 +512,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 +553,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 +630,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);