Merge remote-tracking branch 'origin/test' into dev

# Conflicts:
#	deploy/sql/头程数据库统一增量-MySQL8.sql
This commit is contained in:
2026-09-03 16:19:48 +08:00
10 changed files with 307 additions and 123 deletions
@@ -3,12 +3,17 @@ package com.baiyue.firstleg.repository;
import com.baiyue.firstleg.mapper.SmartLoadingCandidateMapper;
import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.Set;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainerQuery;
import com.baiyue.firstleg.dto.SmartLoadingDtos.InboundCandidateQuery;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
@@ -68,15 +73,54 @@ class SmartLoadingCandidateMapperContractTest {
}
@Test
void globalCandidateQueryHasOptionalWarehouseAndNoContainerJoin() throws Exception {
Method list = SmartLoadingCandidateMapper.class.getDeclaredMethod("globalCandidates", Long.class);
String sql = String.join(" ", list.getAnnotation(Select.class).value());
void globalCandidateQueryPagesUnitsBeforeLoadingDetailsAndHasNoContainerJoin() throws Exception {
Method units = SmartLoadingCandidateMapper.class.getDeclaredMethod("globalCandidateUnits",
Page.class, InboundCandidateQuery.class, Set.class);
String unitSql = String.join(" ", units.getAnnotation(Select.class).value());
Method details = SmartLoadingCandidateMapper.class.getDeclaredMethod(
"globalCandidatesByUnits", List.class);
String detailSql = String.join(" ", details.getAnnotation(Select.class).value());
assertThat(sql)
.contains("#{warehouseId} IS NULL OR o.warehouse_id=#{warehouseId}")
assertThat(unitSql)
.contains("#{query.warehouseId} IS NULL OR o.warehouse_id=#{query.warehouseId}")
.contains("o.channel_code IN")
.contains("o.process_status='WAIT_SPLIT'")
.contains("current_group.id groupId")
.contains("group_member_count.group_id=current_group.id")
.contains("availablePackageCount=current_member.full_package_count")
.doesNotContain("fl_container_booking")
.doesNotContain("bookingId");
assertThat(detailSql)
.contains("collection=\"units\"")
.contains("current_group.id=#{unit.groupId}")
.contains("o.tenant_id=#{unit.accountId}")
.contains("o.id=#{unit.inboundId}")
.doesNotContain("fl_container_booking");
}
@Test
void globalCandidateDynamicSqlRendersForStandaloneAndGroupedUnits() throws Exception {
Configuration configuration = new Configuration();
XMLLanguageDriver driver = new XMLLanguageDriver();
Method units = SmartLoadingCandidateMapper.class.getDeclaredMethod("globalCandidateUnits",
Page.class, InboundCandidateQuery.class, Set.class);
String unitScript = String.join(" ", units.getAnnotation(Select.class).value());
var query = new InboundCandidateQuery(List.of("MATSON_CLX"), 100L, 9L, "IN-",
java.time.LocalDate.parse("2026-09-01"), java.time.LocalDate.parse("2026-09-30"),
null, 1, 20);
String renderedUnits = driver.createSqlSource(configuration, unitScript, Map.class)
.getBoundSql(Map.of("query", query, "actualChannels", Set.of("MATSON_CLX_TRUCK"))).getSql();
assertThat(renderedUnits).contains("o.channel_code IN", "DATE(eligible.expectedArrivalAt)");
Method details = SmartLoadingCandidateMapper.class.getDeclaredMethod(
"globalCandidatesByUnits", List.class);
String detailScript = String.join(" ", details.getAnnotation(Select.class).value());
List<SmartLoadingCandidateMapper.CandidateUnit> page = List.of(
new SmartLoadingCandidateMapper.CandidateUnit(null, 9L, 10L, null, 10L),
new SmartLoadingCandidateMapper.CandidateUnit(700L, null, null, null, 11L));
String renderedDetails = driver.createSqlSource(configuration, detailScript, Map.class)
.getBoundSql(Map.of("units", page)).getSql();
assertThat(renderedDetails).contains("current_group.id=?", "o.tenant_id=?", "o.id=?");
}
@Test
@@ -100,6 +100,26 @@ class SmartLoadingSqlContractTest {
.doesNotContain("split_item.source_measurement_item_id=i.id");
}
@Test
@DisplayName("全局候选分页具备平台维度和关联明细索引")
void shouldProvideGlobalCandidatePagingIndexes() throws Exception {
assertThat(new ClassPathResource("db/phase2-inbound-schema.sql")
.getContentAsString(StandardCharsets.UTF_8))
.contains("idx_inbound_smart_loading (warehouse_id, process_status, channel_code, expected_arrival_at, id)")
.contains("idx_measure_inbound_status_id (tenant_id, inbound_id, status, id)")
.contains("idx_measure_item_candidate (tenant_id, measurement_id, inbound_detail_id)");
assertThat(migration())
.contains("idx_loading_item_candidate (account_id, inbound_id, inbound_detail_id, deleted)");
String unified = unifiedMigration();
assertThat(unified)
.contains("CREATE PROCEDURE baiyue_firstleg.ensure_smart_loading_candidate_indexes()")
.contains("ADD KEY idx_inbound_smart_loading")
.contains("ADD KEY idx_measure_inbound_status_id")
.contains("ADD KEY idx_measure_item_candidate")
.contains("ADD KEY idx_loading_item_candidate")
.contains("ADD KEY idx_inbound_split_candidate");
}
@Test
@DisplayName("仓库备注使用计划先行与明细单表CAS并允许已拣明细仅改备注")
void shouldUpdateWarehouseRemarkWithoutMultiTableTriggerConflict() throws Exception {
@@ -1,6 +1,7 @@
package com.baiyue.firstleg.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.client.OrderCargoClient;
@@ -29,6 +30,7 @@ import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.Member;
import com.baiyue.firstleg.pricebookquery.application.model.PriceBookQueryRows.ChannelRow;
import com.baiyue.firstleg.pricebookquery.application.port.in.PriceBookQueryUseCase;
import com.baiyue.firstleg.mapper.SmartLoadingCandidateMapper.ChannelContainerCountRow;
import com.baiyue.firstleg.mapper.SmartLoadingCandidateMapper.CandidateUnit;
import com.baiyue.firstleg.split.service.SplitPreloadService;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.FeignException;
@@ -62,6 +64,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -156,10 +159,14 @@ class SmartLoadingServiceTest {
"MATSON_CLX_TRUCK", "美森正班海卡");
Candidate max = withChannel(candidate(19L, 11L, 21L, 31L, 6),
"MATSON_MAX_TRUCK", "美森加班海卡");
when(repository.globalCandidates(null)).thenReturn(List.of(clx, max));
InboundCandidateQuery query = new InboundCandidateQuery(
List.of("MATSON_CLX"), null, null, null, null, null, null, 1, 20);
CandidateUnit unit = new CandidateUnit(null, 9L, 10L, null, 10L);
when(repository.globalCandidateUnits(eq(query), anySet()))
.thenReturn(new PageResult<>(List.of(unit), 1, 20, 1, 1));
when(repository.globalCandidatesByUnits(List.of(unit))).thenReturn(List.of(clx));
var result = service.inboundCandidates(new InboundCandidateQuery(
List.of("MATSON_CLX"), null, null, null, null, null, null, 1, 20));
var result = service.inboundCandidates(query);
assertThat(result.total()).isEqualTo(1);
assertThat(result.pages()).isEqualTo(1);
@@ -167,7 +174,9 @@ class SmartLoadingServiceTest {
assertThat(candidate.inboundId()).isEqualTo(10L);
assertThat(candidate.channelCode()).isEqualTo("MATSON_CLX_TRUCK");
});
verify(repository).globalCandidates(null);
verify(repository).globalCandidateUnits(eq(query), anySet());
verify(repository).globalCandidatesByUnits(List.of(unit));
verify(coloadGroups, never()).findByInbound(anyLong(), anyLong());
verify(repository, never()).booking(anyLong());
}
@@ -194,19 +203,20 @@ class SmartLoadingServiceTest {
@Test
void treatsWholeColoadGroupAsOneStablePagingUnit() {
Candidate first = withArrival(withChannel(candidate(9L, 10L, 20L, 30L, 4),
"MATSON_CLX_TRUCK", "美森正班海卡"), "2026-09-01T09:00:00");
Candidate second = withArrival(withChannel(candidate(19L, 11L, 21L, 31L, 6),
"MATSON_CLX_TRUCK", "美森正班海卡"), "2026-09-10T09:00:00");
GroupSnapshot group = group("ACTIVE", null,
new Member(9L, 30L, 10L, "I10", 4), new Member(19L, 31L, 11L, "I11", 6));
when(repository.globalCandidates(100L)).thenReturn(List.of(first, second));
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group);
when(coloadGroups.findByInbound(19L, 11L)).thenReturn(group);
var result = service.inboundCandidates(new InboundCandidateQuery(
Candidate first = withGroup(withArrival(withChannel(candidate(9L, 10L, 20L, 30L, 4),
"MATSON_CLX_TRUCK", "美森正班海卡"), "2026-09-01T09:00:00"), 2, 10);
Candidate second = withGroup(withArrival(withChannel(candidate(19L, 11L, 21L, 31L, 6),
"MATSON_CLX_TRUCK", "美森正班海卡"), "2026-09-10T09:00:00"), 2, 10);
InboundCandidateQuery query = new InboundCandidateQuery(
List.of("MATSON_CLX"), 100L, null, null, LocalDate.parse("2026-09-10"),
LocalDate.parse("2026-09-10"), null, 1, 1));
LocalDate.parse("2026-09-10"), null, 1, 1);
CandidateUnit unit = new CandidateUnit(700L, null, null,
LocalDateTime.parse("2026-09-01T09:00:00"), 10L);
when(repository.globalCandidateUnits(eq(query), anySet()))
.thenReturn(new PageResult<>(List.of(unit), 1, 1, 1, 1));
when(repository.globalCandidatesByUnits(List.of(unit))).thenReturn(List.of(first, second));
var result = service.inboundCandidates(query);
assertThat(result.total()).isEqualTo(1);
assertThat(result.records()).singleElement().satisfies(candidate -> {
@@ -219,18 +229,16 @@ class SmartLoadingServiceTest {
@Test
void excludesIncompleteColoadGroupInsteadOfPublishingPartialMember() {
Candidate first = withChannel(candidate(9L, 10L, 20L, 30L, 4),
"MATSON_CLX_TRUCK", "美森正班海卡");
GroupSnapshot group = group("ACTIVE", null,
new Member(9L, 30L, 10L, "I10", 4), new Member(19L, 31L, 11L, "I11", 6));
when(repository.globalCandidates(100L)).thenReturn(List.of(first));
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group);
InboundCandidateQuery query = new InboundCandidateQuery(
List.of("MATSON_CLX"), 100L, null, null, null, null, null, 1, 20);
when(repository.globalCandidateUnits(eq(query), anySet()))
.thenReturn(new PageResult<>(List.of(), 1, 20, 0, 0));
var result = service.inboundCandidates(new InboundCandidateQuery(
List.of("MATSON_CLX"), 100L, null, null, null, null, null, 1, 20));
var result = service.inboundCandidates(query);
assertThat(result.records()).isEmpty();
assertThat(result.total()).isZero();
verify(repository, never()).globalCandidatesByUnits(anyList());
}
@Test
@@ -1223,6 +1231,15 @@ class SmartLoadingServiceTest {
value.orderStatusSnapshot(), LocalDateTime.parse(expectedArrivalAt));
}
private static Candidate withGroup(Candidate value, int memberCount, int groupTotalBoxes) {
return new Candidate(value.accountId(), value.inboundId(), value.inboundDetailId(), value.orderId(),
value.orderNo(), value.inboundCode(), value.warehouseId(), value.channelCode(), value.channelName(),
value.destinationWarehouseId(), value.destinationWarehouseCode(), value.destinationWarehouseName(),
value.customerName(), value.goodsName(), value.availablePackageCount(), value.unitGrossWeightKg(),
value.unitNetWeightKg(), value.unitVolumeCbm(), 700L, "CLG-700", memberCount, groupTotalBoxes,
false, null, value.orderStatusSnapshot(), value.expectedArrivalAt());
}
private static ChannelRow channel(String channelCode, String channelName) {
ChannelRow value = new ChannelRow();
value.setChannelCode(channelCode); value.setChannelName(channelName);