feat(lastleg): query commercial sources independently of fulfillment types

This commit is contained in:
2026-10-09 16:12:02 +08:00
parent c4a7c2a22d
commit 3b5454fb99
13 changed files with 156 additions and 19 deletions
@@ -38,11 +38,11 @@ class LastlegOrderControllerTest {
private final LastlegOrderCommandService commandService = mock(LastlegOrderCommandService.class);
private final LastlegOrderController controller = controller();
private final LastlegOrderRequests.Query query = new LastlegOrderRequests.Query(
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
@Test
void orderQueryHasOneResolvableConstructorForRequestParameterBinding() {
assertEquals(17, BeanUtils.getResolvableConstructor(LastlegOrderRequests.Query.class).getParameterCount());
assertEquals(20, BeanUtils.getResolvableConstructor(LastlegOrderRequests.Query.class).getParameterCount());
}
@Test
@@ -18,7 +18,7 @@ class MerchantLastlegOrderControllerTest {
private final LastlegOrderService service = mock(LastlegOrderService.class);
private final MerchantLastlegOrderController controller = controller();
private final LastlegOrderRequests.Query query = new LastlegOrderRequests.Query(
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
@Test
void merchantListUsesOnlyAuthenticatedTenant() {
@@ -75,6 +75,19 @@ class LastlegExportProviderServiceTest {
assertTrue(result.columns().stream().noneMatch(c -> c.key().equals("id") || c.key().equals("customerId")));
}
@Test void exportsBusinessReferencesUsingTheSameFiltersAndProjectionAsTheList() {
var order = new ObjectMapper().findAndRegisterModules().convertValue(Map.of(
"id", "1", "orderNo", "WT-1", "orderTypeName", "仓储转运", "businessSource", Map.of(
"businessType", "LOCAL_LOOSEBOARD", "label", "本土散板 · 拼车需求池", "businessOrderNo", "LB-1", "externalBookingNo", "FB-1")), com.baiyue.lastleg.dto.LastlegOrderSummary.class);
when(orders.page(isNull(), any(), isNull())).thenReturn(new PageResult<>(List.of(order),1,1,1,1));
var result = service.batch("LAST_MILE_ORDER_LIST",request(Map.of("businessType","LOCAL_LOOSEBOARD","businessOrderNo","LB-1","externalBookingNo","FB-1"),null,null));
verify(orders).page(isNull(),argThat(q -> "LOCAL_LOOSEBOARD".equals(q.businessType()) && "LB-1".equals(q.businessOrderNo()) && "FB-1".equals(q.externalBookingNo())),isNull());
assertEquals("仓储转运",result.rows().get(0).get("orderTypeName"));
assertEquals("本土散板 · 拼车需求池",result.rows().get(0).get("businessSource.label"));
assertEquals("LB-1",result.rows().get(0).get("businessSource.businessOrderNo"));
assertEquals("FB-1",result.rows().get(0).get("businessSource.externalBookingNo"));
}
@Test void keepsCustomerScopeAndLedgerCustomerFilter() {
when(inventory.ledgers(eq("200"), any())).thenReturn(new PageResult<>(List.of(),1,1,0,0));
service.batch("OVERSEAS_INVENTORY_LEDGER", request(Map.of("customerId","200","customerName","客户"),null,null));
@@ -0,0 +1,75 @@
package com.baiyue.lastleg.service;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baiyue.lastleg.dto.LastlegOrderRequests;
import com.baiyue.lastleg.dto.LastlegOrderSummary;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.*;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
class LastlegOrderBusinessSourceTest {
@Test void projectsAndFiltersFormalSourcesWithinTheOrderCustomer() throws Exception {
var config = new MybatisConfiguration();
config.setEnvironment(new Environment("test", new JdbcTransactionFactory(),
new UnpooledDataSource("org.h2.Driver", "jdbc:h2:mem:business-source;MODE=MySQL", "sa", "")));
config.addMapper(LastlegOrderMapper.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(config, "source-test"), LastlegOrder.class);
try (var session = new SqlSessionFactoryBuilder().build(config).openSession()) {
var db = session.getConnection(); var sql = db.createStatement();
sql.execute("CREATE TABLE ll_order (id BIGINT, customer_id VARCHAR, order_type VARCHAR, source_order_no VARCHAR)");
sql.execute("CREATE TABLE ll_fleet_pool_order_link (lastleg_order_id BIGINT, customer_id VARCHAR, business_type VARCHAR, business_order_no VARCHAR)");
sql.execute("CREATE TABLE ll_fleet_external_booking (customer_id VARCHAR, incoming_no VARCHAR, booking_no VARCHAR)");
sql.execute("INSERT INTO ll_order VALUES (1,'A','LOCAL_LOOSEBOARD','LB-DIRECT'),(2,'A','WAREHOUSE_TRANSFER','WT-POOL')," +
"(3,'A','WAREHOUSE_TRANSFER','WT-TRUCK'),(4,'A','WAREHOUSE_TRANSFER','WT-NORMAL')," +
"(5,'B','WAREHOUSE_TRANSFER','WT-POOL'),(6,'A','WAREHOUSE_TRANSFER','WT-EXTERNAL')");
sql.execute("INSERT INTO ll_fleet_pool_order_link VALUES (2,'A','LOCAL_LOOSEBOARD','LB-POOL')," +
"(3,'A','US_TRUCK','TRUCK-POOL'),(5,'B','LOCAL_LOOSEBOARD','LB-OTHER')," +
"(4,'B','LOCAL_LOOSEBOARD','LB-WRONG-CUSTOMER')");
sql.execute("INSERT INTO ll_fleet_external_booking VALUES ('A','WT-POOL','FB-POOL'),('A','WT-TRUCK','FB-TRUCK')," +
"('B','WT-POOL','FB-OTHER'),('A','WT-EXTERNAL','FB-EXTERNAL')");
var mapper = session.getMapper(LastlegOrderMapper.class);
var sources = mapper.findBusinessSources(List.of(1L,2L,3L,4L,5L,6L));
assertEquals(6, sources.size());
var service = new LastlegOrderService(); ReflectionTestUtils.setField(service,"orderMapper",mapper);
List<LastlegOrder> orders = sources.stream().map(row -> { var o = new LastlegOrder(); o.setId(row.orderId()); return o; }).toList();
Map<Long, LastlegOrderSummary.BusinessSource> values = ReflectionTestUtils.invokeMethod(service,"businessSources",orders);
assertEquals(new LastlegOrderSummary.BusinessSource("LOCAL_LOOSEBOARD","本土散板 · 独立下单","LB-DIRECT",null),values.get(1L));
assertEquals(new LastlegOrderSummary.BusinessSource("LOCAL_LOOSEBOARD","本土散板 · 拼车需求池","LB-POOL","FB-POOL"),values.get(2L));
assertEquals(new LastlegOrderSummary.BusinessSource("US_TRUCK","本土卡车 · 拼车需求池","TRUCK-POOL","FB-TRUCK"),values.get(3L));
assertEquals(new LastlegOrderSummary.BusinessSource("WAREHOUSE_TRANSFER","仓储转运",null,null),values.get(4L));
assertEquals("FB-OTHER",values.get(5L).externalBookingNo());
assertEquals(new LastlegOrderSummary.BusinessSource("WAREHOUSE_TRANSFER","仓储转运 · 外接来货",null,"FB-EXTERNAL"),values.get(6L));
assertEquals(List.of(1L,2L),find(db,"A","LOCAL_LOOSEBOARD",null,null));
assertEquals(List.of(1L,2L,5L),find(db,null,"LOCAL_LOOSEBOARD",null,null));
assertEquals(List.of(4L,6L),find(db,"A","WAREHOUSE_TRANSFER",null,null));
assertEquals(List.of(3L),find(db,"A","US_TRUCK",null,null));
assertEquals(List.of(2L),find(db,"A",null,"LB-POOL","FB-POOL"));
assertEquals(List.of(),find(db,"A",null,"LB-OTHER",null));
assertEquals(List.of(),find(db,"A",null,null,"FB-OTHER"));
assertEquals(List.of(),find(db,"A",null,"' OR 1=1 --",null));
}
}
private List<Long> find(java.sql.Connection db,String customer,String businessType,String businessOrderNo,String bookingNo) throws Exception {
var query = new LastlegOrderRequests.Query(1L,20L,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,businessType,businessOrderNo,bookingNo);
LambdaQueryWrapper<LastlegOrder> filter = ReflectionTestUtils.invokeMethod(new LastlegOrderService(),"filter",customer,query);
var matcher = Pattern.compile("#\\{ew.paramNameValuePairs.([^}]+)}").matcher(filter.getSqlSegment());
var parameters = new ArrayList<Object>(); var bound = new StringBuilder();
while(matcher.find()) { parameters.add(filter.getParamNameValuePairs().get(matcher.group(1))); matcher.appendReplacement(bound,"?"); }
matcher.appendTail(bound);
try(var statement = db.prepareStatement("SELECT id FROM ll_order WHERE " + bound + " ORDER BY id")) {
for(int i=0;i<parameters.size();i++)statement.setObject(i+1,parameters.get(i));
var ids = new ArrayList<Long>(); try(var rows=statement.executeQuery()){while(rows.next())ids.add(rows.getLong(1));} return ids;
}
}
}
@@ -59,7 +59,7 @@ class LastlegOrderSailingStageTest {
private List<Long> find(java.sql.Connection db, String customerId, SailingStage stage, String status) throws Exception {
var query = new LastlegOrderRequests.Query(1L, 20L, null, null, null, null, null, null,
status, null, null, null, null, null, null, null, stage);
status, null, null, null, null, null, null, null, stage, null, null, null);
LambdaQueryWrapper<LastlegOrder> filter = ReflectionTestUtils.invokeMethod(
new LastlegOrderService(), "filter", customerId, query);
String where = filter.getSqlSegment();
@@ -96,7 +96,7 @@ class LastlegOrderServiceTest {
LastlegOrderSummary summary = ReflectionTestUtils.invokeMethod(
service, "summary", order, List.of(), null, "UNSCHEDULED",
new LastlegOrderMapper.OrderWeightRow(11L, new java.math.BigDecimal("30"), new java.math.BigDecimal("22")), List.of());
new LastlegOrderMapper.OrderWeightRow(11L, new java.math.BigDecimal("30"), new java.math.BigDecimal("22")), List.of(), null);
assertNotNull(summary);
assertEquals("BOOKING-1", summary.sourceContainerBookingId());
@@ -379,7 +379,7 @@ class LastlegOrderServiceTest {
@Test
void pageUsesPaginationAndPlatformFiltersWithoutCustomerScope() {
var query = new LastlegOrderRequests.Query(3L, 25L, "LL-", "FIRST_LEG", "IN-", null,
null, "", "PENDING_ASSIGNMENT", null, "LAX", null, null, null, null, null, null);
null, "", "PENDING_ASSIGNMENT", null, "LAX", null, null, null, null, null, null, null, null, null);
var resultPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<LastlegOrder>(3, 25);
resultPage.setTotal(0);
when(orders.selectPage(any(), any())).thenReturn(resultPage);
@@ -536,7 +536,7 @@ class LastlegOrderServiceTest {
new LastlegOrderMapper.PalletMergeRelationRow(11L, "INBOUND-2", "PMG-1"),
new LastlegOrderMapper.PalletMergeRelationRow(11L, "INBOUND-3", "PMG-1")));
var query = new LastlegOrderRequests.Query(1L, 10L, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null, null, null, null, null);
var result = service.page(null, query);
@@ -560,7 +560,7 @@ class LastlegOrderServiceTest {
resultPage.setTotal(3);
when(orders.selectPage(any(), any())).thenReturn(resultPage);
var query = new LastlegOrderRequests.Query(1L, 10L, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null, null, null, null, null);
var result = service.page(null, query);
@@ -592,7 +592,7 @@ class LastlegOrderServiceTest {
when(orders.selectPage(any(), any())).thenReturn(resultPage);
var result = service.page(null, new LastlegOrderRequests.Query(1L, 10L, null, null, null, null,
null, null, null, null, input, null, null, null, null, null, null));
null, null, null, null, input, null, null, null, null, null, null, null, null, null));
ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.Wrapper<LastlegOrder>> filter =
ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.Wrapper.class);