This commit is contained in:
2026-09-20 14:43:26 +08:00
parent 398f4e6983
commit a7d458cddd
11 changed files with 356 additions and 23 deletions
@@ -39,12 +39,17 @@ class ColoadGroupOpenApiContractTest {
.contains("platform:firstleg:smart-loading:assign")
.contains("platform:firstleg:smart-loading:list")
.contains("任一冻结成员缺少有效 inboundCode 时失败关闭")
.contains("全部 WAIT_SPLIT")
.contains("待审核")
.contains("待测量/测量中")
.contains("待分柜或装柜中")
.contains("直接创建并")
.contains("锁定为 LOCKED 组")
.contains("国内仓和渠道一致")
.contains("状态非待分柜")
.contains("已分配装柜计划")
.contains("已在其他同柜组")
.contains("平台错误不得展示 orderId、inboundId 或 groupId")
.contains("仅部分成员进柜")
.contains("分散在不同装柜计划")
.contains("已属于其他活动组")
.contains("平台错误不得展示")
.contains("orderId、inboundId 或 groupId")
.doesNotContain("destinationType")
.doesNotContain("destinationWarehouseId")
.doesNotContain("destinationCode")
@@ -123,11 +123,9 @@ class ColoadGroupServiceTest {
@Test
void createsCrossCustomerGroupAndFreezesAllAvailableFragmentsForEveryOrder() {
InboundCandidateRow alreadyAllocatedFragment = candidate(101L, 11L, 1004L, 40);
alreadyAllocatedFragment.setActiveAllocationCount(1);
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(
candidate(101L, 11L, 1001L, 30), candidate(101L, 11L, 1003L, 20),
alreadyAllocatedFragment, candidate(202L, 12L, 1002L, 50)));
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()))
@@ -154,8 +152,82 @@ class ColoadGroupServiceTest {
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));
verify(mapper, never()).insertMember(anyLong(), eq(groupId.getValue()), eq(101L), eq(11L),
eq(1004L), anyInt());
}
@org.junit.jupiter.params.ParameterizedTest
@org.junit.jupiter.params.provider.ValueSource(strings = {
"PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING", "WAIT_SPLIT"
})
void createsUnlockedGroupBeforeLoading(String status) {
InboundCandidateRow first = candidate(101L, 11L, 1001L, 30); first.setProcessStatus(status);
InboundCandidateRow second = candidate(202L, 12L, 1002L, 50); second.setProcessStatus(status);
stubSuccessfulCreate("status-" + status, List.of(first, second), "ACTIVE", null);
var result = service.create(List.of(11L, 12L), "status-" + status, 7L, "operator");
assertThat(result.status()).isEqualTo("ACTIVE");
verify(mapper, never()).lockToPlan(anyLong(), anyLong(), anyLong(), any());
}
@Test
void createsLockedGroupWhenEveryLoadingMemberIsFullyAllocatedToTheSamePlan() {
InboundCandidateRow first = loadingCandidate(101L, 11L, 1001L, 30, 700L);
InboundCandidateRow second = loadingCandidate(202L, 12L, 1002L, 50, 700L);
stubSuccessfulCreate("loading-key", List.of(first, second), "LOCKED", 700L);
when(mapper.lockToPlan(anyLong(), eq(700L), eq(7L), eq("operator"))).thenReturn(1);
var result = service.create(List.of(11L, 12L), "loading-key", 7L, "operator");
assertThat(result.status()).isEqualTo("LOCKED");
assertThat(result.lockedPlanId()).isEqualTo(700L);
verify(mapper).lockToPlan(anyLong(), eq(700L), eq(7L), eq("operator"));
verify(mapper).insertHistory(anyLong(), anyLong(), eq("LOCK"), eq("ACTIVE"), eq("LOCKED"),
eq(700L), anyString(), isNull(), eq(7L), eq("operator"));
}
@Test
void createsLockedGroupForPreMeasurementOrdersAlreadyFullyReservedInTheSamePlan() {
InboundCandidateRow first = loadingCandidate(101L, 11L, 1001L, 30, 700L);
first.setProcessStatus("PREPARING");
InboundCandidateRow second = loadingCandidate(202L, 12L, 1002L, 50, 700L);
second.setProcessStatus("MEASURING");
stubSuccessfulCreate("reserved-early", List.of(first, second), "LOCKED", 700L);
when(mapper.lockToPlan(anyLong(), eq(700L), eq(7L), eq("operator"))).thenReturn(1);
var result = service.create(List.of(11L, 12L), "reserved-early", 7L, "operator");
assertThat(result.status()).isEqualTo("LOCKED");
assertThat(result.lockedPlanId()).isEqualTo(700L);
}
@Test
void rejectsLoadingMembersSplitAcrossPlansOrNotFullyAllocated() {
when(mapper.insertCommand(anyLong(), eq(7L), eq("CREATE"), anyString(), anyString())).thenReturn(1);
InboundCandidateRow first = loadingCandidate(101L, 11L, 1001L, 30, 700L);
InboundCandidateRow otherPlan = loadingCandidate(202L, 12L, 1002L, 50, 701L);
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(first, otherPlan));
assertThatThrownBy(() -> service.create(List.of(11L, 12L), "different-plans", 7L, "operator"))
.isInstanceOf(BizException.class).hasMessageContaining("不在同一个装柜计划");
InboundCandidateRow partial = loadingCandidate(202L, 12L, 1002L, 50, 700L);
partial.setActiveAllocatedPackageCount(49);
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(first, partial));
assertThatThrownBy(() -> service.create(List.of(11L, 12L), "partial-loading", 7L, "operator"))
.isInstanceOf(BizException.class).hasMessageContaining("未完整分配到唯一装柜计划");
}
@Test
void rejectsSelectionWhenOnlyPartOfTheMembersHasEnteredAPlan() {
when(mapper.insertCommand(anyLong(), eq(7L), eq("CREATE"), anyString(), anyString())).thenReturn(1);
InboundCandidateRow reserved = loadingCandidate(101L, 11L, 1001L, 30, 700L);
reserved.setProcessStatus("WAIT_MEASURE");
InboundCandidateRow unallocated = candidate(202L, 12L, 1002L, 50);
unallocated.setProcessStatus("MEASURING");
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(reserved, unallocated));
assertThatThrownBy(() -> service.create(List.of(11L, 12L), "partly-reserved", 7L, "operator"))
.isInstanceOf(BizException.class).hasMessageContaining("只有部分成员进入装柜计划");
}
@Test
@@ -179,7 +251,7 @@ class ColoadGroupServiceTest {
assertThatThrownBy(() -> service.create(List.of(11L, 12L), "key", 7L, "operator"))
.isInstanceOf(BizException.class)
.hasMessageContaining("订单“FO-11”")
.hasMessageContaining("入仓码“IN-1001”已分配装柜计划")
.hasMessageContaining("入仓码“IN-1001”已在其他同柜组")
.hasMessageNotContaining("订单 11")
.hasMessageNotContaining("groupId")
.hasMessageNotContaining("inboundId");
@@ -219,8 +291,8 @@ class ColoadGroupServiceTest {
when(mapper.insertCommand(anyLong(), eq(7L), eq("CREATE"), anyString(), anyString()))
.thenReturn(1);
InboundCandidateRow status = candidate(101L, 11L, 1001L, 30);
status.setProcessStatus("LOADING");
assertUnavailable(status, "state-key", "状态非待分柜");
status.setProcessStatus("LOADED");
assertUnavailable(status, "state-key", "状态不允许创建同柜组");
InboundCandidateRow grouped = candidate(101L, 11L, 1001L, 30);
grouped.setActiveGroupId(900L);
@@ -393,9 +465,33 @@ class ColoadGroupServiceTest {
value.setProcessStatus("WAIT_SPLIT"); value.setWarehouseId(9L); value.setWarehouseName("义乌仓");
value.setChannelCode("CH"); value.setChannelName("美森海卡");
value.setFullPackageCount(packages); value.setActiveAllocationCount(0);
value.setActiveAllocationPlanCount(0); value.setActiveAllocatedPackageCount(0);
return value;
}
private static InboundCandidateRow loadingCandidate(long tenantId, long orderId, long inboundId,
int packages, long planId) {
InboundCandidateRow value = candidate(tenantId, orderId, inboundId, packages);
value.setProcessStatus("LOADING"); value.setActiveAllocationCount(1);
value.setActiveAllocationPlanCount(1); value.setActiveAllocationPlanId(planId);
value.setActiveAllocatedPackageCount(packages);
return value;
}
private void stubSuccessfulCreate(String key, List<InboundCandidateRow> candidates,
String resultStatus, Long planId) {
when(mapper.insertCommand(anyLong(), eq(7L), eq("CREATE"), eq(key), anyString())).thenReturn(1);
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(candidates);
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(),
anyString(), any(), anyLong(), any())).thenReturn(1);
when(mapper.completeCommand(anyLong(), anyLong())).thenReturn(1);
when(mapper.group(anyLong())).thenReturn(group(500L, resultStatus, planId, "LOCKED".equals(resultStatus) ? 1 : 0));
when(mapper.members(anyLong())).thenReturn(candidates.stream().map(row -> member(
row.getTenantId(), row.getOrderId(), row.getInboundId(), row.getFullPackageCount())).toList());
}
private void assertUnavailable(InboundCandidateRow row, String key, String reason) {
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(
row, candidate(202L, 12L, 1002L, 50)));