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;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
|
|
|
|
|
class DispatchCandidateMapperContractTest {
|
|
|
|
|
private static final List<String> CONSTRUCTOR_ALIASES = List.of(
|
|
|
|
|
"planId", "planPalletId", "warehousePalletId", "orderId", "customerId", "customerName",
|
|
|
|
|
"planNo", "orderType", "volumeWeightKg", "containerNo", "appointmentAt", "expectedLoadingAt",
|
|
|
|
|
"overseasWarehouseId", "overseasWarehouseName", "orderNo", "orderRefs", "inboundCode", "palletNo",
|
|
|
|
|
"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"));
|
|
|
|
|
assertTrue(sql.contains("CAST(ip.overseas_warehouse_id AS CHAR)"));
|
|
|
|
|
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-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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|