2026-08-21 17:21:26 +08:00
package com.baiyue.lastleg.dispatch ;
import com.baiyue.lastleg.dispatch.mapper.DispatchCandidateMapper ;
import org.apache.ibatis.annotations.Select ;
import org.junit.jupiter.api.Test ;
import java.lang.reflect.Method ;
2026-10-02 10:34:00 +08:00
import java.sql.DriverManager ;
import java.util.ArrayList ;
2026-08-21 17:21:26 +08:00
import java.util.List ;
import static org.junit.jupiter.api.Assertions.assertTrue ;
2026-10-03 04:18:52 +08:00
import static org.junit.jupiter.api.Assertions.assertFalse ;
2026-10-02 10:34:00 +08:00
import static org.junit.jupiter.api.Assertions.assertEquals ;
2026-08-21 17:21:26 +08:00
class DispatchCandidateMapperContractTest {
2026-10-05 07:24:53 +08:00
@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" ));
}
}
2026-10-02 10:34:00 +08:00
@Test
void availableAndLockedPalletsApplyTheSameActiveRelabelBoundary () throws Exception {
try ( var connection = DriverManager . getConnection ( "jdbc:h2:mem:dispatch-relabel-boundary;MODE=MySQL" )) {
var statement = connection . createStatement ();
statement . execute ( "CREATE TABLE ll_inbound_plan(id BIGINT, customer_id VARCHAR, creation_mode VARCHAR)" );
statement . execute ( "CREATE TABLE ll_inbound_plan_pallet(id BIGINT, plan_id BIGINT)" );
statement . execute ( "CREATE TABLE ll_order(id BIGINT)" );
statement . execute ( "CREATE TABLE ll_inbound_plan_line(id BIGINT, order_id BIGINT)" );
statement . execute ( "CREATE TABLE ll_inbound_plan_pallet_allocation(pallet_id BIGINT, line_id BIGINT)" );
statement . execute ( "CREATE TABLE ll_relabel_application(id BIGINT, customer_id VARCHAR, status VARCHAR)" );
statement . execute ( "CREATE TABLE ll_relabel_item(application_id BIGINT, customer_id VARCHAR, source_inbound_order_id BIGINT)" );
statement . execute ( "INSERT INTO ll_inbound_plan VALUES (1,'100','AUTO'),(2,'100','AUTO'),(3,'100','WAREHOUSE_TRANSFER'),(4,'100','AUTO'),(5,'100','AUTO')" );
statement . execute ( "INSERT INTO ll_inbound_plan_pallet VALUES (11,1),(12,2),(13,3),(14,4),(15,5)" );
statement . execute ( "INSERT INTO ll_order VALUES (21),(22),(23),(24),(25),(26)" );
statement . execute ( "INSERT INTO ll_inbound_plan_line VALUES (31,21),(32,22),(33,23),(34,24),(35,25),(36,26)" );
statement . execute ( "INSERT INTO ll_inbound_plan_pallet_allocation VALUES (11,31),(12,32),(13,33),(14,34),(15,35),(15,36)" );
statement . execute ( "INSERT INTO ll_relabel_application VALUES (41,'100','PENDING_REVIEW'),(42,'100','COMPLETED'),(43,'100','PENDING_REVIEW'),(44,'200','PENDING_REVIEW'),(45,'100','PENDING_REVIEW')" );
statement . execute ( "INSERT INTO ll_relabel_item VALUES (41,'100',21),(42,'100',22),(43,'100',23),(44,'200',24),(45,'100',25)" );
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 ());
String boundary = sql . substring ( sql . lastIndexOf ( "AND (ip.creation_mode" ), sql . lastIndexOf ( "GROUP BY" ))
. replace ( "#{customerId}" , "'100'" );
var result = statement . executeQuery ( "SELECT p.id FROM ll_inbound_plan_pallet p JOIN ll_inbound_plan ip ON ip.id=p.plan_id "
+ "JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=p.id JOIN ll_inbound_plan_line l ON l.id=a.line_id "
+ "JOIN ll_order o ON o.id=l.order_id WHERE 1=1 " + boundary + " ORDER BY p.id" );
var palletIds = new ArrayList < Long > ();
while ( result . next ()) palletIds . add ( result . getLong ( 1 ));
assertEquals ( List . of ( 12L , 13L , 14L ), palletIds , method . getName ());
}
}
}
2026-08-21 17:21:26 +08:00
private static final List < String > CONSTRUCTOR_ALIASES = List . of (
"planId" , "planPalletId" , "warehousePalletId" , "orderId" , "customerId" , "customerName" ,
2026-09-18 11:41:04 +08:00
"planNo" , "orderType" , "volumeWeightKg" , "grossWeightKg" , "containerNo" , "appointmentAt" , "expectedLoadingAt" ,
"overseasWarehouseId" , "overseasWarehouseCode" , "overseasWarehouseName" , "orderNo" , "orderRefs" , "inboundCode" , "palletNo" ,
2026-08-21 17:21:26 +08:00
"destinationWarehouseId" , "destinationWarehouseCode" , "destinationWarehouseName" , "fbaWarehouseCode" ,
2026-09-17 17:37:29 +08:00
"packageCount" , "volumeCbm" , "sourceStage" , "cartonMarks" , "relabelCartonMarks" ,
"destinationType" , "deliveryAddress" , "postalCode" , "contactName" , "contactPhone"
2026-08-21 17:21:26 +08:00
);
@Test
void candidateQueriesKeepRecordConstructorColumnOrder () throws Exception {
assertConstructorColumnOrder ( DispatchCandidateMapper . class . getMethod ( "available" ,
2026-08-22 10:20:38 +08:00
String . class , List . class , String . class , String . class , String . class , String . class ));
2026-08-21 17:21:26 +08:00
assertConstructorColumnOrder ( DispatchCandidateMapper . class . getMethod ( "lockSelected" ,
String . class , List . class ));
2026-09-14 09:58:30 +08:00
assertConstructorColumnOrder ( DispatchCandidateMapper . class . getMethod ( "relabelAvailable" ,
String . class , String . class , String . class ));
2026-08-21 17:21:26 +08:00
}
@Test
void selectedReceivedPalletMustStillHaveAvailableInventory () throws Exception {
Method method = DispatchCandidateMapper . class . getMethod ( "lockSelected" , String . class , List . class );
String sql = String . join ( " " , method . getAnnotation ( Select . class ). value ());
assertTrue ( sql . contains ( "p.warehouse_pallet_id IS NULL OR EXISTS" ));
assertTrue ( sql . contains ( "pos.available_packages > 0" ));
}
2026-09-17 17:37:29 +08:00
@Test
void candidatesWithoutFbaWarehouseAreExcludedFromReadAndWriteQueries () 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 ( "NULLIF(TRIM(l.fba_warehouse_code),'') IS NOT NULL" ));
}
}
@Test
void candidateKeywordSearchIncludesSourceInboundCode () throws Exception {
Method available = DispatchCandidateMapper . class . getMethod ( "available" , String . class , List . class ,
String . class , String . class , String . class , String . class );
String availableSql = String . join ( " " , available . getAnnotation ( Select . class ). value ());
assertTrue ( availableSql . contains ( "o.order_no LIKE CONCAT('%',#{orderNo},'%') OR l.inbound_code LIKE CONCAT('%',#{orderNo},'%')" ));
Method relabel = DispatchCandidateMapper . class . getMethod ( "relabelAvailable" , String . class , String . class , String . class );
String relabelSql = String . join ( " " , relabel . getAnnotation ( Select . class ). value ());
assertTrue ( relabelSql . contains ( "ri.source_inbound_code LIKE CONCAT('%',#{orderNo},'%')" ));
}
2026-09-07 15:07:34 +08:00
@Test
void candidateWarehouseComesFromPhysicalInventoryOrFrozenInboundPlan () 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 ( "CASE WHEN p.warehouse_pallet_id IS NOT NULL" ));
assertTrue ( sql . contains ( "pos.overseas_warehouse_id" ));
2026-09-18 11:41:04 +08:00
assertTrue ( sql . contains ( "NULLIF(CAST(ip.overseas_warehouse_id AS CHAR),'')" ));
assertTrue ( sql . contains ( "source_order.id=ip.source_order_id" ));
assertTrue ( sql . contains ( "source_order.customer_id=ip.customer_id" ));
assertTrue ( sql . contains ( "source_order.overseas_warehouse_id" ));
assertTrue ( sql . contains ( "AS overseasWarehouseCode" ));
assertTrue ( sql . contains ( "b.overseas_warehouse_code" ));
2026-09-07 15:07:34 +08:00
assertTrue ( sql . contains ( "ll_inventory_balance" ));
}
}
2026-09-14 09:58:30 +08:00
@Test
void candidateMarksFollowPdaRelabelPalletBinding () 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 ( "rp.id=ri.source_inventory_position_id" ));
assertTrue ( sql . contains ( "rp.pallet_id=p.warehouse_pallet_id" ));
assertTrue ( sql . contains ( "ri.fba_carton_mark" ));
assertTrue ( sql . contains ( "AS relabelCartonMarks" ));
}
}
2026-09-18 11:41:04 +08:00
@Test
2026-10-03 04:18:52 +08:00
void candidateGrossWeightUsesPhysicalSourcesAndNeverBillingVolumeWeight () throws Exception {
2026-09-18 11:41:04 +08:00
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 ( "SUM(d.gross_weight_kg)" ));
assertTrue ( sql . contains ( "inbound.gross_weight_kg" ));
assertTrue ( sql . contains ( "ll_inbound_order inbound" ));
assertTrue ( sql . contains ( "a.allocated_volume_cbm" ));
assertTrue ( sql . contains ( "a2.line_id=a.line_id" ));
2026-10-03 04:18:52 +08:00
assertFalse ( sql . contains ( "MIN(o.volume_weight_kg)) AS grossWeightKg" ));
2026-09-18 11:41:04 +08:00
}
}
2026-08-21 17:21:26 +08:00
private void assertConstructorColumnOrder ( Method method ) {
String sql = String . join ( " " , method . getAnnotation ( Select . class ). value ());
int previous = - 1 ;
for ( String alias : CONSTRUCTOR_ALIASES ) {
int current = sql . indexOf ( " AS " + alias , previous + 1 );
assertTrue ( current > previous ,
() -> method . getName () + " must select alias " + alias + " in record constructor order" );
previous = current ;
}
}
}