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; import java.nio.file.Files; import java.nio.file.Path; import java.sql.DriverManager; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; class DispatchCandidateMapperContractTest { @Test void ordinaryAndReturnRelabelCandidatesExcludeDirectOutboundDeliveryModes() throws Exception { for (String method : List.of("relabelAvailable", "nativeReturnRelabelAvailable")) { String sql = String.join(" ", DispatchCandidateMapper.class .getMethod(method, String.class, String.class, String.class).getAnnotation(Select.class).value()); String eligibility = sql.substring(sql.indexOf("WHERE a.delivery_method"), sql.indexOf("(); while (result.next()) palletIds.add(result.getLong(1)); assertEquals(List.of(12L, 13L, 14L), palletIds, method.getName()); } } } private static final List CONSTRUCTOR_ALIASES = List.of( "planId", "planPalletId", "warehousePalletId", "orderId", "customerId", "customerName", "planNo", "orderType", "volumeWeightKg", "grossWeightKg", "containerNo", "appointmentAt", "expectedLoadingAt", "overseasWarehouseId", "overseasWarehouseCode", "overseasWarehouseName", "orderNo", "orderRefs", "inboundCode", "palletNo", "destinationWarehouseId", "destinationWarehouseCode", "destinationWarehouseName", "fbaWarehouseCode", "packageCount", "volumeCbm", "sourceStage", "cartonMarks", "relabelCartonMarks", "destinationType", "deliveryAddress", "postalCode", "contactName", "contactPhone" ); @Test void candidateQueriesKeepRecordConstructorColumnOrder() throws Exception { assertConstructorColumnOrder(DispatchCandidateMapper.class.getMethod("available", String.class, List.class, String.class, String.class, String.class, String.class)); assertConstructorColumnOrder(DispatchCandidateMapper.class.getMethod("lockSelected", String.class, List.class)); assertConstructorColumnOrder(DispatchCandidateMapper.class.getMethod("relabelAvailable", String.class, String.class, String.class)); } @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")); } @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},'%')")); } @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")); 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")); assertTrue(sql.contains("ll_inventory_balance")); } } @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")); } } @Test void relabelCandidatesReadWeightFromWarehouseTransferPalletSnapshot() throws Exception { String source = Files.readString(Path.of("src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchCandidateMapper.java")); int start = source.indexOf("List relabelAvailable"); int end = source.indexOf("List nativeReturnRelabelAvailable"); String sql = source.substring(source.lastIndexOf("@Select", start), end); assertTrue(sql.contains("MAX(wtp.gross_weight_kg) AS grossWeightKg")); assertTrue(sql.contains("MAX(wtp.volume_cbm) AS volumeCbm")); assertTrue(sql.contains("wtp.warehouse_pallet_id=pos.pallet_id")); } @Test void candidateGrossWeightUsesPhysicalSourcesAndNeverBillingVolumeWeight() 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("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")); assertFalse(sql.contains("MIN(o.volume_weight_kg)) AS grossWeightKg")); } } 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; } } }