fix(firstleg): refresh recommendation capacity
This commit is contained in:
@@ -368,6 +368,13 @@ public class SmartLoadingService {
|
|||||||
if (!visitedGroups.add(candidate.coLoadGroupId()) || StringUtils.hasText(candidate.blockReason())) {
|
if (!visitedGroups.add(candidate.coLoadGroupId()) || StringUtils.hasText(candidate.blockReason())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
GroupSnapshot currentGroup = group(candidate.accountId(), candidate.inboundId());
|
||||||
|
if (currentGroup == null || !"ACTIVE".equals(currentGroup.status())
|
||||||
|
|| currentGroup.lockedPlanId() != null
|
||||||
|
|| !groupFits(booking, repository.items(planId), currentGroup,
|
||||||
|
availableCandidates(booking))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
LoadingPlanEntity current = repository.plan(planId);
|
LoadingPlanEntity current = repository.plan(planId);
|
||||||
assignGroup(planId, new AssignGroupRequest(candidate.inboundId(), current.getVersion(),
|
assignGroup(planId, new AssignGroupRequest(candidate.inboundId(), current.getVersion(),
|
||||||
"REC-GRP-" + sha256(operation + '|' + candidate.coLoadGroupId()).substring(0, 48)),
|
"REC-GRP-" + sha256(operation + '|' + candidate.coLoadGroupId()).substring(0, 48)),
|
||||||
@@ -640,8 +647,7 @@ public class SmartLoadingService {
|
|||||||
if (!"ACTIVE".equals(group.status()) || group.lockedPlanId() != null) {
|
if (!"ACTIVE".equals(group.status()) || group.lockedPlanId() != null) {
|
||||||
throw conflict("同柜不可拆组已锁定或已消费");
|
throw conflict("同柜不可拆组已锁定或已消费");
|
||||||
}
|
}
|
||||||
List<Candidate> available = repository.candidates(booking.getWarehouseId()).stream()
|
List<Candidate> available = availableCandidates(booking);
|
||||||
.filter(candidate -> sameChannel(booking, candidate) && sameCustomerScope(booking, candidate)).toList();
|
|
||||||
Map<Long, List<Candidate>> result = new LinkedHashMap<>();
|
Map<Long, List<Candidate>> result = new LinkedHashMap<>();
|
||||||
for (Member member : group.members()) {
|
for (Member member : group.members()) {
|
||||||
if (!repository.activeItemsByInbound(member.tenantId(), member.inboundId()).isEmpty()) {
|
if (!repository.activeItemsByInbound(member.tenantId(), member.inboundId()).isEmpty()) {
|
||||||
@@ -663,6 +669,11 @@ public class SmartLoadingService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Candidate> availableCandidates(ContainerBookingEntity booking) {
|
||||||
|
return repository.candidates(booking.getWarehouseId()).stream()
|
||||||
|
.filter(candidate -> sameChannel(booking, candidate) && sameCustomerScope(booking, candidate)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean groupFits(ContainerBookingEntity booking, List<LoadingPlanItemEntity> planItems,
|
private boolean groupFits(ContainerBookingEntity booking, List<LoadingPlanItemEntity> planItems,
|
||||||
GroupSnapshot group, List<Candidate> available) {
|
GroupSnapshot group, List<Candidate> available) {
|
||||||
if (booking.getCapacityCbm() == null || booking.getMaxWeightKg() == null) return false;
|
if (booking.getCapacityCbm() == null || booking.getMaxWeightKg() == null) return false;
|
||||||
|
|||||||
@@ -262,6 +262,87 @@ class SmartLoadingServiceTest {
|
|||||||
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void recommendRechecksCurrentCapacitySkipsStaleGroupAndContinuesWithLaterCandidate() {
|
||||||
|
Candidate ordinary = candidate(9L, 10L, 20L, 30L, 4);
|
||||||
|
Candidate groupFirst = candidate(9L, 11L, 21L, 31L, 4);
|
||||||
|
Candidate groupSecond = candidate(9L, 12L, 22L, 32L, 4);
|
||||||
|
Candidate later = candidate(9L, 13L, 23L, 33L, 1);
|
||||||
|
GroupSnapshot group = group("ACTIVE", null,
|
||||||
|
new Member(9L, 31L, 11L, 4), new Member(9L, 32L, 12L, 4));
|
||||||
|
ContainerBookingEntity small = booking();
|
||||||
|
small.setCapacityCbm(new BigDecimal("5"));
|
||||||
|
AtomicInteger version = new AtomicInteger();
|
||||||
|
List<LoadingPlanItemEntity> saved = new ArrayList<>();
|
||||||
|
when(repository.plan(1L)).thenAnswer(invocation -> plan("EDITING", version.get()));
|
||||||
|
when(repository.planByBooking(99L)).thenAnswer(invocation -> plan("EDITING", version.get()));
|
||||||
|
when(repository.booking(99L)).thenReturn(small);
|
||||||
|
when(repository.items(1L)).thenAnswer(invocation -> List.copyOf(saved));
|
||||||
|
when(repository.candidates(100L)).thenReturn(List.of(ordinary, groupFirst, groupSecond, later));
|
||||||
|
when(repository.candidate(1L, 100L, 10L, 20L)).thenReturn(ordinary);
|
||||||
|
when(repository.candidate(1L, 100L, 13L, 23L)).thenReturn(later);
|
||||||
|
when(repository.activeItemsByInbound(anyLong(), anyLong(), anyLong())).thenReturn(List.of());
|
||||||
|
when(repository.activeItem(anyLong(), anyLong(), anyLong())).thenReturn(null);
|
||||||
|
when(repository.touch(eq(1L), anyInt(), eq(7L), eq("u"))).thenAnswer(invocation -> {
|
||||||
|
int expected = invocation.getArgument(1);
|
||||||
|
if (expected != version.get()) return 0;
|
||||||
|
version.incrementAndGet();
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
LoadingPlanItemEntity item = invocation.getArgument(0);
|
||||||
|
item.setId(800L + saved.size());
|
||||||
|
saved.add(item);
|
||||||
|
return null;
|
||||||
|
}).when(repository).insert(any(LoadingPlanItemEntity.class));
|
||||||
|
when(cargoClient.detail(anyString(), anyLong(), anyLong(), anyLong())).thenAnswer(invocation -> {
|
||||||
|
long orderId = invocation.getArgument(2);
|
||||||
|
long detailId = invocation.getArgument(3);
|
||||||
|
return ApiResponse.success(new OrderCargoClient.CargoBinding(detailId, orderId, 1,
|
||||||
|
"货物", detailId == 23L ? 1 : 4, BigDecimal.TEN, BigDecimal.ONE));
|
||||||
|
});
|
||||||
|
when(coloadGroups.findByInbound(9L, 11L)).thenReturn(group);
|
||||||
|
when(coloadGroups.findByInbound(9L, 12L)).thenReturn(group);
|
||||||
|
|
||||||
|
Plan result = service.recommend(1L,
|
||||||
|
new RecommendRequest(0, "recommend-refresh-group-capacity"), 7L, "u");
|
||||||
|
|
||||||
|
assertThat(result.version()).isEqualTo(2);
|
||||||
|
assertThat(saved).extracting(LoadingPlanItemEntity::getInboundId,
|
||||||
|
LoadingPlanItemEntity::getPackageCount).containsExactly(
|
||||||
|
org.assertj.core.groups.Tuple.tuple(10L, 4),
|
||||||
|
org.assertj.core.groups.Tuple.tuple(13L, 1));
|
||||||
|
verify(coloadGroups, never()).lockWholeGroupForAssignment(anyLong(), anyLong(), anyLong(),
|
||||||
|
anyString(), anyLong(), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void manualGroupAssignmentOverCurrentCapacityStillReturnsA0422() {
|
||||||
|
Candidate first = candidate(9L, 11L, 21L, 31L, 4);
|
||||||
|
Candidate second = candidate(9L, 12L, 22L, 32L, 4);
|
||||||
|
GroupSnapshot group = group("ACTIVE", null,
|
||||||
|
new Member(9L, 31L, 11L, 4), new Member(9L, 32L, 12L, 4));
|
||||||
|
ContainerBookingEntity small = booking();
|
||||||
|
small.setCapacityCbm(new BigDecimal("3"));
|
||||||
|
when(repository.plan(1L)).thenReturn(plan("EDITING", 0));
|
||||||
|
when(repository.planByBooking(99L)).thenReturn(plan("EDITING", 0));
|
||||||
|
when(repository.booking(99L)).thenReturn(small);
|
||||||
|
when(repository.items(1L)).thenReturn(List.of());
|
||||||
|
when(repository.candidates(100L)).thenReturn(List.of(first, second));
|
||||||
|
when(repository.activeItemsByInbound(anyLong(), anyLong())).thenReturn(List.of());
|
||||||
|
when(coloadGroups.findByInbound(9L, 11L)).thenReturn(group);
|
||||||
|
when(coloadGroups.findByInbound(9L, 12L)).thenReturn(group);
|
||||||
|
|
||||||
|
BizException error = catchThrowableOfType(() -> service.assignGroup(1L,
|
||||||
|
new com.baiyue.firstleg.dto.SmartLoadingDtos.AssignGroupRequest(
|
||||||
|
11L, 0, "manual-group-too-large"), 7L, "u"), BizException.class);
|
||||||
|
|
||||||
|
assertThat(error.getResultCode()).isEqualTo(ResultCode.VALIDATION_FAILED);
|
||||||
|
assertThat(error).hasMessageContaining("110%上限");
|
||||||
|
verify(coloadGroups, never()).lockWholeGroupForAssignment(anyLong(), anyLong(), anyLong(),
|
||||||
|
anyString(), anyLong(), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void adjustAndIndividualDeleteRejectColoadMembersBeforeAnyWrite() {
|
void adjustAndIndividualDeleteRejectColoadMembersBeforeAnyWrite() {
|
||||||
LoadingPlanEntity plan = plan("EDITING", 0);
|
LoadingPlanEntity plan = plan("EDITING", 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user