fix(firstleg): align coload group eligibility
This commit is contained in:
+10
@@ -16,6 +16,10 @@ class ColoadGroupOpenApiContractTest {
|
||||
String source = Files.readString(Path.of("src/main/resources/openapi/coload-groups-v1.yaml"));
|
||||
Map<String, Object> yaml = new Yaml().load(source);
|
||||
Map<String, Object> paths = (Map<String, Object>) yaml.get("paths");
|
||||
Map<String, Object> components = (Map<String, Object>) yaml.get("components");
|
||||
Map<String, Object> schemas = (Map<String, Object>) components.get("schemas");
|
||||
Map<String, Object> orderInfo = (Map<String, Object>) schemas.get("OrderColoadGroupInfo");
|
||||
Map<String, Object> orderInfoProperties = (Map<String, Object>) orderInfo.get("properties");
|
||||
|
||||
assertThat(paths).containsKeys(
|
||||
"/api/platform/v1/first-leg/coload-groups",
|
||||
@@ -32,6 +36,12 @@ class ColoadGroupOpenApiContractTest {
|
||||
.contains("expectedVersion")
|
||||
.contains("fullPackageCount")
|
||||
.contains("platform:firstleg:smart-loading:assign")
|
||||
.contains("全部 WAIT_SPLIT")
|
||||
.contains("国内仓和渠道一致")
|
||||
.doesNotContain("destinationType")
|
||||
.doesNotContain("destinationWarehouseId")
|
||||
.doesNotContain("destinationCode")
|
||||
.doesNotContain("platform:firstleg:coload");
|
||||
assertThat(orderInfoProperties).containsOnlyKeys("orderId", "group");
|
||||
}
|
||||
}
|
||||
|
||||
+28
-30
@@ -34,11 +34,12 @@ class ColoadGroupServiceTest {
|
||||
private ColoadGroupService service;
|
||||
|
||||
@Test
|
||||
void createsCrossCustomerGroupAndFreezesEveryMembersFullPackageCount() {
|
||||
void createsCrossCustomerGroupAndFreezesAllAvailableFragmentsForEveryOrder() {
|
||||
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(
|
||||
candidate(101L, 11L, 1001L, 30), candidate(202L, 12L, 1002L, 50)));
|
||||
when(mapper.insertGroup(anyLong(), anyString(), eq(9L), eq("CH"), eq("FBA"),
|
||||
eq(88L), eq("ONT8"), eq(7L), eq("operator"))).thenReturn(1);
|
||||
candidate(101L, 11L, 1001L, 30), candidate(101L, 11L, 1003L, 20),
|
||||
candidate(202L, 12L, 1002L, 50)));
|
||||
when(mapper.insertGroup(anyLong(), anyString(), eq(9L), eq("CH"),
|
||||
eq(7L), eq("operator"))).thenReturn(1);
|
||||
when(mapper.insertMember(anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyInt()))
|
||||
.thenReturn(1);
|
||||
when(mapper.insertHistory(anyLong(), anyLong(), anyString(), any(), anyString(), any(),
|
||||
@@ -48,17 +49,20 @@ class ColoadGroupServiceTest {
|
||||
when(mapper.completeCommand(anyLong(), anyLong())).thenReturn(1);
|
||||
when(mapper.group(anyLong())).thenReturn(group(500L, "ACTIVE", null, 0));
|
||||
when(mapper.members(anyLong())).thenReturn(List.of(
|
||||
member(101L, 11L, 1001L, 30), member(202L, 12L, 1002L, 50)));
|
||||
member(101L, 11L, 1001L, 30), member(101L, 11L, 1003L, 20),
|
||||
member(202L, 12L, 1002L, 50)));
|
||||
|
||||
var result = service.create(List.of(12L, 11L), "create-key", 7L, "operator");
|
||||
|
||||
assertThat(result.members()).extracting("tenantId", "orderId", "inboundId", "fullPackageCount")
|
||||
.containsExactly(tuple(101L, 11L, 1001L, 30), tuple(202L, 12L, 1002L, 50));
|
||||
assertThat(result.fullPackageCount()).isEqualTo(80);
|
||||
.containsExactly(tuple(101L, 11L, 1001L, 30), tuple(101L, 11L, 1003L, 20),
|
||||
tuple(202L, 12L, 1002L, 50));
|
||||
assertThat(result.fullPackageCount()).isEqualTo(100);
|
||||
ArgumentCaptor<Long> groupId = ArgumentCaptor.forClass(Long.class);
|
||||
verify(mapper).insertGroup(groupId.capture(), anyString(), eq(9L), eq("CH"), eq("FBA"),
|
||||
eq(88L), eq("ONT8"), eq(7L), eq("operator"));
|
||||
verify(mapper).insertGroup(groupId.capture(), anyString(), eq(9L), eq("CH"),
|
||||
eq(7L), eq("operator"));
|
||||
verify(mapper).insertMember(anyLong(), eq(groupId.getValue()), eq(101L), eq(11L), eq(1001L), eq(30));
|
||||
verify(mapper).insertMember(anyLong(), eq(groupId.getValue()), eq(101L), eq(11L), eq(1003L), eq(20));
|
||||
verify(mapper).insertMember(anyLong(), eq(groupId.getValue()), eq(202L), eq(12L), eq(1002L), eq(50));
|
||||
}
|
||||
|
||||
@@ -71,11 +75,9 @@ class ColoadGroupServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMemberThatIsAllocatedSplitOrAlreadyGrouped() {
|
||||
void rejectsOrderWhenNoAvailableFragmentRemains() {
|
||||
InboundCandidateRow invalid = candidate(101L, 11L, 1001L, 30);
|
||||
invalid.setActiveAllocationCount(1);
|
||||
invalid.setChildCount(1);
|
||||
invalid.setSplitBatchCount(1);
|
||||
invalid.setActiveGroupId(900L);
|
||||
when(mapper.insertCommand(anyLong(), eq(7L), eq("CREATE"), eq("key"), anyString())).thenReturn(1);
|
||||
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(
|
||||
@@ -83,16 +85,15 @@ class ColoadGroupServiceTest {
|
||||
|
||||
assertThatThrownBy(() -> service.create(List.of(11L, 12L), "key", 7L, "operator"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("唯一可创建");
|
||||
verify(mapper, never()).insertGroup(anyLong(), anyString(), anyLong(), anyString(), anyString(),
|
||||
any(), any(), anyLong(), any());
|
||||
.hasMessageContaining("不存在可创建");
|
||||
verify(mapper, never()).insertGroup(anyLong(), anyString(), anyLong(), anyString(),
|
||||
anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDifferentWarehouseChannelOrDestination() {
|
||||
void rejectsDifferentWarehouseOrChannel() {
|
||||
InboundCandidateRow second = candidate(202L, 12L, 1002L, 50);
|
||||
second.setDestinationWarehouseId(99L);
|
||||
second.setDestinationCode("LAX9");
|
||||
second.setWarehouseId(10L);
|
||||
when(mapper.insertCommand(anyLong(), eq(7L), eq("CREATE"), eq("route-key"), anyString()))
|
||||
.thenReturn(1);
|
||||
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(
|
||||
@@ -100,7 +101,7 @@ class ColoadGroupServiceTest {
|
||||
|
||||
assertThatThrownBy(() -> service.create(List.of(11L, 12L), "route-key", 7L, "operator"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("同一国内仓、渠道和目的仓港");
|
||||
.hasMessageContaining("同一国内仓和渠道");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,10 +144,12 @@ class ColoadGroupServiceTest {
|
||||
.thenReturn(1);
|
||||
when(mapper.lockGroup(500L)).thenReturn(group(500L, "ACTIVE", null, 0));
|
||||
when(mapper.members(500L)).thenReturn(List.of(
|
||||
member(101L, 11L, 1001L, 30), member(202L, 12L, 1002L, 50)));
|
||||
member(101L, 11L, 1001L, 30), member(101L, 11L, 1003L, 20),
|
||||
member(202L, 12L, 1002L, 50)));
|
||||
InboundCandidateRow first = candidate(101L, 11L, 1001L, 30); first.setActiveGroupId(500L);
|
||||
InboundCandidateRow fragment = candidate(101L, 11L, 1003L, 20); fragment.setActiveGroupId(500L);
|
||||
InboundCandidateRow second = candidate(202L, 12L, 1002L, 50); second.setActiveGroupId(500L);
|
||||
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(first, second));
|
||||
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(first, fragment, second));
|
||||
when(mapper.lockToPlan(500L, 700L, 7L, "operator")).thenReturn(1);
|
||||
when(mapper.completeCommand(anyLong(), eq(500L))).thenReturn(1);
|
||||
when(mapper.group(500L)).thenReturn(group(500L, "LOCKED", 700L, 1));
|
||||
@@ -156,8 +159,8 @@ class ColoadGroupServiceTest {
|
||||
|
||||
assertThat(result.status()).isEqualTo("LOCKED");
|
||||
assertThat(result.lockedPlanId()).isEqualTo(700L);
|
||||
assertThat(result.members()).hasSize(2);
|
||||
assertThat(result.fullPackageCount()).isEqualTo(80);
|
||||
assertThat(result.members()).hasSize(3);
|
||||
assertThat(result.fullPackageCount()).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -184,20 +187,15 @@ class ColoadGroupServiceTest {
|
||||
private static InboundCandidateRow candidate(long tenantId, long orderId, long inboundId, int packages) {
|
||||
InboundCandidateRow value = new InboundCandidateRow();
|
||||
value.setTenantId(tenantId); value.setOrderId(orderId); value.setInboundId(inboundId);
|
||||
value.setProcessStatus("WAIT_SPLIT"); value.setInboundRole("MASTER");
|
||||
value.setSplitStatus("NOT_SPLIT"); value.setSplitPurpose("MASTER");
|
||||
value.setWarehouseId(9L); value.setChannelCode("CH"); value.setDestinationType("FBA");
|
||||
value.setDestinationWarehouseId(88L); value.setDestinationCode("ONT8");
|
||||
value.setProcessStatus("WAIT_SPLIT"); value.setWarehouseId(9L); value.setChannelCode("CH");
|
||||
value.setFullPackageCount(packages); value.setActiveAllocationCount(0);
|
||||
value.setChildCount(0); value.setSplitBatchCount(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static GroupRow group(long id, String status, Long planId, int version) {
|
||||
GroupRow value = new GroupRow();
|
||||
value.setId(id); value.setGroupNo("CLG-" + id); value.setGroupStatus(status);
|
||||
value.setWarehouseId(9L); value.setChannelCode("CH"); value.setDestinationType("FBA");
|
||||
value.setDestinationWarehouseId(88L); value.setDestinationCode("ONT8");
|
||||
value.setWarehouseId(9L); value.setChannelCode("CH");
|
||||
value.setLockedPlanId(planId); value.setVersion(version);
|
||||
value.setCreatedAt(LocalDateTime.of(2026, 8, 29, 9, 0));
|
||||
value.setUpdatedAt(value.getCreatedAt());
|
||||
|
||||
+21
-5
@@ -27,11 +27,16 @@ class ColoadGroupSqlContractTest {
|
||||
.contains("full_package_count")
|
||||
.contains("uk_coload_command")
|
||||
.contains("locked_plan_id");
|
||||
assertThat(tableDefinition(source, "fl_coload_group"))
|
||||
.doesNotContain("chk_coload_group_destination")
|
||||
.doesNotContain("destination_type")
|
||||
.doesNotContain("destination_warehouse_id")
|
||||
.doesNotContain("destination_code");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapperLocksAuthoritativeInboundAndRejectsAllocationChildrenAndSplitFacts() throws Exception {
|
||||
void mapperLocksEveryInboundFragmentAndOnlyProjectsCurrentAllocationAndGroupFacts() throws Exception {
|
||||
assertThat(ColoadGroupMapper.class.getAnnotation(Mapper.class)).isNotNull();
|
||||
Method method = ColoadGroupMapper.class.getDeclaredMethod("lockCandidates", java.util.List.class);
|
||||
String sql = String.join(" ", method.getAnnotation(Select.class).value());
|
||||
@@ -39,10 +44,21 @@ class ColoadGroupSqlContractTest {
|
||||
.contains("FROM fl_inbound_order o")
|
||||
.contains("FROM fl_loading_plan_item item")
|
||||
.contains("item.deleted=0")
|
||||
.contains("child.parent_inbound_id=o.id")
|
||||
.contains("FROM fl_inbound_split_batch batch")
|
||||
.contains("batch.status IN ('PREPARED','APPLIED')")
|
||||
.contains("member.active_flag=1")
|
||||
.contains("FOR UPDATE");
|
||||
.contains("FOR UPDATE")
|
||||
.doesNotContain("inbound_role")
|
||||
.doesNotContain("split_status")
|
||||
.doesNotContain("split_purpose")
|
||||
.doesNotContain("parent_inbound_id")
|
||||
.doesNotContain("fl_inbound_split_batch")
|
||||
.doesNotContain("destination_type")
|
||||
.doesNotContain("destination_warehouse_id")
|
||||
.doesNotContain("destination_code");
|
||||
}
|
||||
|
||||
private static String tableDefinition(String source, String tableName) {
|
||||
int start = source.indexOf("CREATE TABLE IF NOT EXISTS " + tableName + " (");
|
||||
int end = source.indexOf(';', start);
|
||||
return source.substring(start, end + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user