优化
This commit is contained in:
+4
@@ -56,6 +56,7 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
authority("adjust","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,long.class,AdjustRequest.class);
|
||||
authority("warehouseRemark","platform:firstleg:preload:edit",AuthenticatedUser.class,long.class,long.class,WarehouseRemarkRequest.class);
|
||||
authority("delete","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,long.class,DeleteRequest.class);
|
||||
authority("removeUnpicked","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,RemoveUnpickedRequest.class);
|
||||
}
|
||||
@Test void openApiHasNoManualCreateOrPushStep() throws Exception {
|
||||
String yaml=java.nio.file.Files.readString(java.nio.file.Path.of("src/main/resources/openapi/firstleg-v1.yaml"));
|
||||
@@ -75,6 +76,7 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
.contains("SmartLoadingAssignRequest:")
|
||||
.contains("SmartLoadingAdjustRequest:")
|
||||
.contains("SmartLoadingDeleteRequest:")
|
||||
.contains("SmartLoadingRemoveUnpickedRequest:")
|
||||
.contains("SmartLoadingWarehouseRemarkRequest:")
|
||||
.contains("/items/{itemId}/warehouse-remark:")
|
||||
.contains("需要 platform:firstleg:preload:edit 权限")
|
||||
@@ -83,6 +85,8 @@ class PlatformSmartLoadingControllerContractTest {
|
||||
.contains("items[].version 是递增后的 itemVersion")
|
||||
.contains("数量调整或动态增量不会覆盖已有 warehouseRemark")
|
||||
.contains("柜内总容积和总重量分别允许达到柜型额定值的150%")
|
||||
.contains("人工分配不限制订单渠道与柜次渠道一致")
|
||||
.contains("渠道只约束智能推荐")
|
||||
.contains("canAssign: {type: boolean}")
|
||||
.contains("canEdit: {type: boolean}")
|
||||
.contains("pickedPackageCount: {type: integer, minimum: 0}")
|
||||
|
||||
@@ -332,16 +332,16 @@ class SmartLoadingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignsPendingReviewOrderAsAProvisionalContainerAllocation() {
|
||||
void manuallyAssignsCrossChannelPendingReviewOrderAsAProvisionalContainerAllocation() {
|
||||
long customerId = 2092797167699816450L;
|
||||
long orderId = 2095052002489335810L;
|
||||
String inboundCode = "EXXHK2609020361";
|
||||
var pending = new PendingReviewOrderClient.Row(orderId, "FL-361", inboundCode,
|
||||
customerId, "客户", 100L, "FBA", "FBA渠道", 5,
|
||||
customerId, "客户", 100L, "SLOW", "慢船", 5,
|
||||
LocalDateTime.parse("2026-09-02T15:31:36"), "PENDING_REVIEW");
|
||||
var snapshot = new com.baiyue.firstleg.dto.OrderApprovedInboundCommand(
|
||||
customerId, orderId, "FL-361", inboundCode, customerId, "客户", 100L, "义乌仓",
|
||||
null, null, null, "FIRST_LEG", "FBA", "FBA渠道", null, null, null,
|
||||
null, null, null, "FIRST_LEG", "SLOW", "慢船", null, null, null,
|
||||
null, null, false, "FBA", 200L, "FBA1", "FBA仓", null, null, null, 5);
|
||||
var label = new OrderCargoClient.CargoLabelBinding(20L, orderId, 1, "货物", 5,
|
||||
null, null, null);
|
||||
@@ -381,7 +381,7 @@ class SmartLoadingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignsWaitMeasureInboundUsingDeclaredFactsWithoutTreatingStatusAsAGate() {
|
||||
void manuallyAssignsCrossChannelWaitMeasureInboundUsingDeclaredFacts() {
|
||||
long customerId = 9L;
|
||||
long inboundId = 10L;
|
||||
long orderId = 30L;
|
||||
@@ -391,11 +391,11 @@ class SmartLoadingServiceTest {
|
||||
projection.setOrderId(orderId);
|
||||
projection.setInboundCode("I10");
|
||||
projection.setWarehouseId(100L);
|
||||
projection.setChannelCode("FBA");
|
||||
projection.setChannelCode("SLOW");
|
||||
projection.setProcessStatus("WAIT_MEASURE");
|
||||
var snapshot = new com.baiyue.firstleg.dto.OrderApprovedInboundCommand(
|
||||
customerId, orderId, "O30", "I10", customerId, "客户", 100L, "义乌仓",
|
||||
null, null, null, "FIRST_LEG", "FBA", "FBA渠道", null, null, null,
|
||||
null, null, null, "FIRST_LEG", "SLOW", "慢船", null, null, null,
|
||||
null, null, false, "FBA", 200L, "FBA1", "FBA仓", null, null, null, 5);
|
||||
var label = new OrderCargoClient.CargoLabelBinding(20L, orderId, 1, "货物", 5,
|
||||
null, null, null);
|
||||
@@ -575,8 +575,8 @@ class SmartLoadingServiceTest {
|
||||
|
||||
@Test
|
||||
void atomicallyAssignsEveryInboundFragmentOfSameOrderAcrossDifferentDestinations() {
|
||||
Candidate first = candidate(9L, 10L, 20L, 30L, 4);
|
||||
Candidate second = candidate(9L, 11L, 21L, 30L, 6, "FBA2");
|
||||
Candidate first = withChannel(candidate(9L, 10L, 20L, 30L, 4), "SLOW", "慢船");
|
||||
Candidate second = withChannel(candidate(9L, 11L, 21L, 30L, 6, "FBA2"), "SLOW", "慢船");
|
||||
GroupSnapshot active = group("ACTIVE", null,
|
||||
new Member(9L, 30L, 10L, "IN-10", 4), new Member(9L, 30L, 11L, "IN-11", 6));
|
||||
GroupSnapshot locked = group("LOCKED", 1L,
|
||||
@@ -796,6 +796,94 @@ class SmartLoadingServiceTest {
|
||||
org.mockito.ArgumentMatchers.startsWith("UNLOCK-"), eq(7L), eq("u"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removesAllUnpickedItemsWithOnePlanVersionChange() {
|
||||
LoadingPlanEntity initial = plan("EDITING", 2);
|
||||
LoadingPlanEntity after = plan("EDITING", 3);
|
||||
LoadingPlanItemEntity first = item(8L, 9L, 20L, 4);
|
||||
LoadingPlanItemEntity second = item(9L, 19L, 21L, 6);
|
||||
second.setInboundId(11L);
|
||||
when(repository.plan(1L)).thenReturn(initial, after);
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of(first, second), List.of());
|
||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||
when(repository.remove(eq(1L), anyLong(), eq(0), eq(7L), eq("u"))).thenReturn(1);
|
||||
|
||||
Plan result = service.removeUnpicked(1L,
|
||||
new com.baiyue.firstleg.dto.SmartLoadingDtos.RemoveUnpickedRequest(2, "remove-unpicked"),
|
||||
7L, "u");
|
||||
|
||||
assertThat(result.items()).isEmpty();
|
||||
verify(repository).touch(1L, 2, 7L, "u");
|
||||
verify(repository, times(2)).remove(eq(1L), anyLong(), eq(0), eq(7L), eq("u"));
|
||||
verify(loadingPickService, times(2)).remove(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkRemovalKeepsWholeGroupWhenAnyMemberWasPicked() {
|
||||
LoadingPlanEntity initial = plan("LOADING", 2);
|
||||
LoadingPlanEntity after = plan("LOADING", 3);
|
||||
LoadingPlanItemEntity first = item(8L, 9L, 20L, 4);
|
||||
LoadingPlanItemEntity second = item(9L, 19L, 21L, 6);
|
||||
second.setInboundId(11L);
|
||||
LoadingPlanItemEntity standalone = item(10L, 29L, 22L, 3);
|
||||
standalone.setInboundId(12L);
|
||||
GroupSnapshot group = group("LOCKED", 1L,
|
||||
new Member(9L, 30L, 10L, "IN-10", 4), new Member(19L, 31L, 11L, "IN-11", 6));
|
||||
when(repository.plan(1L)).thenReturn(initial, after);
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of(first, second, standalone), List.of(first, second));
|
||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||
when(repository.remove(1L, 10L, 0, 7L, "u")).thenReturn(1);
|
||||
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group);
|
||||
when(coloadGroups.findByInbound(19L, 11L)).thenReturn(group);
|
||||
when(loadingPickService.mutationSnapshot(1L)).thenReturn(
|
||||
new LoadingPickService.MutationSnapshot(77L, "LOADING", 5, Map.of(8L, 1)));
|
||||
|
||||
Plan result = service.removeUnpicked(1L,
|
||||
new com.baiyue.firstleg.dto.SmartLoadingDtos.RemoveUnpickedRequest(2, "keep-picked-group"),
|
||||
7L, "u");
|
||||
|
||||
assertThat(result.items()).extracting(LoadingPlanItemEntity::getId).containsExactly(8L, 9L);
|
||||
verify(repository).remove(1L, 10L, 0, 7L, "u");
|
||||
verify(repository, never()).remove(eq(1L), eq(8L), anyInt(), anyLong(), anyString());
|
||||
verify(repository, never()).remove(eq(1L), eq(9L), anyInt(), anyLong(), anyString());
|
||||
verify(coloadGroups, never()).unlockWholeGroupAfterRemoval(anyLong(), anyLong(), anyString(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkRemovalRemovesAndUnlocksACompletelyUnpickedGroupOnce() {
|
||||
LoadingPlanEntity initial = plan("EDITING", 2);
|
||||
LoadingPlanEntity after = plan("EDITING", 3);
|
||||
LoadingPlanItemEntity first = item(8L, 9L, 20L, 4);
|
||||
LoadingPlanItemEntity second = item(9L, 19L, 21L, 6);
|
||||
second.setInboundId(11L);
|
||||
GroupSnapshot group = group("LOCKED", 1L,
|
||||
new Member(9L, 30L, 10L, "IN-10", 4), new Member(19L, 31L, 11L, "IN-11", 6));
|
||||
when(repository.plan(1L)).thenReturn(initial, after);
|
||||
when(repository.booking(99L)).thenReturn(booking());
|
||||
when(repository.items(1L)).thenReturn(List.of(first, second), List.of());
|
||||
when(repository.activeItemsByInbound(9L, 10L)).thenReturn(List.of(first));
|
||||
when(repository.activeItemsByInbound(19L, 11L)).thenReturn(List.of(second));
|
||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||
when(repository.remove(eq(1L), anyLong(), eq(0), eq(7L), eq("u"))).thenReturn(1);
|
||||
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group);
|
||||
when(coloadGroups.findByInbound(19L, 11L)).thenReturn(group);
|
||||
GroupSnapshot unlocked = group("ACTIVE", null,
|
||||
new Member(9L, 30L, 10L, "IN-10", 4), new Member(19L, 31L, 11L, "IN-11", 6));
|
||||
when(coloadGroups.unlockWholeGroupAfterRemoval(eq(700L), eq(1L), anyString(), eq(7L), eq("u")))
|
||||
.thenReturn(unlocked);
|
||||
|
||||
Plan result = service.removeUnpicked(1L,
|
||||
new com.baiyue.firstleg.dto.SmartLoadingDtos.RemoveUnpickedRequest(2, "remove-unpicked-group"),
|
||||
7L, "u");
|
||||
|
||||
assertThat(result.items()).isEmpty();
|
||||
verify(repository, times(2)).remove(eq(1L), anyLong(), eq(0), eq(7L), eq("u"));
|
||||
verify(coloadGroups).unlockWholeGroupAfterRemoval(eq(700L), eq(1L),
|
||||
org.mockito.ArgumentMatchers.startsWith("UNLOCK-"), eq(7L), eq("u"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proportionallyDistributesInboundQuantityWithoutLosingCartons() {
|
||||
Candidate first = new Candidate(9L, 10L, 20L, 30L, "O", "I", 100L,
|
||||
|
||||
Reference in New Issue
Block a user