This commit is contained in:
2026-09-04 11:35:19 +08:00
parent c3729249c4
commit f23f3ea26d
21 changed files with 476 additions and 97 deletions
@@ -23,6 +23,7 @@ public interface PendingReviewOrderClient {
@RequestParam(required = false) String inboundCode,
@RequestParam(required = false) String createdStart,
@RequestParam(required = false) String createdEnd,
@RequestParam(required = false) List<Long> excludedOrderIds,
@RequestParam long page,
@RequestParam long size);
@@ -28,6 +28,27 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
""")
List<PlanColoadMemberRow> planColoadMembers(@Param("planId") long planId);
@Select("""
<script>
SELECT DISTINCT item.order_id
FROM fl_loading_plan_item item
JOIN fl_loading_plan plan ON plan.id=item.loading_plan_id
JOIN fl_inbound_order inbound
ON inbound.tenant_id=item.account_id AND inbound.id=item.inbound_id
WHERE item.deleted=0 AND plan.plan_status IN ('EDITING','LOADING')
AND item.order_id IS NOT NULL
AND inbound.channel_code IN
<foreach collection="actualChannels" item="channel" open="(" separator="," close=")">#{channel}</foreach>
<if test="query.warehouseId != null">AND inbound.warehouse_id=#{query.warehouseId}</if>
<if test="query.customerId != null">AND item.account_id=#{query.customerId}</if>
<if test="query.inboundCode != null and query.inboundCode.trim() != ''">
AND item.inbound_code=#{query.inboundCode}
</if>
</script>
""")
List<Long> occupiedOrderIds(@Param("query") InboundCandidateQuery query,
@Param("actualChannels") Set<String> actualChannels);
@Select("""
SELECT o.tenant_id accountId,o.id inboundId,i.inbound_detail_id inboundDetailId,
o.order_id orderId,o.order_no orderNo,o.inbound_code inboundCode,
@@ -113,6 +134,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
o.process_status IN ('WAIT_MEASURE','MEASURING','MEASURE_PENDING','MEASURE_REJECTED',
'WAIT_PRICING','FEE_CONFIRMING','INBOUNDING','WAIT_SPLIT','LOADING')
)
AND NOT EXISTS (
SELECT 1 FROM fl_loading_plan_item active_item
WHERE active_item.account_id=o.tenant_id AND active_item.inbound_id=o.id
AND active_item.deleted=0
)
) eligible
LEFT JOIN fl_coload_group_member current_member
ON current_member.tenant_id=eligible.accountId AND current_member.inbound_id=eligible.inboundId
@@ -59,6 +59,9 @@ public class SmartLoadingRepository {
public List<PlanColoadMemberRow> planColoadMembers(long planId) {
return candidates.planColoadMembers(planId);
}
public List<Long> occupiedOrderIds(InboundCandidateQuery query, Set<String> actualChannels) {
return candidates.occupiedOrderIds(query, actualChannels);
}
public Candidate candidate(long planId, long warehouseId, long inboundId, long detailId) {
return candidates.candidate(planId, warehouseId, inboundId, detailId);
}
@@ -253,6 +253,8 @@ public class LoadingPickService {
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
if (replay != null) return replay;
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
if (replay != null) return replay;
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
if (existing == null) {
if (request.expectedAssignmentVersion() != 0
@@ -282,6 +284,8 @@ public class LoadingPickService {
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
if (replay != null) return replay;
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
if (replay != null) return replay;
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
int changed = existing == null
? request.expectedAssignmentVersion() == 0
@@ -310,6 +314,12 @@ public class LoadingPickService {
return view(requiredTask(taskId), request.warehouseId());
}
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
requireNoTaskOperation(operationId);
replay = repository.history(operationId);
if (replay != null) {
assertReplay(replay, taskId, itemId, requestHash);
return view(task, request.warehouseId());
}
LoadingPickItemEntity item = repository.item(taskId, itemId, request.warehouseId());
if (item == null) {
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在");
@@ -347,6 +357,12 @@ public class LoadingPickService {
return view(requiredTask(taskId), request.warehouseId());
}
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
requireNoTaskOperation(operationId);
replay = repository.history(operationId);
if (replay != null) {
assertReplay(replay, taskId, itemId, requestHash);
return view(task, request.warehouseId());
}
requireWarehouseItem(taskId, itemId, request.warehouseId());
if (repository.partial(itemId, request.finalPickedPackageCount(), release.mode(), release.json(),
request.version(), request.warehouseId()) != 1) {
@@ -372,6 +388,12 @@ public class LoadingPickService {
return view(requiredTask(taskId), request.warehouseId());
}
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
requireNoTaskOperation(operationId);
replay = repository.history(operationId);
if (replay != null) {
assertReplay(replay, taskId, itemId, requestHash);
return view(task, request.warehouseId());
}
LoadingPickItemEntity item = requireWarehouseItem(taskId, itemId, request.warehouseId());
if (repository.hasPickedOrPartialItem(taskId, item.getInboundId())) {
throw conflict("同一入库单已存在已拣或部分拣货明细,不能跳拣并恢复智能装柜候选");
@@ -125,24 +125,28 @@ public class SmartLoadingService {
? EnumSet.allOf(CandidateOrderStatus.class)
: EnumSet.copyOf(query.orderStatuses());
boolean includePending = selected.remove(CandidateOrderStatus.PENDING_REVIEW);
InboundCandidateQuery firstlegQuery = withStatuses(query, List.copyOf(selected), 1, 1);
if (!includePending) return firstlegCandidates(withStatuses(query, List.copyOf(selected),
query.page(), query.size()), actualChannels);
if (selected.isEmpty()) return pendingReviewCandidates(query, actualChannels, query.page(), query.size());
PageResult<InboundCandidate> pendingCount = pendingReviewCandidates(query, actualChannels, 1, 1);
PageResult<InboundCandidate> firstlegCount = firstlegCandidates(firstlegQuery, actualChannels);
long total = pendingCount.total() + firstlegCount.total();
long offset = Math.multiplyExact(query.page() - 1, query.size());
PageResult<InboundCandidate> pendingPage = pendingReviewCandidates(query, actualChannels,
query.page(), query.size());
List<InboundCandidate> records = new ArrayList<>();
if (offset < pendingCount.total()) {
records.addAll(pendingReviewRange(query, actualChannels, offset, query.size()));
}
long firstlegOffset = Math.max(0, offset - pendingCount.total());
int remaining = Math.toIntExact(query.size() - records.size());
if (remaining > 0 && firstlegOffset < firstlegCount.total()) {
records.addAll(firstlegRange(query, actualChannels, List.copyOf(selected), firstlegOffset, remaining));
PageResult<InboundCandidate> firstlegPage;
if (offset < pendingPage.total()) {
records.addAll(pendingPage.records());
int remaining = Math.toIntExact(query.size() - records.size());
firstlegPage = firstlegCandidates(withStatuses(query, List.copyOf(selected), 1,
Math.max(1, remaining)), actualChannels);
if (remaining > 0) records.addAll(firstlegPage.records());
} else {
long firstlegOffset = offset - pendingPage.total();
firstlegPage = firstlegRange(query, actualChannels, List.copyOf(selected),
firstlegOffset, query.size());
records.addAll(firstlegPage.records());
}
long total = pendingPage.total() + firstlegPage.total();
long pages = total == 0 ? 0 : (total + query.size() - 1) / query.size();
return new PageResult<>(records, query.page(), query.size(), total, pages);
}
@@ -184,7 +188,7 @@ public class SmartLoadingService {
try {
response = pendingReviewOrders.page(internalToken, List.copyOf(actualChannels), query.warehouseId(),
query.customerId(), query.inboundCode(), isoDate(query.createdStart()), isoDate(query.createdEnd()),
page, size);
repository.occupiedOrderIds(query, actualChannels), page, size);
} catch (RuntimeException exception) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "待审核订单服务暂时不可用");
}
@@ -205,27 +209,20 @@ public class SmartLoadingService {
return value == null ? null : value.toString();
}
private List<InboundCandidate> pendingReviewRange(InboundCandidateQuery query, Set<String> channels,
long offset, long limit) {
return pageRange(offset, limit, (page, size) -> pendingReviewCandidates(query, channels, page, size));
}
private List<InboundCandidate> firstlegRange(InboundCandidateQuery query, Set<String> channels,
List<CandidateOrderStatus> statuses,
long offset, long limit) {
return pageRange(offset, limit, (page, size) -> firstlegCandidates(
withStatuses(query, statuses, page, size), channels));
}
private static List<InboundCandidate> pageRange(long offset, long limit,
java.util.function.BiFunction<Long, Long, PageResult<InboundCandidate>> source) {
if (limit <= 0) return List.of();
private PageResult<InboundCandidate> firstlegRange(InboundCandidateQuery query, Set<String> channels,
List<CandidateOrderStatus> statuses,
long offset, long limit) {
if (limit <= 0) return new PageResult<>(List.of(), 1, 1, 0, 0);
final long chunk = 200;
long page = offset / chunk + 1;
int skip = Math.toIntExact(offset % chunk);
List<InboundCandidate> result = new ArrayList<>();
long total = 0;
while (result.size() < limit) {
List<InboundCandidate> rows = source.apply(page++, chunk).records();
PageResult<InboundCandidate> sourcePage = firstlegCandidates(
withStatuses(query, statuses, page++, chunk), channels);
total = sourcePage.total();
List<InboundCandidate> rows = sourcePage.records();
if (rows.isEmpty()) break;
int from = Math.min(skip, rows.size());
int take = Math.min(Math.toIntExact(limit - result.size()), rows.size() - from);
@@ -233,7 +230,8 @@ public class SmartLoadingService {
skip = 0;
if (rows.size() < chunk) break;
}
return List.copyOf(result);
return new PageResult<>(List.copyOf(result), query.page(), query.size(), total,
total == 0 ? 0 : (total + query.size() - 1) / query.size());
}
private static InboundCandidateQuery withStatuses(InboundCandidateQuery source,
@@ -1268,7 +1266,8 @@ public class SmartLoadingService {
private PendingReviewOrderClient.Row requirePendingReviewOrder(AssignPendingReviewRequest request) {
try {
ApiResponse<PageResult<PendingReviewOrderClient.Row>> response = pendingReviewOrders.page(
internalToken, null, null, request.customerId(), request.inboundCode(), null, null, 1, 2);
internalToken, null, null, request.customerId(), request.inboundCode(), null, null,
null, 1, 2);
if (response == null || !response.successful() || response.data() == null) throw unavailable();
return response.data().records().stream()
.filter(row -> Objects.equals(row.orderId(), request.orderId())
@@ -134,9 +134,42 @@ class CabinetLoadingEvidenceServiceTest {
verify(files, times(1)).upload(anyString(), anyLong(), anyLong(), anyString(), any());
}
@Test
void multipartReplayRejectsChangedContentBeforeUploadingAnotherFile() {
var first = new MockMultipartFile("file", "photo.png", "image/png", new byte[] {1, 2, 3});
var changed = new MockMultipartFile("file", "photo.png", "image/png", new byte[] {1, 2, 4});
CabinetLoadingEvidenceEntity existing = evidence("PHOTO_STEP_1");
existing.setSourceTaskId(20L);
existing.setOperationKey("same-key");
existing.setRequestHash(uploadHashForTest(8L, 0, "PHOTO_STEP_1", null, "remark", first));
when(mapper.byOperation(20L, "same-key")).thenReturn(existing);
assertThatThrownBy(() -> service.upload(20L, 8L, "same-key", 0, "PHOTO_STEP_1",
null, "remark", changed, 9L, "operator"))
.isInstanceOf(BizException.class).hasMessageContaining("请求内容不一致");
verify(files, never()).upload(anyString(), anyLong(), anyLong(), anyString(), any());
}
private static CabinetLoadingEvidenceEntity evidence(String type) {
CabinetLoadingEvidenceEntity value = new CabinetLoadingEvidenceEntity();
value.setEvidenceType(type); value.setStatus("ACTIVE"); value.setContainerBookingId(99L);
return value;
}
private static String uploadHashForTest(long warehouseId, int expectedVersion, String evidenceType,
String sealNo, String remark, MockMultipartFile multipart) {
try {
var digest = java.security.MessageDigest.getInstance("SHA-256");
String metadata = warehouseId + "|" + expectedVersion + "|" + evidenceType + "|"
+ java.util.Objects.toString(sealNo, "") + "|" + java.util.Objects.toString(remark, "") + "|"
+ java.util.Objects.toString(multipart.getOriginalFilename(), "") + "|"
+ java.util.Objects.toString(multipart.getContentType(), "") + "|" + multipart.getSize() + "|";
digest.update(metadata.getBytes(java.nio.charset.StandardCharsets.UTF_8));
digest.update(multipart.getBytes());
return java.util.HexFormat.of().formatHex(digest.digest());
} catch (java.io.IOException | java.security.NoSuchAlgorithmException exception) {
throw new IllegalStateException(exception);
}
}
}
@@ -75,6 +75,7 @@ class SmartLoadingCandidateMapperContractTest {
.contains("status.name() == 'WAIT_MEASURE'")
.contains("status.name() == 'WAIT_SPLIT'")
.contains("status.name() == 'LOADING'")
.contains("NOT EXISTS (", "active_item.inbound_id=o.id", "active_item.deleted=0")
.contains("current_group.id groupId")
.contains("group_member_count.group_id=current_group.id")
.doesNotContain("fl_container_booking")
@@ -8,6 +8,7 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
import com.baiyue.firstleg.dto.LoadingPickDtos.BatchRequest;
import com.baiyue.firstleg.dto.LoadingPickDtos.BatchItemRequest;
import com.baiyue.firstleg.dto.LoadingPickDtos.ClaimRequest;
import com.baiyue.firstleg.dto.LoadingPickDtos.AssignRequest;
import com.baiyue.firstleg.dto.LoadingPickDtos.CompleteLoadingRequest;
import com.baiyue.firstleg.dto.LoadingPickDtos.Task;
import com.baiyue.firstleg.client.WarehousePalletLocationClient;
@@ -513,6 +514,99 @@ class LoadingPickServiceTest {
verify(repository, times(1)).createAssignment(20L, 9L, "operator", 9L, "operator", "CLAIMED");
}
@Test
void claimRechecksSameOperationAfterWaitingForTaskLock() {
LoadingPickTaskEntity task = task();
LoadingPickOperationEntity completed = taskOperation("claim-race", "CLAIM");
when(repository.operation("claim-race")).thenReturn(null, completed);
when(repository.taskForUpdate(20L)).thenReturn(task);
when(repository.task(20L)).thenReturn(task);
when(repository.items(20L)).thenReturn(List.of());
Task replay = service.claim(20L, new ClaimRequest(0, "claim-race"), 9L, "operator");
assertThat(replay.id()).isEqualTo(20L);
verify(repository, never()).createAssignment(anyLong(), anyLong(), anyString(), anyLong(),
anyString(), anyString());
verify(smartLoadingRepository, never()).startPicking(anyLong(), anyLong(), anyString());
}
@Test
void assignRechecksSameOperationAfterWaitingForTaskLock() {
LoadingPickTaskEntity task = task();
LoadingPickOperationEntity completed = taskOperation("assign-race", "ASSIGN");
when(repository.operation("assign-race")).thenReturn(null, completed);
when(repository.taskForUpdate(20L)).thenReturn(task);
when(repository.task(20L)).thenReturn(task);
when(repository.items(20L)).thenReturn(List.of());
Task replay = service.assign(20L, new AssignRequest(11L, "picker", 0, "assign-race"),
9L, "operator");
assertThat(replay.id()).isEqualTo(20L);
verify(repository, never()).createAssignment(anyLong(), anyLong(), anyString(), anyLong(),
anyString(), anyString());
verify(repository, never()).reassign(anyLong(), anyLong(), anyString(), anyLong(),
anyString(), anyString(), anyInt());
}
@Test
void pickRechecksSameHistoryAfterWaitingForTaskLock() {
LoadingPickTaskEntity task = task();
LoadingPickHistoryEntity completed = itemHistory("pick-race", "PICK",
"20|30|101|2|MANUAL|[]");
when(repository.history("pick-race")).thenReturn(null, completed);
when(repository.taskForUpdate(20L)).thenReturn(task);
when(repository.assignment(20L)).thenReturn(assignment());
when(repository.items(20L, 101L)).thenReturn(List.of(item()));
Task replay = service.pick(20L, 30L,
new PickRequest(101L, 2, List.of(), 0, "pick-race"), 9L, "operator");
assertThat(replay.id()).isEqualTo(20L);
verify(repository, never()).pick(anyLong(), anyInt(), anyString(), anyString(), anyInt(), anyLong());
verify(repository, never()).insert(any(LoadingPickHistoryEntity.class));
verify(repository, never()).touchTask(anyLong(), anyInt());
}
@Test
void partialRechecksSameHistoryAfterWaitingForTaskLock() {
LoadingPickTaskEntity task = task();
LoadingPickHistoryEntity completed = itemHistory("partial-race", "PARTIAL",
"PARTIAL|20|30|101|1|MANUAL|[]");
when(repository.history("partial-race")).thenReturn(null, completed);
when(repository.taskForUpdate(20L)).thenReturn(task);
when(repository.assignment(20L)).thenReturn(assignment());
when(repository.items(20L, 101L)).thenReturn(List.of(item()));
Task replay = service.partial(20L, 30L,
new PartialPickRequest(101L, 1, List.of(), 0, "partial-race"), 9L, "operator");
assertThat(replay.id()).isEqualTo(20L);
verify(repository, never()).partial(anyLong(), anyInt(), anyString(), anyString(), anyInt(), anyLong());
verify(repository, never()).insert(any(LoadingPickHistoryEntity.class));
verify(repository, never()).touchTask(anyLong(), anyInt());
}
@Test
void skipRechecksSameHistoryAfterWaitingForTaskLock() {
LoadingPickTaskEntity task = task();
LoadingPickHistoryEntity completed = itemHistory("skip-race", "SKIP",
"SKIP|20|30|101|damaged");
when(repository.history("skip-race")).thenReturn(null, completed);
when(repository.taskForUpdate(20L)).thenReturn(task);
when(repository.assignment(20L)).thenReturn(assignment());
when(repository.items(20L, 101L)).thenReturn(List.of(item()));
Task replay = service.skip(20L, 30L,
new SkipRequest(101L, 0, "skip-race", "damaged"), 9L, "operator");
assertThat(replay.id()).isEqualTo(20L);
verify(repository, never()).skip(anyLong(), anyInt(), anyLong());
verify(repository, never()).insert(any(LoadingPickHistoryEntity.class));
verify(repository, never()).touchTask(anyLong(), anyInt());
}
@Test
void keepsPartialPickLockedUntilManualCompletion() {
LoadingPickTaskEntity task = task();
@@ -1289,6 +1383,41 @@ class LoadingPickServiceTest {
return assignment;
}
private static LoadingPickOperationEntity taskOperation(String operationId, String action) {
LoadingPickOperationEntity operation = new LoadingPickOperationEntity();
operation.setPickTaskId(20L);
operation.setOperationId(operationId);
operation.setRequestHash(hashForTest(action + "|20|0"));
if ("ASSIGN".equals(action)) {
operation.setRequestHash(hashForTest("ASSIGN|20|11|picker|0"));
}
operation.setActionType(action);
operation.setOperatorId(9L);
operation.setOperatorName("operator");
return operation;
}
private static LoadingPickHistoryEntity itemHistory(String operationId, String action, String canonical) {
LoadingPickHistoryEntity history = new LoadingPickHistoryEntity();
history.setPickTaskId(20L);
history.setPickItemId(30L);
history.setOperationId(operationId);
history.setRequestHash(hashForTest(canonical));
history.setActionType(action);
history.setOperatorId(9L);
history.setOperatorName("operator");
return history;
}
private static String hashForTest(String value) {
try {
return java.util.HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256")
.digest(value.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
} catch (java.security.NoSuchAlgorithmException exception) {
throw new IllegalStateException(exception);
}
}
private static GroupSnapshot coloadGroup(String status, Long lockedPlanId, Member... members) {
GroupSnapshot group = mock(GroupSnapshot.class);
when(group.groupId()).thenReturn(700L);
@@ -81,7 +81,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.atLeastOnce;
@SpringJUnitConfig(SmartLoadingServiceTest.Config.class)
class SmartLoadingServiceTest {
@@ -108,6 +107,7 @@ class SmartLoadingServiceTest {
when(documentNumbers.next("LP")).thenReturn("LP-260818-0001");
when(loadingPickService.mutationSnapshot(anyLong())).thenReturn(
new LoadingPickService.MutationSnapshot(null, null, null, Map.of()));
when(repository.occupiedOrderIds(any(), anySet())).thenReturn(List.of());
when(cargoClient.detail(anyString(), anyLong(), anyLong(), anyLong())).thenReturn(
ApiResponse.success(new OrderCargoClient.CargoBinding(20L, 30L, 1, "货物", 5,
new BigDecimal("10"), new BigDecimal("2"))));
@@ -224,9 +224,9 @@ class SmartLoadingServiceTest {
100L, "EXX_TRUCK", "EXX海卡", 3, LocalDateTime.parse("2026-09-01T09:00:00"),
"PENDING_REVIEW");
when(pendingReviewOrders.page(anyString(), anyList(), isNull(), isNull(), isNull(),
isNull(), isNull(), anyLong(), anyLong())).thenAnswer(invocation -> {
long page = invocation.getArgument(7);
long size = invocation.getArgument(8);
isNull(), isNull(), anyList(), anyLong(), anyLong())).thenAnswer(invocation -> {
long page = invocation.getArgument(8);
long size = invocation.getArgument(9);
return ApiResponse.success(new PageResult<>(page == 1 ? List.of(pending) : List.of(),
page, size, 1, 1));
});
@@ -248,11 +248,15 @@ class SmartLoadingServiceTest {
.containsExactly(true, true);
ArgumentCaptor<InboundCandidateQuery> firstlegQueries =
ArgumentCaptor.forClass(InboundCandidateQuery.class);
verify(repository, atLeastOnce()).globalCandidateUnits(firstlegQueries.capture(), anySet());
verify(repository).globalCandidateUnits(firstlegQueries.capture(), anySet());
assertThat(firstlegQueries.getValue().page()).isEqualTo(1);
assertThat(firstlegQueries.getValue().size()).isEqualTo(19);
assertThat(firstlegQueries.getAllValues()).allSatisfy(requested ->
assertThat(requested.orderStatuses()).containsExactly(
CandidateOrderStatus.WAIT_MEASURE, CandidateOrderStatus.WAIT_SPLIT,
CandidateOrderStatus.LOADING));
verify(pendingReviewOrders).page(anyString(), anyList(), isNull(), isNull(), isNull(),
isNull(), isNull(), anyList(), eq(1L), eq(20L));
}
@Test
@@ -311,9 +315,10 @@ class SmartLoadingServiceTest {
"EXXHK2609020361", 2092797167699816450L, "客户", 193000000000200002L,
"EXX_TRUCK", "EXX海卡", 100, LocalDateTime.parse("2026-09-02T15:31:36"),
"PENDING_REVIEW");
when(repository.occupiedOrderIds(eq(query), anySet())).thenReturn(List.of(998L));
when(pendingReviewOrders.page(anyString(), anyList(), eq(193000000000200002L),
isNull(), eq("EXXHK2609020361"), eq("2026-09-02"),
eq("2026-09-02"), eq(1L), eq(20L)))
eq("2026-09-02"), eq(List.of(998L)), eq(1L), eq(20L)))
.thenReturn(ApiResponse.success(new PageResult<>(List.of(row), 1, 20, 1, 1)));
var result = service.inboundCandidates(query);
@@ -329,6 +334,7 @@ class SmartLoadingServiceTest {
});
verify(repository, never()).globalCandidateUnits(any(), anySet());
verify(repository, never()).globalCandidatesByUnits(anyList());
verify(repository).occupiedOrderIds(eq(query), anySet());
}
@Test
@@ -348,7 +354,7 @@ class SmartLoadingServiceTest {
var cargo = new OrderCargoClient.CargoBinding(20L, orderId, 1, "货物", 5,
new BigDecimal("50"), new BigDecimal("2.5"));
when(pendingReviewOrders.page(anyString(), isNull(), isNull(), eq(customerId),
eq(inboundCode), isNull(), isNull(), eq(1L), eq(2L)))
eq(inboundCode), isNull(), isNull(), isNull(), eq(1L), eq(2L)))
.thenReturn(ApiResponse.success(new PageResult<>(List.of(pending), 1, 2, 1, 1)));
when(cargoClient.inboundSnapshot(anyString(), eq(customerId), eq(orderId)))
.thenReturn(ApiResponse.success(snapshot));
@@ -430,7 +436,7 @@ class SmartLoadingServiceTest {
List.of("EXX"), null, null, null, null, null,
List.of(CandidateOrderStatus.PENDING_REVIEW), 1, 20);
when(pendingReviewOrders.page(anyString(), anyList(), isNull(), isNull(), isNull(),
isNull(), isNull(), eq(1L), eq(20L))).thenThrow(new RuntimeException("offline"));
isNull(), isNull(), anyList(), eq(1L), eq(20L))).thenThrow(new RuntimeException("offline"));
assertThatThrownBy(() -> service.inboundCandidates(query))
.isInstanceOf(BizException.class)