fix(firstleg): align coload loading route gates
This commit is contained in:
@@ -165,7 +165,7 @@ public class SmartLoadingService {
|
||||
if (total > availableIncludingCurrent) throw validation("累计分配箱数超过可用箱数");
|
||||
List<LoadingPlanItemEntity> lines = new ArrayList<>(repository.items(planId));
|
||||
if (existing != null) lines.removeIf(x -> Objects.equals(x.getId(), existing.getId()));
|
||||
checkRouteAndCapacity(booking, lines, candidate, total);
|
||||
checkRouteAndCapacity(booking, lines, candidate, total, !wholeGroupAssignment);
|
||||
touch(plan, operatorId, operatorName);
|
||||
LoadingPlanItemEntity changed;
|
||||
if (existing == null) {
|
||||
@@ -364,8 +364,6 @@ public class SmartLoadingService {
|
||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||
Set<Long> visitedGroups = new HashSet<>();
|
||||
for (InboundCandidate candidate : inboundCandidates(plan.getContainerBookingId(), booking.getWarehouseId())) {
|
||||
if (StringUtils.hasText(booking.getDestinationWarehouseCode())
|
||||
&& !Objects.equals(booking.getDestinationWarehouseCode(), candidate.destinationWarehouseCode())) continue;
|
||||
if (candidate.coLoadGroupId() != null) {
|
||||
if (!visitedGroups.add(candidate.coLoadGroupId()) || StringUtils.hasText(candidate.blockReason())) {
|
||||
continue;
|
||||
@@ -376,6 +374,8 @@ public class SmartLoadingService {
|
||||
operatorId, operatorName);
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.hasText(booking.getDestinationWarehouseCode())
|
||||
&& !Objects.equals(booking.getDestinationWarehouseCode(), candidate.destinationWarehouseCode())) continue;
|
||||
int count = fittingBoxes(booking, repository.items(planId), candidate.availablePackageCount(),
|
||||
candidate.averageUnitVolumeCbm(), candidate.unitGrossWeightKg());
|
||||
if (count > 0) {
|
||||
@@ -423,7 +423,7 @@ public class SmartLoadingService {
|
||||
OrderCargoClient.CargoBinding cargo = loadCargo(candidate);
|
||||
List<LoadingPlanItemEntity> others = new ArrayList<>(repository.items(planId));
|
||||
others.removeIf(x -> Objects.equals(x.getId(), itemId));
|
||||
checkRouteAndCapacity(booking, others, candidate, request.packageCount());
|
||||
checkRouteAndCapacity(booking, others, candidate, request.packageCount(), true);
|
||||
String before = json(adjustSnapshot(item));
|
||||
LoadingPlanItemEntity desired = new LoadingPlanItemEntity();
|
||||
desired.setPackageCount(request.packageCount());
|
||||
@@ -808,10 +808,14 @@ public class SmartLoadingService {
|
||||
throw unavailable();
|
||||
}
|
||||
}
|
||||
private void checkRouteAndCapacity(ContainerBookingEntity b,List<LoadingPlanItemEntity> lines,Candidate c,int count){
|
||||
private void checkRouteAndCapacity(ContainerBookingEntity b,List<LoadingPlanItemEntity> lines,Candidate c,
|
||||
int count, boolean requireSameDestination){
|
||||
requireSameChannel(b, c);
|
||||
if (!Objects.equals(b.getWarehouseId(), c.warehouseId())) throw validation("装柜仓库不一致");
|
||||
if (StringUtils.hasText(b.getDestinationWarehouseCode()) && !Objects.equals(b.getDestinationWarehouseCode(), c.destinationWarehouseCode())) throw validation("目的仓/港不一致");
|
||||
if (requireSameDestination && StringUtils.hasText(b.getDestinationWarehouseCode())
|
||||
&& !Objects.equals(b.getDestinationWarehouseCode(), c.destinationWarehouseCode())) {
|
||||
throw validation("目的仓/港不一致");
|
||||
}
|
||||
BigDecimal v=lines.stream().map(x->unit(x.getVolumeCbm())).reduce(BigDecimal.ZERO,BigDecimal::add).add(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count)));
|
||||
BigDecimal w=lines.stream().map(x->unit(x.getGrossWeightKg())).reduce(BigDecimal.ZERO,BigDecimal::add).add(unit(c.unitGrossWeightKg()).multiply(BigDecimal.valueOf(count)));
|
||||
if (b.getCapacityCbm()==null || b.getMaxWeightKg()==null) throw validation("货柜容量和最大承重未配置");
|
||||
|
||||
@@ -58,6 +58,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -821,15 +822,15 @@ class LoadingPickServiceTest {
|
||||
complete.setPickedPackageCount(5);
|
||||
complete.setItemStatus("PICKED");
|
||||
GroupSnapshot group = coloadGroup("LOCKED", 10L,
|
||||
new Member(101L, 301L, 50L, 5), new Member(202L, 302L, 60L, 5));
|
||||
new Member(101L, 301L, 50L, 5), new Member(101L, 301L, 60L, 5));
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.allTaskItemsResolved(20L)).thenReturn(true);
|
||||
when(repository.inboundOwners(20L)).thenReturn(List.of(
|
||||
new LoadingPickItemMapper.InboundOwner(101L, 50L),
|
||||
new LoadingPickItemMapper.InboundOwner(202L, 60L)));
|
||||
new LoadingPickItemMapper.InboundOwner(101L, 60L)));
|
||||
when(repository.items(20L)).thenReturn(List.of(shortPicked, complete));
|
||||
when(coloadGroups.findByInbound(101L, 50L)).thenReturn(group);
|
||||
when(coloadGroups.findByInbound(202L, 60L)).thenReturn(group);
|
||||
when(coloadGroups.findByInbound(101L, 60L)).thenReturn(group);
|
||||
|
||||
assertThatThrownBy(() -> service.complete(20L,
|
||||
new CompleteLoadingRequest(0, "complete-short-coload", "短拣异常"), 9L, "operator"))
|
||||
@@ -1247,8 +1248,14 @@ class LoadingPickServiceTest {
|
||||
}
|
||||
|
||||
private static GroupSnapshot coloadGroup(String status, Long lockedPlanId, Member... members) {
|
||||
return new GroupSnapshot(700L, "CLG-700", status, 101L, "FBA", "FBA", 201L,
|
||||
"FBA1", lockedPlanId, 1, List.of(members), LocalDateTime.now(), LocalDateTime.now());
|
||||
GroupSnapshot group = mock(GroupSnapshot.class);
|
||||
when(group.groupId()).thenReturn(700L);
|
||||
when(group.status()).thenReturn(status);
|
||||
when(group.lockedPlanId()).thenReturn(lockedPlanId);
|
||||
when(group.members()).thenReturn(List.of(members));
|
||||
when(group.fullPackageCount()).thenReturn(List.of(members).stream()
|
||||
.mapToInt(Member::fullPackageCount).sum());
|
||||
return group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -165,8 +165,9 @@ class SmartLoadingServiceTest {
|
||||
@Test
|
||||
void rejectsIndividualAssignmentForColoadMemberWithoutWriting() {
|
||||
stubAssignment();
|
||||
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group("ACTIVE", null,
|
||||
new Member(9L, 30L, 10L, 10), new Member(19L, 31L, 11L, 5)));
|
||||
GroupSnapshot group = group("ACTIVE", null,
|
||||
new Member(9L, 30L, 10L, 10), new Member(19L, 31L, 11L, 5));
|
||||
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group);
|
||||
|
||||
assertThatThrownBy(() -> service.assign(1L,
|
||||
new AssignRequest(10L, 20L, 10, 0, "individual-group-member"), 7L, "u"))
|
||||
@@ -178,13 +179,13 @@ class SmartLoadingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicallyAssignsEveryMemberWithItsFullAvailablePackageCount() {
|
||||
void atomicallyAssignsEveryInboundFragmentOfSameOrderAcrossDifferentDestinations() {
|
||||
Candidate first = candidate(9L, 10L, 20L, 30L, 4);
|
||||
Candidate second = candidate(19L, 11L, 21L, 31L, 6);
|
||||
Candidate second = candidate(9L, 11L, 21L, 30L, 6, "FBA2");
|
||||
GroupSnapshot active = group("ACTIVE", null,
|
||||
new Member(9L, 30L, 10L, 4), new Member(19L, 31L, 11L, 6));
|
||||
new Member(9L, 30L, 10L, 4), new Member(9L, 30L, 11L, 6));
|
||||
GroupSnapshot locked = group("LOCKED", 1L,
|
||||
new Member(9L, 30L, 10L, 4), new Member(19L, 31L, 11L, 6));
|
||||
new Member(9L, 30L, 10L, 4), new Member(9L, 30L, 11L, 6));
|
||||
AtomicInteger version = new AtomicInteger();
|
||||
List<LoadingPlanItemEntity> saved = new ArrayList<>();
|
||||
when(repository.plan(1L)).thenAnswer(invocation -> plan("EDITING", version.get()));
|
||||
@@ -213,10 +214,10 @@ class SmartLoadingServiceTest {
|
||||
long orderId = invocation.getArgument(2);
|
||||
long detailId = invocation.getArgument(3);
|
||||
return ApiResponse.success(new OrderCargoClient.CargoBinding(detailId, orderId, 1,
|
||||
"货物", orderId == 30L ? 4 : 6, BigDecimal.TEN, BigDecimal.ONE));
|
||||
"货物", detailId == 20L ? 4 : 6, BigDecimal.TEN, BigDecimal.ONE));
|
||||
});
|
||||
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(active);
|
||||
when(coloadGroups.findByInbound(19L, 11L)).thenReturn(active);
|
||||
when(coloadGroups.findByInbound(9L, 11L)).thenReturn(active);
|
||||
when(coloadGroups.lockWholeGroupForAssignment(eq(9L), eq(10L), eq(1L),
|
||||
anyString(), eq(7L), eq("u"))).thenReturn(locked);
|
||||
|
||||
@@ -265,10 +266,11 @@ class SmartLoadingServiceTest {
|
||||
void adjustAndIndividualDeleteRejectColoadMembersBeforeAnyWrite() {
|
||||
LoadingPlanEntity plan = plan("EDITING", 0);
|
||||
LoadingPlanItemEntity item = item(8L, 9L, 20L, 4);
|
||||
GroupSnapshot group = group("LOCKED", 1L,
|
||||
new Member(9L, 30L, 10L, 4), new Member(19L, 31L, 11L, 6));
|
||||
when(repository.plan(1L)).thenReturn(plan);
|
||||
when(repository.item(1L, 8L)).thenReturn(item);
|
||||
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group("LOCKED", 1L,
|
||||
new Member(9L, 30L, 10L, 4), new Member(19L, 31L, 11L, 6)));
|
||||
when(coloadGroups.findByInbound(9L, 10L)).thenReturn(group);
|
||||
|
||||
assertThatThrownBy(() -> service.adjust(1L, 8L,
|
||||
new AdjustRequest(4, 0, 0, "adjust-group-member"), 7L, "u"))
|
||||
@@ -323,9 +325,10 @@ class SmartLoadingServiceTest {
|
||||
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, 4), new Member(19L, 31L, 11L, 6));
|
||||
when(coloadGroups.unlockWholeGroupAfterRemoval(eq(700L), eq(1L),
|
||||
anyString(), eq(7L), eq("u"))).thenReturn(group("ACTIVE", null,
|
||||
new Member(9L, 30L, 10L, 4), new Member(19L, 31L, 11L, 6)));
|
||||
anyString(), eq(7L), eq("u"))).thenReturn(unlocked);
|
||||
|
||||
Plan result = service.removeGroup(1L, 700L,
|
||||
new com.baiyue.firstleg.dto.SmartLoadingDtos.RemoveGroupRequest(
|
||||
@@ -1202,15 +1205,29 @@ class SmartLoadingServiceTest {
|
||||
}
|
||||
|
||||
private static Candidate candidate(long tenantId, long inboundId, long detailId, long orderId, int boxes) {
|
||||
return candidate(tenantId, inboundId, detailId, orderId, boxes, "FBA1");
|
||||
}
|
||||
|
||||
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, "FBA1", "FBA仓", "客户", "货物", boxes,
|
||||
100L, "FBA", "FBA渠道", 200L, destinationCode, "FBA仓", "客户", "货物", boxes,
|
||||
new BigDecimal("2"), new BigDecimal("1.5"),
|
||||
new BigDecimal("0.5"), null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private static GroupSnapshot group(String status, Long lockedPlanId, Member... members) {
|
||||
return new GroupSnapshot(700L, "CLG-700", status, 100L, "FBA", "FBA", 200L,
|
||||
"FBA1", lockedPlanId, 0, List.of(members), LocalDateTime.now(), LocalDateTime.now());
|
||||
GroupSnapshot group = mock(GroupSnapshot.class);
|
||||
when(group.groupId()).thenReturn(700L);
|
||||
when(group.groupNo()).thenReturn("CLG-700");
|
||||
when(group.status()).thenReturn(status);
|
||||
when(group.warehouseId()).thenReturn(100L);
|
||||
when(group.channelCode()).thenReturn("FBA");
|
||||
when(group.lockedPlanId()).thenReturn(lockedPlanId);
|
||||
when(group.members()).thenReturn(List.of(members));
|
||||
when(group.fullPackageCount()).thenReturn(List.of(members).stream()
|
||||
.mapToInt(Member::fullPackageCount).sum());
|
||||
return group;
|
||||
}
|
||||
|
||||
private static ContainerBookingEntity booking() {
|
||||
|
||||
Reference in New Issue
Block a user