feat(firstleg): align simulated containers with channel groups
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
package com.baiyue.firstleg.domain;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SmartLoadingChannelGroupCatalogTest {
|
||||
@Test
|
||||
void exposesNinePlanningGroupsAndMapsBothExxBookingChannels() {
|
||||
assertThat(SmartLoadingChannelGroupCatalog.GROUPS).hasSize(9);
|
||||
assertThat(SmartLoadingChannelGroupCatalog.require("EXX").channelCodes())
|
||||
.contains("EXX_TRUCK", "EXX_RAIL_NY", "EXXHK", "EXXHWK", "NYEXX");
|
||||
assertThat(SmartLoadingChannelGroupCatalog.contains("EXX", "EXX_TRUCK")).isTrue();
|
||||
assertThat(SmartLoadingChannelGroupCatalog.contains("EXX", "EXX_RAIL_NY")).isTrue();
|
||||
assertThat(SmartLoadingChannelGroupCatalog.contains("EXX", "MATSON_CLX_TRUCK")).isFalse();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -80,7 +80,7 @@ class PlatformOceanBookingControllerContractTest {
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/ocean-bookings/link-candidates:")
|
||||
.contains("x-permission: platform:firstleg:ocean-booking:list")
|
||||
.contains("warehouseId", "channelCode", "containerType")
|
||||
.contains("warehouseId", "channelGroupCode", "containerType")
|
||||
.contains("LinkCandidateResponse", "requiredQuantity", "allocatedQuantity", "remainingQuantity")
|
||||
.contains("Capacity and max weight are derived display snapshots and never eligibility fields")
|
||||
.contains("Returns only linkable requirement candidates")
|
||||
|
||||
+2
-2
@@ -49,12 +49,12 @@ class OceanBookingMapperContractTest {
|
||||
@Test
|
||||
void linkCandidatesUsesOneBoundedSetQueryAndExactRemainingSemantics() throws Exception {
|
||||
String sql = String.join("\n", OceanBookingMapper.class.getDeclaredMethod("selectLinkCandidates",
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page.class, LinkCandidateQuery.class)
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page.class, LinkCandidateQuery.class, java.util.Set.class)
|
||||
.getAnnotation(Select.class).value());
|
||||
assertThat(sql)
|
||||
.contains("booking.status='RELEASED'")
|
||||
.contains("booking.warehouse_id=#{query.warehouseId}")
|
||||
.contains("booking.channel_code=#{query.channelCode}")
|
||||
.contains("<foreach collection=\"channelCodes\"", "#{channelCode}")
|
||||
.contains("requirement.container_type=#{query.containerType}")
|
||||
.contains("FROM fl_ocean_booking_container_allocation")
|
||||
.contains("container.record_status='ACTIVE'")
|
||||
|
||||
+9
-7
@@ -329,19 +329,21 @@ class OceanBookingServiceTest {
|
||||
LinkCandidate candidate = new LinkCandidate(101L, "OB-260818-0001", 201L, "40HC",
|
||||
new BigDecimal("76"), new BigDecimal("28000"), 3, 1, 2,
|
||||
"客户A", "深圳中转仓", "美西海运");
|
||||
when(repository.linkCandidates(any())).thenReturn(new PageResult<>(List.of(candidate), 1, 20, 1, 1));
|
||||
when(repository.linkCandidates(any(), anySet())).thenReturn(new PageResult<>(List.of(candidate), 1, 20, 1, 1));
|
||||
|
||||
PageResult<LinkCandidate> result = service.linkCandidates(new LinkCandidateQuery(701L,
|
||||
" SEA-US ", " 40HC ", null, null));
|
||||
" MATSON_CLX ", " 40HC ", null, null));
|
||||
|
||||
assertThat(result.records()).containsExactly(candidate);
|
||||
assertThat(result.records().get(0).remainingQuantity()).isEqualTo(2);
|
||||
ArgumentCaptor<LinkCandidateQuery> query = ArgumentCaptor.forClass(LinkCandidateQuery.class);
|
||||
verify(repository).linkCandidates(query.capture());
|
||||
assertThat(query.getValue().channelCode()).isEqualTo("SEA-US");
|
||||
@SuppressWarnings("unchecked") ArgumentCaptor<java.util.Set<String>> channels = ArgumentCaptor.forClass(java.util.Set.class);
|
||||
verify(repository).linkCandidates(query.capture(), channels.capture());
|
||||
assertThat(query.getValue().channelGroupCode()).isEqualTo("MATSON_CLX");
|
||||
assertThat(query.getValue().containerType()).isEqualTo("40HC");
|
||||
assertThat(query.getValue().page()).isEqualTo(1L);
|
||||
assertThat(query.getValue().size()).isEqualTo(20L);
|
||||
assertThat(channels.getValue()).contains("MATSON_CLX_TRUCK", "MATSON_CLX_PARCEL", "ZBYHK");
|
||||
verify(repository, never()).find(anyLong());
|
||||
verify(repository, never()).requirements(anyLong());
|
||||
verify(repository, never()).containers(anyLong());
|
||||
@@ -349,15 +351,15 @@ class OceanBookingServiceTest {
|
||||
|
||||
@Test
|
||||
void linkCandidatesReturnsNormalEmptyPageWithOneSetQueryAndNoDiagnosticFields() {
|
||||
when(repository.linkCandidates(any())).thenReturn(new PageResult<>(List.of(), 1, 20, 0, 0));
|
||||
when(repository.linkCandidates(any(), anySet())).thenReturn(new PageResult<>(List.of(), 1, 20, 0, 0));
|
||||
|
||||
PageResult<LinkCandidate> result = service.linkCandidates(
|
||||
new LinkCandidateQuery(701L, "SEA-US", "45HC", null, null));
|
||||
new LinkCandidateQuery(701L, "MATSON_CLX", "45HC", null, null));
|
||||
|
||||
assertThat(result.records()).isEmpty();
|
||||
assertThat(result.total()).isZero();
|
||||
assertThat(result.pages()).isZero();
|
||||
verify(repository, times(1)).linkCandidates(any());
|
||||
verify(repository, times(1)).linkCandidates(any(), anySet());
|
||||
assertThat(LinkCandidate.class.getRecordComponents()).extracting(java.lang.reflect.RecordComponent::getName)
|
||||
.doesNotContain("mismatchReasons", "disabledReason", "warehouseId", "channelCode");
|
||||
}
|
||||
|
||||
@@ -80,7 +80,9 @@ class ContainerServiceTest {
|
||||
assertThat(saved.getCabinetNo()).isEqualTo("CAB-260818-0001");
|
||||
assertThat(saved.getContainerNo()).isNull();
|
||||
assertThat(saved.getOceanBookingId()).isNull();
|
||||
assertThat(saved.getChannelName()).isEqualTo("美西海运");
|
||||
assertThat(saved.getChannelGroupCode()).isEqualTo("MATSON_CLX");
|
||||
assertThat(saved.getChannelCode()).isNull();
|
||||
assertThat(saved.getChannelName()).isNull();
|
||||
assertThat(saved.getWarehouseId()).isEqualTo(101L);
|
||||
assertThat(saved.getWarehouseName()).isEqualTo("上海中转仓");
|
||||
assertThat(saved.getCapacityCbm()).isEqualByComparingTo("76.0000");
|
||||
@@ -94,7 +96,7 @@ class ContainerServiceTest {
|
||||
@DisplayName("新增模拟柜拒绝柜型与固定规格不匹配")
|
||||
void rejectsSimulationWithNonCanonicalSpecification() {
|
||||
SimulationCreateRequest invalid = new SimulationCreateRequest(
|
||||
"SEA-US", 101L, "40HC", new BigDecimal("100"), new BigDecimal("28000"));
|
||||
"MATSON_CLX", 101L, "40HC", new BigDecimal("100"), new BigDecimal("28000"));
|
||||
|
||||
assertThatThrownBy(() -> service.createSimulation(9L, "admin", invalid))
|
||||
.isInstanceOf(BizException.class)
|
||||
@@ -202,10 +204,10 @@ class ContainerServiceTest {
|
||||
container.setPlanId(501L);
|
||||
container.setPlanNo("LP-260818-0001");
|
||||
container.setContainerNo(null);
|
||||
container.setWarehouseId(101L); container.setChannelCode("SEA-US"); container.setContainerType("40HC");
|
||||
container.setWarehouseId(101L); container.setChannelGroupCode("EXX"); container.setChannelCode(null); container.setContainerType("40HC");
|
||||
container.setCapacityCbm(new BigDecimal("85.5000")); container.setMaxWeightKg(new BigDecimal("27500.000"));
|
||||
OceanBookingEntity booking = new OceanBookingEntity();
|
||||
booking.setId(21L); booking.setStatus("RELEASED"); booking.setWarehouseId(101L); booking.setChannelCode("SEA-US");
|
||||
booking.setId(21L); booking.setStatus("RELEASED"); booking.setWarehouseId(101L); booking.setChannelCode("EXX_RAIL_NY");
|
||||
OceanBookingContainerRequirementEntity requirement = new OceanBookingContainerRequirementEntity();
|
||||
requirement.setId(31L); requirement.setQuantity(1); requirement.setContainerType("40HC"); requirement.setCapacityCbm(new BigDecimal("76.0000")); requirement.setMaxWeightKg(new BigDecimal("28000.000"));
|
||||
when(repository.find(11L)).thenReturn(container, container);
|
||||
@@ -311,13 +313,13 @@ class ContainerServiceTest {
|
||||
void linksMultipleRealContainersToTheSameReleasedBookingRequirement() {
|
||||
ContainerBookingEntity first = booking("WAIT_LOADING", 2);
|
||||
first.setId(11L); first.setContainerNo(null); first.setWarehouseId(101L);
|
||||
first.setChannelCode("SEA-US"); first.setContainerType("40HC");
|
||||
first.setChannelGroupCode("MATSON_CLX"); first.setChannelCode(null); first.setContainerType("40HC");
|
||||
ContainerBookingEntity second = booking("WAIT_LOADING", 4);
|
||||
second.setId(12L); second.setContainerNo(null); second.setWarehouseId(101L);
|
||||
second.setChannelCode("SEA-US"); second.setContainerType("40HC");
|
||||
second.setChannelGroupCode("MATSON_CLX"); second.setChannelCode(null); second.setContainerType("40HC");
|
||||
OceanBookingEntity oceanBooking = new OceanBookingEntity();
|
||||
oceanBooking.setId(21L); oceanBooking.setStatus("RELEASED");
|
||||
oceanBooking.setWarehouseId(101L); oceanBooking.setChannelCode("SEA-US");
|
||||
oceanBooking.setWarehouseId(101L); oceanBooking.setChannelCode("MATSON_CLX_TRUCK");
|
||||
OceanBookingContainerRequirementEntity requirement = new OceanBookingContainerRequirementEntity();
|
||||
requirement.setId(31L); requirement.setQuantity(2); requirement.setContainerType("40HC");
|
||||
when(repository.find(11L)).thenReturn(first);
|
||||
@@ -360,19 +362,19 @@ class ContainerServiceTest {
|
||||
@Test
|
||||
@DisplayName("首次关联拒绝装柜仓库不一致")
|
||||
void rejectsLinkWhenWarehouseDiffers() {
|
||||
assertThreeFieldMismatch(102L, "SEA-US", "40HC");
|
||||
assertThreeFieldMismatch(102L, "MATSON_CLX_TRUCK", "40HC");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("首次关联拒绝物流渠道不一致")
|
||||
void rejectsLinkWhenChannelDiffers() {
|
||||
assertThreeFieldMismatch(101L, "SEA-EU", "40HC");
|
||||
assertThreeFieldMismatch(101L, "HEDE_TRUCK", "40HC");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("首次关联拒绝柜型不一致")
|
||||
void rejectsLinkWhenContainerTypeDiffers() {
|
||||
assertThreeFieldMismatch(101L, "SEA-US", "45HC");
|
||||
assertThreeFieldMismatch(101L, "MATSON_CLX_TRUCK", "45HC");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -415,7 +417,7 @@ class ContainerServiceTest {
|
||||
String requirementType) {
|
||||
ContainerBookingEntity container = booking("WAIT_LOADING", 2);
|
||||
container.setContainerNo(null);
|
||||
container.setWarehouseId(101L); container.setChannelCode("SEA-US");
|
||||
container.setWarehouseId(101L); container.setChannelGroupCode("MATSON_CLX"); container.setChannelCode(null);
|
||||
container.setContainerType("40HC");
|
||||
OceanBookingEntity oceanBooking = new OceanBookingEntity();
|
||||
oceanBooking.setId(21L); oceanBooking.setStatus("RELEASED");
|
||||
@@ -429,7 +431,7 @@ class ContainerServiceTest {
|
||||
|
||||
assertThatThrownBy(() -> service.linkOceanBooking(11L, 9L, "admin",
|
||||
new OceanBookingLinkRequest(21L, 31L, "MSCU6639870", 2)))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("仓库、渠道或柜型不一致");
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("仓库、渠道分组或柜型不一致");
|
||||
verify(repository, never()).linkReleasedBooking(anyLong(), anyLong(), anyLong(), anyString(),
|
||||
any(), anyInt(), anyLong(), anyString());
|
||||
}
|
||||
@@ -437,11 +439,11 @@ class ContainerServiceTest {
|
||||
private void stubLinkContext() {
|
||||
ContainerBookingEntity container = booking("WAIT_LOADING", 2);
|
||||
container.setContainerNo(null);
|
||||
container.setWarehouseId(101L); container.setChannelCode("SEA-US");
|
||||
container.setWarehouseId(101L); container.setChannelGroupCode("MATSON_CLX"); container.setChannelCode(null);
|
||||
container.setContainerType("40HC");
|
||||
OceanBookingEntity oceanBooking = new OceanBookingEntity();
|
||||
oceanBooking.setId(21L); oceanBooking.setStatus("RELEASED");
|
||||
oceanBooking.setWarehouseId(101L); oceanBooking.setChannelCode("SEA-US");
|
||||
oceanBooking.setWarehouseId(101L); oceanBooking.setChannelCode("MATSON_CLX_TRUCK");
|
||||
OceanBookingContainerRequirementEntity requirement = new OceanBookingContainerRequirementEntity();
|
||||
requirement.setId(31L); requirement.setQuantity(2); requirement.setContainerType("40HC");
|
||||
when(repository.find(11L)).thenReturn(container);
|
||||
@@ -491,10 +493,10 @@ class ContainerServiceTest {
|
||||
}
|
||||
|
||||
private static SimulationCreateRequest simulation() {
|
||||
return new SimulationCreateRequest("SEA-US", 101L, "40HC", new BigDecimal("76.0000"), new BigDecimal("28000.000"));
|
||||
return new SimulationCreateRequest("MATSON_CLX", 101L, "40HC", new BigDecimal("76.0000"), new BigDecimal("28000.000"));
|
||||
}
|
||||
|
||||
private static ChannelRow channel() {
|
||||
ChannelRow channel = new ChannelRow(); channel.setChannelCode("SEA-US"); channel.setChannelName("美西海运"); return channel;
|
||||
ChannelRow channel = new ChannelRow(); channel.setChannelCode("MATSON_CLX_TRUCK"); channel.setChannelName("美西海运"); return channel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,11 +171,11 @@ class SmartLoadingServiceTest {
|
||||
@Test
|
||||
void groupsCandidateSpecificationsByInboundCodeForQuantityOnlyPlanning() {
|
||||
Candidate first = new Candidate(9L, 10L, 20L, 30L, "O", "I", 100L,
|
||||
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "服装", 3,
|
||||
"MATSON_CLX_TRUCK", "美森正班海卡", 200L, "FBA1", "FBA仓", "客户", "服装", 3,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.2"),
|
||||
null, null, null, null, null, null, "WAIT_SPLIT", LocalDateTime.parse("2026-09-10T09:30:00"));
|
||||
Candidate second = new Candidate(9L, 10L, 21L, 30L, "O", "I", 100L,
|
||||
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "鞋类", 7,
|
||||
"MATSON_CLX_TRUCK", "美森正班海卡", 200L, "FBA1", "FBA仓", "客户", "鞋类", 7,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.5"),
|
||||
null, null, null, null, null, null, "WAIT_SPLIT", LocalDateTime.parse("2026-09-10T09:30:00"));
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
@@ -515,7 +515,7 @@ class SmartLoadingServiceTest {
|
||||
@Test
|
||||
void pagesWorkbenchContainersWithTheSameGlobalFilters() {
|
||||
WorkbenchContainer container = new WorkbenchContainer(99L, "CB-1", "CAB-1", null,
|
||||
100L, "义乌仓", "MATSON_CLX_TRUCK", "美森正班海卡", "40HC",
|
||||
100L, "义乌仓", "MATSON_CLX", null, null, "40HC",
|
||||
new BigDecimal("68"), new BigDecimal("28000"),
|
||||
1L, "LP-1", "EDITING", 2);
|
||||
WorkbenchContainerQuery query = new WorkbenchContainerQuery(List.of("MATSON_CLX"), 100L,
|
||||
@@ -530,8 +530,7 @@ class SmartLoadingServiceTest {
|
||||
@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");
|
||||
assertThat(channels.getValue()).containsExactly("MATSON_CLX");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1694,7 +1693,7 @@ class SmartLoadingServiceTest {
|
||||
private static Candidate candidate(long tenantId, long inboundId, long detailId, long orderId, int boxes,
|
||||
String destinationCode) {
|
||||
return new Candidate(tenantId, inboundId, detailId, orderId, "O" + orderId, "I" + inboundId,
|
||||
100L, "FBA", "FBA渠道", 200L, destinationCode, "FBA仓", "客户", "货物", boxes,
|
||||
100L, "MATSON_CLX_TRUCK", "美森正班海卡", 200L, destinationCode, "FBA仓", "客户", "货物", boxes,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"),
|
||||
new BigDecimal("0.5"), null, null, null, null, null, null, null, null);
|
||||
}
|
||||
@@ -1771,7 +1770,7 @@ class SmartLoadingServiceTest {
|
||||
private static ContainerBookingEntity booking() {
|
||||
ContainerBookingEntity value = new ContainerBookingEntity();
|
||||
value.setId(99L); value.setWarehouseId(100L); value.setDestinationWarehouseCode("FBA1");
|
||||
value.setChannelCode("FBA");
|
||||
value.setChannelGroupCode("MATSON_CLX"); value.setChannelCode(null);
|
||||
value.setContainerNo("MSCU1234567");
|
||||
value.setCapacityCbm(new BigDecimal("60")); value.setMaxWeightKg(new BigDecimal("20000"));
|
||||
return value;
|
||||
|
||||
Reference in New Issue
Block a user