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

# Conflicts:
#	deploy/maven/settings-cn.xml
This commit is contained in:
2026-09-03 15:23:55 +08:00
28 changed files with 867 additions and 156 deletions
@@ -4,6 +4,7 @@ import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.SmartLoadingDtos.*;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
@@ -12,16 +13,27 @@ class PlatformSmartLoadingControllerContractTest {
authority("draftByBooking","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class);
authority("detail","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class);
authority("candidates","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class,long.class);
authority("inboundCandidates","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class,long.class);
authority("inboundCandidates","platform:firstleg:smart-loading:list",AuthenticatedUser.class,InboundCandidateQuery.class);
authority("containers","platform:firstleg:smart-loading:list",AuthenticatedUser.class,WorkbenchContainerQuery.class);
authority("channels","platform:firstleg:smart-loading:list",AuthenticatedUser.class,long.class);
}
@Test void openApiRequiresBookingAndWarehouseForCandidates() throws Exception {
@Test void openApiFreezesFilteredWorkbenchContainers() throws Exception {
String yaml=java.nio.file.Files.readString(java.nio.file.Path.of("src/main/resources/openapi/firstleg-v1.yaml"));
assertThat(yaml).contains("/api/platform/v1/first-leg/smart-loading/candidates:")
.contains("/api/platform/v1/first-leg/smart-loading/channels:")
.contains("零候选或零可用柜次的有效渠道仍返回")
.contains("name: bookingId, in: query, required: true")
.contains("name: warehouseId, in: query, required: true");
assertThat(yaml).contains("/api/platform/v1/first-leg/smart-loading/containers:")
.contains("SmartLoadingWorkbenchContainerPageEnvelope:")
.contains("只返回 WAIT_LOADING、EDITING 或 LOADING");
}
@Test void openApiFreezesGlobalPagedInboundCandidates() throws Exception {
Method method=PlatformSmartLoadingController.class.getDeclaredMethod(
"inboundCandidates",AuthenticatedUser.class,InboundCandidateQuery.class);
assertThat(method.getAnnotation(GetMapping.class).value()).containsExactly("/inbound-candidates");
String yaml=java.nio.file.Files.readString(java.nio.file.Path.of("src/main/resources/openapi/firstleg-v1.yaml"));
String contract=yaml.substring(yaml.indexOf("/api/platform/v1/first-leg/smart-loading/inbound-candidates:"),
yaml.indexOf("/api/platform/v1/first-leg/loading-associations/summary:"));
assertThat(contract).contains("name: channelGroupCodes")
.contains("name: warehouseId")
.contains("SmartLoadingInboundCandidatePageEnvelope")
.doesNotContain("name: bookingId");
}
@Test void everyMutationDeclaresDedicatedPermission() throws Exception {
authority("assign","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,AssignRequest.class);
@@ -6,6 +6,9 @@ import org.apache.ibatis.annotations.Select;
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 static org.assertj.core.api.Assertions.assertThat;
@@ -64,6 +67,35 @@ class SmartLoadingCandidateMapperContractTest {
.isLessThan(sql.indexOf("o.order_status_snapshot orderStatusSnapshot"));
}
@Test
void globalCandidateQueryHasOptionalWarehouseAndNoContainerJoin() throws Exception {
Method list = SmartLoadingCandidateMapper.class.getDeclaredMethod("globalCandidates", Long.class);
String sql = String.join(" ", list.getAnnotation(Select.class).value());
assertThat(sql)
.contains("#{warehouseId} IS NULL OR o.warehouse_id=#{warehouseId}")
.contains("o.process_status='WAIT_SPLIT'")
.doesNotContain("fl_container_booking")
.doesNotContain("bookingId");
}
@Test
void workbenchContainerQueryExcludesCompletedAndMatchesRelatedInboundFacts() throws Exception {
Method list = SmartLoadingCandidateMapper.class.getDeclaredMethod("workbenchContainers",
Page.class, WorkbenchContainerQuery.class, Set.class, String.class);
String sql = String.join(" ", list.getAnnotation(Select.class).value());
assertThat(sql)
.contains("p.plan_status IN ('EDITING','LOADING')")
.contains("b.channel_code IN")
.contains("fl_loading_plan_item filter_item")
.contains("fl_inbound_order filter_inbound")
.contains("filter_item.inbound_code LIKE")
.contains("filter_inbound.expected_arrival_at")
.contains("filter_inbound.process_status='LOADING'")
.doesNotContain("p.plan_status IN ('EDITING','LOADING','COMPLETED')");
}
@Test
void detailUsesSameBatchRule() throws Exception {
Method detail = SmartLoadingCandidateMapper.class.getDeclaredMethod(
@@ -90,10 +90,10 @@ class CartonMeasurementServiceTest {
assertThat(result.scannedShipmentId()).isEqualTo("FBA-SCAN-002");
assertThat(insertedItem.get().getScannedShipmentId()).isEqualTo("FBA-SCAN-002");
assertThat(insertedItem.get().getMeasurementId()).isEqualTo(30001L);
assertThat(insertedItem.get().getTotalVolumeCbm()).isEqualByComparingTo("0.1870");
assertThat(insertedItem.get().getOuterLengthCm()).isEqualByComparingTo("64.000");
assertThat(insertedItem.get().getOuterWidthCm()).isEqualByComparingTo("54.000");
assertThat(insertedItem.get().getOuterHeightCm()).isEqualByComparingTo("54.000");
assertThat(insertedItem.get().getTotalVolumeCbm()).isEqualByComparingTo("0.1680");
assertThat(insertedItem.get().getOuterLengthCm()).isEqualByComparingTo("62.000");
assertThat(insertedItem.get().getOuterWidthCm()).isEqualByComparingTo("52.000");
assertThat(insertedItem.get().getOuterHeightCm()).isEqualByComparingTo("52.000");
assertThat(measurement.getSourceType()).isEqualTo(MeasurementBatchService.SOURCE_THIRD_PARTY);
assertThat(measurement.getSourceCode()).isEqualTo("DEVICE");
}
@@ -155,16 +155,16 @@ class CartonMeasurementServiceTest {
assertThat(result.created()).isFalse();
assertThat(result.confirmedPackageCount()).isEqualTo(4);
assertThat(existing.getLengthCm()).isEqualByComparingTo("59.500");
assertThat(existing.getUnitVolumeCbm()).isEqualByComparingTo("0.187");
assertThat(existing.getTotalVolumeCbm()).isEqualByComparingTo("0.7480");
assertThat(existing.getVolumeWeightKg()).isEqualByComparingTo("124.916");
assertThat(existing.getUnitVolumeCbm()).isEqualByComparingTo("0.168");
assertThat(existing.getTotalVolumeCbm()).isEqualByComparingTo("0.6720");
assertThat(existing.getVolumeWeightKg()).isEqualByComparingTo("112.224");
assertThat(existing.getSourceImageFileId()).isEqualTo(99001L);
assertThat(existing.getScannedShipmentId()).isEqualTo("FBA-SCAN-002");
verify(images).delete("internal-token", 90001L, 10001L, 70001L, 88001L);
}
@Test
void scanOutsideConfiguredWeightingRulesKeepsImageAndActualDimensions() {
void scanOutsideConfiguredWeightingRulesKeepsImageAndUsesLargestTierAddition() {
InboundMeasurementEntity measurement = measurement(30001L, "RECEIVED", 0);
when(repository.findItemByBoxCode(10001L, "RK-001#5-1")).thenReturn(null);
when(repository.findLatestEditableMeasurement(10001L, 70001L)).thenReturn(measurement);
@@ -183,7 +183,7 @@ class CartonMeasurementServiceTest {
assertThat(insertedItem.get().getSourceImageFileId()).isEqualTo(99001L);
assertThat(insertedItem.get().getLengthCm()).isEqualByComparingTo("60.100");
assertThat(insertedItem.get().getCalculationRuleVersion())
.isEqualTo("ACTUAL_DIMENSION_V1");
.isEqualTo("CUSTOMER_DIMENSION_EXTRA_LARGE_V0");
assertThat(insertedItem.get().getOverLimitWarning()).isNull();
verify(images, never()).delete(any(), any(Long.TYPE), any(Long.TYPE), any(Long.TYPE), any(Long.TYPE));
}
@@ -95,10 +95,10 @@ class CustomerDimensionConfigServiceTest {
assertThat(result.rules()).extracting(rule -> rule.boxType())
.containsExactly(CustomerDimensionBoxType.values());
assertThat(result.rules().get(0).boxTypeName()).isEqualTo("小箱");
assertThat(result.rules().get(0).outerLengthCm()).isEqualByComparingTo("34.000");
assertThat(result.rules().get(0).outerWidthCm()).isEqualByComparingTo("24.000");
assertThat(result.rules().get(0).outerHeightCm()).isEqualByComparingTo("24.000");
assertThat(result.rules().get(0).volumeCbm()).isEqualByComparingTo("0.020");
assertThat(result.rules().get(0).outerLengthCm()).isEqualByComparingTo("32.000");
assertThat(result.rules().get(0).outerWidthCm()).isEqualByComparingTo("22.000");
assertThat(result.rules().get(0).outerHeightCm()).isEqualByComparingTo("22.000");
assertThat(result.rules().get(0).volumeCbm()).isEqualByComparingTo("0.015");
}
@Test
@@ -32,7 +32,7 @@ class CustomerDimensionPricingFlowTest {
var kgEstimate = pricing.estimate(rate("KG", "8.700000"), actualWeight, twoCartonsVolume);
var cbmEstimate = pricing.estimate(rate("CBM", "1400.000000"), actualWeight, twoCartonsVolume);
assertThat(twoCartonsVolume).isEqualByComparingTo("0.040");
assertThat(twoCartonsVolume).isEqualByComparingTo("0.030");
assertThat(kgEstimate.quantity()).isEqualByComparingTo("24.000000");
assertThat(kgEstimate.amount()).isEqualByComparingTo("208.80");
assertThat(cbmEstimate.quantity()).isEqualByComparingTo("0.066116");
@@ -26,16 +26,16 @@ class CustomerDimensionWeightingServiceTest {
}
@Test
void measuredDimensionsUseSmallestContainingRuleAndAddEachEdgeTwice() {
void measuredDimensionsUseSmallestContainingRuleAndAddConfiguredValueOncePerDimension() {
var weighted = service.load(10001L).resolve(
new BigDecimal("10"), new BigDecimal("10"), new BigDecimal("10"));
assertThat(weighted.boxType()).isEqualTo(CustomerDimensionBoxType.SMALL);
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("34.000");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("24.000");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("24.000");
assertThat(weighted.rawVolumeCbm()).isEqualByComparingTo("0.019584");
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.020");
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("32.000");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("22.000");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("22.000");
assertThat(weighted.rawVolumeCbm()).isEqualByComparingTo("0.015488");
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.015");
}
@Test
@@ -52,19 +52,20 @@ class CustomerDimensionWeightingServiceTest {
new BigDecimal("30.001"), new BigDecimal("20"), new BigDecimal("20"));
assertThat(weighted.boxType()).isEqualTo(CustomerDimensionBoxType.MEDIUM);
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.051");
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.043");
}
@Test
void dimensionsOutsideConfiguredWeightingRulesUseActualDimensions() {
void dimensionsOutsideConfiguredWeightingRulesUseLargestTierAdditionOnActualDimensions() {
var weighted = service.load(10001L).resolve(
new BigDecimal("60.001"), new BigDecimal("50"), new BigDecimal("50"));
assertThat(weighted.boxType()).isNull();
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("60.001");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("50");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("50");
assertThat(weighted.calculationRuleVersion()).isEqualTo("ACTUAL_DIMENSION_V1");
assertThat(weighted.boxType()).isEqualTo(CustomerDimensionBoxType.EXTRA_LARGE);
assertThat(weighted.edgeAdditionCm()).isEqualByComparingTo("2.000");
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("62.001");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("52.000");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("52.000");
assertThat(weighted.calculationRuleVersion()).isEqualTo("CUSTOMER_DIMENSION_EXTRA_LARGE_V3");
}
@Test
@@ -24,13 +24,13 @@ class CustomerDimensionWeightingTerminalRulesTest {
var ruleSet = service.load(10001L);
assertWeighted(ruleSet, "10", "10", "10", CustomerDimensionBoxType.SMALL,
"34", "24", "24", "0.020");
"32", "22", "22", "0.015");
assertWeighted(ruleSet, "35", "25", "25", CustomerDimensionBoxType.MEDIUM,
"44", "34", "34", "0.051");
"42", "32", "32", "0.043");
assertWeighted(ruleSet, "45", "35", "35", CustomerDimensionBoxType.LARGE,
"54", "44", "44", "0.105");
"52", "42", "42", "0.092");
assertWeighted(ruleSet, "55", "45", "45", CustomerDimensionBoxType.EXTRA_LARGE,
"64", "54", "54", "0.187");
"62", "52", "52", "0.168");
}
private static void assertWeighted(CustomerDimensionWeightingService.RuleSet rules,
@@ -152,8 +152,8 @@ class MeasurementBatchServiceTest {
+ "LARGE:50,40,40;系数:1.3;大箱:—;EXTRA_LARGE:60,50,50;系数:1.4;特大箱:—");
when(pricingRepository.findDefaultRule(1L, 1L)).thenReturn(rule);
MeasurementBatchCommand.Item small = new MeasurementBatchCommand.Item(
80001L, "001", "A", 1, new BigDecimal("30"), new BigDecimal("20"),
new BigDecimal("20"), 500L, null, null, null, null, null, null, "小箱");
80001L, "001", "A", 1, new BigDecimal("10"), new BigDecimal("10"),
new BigDecimal("10"), 500L, null, null, null, null, null, null, "低于最小档");
MeasurementBatchCommand.Item medium = new MeasurementBatchCommand.Item(
80002L, "002", "B", 1, new BigDecimal("31"), new BigDecimal("20"),
new BigDecimal("20"), 501L, null, null, null, null, null, null, "中箱");
@@ -166,9 +166,9 @@ class MeasurementBatchServiceTest {
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("33.000");
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("22.000");
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("22.000");
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("11.000");
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("11.000");
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("11.000");
assertThat(items.getValue().get(1).getOuterLengthCm()).isEqualByComparingTo("37.200");
assertThat(items.getValue().get(1).getOuterWidthCm()).isEqualByComparingTo("24.000");
assertThat(items.getValue().get(1).getOuterHeightCm()).isEqualByComparingTo("24.000");
@@ -221,7 +221,32 @@ class MeasurementBatchServiceTest {
}
@Test
void warehouseOuterDimensionsOverrideCalculatedCartonAndAllDerivedValues() {
void dimensionsAboveLargestTierAddLargestTierValueToMeasuredDimensions() {
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 2, new BigDecimal("60.001"), new BigDecimal("50"),
new BigDecimal("50"), 500L, null, null, null, null, null, "ADD", "超过最大箱规");
MeasurementBatchCommand command = new MeasurementBatchCommand(
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(source));
service.saveBatch(command);
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("62.001");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("52.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("52.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("270.001");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.168");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.3360");
assertThat(calculated.getCalculationRuleVersion())
.isEqualTo("CUSTOMER_DIMENSION_EXTRA_LARGE_V0");
}
@Test
void warehouseOuterDimensionsBecomeBaseValuesBeforeAddStrategy() {
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
new BigDecimal("20"), 500L, null, new BigDecimal("50"),
@@ -239,10 +264,10 @@ class MeasurementBatchServiceTest {
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("50.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("40.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("30.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("190.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0600");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1200");
assertThat(calculated.getVolumeWeightKg()).isEqualByComparingTo("20.040");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("200.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0699");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1398");
assertThat(calculated.getVolumeWeightKg()).isEqualByComparingTo("23.347");
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_V1");
}
@@ -265,7 +290,47 @@ class MeasurementBatchServiceTest {
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("50.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("32.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("22.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0352");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0367");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.0734");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("160.000");
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_L_V1");
}
@Test
void warehouseOverrideUsesTheTierMatchedByItsBaseDimensionsBeforeMultiplyStrategy() {
CustomerChargeRuleEntity rule = new CustomerChargeRuleEntity();
rule.setId(79L);
rule.setTenantId(1L);
rule.setCustomerId(1L);
rule.setRuleCode("TIERED-OVERRIDE");
rule.setRuleVersion(1);
rule.setStatus("ACTIVE");
rule.setDefaultRule(true);
rule.setLengthFactor(new BigDecimal("1.1"));
rule.setWidthFactor(new BigDecimal("1.1"));
rule.setHeightFactor(new BigDecimal("1.1"));
rule.setRemark("SMALL:30,20,20;系数:1.1;小箱:—;MEDIUM:40,30,30;系数:1.2;中箱:—");
when(pricingRepository.findDefaultRule(1L, 1L)).thenReturn(rule);
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 1, new BigDecimal("30"), new BigDecimal("20"),
new BigDecimal("20"), 500L, null, new BigDecimal("31"),
null, null, null, "MULTIPLY", "仅修改长度");
MeasurementBatchCommand command = new MeasurementBatchCommand(
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(source));
service.saveBatch(command);
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("31.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("24.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("24.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0215");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("133.200");
assertThat(calculated.getChargeRuleId()).isEqualTo(79L);
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_L_V1");
}
@@ -7,7 +7,12 @@ import com.baiyue.firstleg.client.OrderCargoClient;
import com.baiyue.firstleg.dto.SmartLoadingDtos.AssignRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.AdjustRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate;
import com.baiyue.firstleg.dto.SmartLoadingDtos.CandidateOrderStatus;
import com.baiyue.firstleg.dto.SmartLoadingDtos.DeleteRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.GroupMemberCandidate;
import com.baiyue.firstleg.dto.SmartLoadingDtos.InboundCandidateQuery;
import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainer;
import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainerQuery;
import com.baiyue.firstleg.dto.SmartLoadingDtos.Plan;
import com.baiyue.firstleg.dto.SmartLoadingDtos.RecommendRequest;
import com.baiyue.firstleg.dto.SmartLoadingDtos.WarehouseRemarkRequest;
@@ -39,6 +44,7 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.sql.SQLException;
@@ -144,6 +150,89 @@ class SmartLoadingServiceTest {
});
}
@Test
void pagesGlobalCandidatesWithoutBindingAContainer() {
Candidate clx = withChannel(candidate(9L, 10L, 20L, 30L, 4),
"MATSON_CLX_TRUCK", "美森正班海卡");
Candidate max = withChannel(candidate(19L, 11L, 21L, 31L, 6),
"MATSON_MAX_TRUCK", "美森加班海卡");
when(repository.globalCandidates(null)).thenReturn(List.of(clx, max));
var result = service.inboundCandidates(new InboundCandidateQuery(
List.of("MATSON_CLX"), null, null, null, null, null, null, 1, 20));
assertThat(result.total()).isEqualTo(1);
assertThat(result.pages()).isEqualTo(1);
assertThat(result.records()).singleElement().satisfies(candidate -> {
assertThat(candidate.inboundId()).isEqualTo(10L);
assertThat(candidate.channelCode()).isEqualTo("MATSON_CLX_TRUCK");
});
verify(repository).globalCandidates(null);
verify(repository, never()).booking(anyLong());
}
@Test
void pagesWorkbenchContainersWithTheSameGlobalFilters() {
WorkbenchContainer container = new WorkbenchContainer(99L, "CB-1", "CAB-1", null,
100L, "义乌仓", "MATSON_CLX_TRUCK", "美森正班海卡", "40HC",
1L, "LP-1", "EDITING", 2);
WorkbenchContainerQuery query = new WorkbenchContainerQuery(List.of("MATSON_CLX"), 100L,
9L, "IN-10", LocalDate.parse("2026-09-01"), LocalDate.parse("2026-09-30"),
CandidateOrderStatus.WAIT_SPLIT, 1, 20);
when(repository.workbenchContainers(eq(query), any())).thenReturn(
new com.baiyue.common.core.api.PageResult<>(List.of(container), 1, 20, 1, 1));
var result = service.containers(query);
assertThat(result.records()).containsExactly(container);
@SuppressWarnings("unchecked") ArgumentCaptor<java.util.Set<String>> channels =
ArgumentCaptor.forClass(java.util.Set.class);
verify(repository).workbenchContainers(eq(query), channels.capture());
assertThat(channels.getValue()).containsExactlyInAnyOrder(
"MATSON_CLX_TRUCK", "MATSON_CLX_PARCEL");
}
@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(
List.of("MATSON_CLX"), 100L, null, null, LocalDate.parse("2026-09-10"),
LocalDate.parse("2026-09-10"), null, 1, 1));
assertThat(result.total()).isEqualTo(1);
assertThat(result.records()).singleElement().satisfies(candidate -> {
assertThat(candidate.coLoadGroupId()).isEqualTo(700L);
assertThat(candidate.availablePackageCount()).isEqualTo(10);
assertThat(candidate.groupMembers()).extracting(GroupMemberCandidate::inboundId)
.containsExactly(10L, 11L);
});
}
@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);
var result = service.inboundCandidates(new InboundCandidateQuery(
List.of("MATSON_CLX"), 100L, null, null, null, null, null, 1, 20));
assertThat(result.records()).isEmpty();
assertThat(result.total()).isZero();
}
@Test
void channelDirectoryKeepsActiveZeroStatChannelAndAggregatesCandidateAndContainerFacts() {
ChannelRow maxTruck = channel("MATSON_MAX_TRUCK", "美森加班海卡");
@@ -1124,6 +1213,16 @@ class SmartLoadingServiceTest {
value.orderStatusSnapshot(), value.expectedArrivalAt());
}
private static Candidate withArrival(Candidate value, String expectedArrivalAt) {
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(), value.coLoadGroupId(), value.groupNo(),
value.memberCount(), value.groupTotalBoxes(), value.locked(), value.blockReason(),
value.orderStatusSnapshot(), LocalDateTime.parse(expectedArrivalAt));
}
private static ChannelRow channel(String channelCode, String channelName) {
ChannelRow value = new ChannelRow();
value.setChannelCode(channelCode); value.setChannelName(channelName);