fix(firstleg): scope loading evidence to cabinet tasks
This commit is contained in:
+3
-13
@@ -9,28 +9,18 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LoadingDeliveryOpenApiContractTest {
|
||||
@Test
|
||||
void publishesRealFileFiveEvidenceAndPickingGate() throws Exception {
|
||||
void removesInboundEvidenceProtocolAndKeepsPickingGate() throws Exception {
|
||||
String yaml = new ClassPathResource("openapi/phase3-loading-delivery-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/loading/evidence:")
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/loading/evidence/pda:")
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/loading/evidence/{evidenceId}/content/pda:")
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/loading/evidence/{evidenceId}/void:")
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/loading/picking/start:")
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/loading/picking/complete:")
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/loading/complete:")
|
||||
.contains("required: [expectedVersion, evidenceType, fileId]")
|
||||
.contains("previewUrl:")
|
||||
.contains("enum: [PHOTO_STEP_1, PHOTO_STEP_2, PHOTO_STEP_3, PHOTO_STEP_4, SEAL]")
|
||||
.contains("同类型可多次追加")
|
||||
.contains("Required when evidenceType is SEAL")
|
||||
.contains("compatibility projection")
|
||||
.contains("associated inbound process status to be LOADING")
|
||||
.contains("workflowStatus AWAITING_REVIEW does not bypass this gate")
|
||||
.contains("must exactly match the server-derived container-task aggregate")
|
||||
.contains("cannot complete canonical picking");
|
||||
assertThat(yaml).doesNotContain("/delivery/complete").doesNotContain("DELIVERED");
|
||||
assertThat(yaml).doesNotContain("/loading/evidence").doesNotContain("EvidenceResponse:")
|
||||
.doesNotContain("/delivery/complete");
|
||||
}
|
||||
}
|
||||
|
||||
+3
-74
@@ -1,17 +1,7 @@
|
||||
package com.baiyue.firstleg.loading.controller;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.CompleteLoadingRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.PickingRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.UpsertEvidenceRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.VoidEvidenceRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
@@ -20,72 +10,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PlatformLoadingDeliveryControllerContractTest {
|
||||
@Test
|
||||
void keepsInboundScopedPathsAndPermissions() throws Exception {
|
||||
void removesInboundEvidenceProtocolAndKeepsLoadingActions() {
|
||||
assertThat(PlatformLoadingDeliveryController.class.getAnnotation(RequestMapping.class).value())
|
||||
.containsExactly("/api/platform/v1/inbounds");
|
||||
|
||||
post("evidence", "/{inboundId}/loading/evidence",
|
||||
"platform:firstleg:inbound:loading:edit", UpsertEvidenceRequest.class);
|
||||
post("pdaEvidence", "/{inboundId}/loading/evidence/pda",
|
||||
"platform:firstleg:pda:work", UpsertEvidenceRequest.class);
|
||||
get("pdaEvidence", "/{inboundId}/loading/evidence/pda",
|
||||
"platform:firstleg:pda:work");
|
||||
Method pdaContent = PlatformLoadingDeliveryController.class.getDeclaredMethod("pdaEvidenceContent",
|
||||
AuthenticatedUser.class, long.class, long.class, long.class);
|
||||
assertThat(pdaContent.getAnnotation(GetMapping.class).value())
|
||||
.containsExactly("/{inboundId}/loading/evidence/{evidenceId}/content/pda");
|
||||
assertAuthority(pdaContent, "platform:firstleg:pda:work");
|
||||
Method pdaVoid = PlatformLoadingDeliveryController.class.getDeclaredMethod("pdaVoidEvidence",
|
||||
AuthenticatedUser.class, long.class, long.class, long.class, String.class, VoidEvidenceRequest.class);
|
||||
assertThat(pdaVoid.getAnnotation(PostMapping.class).value())
|
||||
.containsExactly("/{inboundId}/loading/evidence/{evidenceId}/void/pda");
|
||||
assertAuthority(pdaVoid, "platform:firstleg:pda:work");
|
||||
get("evidence", "/{inboundId}/loading/evidence",
|
||||
"platform:firstleg:inbound:loading:view");
|
||||
postVoidEvidence();
|
||||
post("completeLoading", "/{inboundId}/loading/complete",
|
||||
"platform:firstleg:inbound:loading:complete", CompleteLoadingRequest.class);
|
||||
post("startPicking", "/{inboundId}/loading/picking/start",
|
||||
"platform:firstleg:inbound:picking:edit", PickingRequest.class);
|
||||
post("completePicking", "/{inboundId}/loading/picking/complete",
|
||||
"platform:firstleg:inbound:picking:edit", PickingRequest.class);
|
||||
get("picking", "/{inboundId}/loading/picking",
|
||||
"platform:firstleg:inbound:picking:view");
|
||||
assertThat(Arrays.stream(PlatformLoadingDeliveryController.class.getDeclaredMethods())
|
||||
.map(Method::getName)).doesNotContain("completeDelivery");
|
||||
}
|
||||
|
||||
private static void postVoidEvidence() throws Exception {
|
||||
Method method = PlatformLoadingDeliveryController.class.getDeclaredMethod("voidEvidence",
|
||||
AuthenticatedUser.class, long.class, long.class, long.class, String.class, VoidEvidenceRequest.class);
|
||||
assertThat(method.getAnnotation(PostMapping.class).value())
|
||||
.containsExactly("/{inboundId}/loading/evidence/{evidenceId}/void");
|
||||
assertAuthority(method, "platform:firstleg:inbound:loading:edit");
|
||||
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class)).isNotNull();
|
||||
assertThat(method.getParameters()[4].getAnnotation(RequestHeader.class).value())
|
||||
.isEqualTo("Idempotency-Key");
|
||||
}
|
||||
|
||||
private static void post(String name, String path, String authority, Class<?> requestType) throws Exception {
|
||||
Method method = PlatformLoadingDeliveryController.class.getDeclaredMethod(name,
|
||||
AuthenticatedUser.class, long.class, long.class, String.class, requestType);
|
||||
assertThat(method.getAnnotation(PostMapping.class).value()).containsExactly(path);
|
||||
assertAuthority(method, authority);
|
||||
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class)).isNotNull();
|
||||
assertThat(method.getParameters()[3].getAnnotation(RequestHeader.class).value())
|
||||
.isEqualTo("Idempotency-Key");
|
||||
}
|
||||
|
||||
private static void get(String name, String path, String authority) throws Exception {
|
||||
Method method = PlatformLoadingDeliveryController.class.getDeclaredMethod(name,
|
||||
AuthenticatedUser.class, long.class, long.class);
|
||||
assertThat(method.getAnnotation(GetMapping.class).value()).containsExactly(path);
|
||||
assertAuthority(method, authority);
|
||||
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class)).isNotNull();
|
||||
}
|
||||
|
||||
private static void assertAuthority(Method method, String authority) {
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('" + authority + "')");
|
||||
.map(Method::getName)).contains("completeLoading", "startPicking", "completePicking", "picking")
|
||||
.doesNotContain("evidence", "pdaEvidence", "pdaEvidenceContent", "voidEvidence", "pdaVoidEvidence");
|
||||
}
|
||||
}
|
||||
|
||||
+12
-29
@@ -11,43 +11,26 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LoadingEvidenceSqlContractTest {
|
||||
@Test
|
||||
void schemaSupportsAppendOnlyEvidenceAndVoidAudit() throws Exception {
|
||||
void schemaOwnsEvidenceByCabinetWithOneActiveFactPerType() throws Exception {
|
||||
String sql = new ClassPathResource("db/phase3-loading-delivery-schema.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(sql)
|
||||
.contains("file_id BIGINT UNSIGNED NOT NULL")
|
||||
.contains("KEY idx_loading_evidence_type (tenant_id, inbound_id, evidence_type, status)")
|
||||
.contains("status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE'")
|
||||
.contains("void_reason VARCHAR(500)")
|
||||
.contains("voided_by BIGINT UNSIGNED")
|
||||
.contains("voided_at DATETIME(3)")
|
||||
.contains("DROP INDEX uk_loading_evidence_type")
|
||||
.contains("DROP INDEX uk_loading_evidence_seal")
|
||||
.contains("idx_loading_evidence_seal (tenant_id, seal_no, status)")
|
||||
.contains("fl_inbound_loading_pick_task");
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_loading_cabinet_evidence")
|
||||
.contains("container_booking_id BIGINT UNSIGNED NOT NULL")
|
||||
.contains("source_task_id BIGINT UNSIGNED NOT NULL")
|
||||
.contains("UNIQUE KEY uk_cabinet_evidence_active (container_booking_id,evidence_type,active_flag)")
|
||||
.contains("UNIQUE KEY uk_cabinet_evidence_operation (source_task_id,operation_key)")
|
||||
.contains("FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id)")
|
||||
.doesNotContain("CREATE TABLE IF NOT EXISTS fl_loading_evidence");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unifiedEntryMigratesLegacySingleSlotEvidenceWithoutRecreatingUniqueIndexes() throws Exception {
|
||||
void unifiedEntryContainsCabinetEvidenceTable() throws Exception {
|
||||
Path direct = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
Path unified = Files.exists(direct) ? direct : Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
String sql = Files.readString(unified, StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(sql)
|
||||
.contains("file_id BIGINT UNSIGNED NOT NULL")
|
||||
.contains("status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE'")
|
||||
.contains("phase3_loading_evidence_add_column('status', 'VARCHAR(16) NOT NULL DEFAULT ''ACTIVE'' COMMENT ''ACTIVE/VOID''')")
|
||||
.contains("phase3_loading_evidence_add_column('void_reason'")
|
||||
.contains("phase3_loading_evidence_add_column('voided_by'")
|
||||
.contains("phase3_loading_evidence_add_column('voided_by_name'")
|
||||
.contains("phase3_loading_evidence_add_column('voided_at'")
|
||||
.contains("DROP INDEX uk_loading_evidence_type")
|
||||
.contains("DROP INDEX uk_loading_evidence_seal")
|
||||
.contains("ADD KEY idx_loading_evidence_type (tenant_id,inbound_id,evidence_type,status)")
|
||||
.contains("ADD KEY idx_loading_evidence_seal (tenant_id,seal_no,status)")
|
||||
.doesNotContain("phase3_loading_evidence_add_column('status', \"VARCHAR(16)")
|
||||
.doesNotContain("UNIQUE KEY uk_loading_evidence_type")
|
||||
.doesNotContain("UNIQUE KEY uk_loading_evidence_seal");
|
||||
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS fl_loading_cabinet_evidence")
|
||||
.contains("uk_cabinet_evidence_active")
|
||||
.contains("uk_cabinet_evidence_operation");
|
||||
}
|
||||
}
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.baiyue.firstleg.loading.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.client.LoadingEvidenceFileClient;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
||||
import com.baiyue.firstleg.loading.entity.CabinetLoadingEvidenceEntity;
|
||||
import com.baiyue.firstleg.loading.mapper.CabinetLoadingEvidenceMapper;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(CabinetLoadingEvidenceService.class)
|
||||
class CabinetLoadingEvidenceServiceTest {
|
||||
@MockBean CabinetLoadingEvidenceMapper mapper;
|
||||
@MockBean LoadingPickRepository loadingPicks;
|
||||
@MockBean LoadingEvidenceFileClient files;
|
||||
@MockBean ObjectMapper objectMapper;
|
||||
@Resource CabinetLoadingEvidenceService service;
|
||||
|
||||
@BeforeEach
|
||||
void taskScope() {
|
||||
LoadingPickTaskEntity task = new LoadingPickTaskEntity();
|
||||
task.setId(20L); task.setContainerBookingId(99L); task.setTaskStatus("LOADING");
|
||||
ContainerBookingEntity booking = new ContainerBookingEntity();
|
||||
booking.setId(99L); booking.setWarehouseId(8L); booking.setRecordStatus("ACTIVE");
|
||||
LoadingPickAssignmentEntity assignment = new LoadingPickAssignmentEntity();
|
||||
assignment.setPickTaskId(20L); assignment.setAssigneeId(9L);
|
||||
when(loadingPicks.task(20L)).thenReturn(task);
|
||||
when(loadingPicks.activeBookingByTask(20L)).thenReturn(booking);
|
||||
when(loadingPicks.assignment(20L)).thenReturn(assignment);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCrossWarehouseBeforeReadingEvidence() {
|
||||
assertThatThrownBy(() -> service.list(20L, 7L, 9L)).isInstanceOf(BizException.class);
|
||||
verify(mapper, never()).byContainer(99L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOperatorWhoDidNotClaimTaskBeforeReadingEvidence() {
|
||||
assertThatThrownBy(() -> service.list(20L, 8L, 10L)).isInstanceOf(BizException.class);
|
||||
verify(mapper, never()).byContainer(99L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCrossContainerEvidenceBeforeCallingFile() {
|
||||
when(mapper.byContainer(99L)).thenReturn(List.of());
|
||||
assertThatThrownBy(() -> service.content(20L, 8L, 777L, 9L)).isInstanceOf(BizException.class);
|
||||
verify(files, never()).download(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsTaskWhoseActiveBookingIdentityChanged() {
|
||||
ContainerBookingEntity another = new ContainerBookingEntity();
|
||||
another.setId(100L); another.setWarehouseId(8L);
|
||||
when(loadingPicks.activeBookingByTask(20L)).thenReturn(another);
|
||||
assertThatThrownBy(() -> service.list(20L, 8L, 9L)).isInstanceOf(BizException.class);
|
||||
verify(mapper, never()).byContainer(99L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cabinetGateAcceptsFiveFactsRegardlessOfOwnerOrder() {
|
||||
when(mapper.byContainer(99L)).thenReturn(List.of(evidence("SEAL"), evidence("PHOTO_STEP_4"),
|
||||
evidence("PHOTO_STEP_2"), evidence("PHOTO_STEP_1"), evidence("PHOTO_STEP_3")));
|
||||
service.requireComplete(99L);
|
||||
verify(mapper).byContainer(99L);
|
||||
}
|
||||
|
||||
private static CabinetLoadingEvidenceEntity evidence(String type) {
|
||||
CabinetLoadingEvidenceEntity value = new CabinetLoadingEvidenceEntity();
|
||||
value.setEvidenceType(type); value.setStatus("ACTIVE"); value.setContainerBookingId(99L);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+32
-585
@@ -1,601 +1,48 @@
|
||||
package com.baiyue.firstleg.loading.service;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.firstleg.client.LoadingEvidenceFileClient;
|
||||
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.CompleteLoadingRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.PickingRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.UpsertEvidenceRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.VoidEvidenceRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingResponses.LoadingActionResponse;
|
||||
import com.baiyue.firstleg.loading.entity.LoadingEvidenceEntity;
|
||||
import com.baiyue.firstleg.loading.entity.LoadingOperationEntity;
|
||||
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper.Snapshot;
|
||||
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper.CompletionTarget;
|
||||
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
|
||||
import com.baiyue.firstleg.mapper.LoadingPickItemMapper;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper.Snapshot;
|
||||
import com.baiyue.firstleg.loading.repository.LoadingRepository;
|
||||
import com.baiyue.firstleg.mapper.InboundStatusHistoryMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingPickItemMapper.InboundProgress;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@SpringJUnitConfig(LoadingDeliveryService.class)
|
||||
class LoadingDeliveryServiceTest {
|
||||
@Mock LoadingRepository repository;
|
||||
@Mock LoadingModelBridgeService loadingModelBridge;
|
||||
@Mock LoadingCompletionGateService completionGate;
|
||||
@Mock LoadingPickRepository loadingPicks;
|
||||
@Mock InboundStatusHistoryMapper histories;
|
||||
@Mock LoadingEvidenceFileClient files;
|
||||
@InjectMocks LoadingDeliveryService service;
|
||||
|
||||
@BeforeEach
|
||||
void injectObjectMapper() throws Exception {
|
||||
var field = LoadingDeliveryService.class.getDeclaredField("objectMapper");
|
||||
field.setAccessible(true);
|
||||
field.set(service, new ObjectMapper());
|
||||
var token = LoadingDeliveryService.class.getDeclaredField("internalToken");
|
||||
token.setAccessible(true);
|
||||
token.set(service, "token");
|
||||
}
|
||||
@MockBean LoadingRepository repository;
|
||||
@MockBean LoadingPickRepository loadingPicks;
|
||||
@MockBean LoadingModelBridgeService loadingModelBridge;
|
||||
@MockBean LoadingCompletionGateService completionGate;
|
||||
@MockBean InboundStatusHistoryMapper histories;
|
||||
@MockBean CabinetLoadingEvidenceService cabinetEvidence;
|
||||
@MockBean ObjectMapper objectMapper;
|
||||
@Resource LoadingDeliveryService service;
|
||||
|
||||
@Test
|
||||
void returnsAuthenticatedPreviewUrlAndProxiesOwnedEvidenceContent() {
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
LoadingEvidenceEntity target = evidence("PHOTO_STEP_1", 11L, null);
|
||||
target.setId(99L); target.setInboundId(20L); target.setTenantId(10L); target.setStatus("ACTIVE");
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(target));
|
||||
when(files.download("token", 10, 20, 11)).thenReturn(ResponseEntity.ok(new byte[]{1, 2, 3}));
|
||||
void manualCompletionUsesCabinetEvidenceResolvedFromInbound() throws Exception {
|
||||
when(objectMapper.writeValueAsString(org.mockito.ArgumentMatchers.any())).thenReturn("request");
|
||||
when(repository.claimOperation(eq(10L), eq(20L), eq(9L), eq("COMPLETE_LOADING"), eq("op-1"), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10L, 20L)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
when(cabinetEvidence.requireContainerBookingId(10L, 20L)).thenReturn(99L);
|
||||
InboundProgress progress = new InboundProgress();
|
||||
progress.setExpectedCount(2); progress.setPickedCount(2); progress.setTaskCount(1);
|
||||
progress.setCompletedTaskCount(1); progress.setUnresolvedCount(0);
|
||||
when(loadingPicks.inboundProgress(10L, 20L)).thenReturn(progress);
|
||||
when(repository.completeLoading(10L, 20L, 9L, 3)).thenReturn(1);
|
||||
|
||||
var listed = service.evidence(10, 20);
|
||||
var content = service.evidenceContent(10, 20, 99);
|
||||
service.completeLoading(10L, 20L, 9L, "operator", "op-1",
|
||||
new com.baiyue.firstleg.loading.dto.LoadingRequests.CompleteLoadingRequest(3, "done"));
|
||||
|
||||
assertThat(listed.evidences()).singleElement().satisfies(item ->
|
||||
assertThat(item.previewUrl()).isEqualTo(
|
||||
"/api/platform/v1/inbounds/20/loading/evidence/99/content/pda?tenantId=10"));
|
||||
assertThat(content.getBody()).containsExactly(1, 2, 3);
|
||||
verify(files).download("token", 10, 20, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
void completesLoadingOnlyAfterPickingAndFiveEvidenceFiles() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
LoadingPickItemMapper.InboundProgress progress = mock(LoadingPickItemMapper.InboundProgress.class);
|
||||
when(progress.getExpectedCount()).thenReturn(5);
|
||||
when(progress.getPickedCount()).thenReturn(5);
|
||||
when(progress.getTaskCount()).thenReturn(1);
|
||||
when(progress.getCompletedTaskCount()).thenReturn(1);
|
||||
when(progress.getUnresolvedCount()).thenReturn(0);
|
||||
when(loadingPicks.inboundProgress(10, 20)).thenReturn(progress);
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(
|
||||
evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null),
|
||||
evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null),
|
||||
evidence("SEAL", 5L, "LOCK-001")));
|
||||
when(files.validate(eq("token"), eq(10L), eq(20L), anyLong())).thenAnswer(invocation -> {
|
||||
long fileId = invocation.getArgument(3);
|
||||
String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId;
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(
|
||||
fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png"));
|
||||
});
|
||||
when(repository.completeLoading(10, 20, 7, 3)).thenReturn(1);
|
||||
LoadingActionResponse result = service.completeLoading(10, 20, 7, "admin", "k1", new CompleteLoadingRequest(3, null));
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(loadingModelBridge).requireCompleted(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(histories).insert(any(InboundStatusHistoryEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaCompletionPreflightChecksStartedPlanTransportAndEvidenceWithoutWriting() {
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(
|
||||
evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null),
|
||||
evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null),
|
||||
evidence("SEAL", 5L, "LOCK-001")));
|
||||
when(files.validate(eq("token"), eq(10L), eq(20L), anyLong())).thenAnswer(invocation -> {
|
||||
long fileId = invocation.getArgument(3);
|
||||
String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId;
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(
|
||||
fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png"));
|
||||
});
|
||||
|
||||
service.requirePdaCompletionReady(10, 20);
|
||||
|
||||
verify(loadingModelBridge).requireStarted(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(loadingModelBridge, never()).requireCompleted(anyLong(), anyLong());
|
||||
verify(repository, never()).completeLoading(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
verify(repository, never()).claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaCompletionPreflightReportsBookingBlockerBeforeEvidenceOrWrites() {
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
doThrow(new com.baiyue.common.core.exception.BizException(
|
||||
com.baiyue.common.core.api.ResultCode.CONFLICT,
|
||||
"LOADED门禁未通过:订舱尚未完成(必须为 RELEASED)"))
|
||||
.when(completionGate).requireReleasedBooking(10, 20);
|
||||
|
||||
assertThatThrownBy(() -> service.requirePdaCompletionReady(10, 20))
|
||||
.hasMessageContaining("订舱尚未完成");
|
||||
|
||||
verify(repository, never()).listEvidence(anyLong(), anyLong());
|
||||
verify(repository, never()).completeLoading(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completesLoadedFulfillmentSliceBeforeAggregatingRoot() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "WAIT_SPLIT", 3));
|
||||
when(repository.completionTarget(10, 20)).thenReturn(new CompletionTarget(
|
||||
20L, null, 20L, "LOADING", 3, "LOADING", 3));
|
||||
LoadingPickItemMapper.InboundProgress progress = mock(LoadingPickItemMapper.InboundProgress.class);
|
||||
when(progress.getExpectedCount()).thenReturn(5);
|
||||
when(progress.getPickedCount()).thenReturn(5);
|
||||
when(progress.getTaskCount()).thenReturn(1);
|
||||
when(progress.getCompletedTaskCount()).thenReturn(1);
|
||||
when(progress.getUnresolvedCount()).thenReturn(0);
|
||||
when(loadingPicks.inboundProgress(10, 20)).thenReturn(progress);
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(
|
||||
evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null),
|
||||
evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null),
|
||||
evidence("SEAL", 5L, "LOCK-001")));
|
||||
when(files.validate(eq("token"), eq(10L), eq(20L), anyLong())).thenAnswer(invocation -> {
|
||||
long fileId = invocation.getArgument(3);
|
||||
String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId;
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(
|
||||
fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png"));
|
||||
});
|
||||
when(repository.completeLoadedChild(10, 20, 7, 3)).thenReturn(1);
|
||||
|
||||
LoadingActionResponse result = service.completeLoading(10, 20, 7, "admin", "slice-1",
|
||||
new CompleteLoadingRequest(3, null));
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(loadingModelBridge).requireCompleted(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(repository).completeLoadedChild(10, 20, 7, 3);
|
||||
verify(repository).aggregateRootLoaded(10, 20, 7);
|
||||
verify(histories).insert(argThat((InboundStatusHistoryEntity history) ->
|
||||
Long.valueOf(20L).equals(history.getInboundId())));
|
||||
verify(repository, never()).completeLoading(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaFinalizeUsesSourceVersionIncrementedByFullSettlement() {
|
||||
readyForPdaFinalize();
|
||||
CompletionTarget settled = new CompletionTarget(20L, null, 20L,
|
||||
"LOADING", 7, "LOADING", 7);
|
||||
when(repository.completionTarget(10, 20)).thenReturn(settled);
|
||||
when(repository.findInboundForUpdate(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 7));
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 7));
|
||||
when(repository.completeLoadedChild(10, 20, 7, 7)).thenReturn(1);
|
||||
|
||||
LoadingActionResponse result = service.completeAfterPdaSettlement(
|
||||
10, 20, 7, "admin", "pda-full", "完成");
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(repository).findInboundForUpdate(10, 20);
|
||||
verify(repository).completeLoadedChild(10, 20, 7, 7);
|
||||
verify(repository, never()).completeLoadedChild(10, 20, 7, 6);
|
||||
verify(loadingModelBridge).requireStarted(10, 20);
|
||||
verify(loadingModelBridge, never()).requireCompleted(10, 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaFinalizeResumesSameProcessingClaimLeftByAnEarlierWorkerFailure() {
|
||||
readyForPdaFinalizeFacts();
|
||||
CompletionTarget settled = new CompletionTarget(20L, null, 20L,
|
||||
"LOADING", 7, "LOADING", 7);
|
||||
when(repository.completionTarget(10, 20)).thenReturn(settled);
|
||||
when(repository.findInboundForUpdate(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 7));
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 7));
|
||||
when(repository.completeLoadedChild(10, 20, 7, 7)).thenReturn(1);
|
||||
AtomicReference<String> hash = new AtomicReference<>();
|
||||
when(repository.claimOperation(eq(10L), eq(20L), eq(7L), eq("COMPLETE_LOADING"),
|
||||
eq("pda-resume"), anyString())).thenAnswer(invocation -> {
|
||||
hash.set(invocation.getArgument(5));
|
||||
return false;
|
||||
});
|
||||
when(repository.findOperation(10L, 20L, 7L, "COMPLETE_LOADING", "pda-resume"))
|
||||
.thenAnswer(invocation -> processingOperation(hash.get()));
|
||||
|
||||
LoadingActionResponse result = service.completeAfterPdaSettlement(
|
||||
10, 20, 7, "admin", "pda-resume", "完成");
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(repository).completeLoadedChild(10, 20, 7, 7);
|
||||
verify(repository).completeOperation(eq(10L), eq(20L), eq(7L),
|
||||
eq("COMPLETE_LOADING"), eq("pda-resume"), anyString());
|
||||
verify(loadingModelBridge).requireStarted(10, 20);
|
||||
verify(loadingModelBridge, never()).requireCompleted(10, 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaFinalizeConvergesAnAlreadyLoadedInboundBeforeThePlanIsCompleted() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(true);
|
||||
when(repository.findInboundForUpdate(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADED", 8));
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADED", 8));
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of());
|
||||
|
||||
LoadingActionResponse result = service.completeAfterPdaSettlement(
|
||||
10, 20, 7, "admin", "pda-already-loaded", "完成");
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(loadingModelBridge).requireStarted(10, 20);
|
||||
verify(loadingModelBridge, never()).requireCompleted(10, 20);
|
||||
verify(repository).completeOperation(eq(10L), eq(20L), eq(7L),
|
||||
eq("COMPLETE_LOADING"), eq("pda-already-loaded"), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaFinalizeEntryPointsAreTransactional() throws Exception {
|
||||
assertThat(LoadingDeliveryService.class.getMethod("completeAfterPdaSettlement",
|
||||
long.class, long.class, long.class, String.class, String.class, String.class)
|
||||
.getAnnotation(Transactional.class)).isNotNull();
|
||||
assertThat(LoadingDeliveryService.class.getMethod("completeAfterPdaSettlement",
|
||||
long.class, long.class, long.class, long.class, long.class,
|
||||
String.class, String.class, String.class)
|
||||
.getAnnotation(Transactional.class)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaPreflightUsesCabinetEvidenceOwnerWithoutCrossCustomerLookup() {
|
||||
when(repository.findInbound(20, 200)).thenReturn(new Snapshot(200L, 20L, "LOADING", 3));
|
||||
when(repository.listEvidence(10, 100)).thenReturn(List.of(
|
||||
evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null),
|
||||
evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null),
|
||||
evidence("SEAL", 5L, "LOCK-001")));
|
||||
when(files.validate(eq("token"), eq(10L), eq(100L), anyLong())).thenAnswer(invocation -> {
|
||||
long fileId = invocation.getArgument(3);
|
||||
String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId;
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(
|
||||
fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png"));
|
||||
});
|
||||
|
||||
service.requirePdaCompletionReady(20, 200, 10, 100);
|
||||
|
||||
verify(loadingModelBridge).requireStarted(20, 200);
|
||||
verify(completionGate).requireReleasedBooking(20, 200);
|
||||
verify(repository).listEvidence(10, 100);
|
||||
verify(repository, never()).listEvidence(20, 100);
|
||||
verify(files, times(5)).validate(eq("token"), eq(10L), eq(100L), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaFinalizeKeepsOperationOnBusinessOwnerWhileUsingCabinetEvidenceOwner() {
|
||||
when(repository.claimOperation(eq(20L), eq(200L), eq(7L), eq("COMPLETE_LOADING"),
|
||||
eq("pda-multi-customer"), anyString())).thenReturn(true);
|
||||
when(repository.findInboundForUpdate(20, 200)).thenReturn(new Snapshot(200L, 20L, "LOADING", 7));
|
||||
when(repository.findInbound(20, 200)).thenReturn(new Snapshot(200L, 20L, "LOADING", 7));
|
||||
LoadingPickItemMapper.InboundProgress progress = mock(LoadingPickItemMapper.InboundProgress.class);
|
||||
when(progress.getExpectedCount()).thenReturn(1);
|
||||
when(progress.getPickedCount()).thenReturn(1);
|
||||
when(progress.getTaskCount()).thenReturn(1);
|
||||
when(progress.getCompletedTaskCount()).thenReturn(0);
|
||||
when(progress.getUnresolvedCount()).thenReturn(0);
|
||||
when(loadingPicks.inboundProgress(20, 200)).thenReturn(progress);
|
||||
when(repository.listEvidence(10, 100)).thenReturn(List.of(
|
||||
evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null),
|
||||
evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null),
|
||||
evidence("SEAL", 5L, "LOCK-001")));
|
||||
when(files.validate(eq("token"), eq(10L), eq(100L), anyLong())).thenAnswer(invocation -> {
|
||||
long fileId = invocation.getArgument(3);
|
||||
String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId;
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(
|
||||
fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png"));
|
||||
});
|
||||
when(repository.completeLoading(20, 200, 7, 7)).thenReturn(1);
|
||||
|
||||
LoadingActionResponse result = service.completeAfterPdaSettlement(
|
||||
20, 200, 10, 100, 7, "admin", "pda-multi-customer", "完成");
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(repository).completeLoading(20, 200, 7, 7);
|
||||
verify(repository).completeOperation(eq(20L), eq(200L), eq(7L),
|
||||
eq("COMPLETE_LOADING"), eq("pda-multi-customer"), anyString());
|
||||
verify(repository, never()).completeOperation(eq(20L), eq(100L), anyLong(),
|
||||
anyString(), anyString(), anyString());
|
||||
verify(repository).listEvidence(10, 100);
|
||||
verify(repository, never()).listEvidence(20, 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaFinalizeLocksAndUsesLoadedSliceVersionAfterPartialSettlement() {
|
||||
readyForPdaFinalize();
|
||||
CompletionTarget settled = new CompletionTarget(20L, 21L, 20L,
|
||||
"LOADING", 7, "LOADING", 2);
|
||||
when(repository.completionTarget(10, 20)).thenReturn(settled);
|
||||
when(repository.findInboundForUpdate(10, 21)).thenReturn(new Snapshot(21L, 10L, "LOADING", 2));
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 7));
|
||||
when(repository.completeLoadedChild(10, 20, 7, 2)).thenReturn(1);
|
||||
|
||||
LoadingActionResponse result = service.completeAfterPdaSettlement(
|
||||
10, 20, 7, "admin", "pda-partial", "完成");
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(repository).findInboundForUpdate(10, 21);
|
||||
verify(repository).completeLoadedChild(10, 20, 7, 2);
|
||||
verify(loadingModelBridge).requireStarted(10, 20);
|
||||
verify(loadingModelBridge, never()).requireCompleted(10, 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaFinalizeKeepsOptimisticConflictForRealConcurrentChange() {
|
||||
readyForPdaFinalize();
|
||||
CompletionTarget settled = new CompletionTarget(20L, null, 20L,
|
||||
"LOADING", 7, "LOADING", 7);
|
||||
when(repository.completionTarget(10, 20)).thenReturn(settled);
|
||||
when(repository.findInboundForUpdate(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 7));
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 7));
|
||||
when(repository.completeLoadedChild(10, 20, 7, 7)).thenReturn(0);
|
||||
|
||||
assertThatThrownBy(() -> service.completeAfterPdaSettlement(
|
||||
10, 20, 7, "admin", "pda-concurrent", "完成"))
|
||||
.hasMessageContaining("数据已被其他人修改");
|
||||
|
||||
verify(histories, never()).insert(any(InboundStatusHistoryEntity.class));
|
||||
verify(repository, never()).completeOperation(anyLong(), anyLong(), anyLong(),
|
||||
anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completesSingleRemainderSourceOnceAndReplaysSameKeyWithoutRepeatingSideEffects() {
|
||||
AtomicInteger claims = new AtomicInteger();
|
||||
AtomicReference<String> requestHash = new AtomicReference<>();
|
||||
AtomicReference<String> resultJson = new AtomicReference<>();
|
||||
when(repository.claimOperation(eq(10L), eq(20L), eq(7L), eq("COMPLETE_LOADING"),
|
||||
eq("slice-replay"), anyString())).thenAnswer(invocation -> {
|
||||
requestHash.set(invocation.getArgument(5));
|
||||
return claims.getAndIncrement() == 0;
|
||||
});
|
||||
doAnswer(invocation -> {
|
||||
resultJson.set(invocation.getArgument(5));
|
||||
return null;
|
||||
}).when(repository).completeOperation(eq(10L), eq(20L), eq(7L), eq("COMPLETE_LOADING"),
|
||||
eq("slice-replay"), anyString());
|
||||
when(repository.findOperation(10L, 20L, 7L, "COMPLETE_LOADING", "slice-replay"))
|
||||
.thenAnswer(invocation -> successfulOperation(requestHash.get(), resultJson.get()));
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 5));
|
||||
when(repository.completionTarget(10, 20)).thenReturn(new CompletionTarget(
|
||||
20L, null, 10L, "LOADING", 5, "LOADING", 5));
|
||||
LoadingPickItemMapper.InboundProgress progress = mock(LoadingPickItemMapper.InboundProgress.class);
|
||||
when(progress.getExpectedCount()).thenReturn(5);
|
||||
when(progress.getPickedCount()).thenReturn(5);
|
||||
when(progress.getTaskCount()).thenReturn(1);
|
||||
when(progress.getCompletedTaskCount()).thenReturn(1);
|
||||
when(progress.getUnresolvedCount()).thenReturn(0);
|
||||
when(loadingPicks.inboundProgress(10, 20)).thenReturn(progress);
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(
|
||||
evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null),
|
||||
evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null),
|
||||
evidence("SEAL", 5L, "LOCK-001")));
|
||||
when(files.validate(eq("token"), eq(10L), eq(20L), anyLong())).thenAnswer(invocation -> {
|
||||
long fileId = invocation.getArgument(3);
|
||||
String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId;
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(
|
||||
fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png"));
|
||||
});
|
||||
when(repository.completeLoadedChild(10, 20, 7, 5)).thenReturn(1);
|
||||
|
||||
LoadingActionResponse first = service.completeLoading(10, 20, 7, "admin", "slice-replay",
|
||||
new CompleteLoadingRequest(5, "完成"));
|
||||
LoadingActionResponse replay = service.completeLoading(10, 20, 7, "admin", "slice-replay",
|
||||
new CompleteLoadingRequest(5, "完成"));
|
||||
|
||||
assertThat(first.mainStatus()).isEqualTo("LOADED");
|
||||
assertThat(replay).isEqualTo(first);
|
||||
verify(repository, times(2)).claimOperation(eq(10L), eq(20L), eq(7L), eq("COMPLETE_LOADING"),
|
||||
eq("slice-replay"), anyString());
|
||||
verify(repository).completeLoadedChild(10, 20, 7, 5);
|
||||
verify(repository).aggregateRootLoaded(10, 10, 7);
|
||||
verify(loadingModelBridge).requireCompleted(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(histories).insert(any(InboundStatusHistoryEntity.class));
|
||||
verify(repository).completeOperation(10L, 20L, 7L, "COMPLETE_LOADING", "slice-replay", resultJson.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsHistoricalLoadedChildAsCompletionTarget() {
|
||||
CompletionTarget historical = new CompletionTarget(
|
||||
20L, 21L, 20L, "WAIT_SPLIT", 3, "LOADING", 1);
|
||||
|
||||
assertThat(historical.completionInboundId()).isEqualTo(21L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCompletionWhenEvidenceIsIncomplete() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
LoadingPickItemMapper.InboundProgress progress = mock(LoadingPickItemMapper.InboundProgress.class);
|
||||
when(progress.getExpectedCount()).thenReturn(5);
|
||||
when(progress.getPickedCount()).thenReturn(5);
|
||||
when(progress.getTaskCount()).thenReturn(1);
|
||||
when(progress.getCompletedTaskCount()).thenReturn(1);
|
||||
when(progress.getUnresolvedCount()).thenReturn(0);
|
||||
when(loadingPicks.inboundProgress(10, 20)).thenReturn(progress);
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(evidence("SEAL", 5L, "LOCK-001")));
|
||||
|
||||
assertThatThrownBy(() -> service.completeLoading(10, 20, 7, "admin", "k-missing",
|
||||
new CompleteLoadingRequest(3, null))).hasMessageContaining("必须齐全");
|
||||
verify(repository, never()).completeLoading(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void evidenceCanBeEditedAndDoesNotChangeMainStatus() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of());
|
||||
when(files.validate("token", 10, 20, 1)).thenReturn(ApiResponse.success(
|
||||
new LoadingEvidenceFileClient.FileMetadata(1L, "photo.jpg", "LOADING_PHOTO_STEP_1", "image/jpeg")));
|
||||
LoadingActionResponse result = service.upsertEvidence(10, 20, 7, "admin", "k2",
|
||||
new UpsertEvidenceRequest(0, "PHOTO_STEP_1", 1L, "ignored.jpg", null, "updated"));
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADING");
|
||||
verify(repository).createEvidence(any(LoadingEvidenceEntity.class));
|
||||
verify(repository, never()).completeLoading(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void appendsMultiplePhotosOfTheSameType() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of());
|
||||
when(files.validate(eq("token"), eq(10L), eq(20L), anyLong())).thenAnswer(invocation -> {
|
||||
long id = invocation.getArgument(3);
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(id, "photo-" + id + ".jpg",
|
||||
"LOADING_PHOTO_STEP_1", "image/jpeg"));
|
||||
});
|
||||
|
||||
service.upsertEvidence(10, 20, 7, "admin", "append-1",
|
||||
new UpsertEvidenceRequest(0, "PHOTO_STEP_1", 11L, null, null, null));
|
||||
service.upsertEvidence(10, 20, 7, "admin", "append-2",
|
||||
new UpsertEvidenceRequest(0, "PHOTO_STEP_1", 12L, null, null, null));
|
||||
|
||||
verify(repository, times(2)).createEvidence(argThat(value ->
|
||||
"PHOTO_STEP_1".equals(value.getEvidenceType()) && "ACTIVE".equals(value.getStatus())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaysTheSameActiveEvidenceFileWithoutAppendingAnotherRecord() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
LoadingEvidenceEntity existing = evidence("SEAL", 11L, "LOCK-001");
|
||||
existing.setId(99L); existing.setInboundId(20L); existing.setTenantId(10L); existing.setStatus("ACTIVE");
|
||||
when(repository.findEvidenceBySealNo(10, "LOCK-001")).thenReturn(existing);
|
||||
when(repository.findActiveEvidenceByFile(10, 20, "SEAL", 11L)).thenReturn(existing);
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(existing));
|
||||
|
||||
LoadingActionResponse result = service.upsertEvidence(10, 20, 7, "admin", "same-seal-photo",
|
||||
new UpsertEvidenceRequest(0, "SEAL", 11L, null, "LOCK-001", null));
|
||||
|
||||
assertThat(result.evidences().evidences()).hasSize(1);
|
||||
verify(repository, never()).createEvidence(any());
|
||||
verify(files, never()).validate(anyString(), anyLong(), anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void voidsPhotoWithReasonAndKeepsTheRecord() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
LoadingEvidenceEntity target = evidence("PHOTO_STEP_1", 11L, null);
|
||||
target.setId(99L); target.setStatus("ACTIVE"); target.setVersion(2);
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(target));
|
||||
when(repository.voidEvidence(10, 20, 99L, "模糊", 7L, "admin", 2)).thenReturn(1);
|
||||
|
||||
LoadingActionResponse result = service.voidEvidence(10, 20, 99L, 7L, "admin", "void-1",
|
||||
new VoidEvidenceRequest(2, "模糊"));
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADING");
|
||||
verify(repository).voidEvidence(10, 20, 99L, "模糊", 7L, "admin", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateSealUsedByAnotherInbound() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
LoadingEvidenceEntity duplicate = evidence("SEAL", 9L, "LOCK-DUP");
|
||||
duplicate.setInboundId(21L);
|
||||
when(repository.findEvidenceBySealNo(10, "LOCK-DUP")).thenReturn(duplicate);
|
||||
|
||||
assertThatThrownBy(() -> service.upsertEvidence(10, 20, 7, "admin", "k-duplicate-seal",
|
||||
new UpsertEvidenceRequest(0, "SEAL", 5L, null, "LOCK-DUP", null)))
|
||||
.hasMessageContaining("锁码已用于其他装柜任务");
|
||||
verify(repository, never()).createEvidence(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsEvidenceAppendAndVoidAfterLoadingCompleted() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADED", 4));
|
||||
|
||||
assertThatThrownBy(() -> service.upsertEvidence(10, 20, 7, "admin", "loaded-append",
|
||||
new UpsertEvidenceRequest(0, "PHOTO_STEP_1", 11L, null, null, null)))
|
||||
.hasMessageContaining("装柜完成后不允许在线修改证据");
|
||||
assertThatThrownBy(() -> service.voidEvidence(10, 20, 99L, 7L, "admin", "loaded-void",
|
||||
new VoidEvidenceRequest(0, "拍错")))
|
||||
.hasMessageContaining("装柜完成后不允许在线修改证据");
|
||||
|
||||
verify(repository, never()).createEvidence(any());
|
||||
verify(repository, never()).voidEvidence(anyLong(), anyLong(), anyLong(), anyString(),
|
||||
anyLong(), anyString(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCompletingPickingWithoutCanonicalContainerTask() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
|
||||
assertThatThrownBy(() -> service.completePicking(10, 20, 7, "admin", "k-picking",
|
||||
new PickingRequest(0, 1, 1, null))).hasMessageContaining("真实柜次");
|
||||
verify(repository, never()).createPickTask(any());
|
||||
}
|
||||
|
||||
private static LoadingEvidenceEntity evidence(String type, Long fileId, String sealNo) {
|
||||
LoadingEvidenceEntity value = new LoadingEvidenceEntity();
|
||||
value.setId(fileId); value.setInboundId(20L); value.setTenantId(10L);
|
||||
value.setEvidenceType(type); value.setFileId(fileId); value.setSealNo(sealNo);
|
||||
return value;
|
||||
}
|
||||
|
||||
private void readyForPdaFinalize() {
|
||||
when(repository.claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(true);
|
||||
readyForPdaFinalizeFacts();
|
||||
}
|
||||
|
||||
private void readyForPdaFinalizeFacts() {
|
||||
LoadingPickItemMapper.InboundProgress progress = mock(LoadingPickItemMapper.InboundProgress.class);
|
||||
when(progress.getExpectedCount()).thenReturn(5);
|
||||
when(progress.getPickedCount()).thenReturn(5);
|
||||
when(progress.getTaskCount()).thenReturn(1);
|
||||
// completion worker runs before task finalization; accepted COMPLETING is not COMPLETED yet.
|
||||
when(progress.getCompletedTaskCount()).thenReturn(0);
|
||||
when(progress.getUnresolvedCount()).thenReturn(0);
|
||||
when(loadingPicks.inboundProgress(10, 20)).thenReturn(progress);
|
||||
when(repository.listEvidence(10, 20)).thenReturn(List.of(
|
||||
evidence("PHOTO_STEP_1", 1L, null), evidence("PHOTO_STEP_2", 2L, null),
|
||||
evidence("PHOTO_STEP_3", 3L, null), evidence("PHOTO_STEP_4", 4L, null),
|
||||
evidence("SEAL", 5L, "LOCK-001")));
|
||||
when(files.validate(eq("token"), eq(10L), eq(20L), anyLong())).thenAnswer(invocation -> {
|
||||
long fileId = invocation.getArgument(3);
|
||||
String type = fileId == 5L ? "SEAL" : "PHOTO_STEP_" + fileId;
|
||||
return ApiResponse.success(new LoadingEvidenceFileClient.FileMetadata(
|
||||
fileId, type.toLowerCase() + ".png", "LOADING_" + type, "image/png"));
|
||||
});
|
||||
}
|
||||
|
||||
private static LoadingOperationEntity successfulOperation(String requestHash, String resultJson) {
|
||||
LoadingOperationEntity value = new LoadingOperationEntity();
|
||||
value.setRequestHash(requestHash);
|
||||
value.setStatus("SUCCESS");
|
||||
value.setResultJson(resultJson);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static LoadingOperationEntity processingOperation(String requestHash) {
|
||||
LoadingOperationEntity value = new LoadingOperationEntity();
|
||||
value.setRequestHash(requestHash);
|
||||
value.setStatus("PROCESSING");
|
||||
return value;
|
||||
verify(cabinetEvidence).requireComplete(99L);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ class ContainerOverviewServiceTest {
|
||||
61L, 52L, 41L, "LOADING", 7));
|
||||
when(mapper.photos(52L)).thenReturn(java.util.List.of(new ContainerOverviewMapper.PhotoRow(
|
||||
71L, 81L, 91L, "LOADING_DOOR_OPEN", 101L, "door.jpg", "ACTIVE", LocalDateTime.now())));
|
||||
when(evidenceFiles.download("internal-token", 81L, 91L, 101L)).thenReturn(ResponseEntity.ok()
|
||||
when(evidenceFiles.download("internal-token", 41L, "LOADING_DOOR_OPEN", 101L)).thenReturn(ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG).body(new byte[]{1, 2, 3}));
|
||||
|
||||
var download = service.downloadPhoto(41L, 101L);
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
|
||||
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper;
|
||||
import com.baiyue.firstleg.loading.service.LoadingDeliveryService;
|
||||
import com.baiyue.firstleg.loading.service.CabinetLoadingEvidenceService;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
|
||||
import com.baiyue.firstleg.loading.coload.port.ColoadGroupAssignmentPort;
|
||||
@@ -83,6 +84,8 @@ class LoadingPickServiceTest {
|
||||
@MockBean
|
||||
private LoadingDeliveryService loadingDeliveryService;
|
||||
@MockBean
|
||||
private CabinetLoadingEvidenceService cabinetEvidenceService;
|
||||
@MockBean
|
||||
private ColoadGroupAssignmentPort coloadGroups;
|
||||
@MockBean
|
||||
private TruckAppointmentService truckAppointmentService;
|
||||
@@ -574,17 +577,17 @@ class LoadingPickServiceTest {
|
||||
verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(settlementService).settle(20L, 9L, "operator");
|
||||
verify(repository).syncInboundProjections(20L, 9L, "operator");
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L, 101L, 50L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L, 101L, 50L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L);
|
||||
verify(truckAppointmentService).completeLoadingFromPda(99L, 20L, 9L, "operator");
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator",
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator",
|
||||
"PDA-complete-1-50", "checked");
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 60L, 101L, 50L, 9L, "operator",
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 60L, 9L, "operator",
|
||||
"PDA-complete-1-60", "checked");
|
||||
verify(loadingListService).publishCompletedEvent(10L);
|
||||
InOrder finalization = inOrder(loadingListService, loadingDeliveryService);
|
||||
finalization.verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
finalization.verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator",
|
||||
finalization.verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator",
|
||||
"PDA-complete-1-50", "checked");
|
||||
finalization.verify(loadingListService).publishCompletedEvent(10L);
|
||||
}
|
||||
@@ -615,7 +618,7 @@ class LoadingPickServiceTest {
|
||||
verify(settlementService).settle(20L, 9L, "operator");
|
||||
verify(repository).approve(20L, 9L, "operator", "checked");
|
||||
verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator",
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator",
|
||||
"PDA-complete-not-blocked-50", "checked");
|
||||
verify(loadingListService).publishCompletedEvent(10L);
|
||||
}
|
||||
@@ -634,7 +637,7 @@ class LoadingPickServiceTest {
|
||||
new LoadingInboundMapper.Snapshot(50L, 101L, "LOADING", 7));
|
||||
doThrow(new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT,
|
||||
"入库单并发修改"))
|
||||
.when(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 101L, 50L, 9L, "operator",
|
||||
.when(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator",
|
||||
"PDA-complete-finalize-fails-50", "checked");
|
||||
|
||||
assertThatThrownBy(() -> service.complete(20L,
|
||||
@@ -694,7 +697,7 @@ class LoadingPickServiceTest {
|
||||
verify(loadingListService, never()).finishFromPda(anyLong(), anyLong(), anyLong(), anyString());
|
||||
verify(loadingListService, never()).publishCompletedEvent(anyLong());
|
||||
verify(loadingDeliveryService, never()).completeAfterPdaSettlement(anyLong(), anyLong(), anyLong(),
|
||||
anyLong(), anyLong(), anyString(), anyString(), anyString());
|
||||
anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -869,7 +872,7 @@ class LoadingPickServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void workerUsesOneStableCabinetEvidenceOwnerAcrossTwoCustomers() {
|
||||
void workerUsesCabinetEvidenceOnceWithReversedTwoCustomerOwnerOrder() {
|
||||
LoadingPickTaskEntity completing = task();
|
||||
completing.setTaskStatus("COMPLETING");
|
||||
when(repository.task(20L)).thenReturn(completing);
|
||||
@@ -889,12 +892,13 @@ class LoadingPickServiceTest {
|
||||
|
||||
service.processCompletion(completionCommand(903L, "async-multi"), "worker-multi");
|
||||
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L, 101L, 50L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L, 101L, 50L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(202L, 70L, 101L, 50L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(202L, 80L, 101L, 50L);
|
||||
verify(cabinetEvidenceService, times(1)).requireComplete(99L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 60L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(202L, 70L);
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(202L, 80L);
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(
|
||||
202L, 70L, 101L, 50L, 9L, "operator", "PDA-async-multi-70", "checked");
|
||||
202L, 70L, 9L, "operator", "PDA-async-multi-70", "checked");
|
||||
verify(settlementService, times(4)).settleOwner(eq(20L), anyLong(), eq(9L), eq("operator"));
|
||||
verify(repository, times(1)).insert(argThat((LoadingPickOperationEntity operation) ->
|
||||
"COMPLETE_LOADING".equals(operation.getActionType())
|
||||
@@ -914,7 +918,7 @@ class LoadingPickServiceTest {
|
||||
new LoadingPickItemMapper.InboundOwner(101L, 50L),
|
||||
new LoadingPickItemMapper.InboundOwner(202L, 70L)));
|
||||
doThrow(new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT, "第二客户门禁未就绪"))
|
||||
.when(loadingDeliveryService).requirePdaCompletionReady(202L, 70L, 101L, 50L);
|
||||
.when(loadingDeliveryService).requirePdaCompletionReady(202L, 70L);
|
||||
|
||||
assertThatThrownBy(() -> service.processCompletion(
|
||||
completionCommand(904L, "async-owner-ready-fails"), "worker-failure"))
|
||||
@@ -924,7 +928,7 @@ class LoadingPickServiceTest {
|
||||
|
||||
verify(settlementService, never()).settleOwner(anyLong(), anyLong(), anyLong(), anyString());
|
||||
verify(loadingDeliveryService, never()).completeAfterPdaSettlement(anyLong(), anyLong(), anyLong(),
|
||||
anyLong(), anyLong(), anyString(), anyString(), anyString());
|
||||
anyString(), anyString(), anyString());
|
||||
verify(completionCommands).checkpoint(904L, 101L, 50L, 1, "READY");
|
||||
verify(completionCommands, never()).checkpoint(eq(904L), anyLong(), anyLong(), anyInt(),
|
||||
eq("SETTLED"));
|
||||
@@ -952,8 +956,8 @@ class LoadingPickServiceTest {
|
||||
|
||||
verify(settlementService, never()).settleOwner(anyLong(), anyLong(), anyLong(), anyString());
|
||||
verify(loadingDeliveryService, never()).completeAfterPdaSettlement(eq(101L), eq(50L), anyLong(),
|
||||
anyLong(), anyLong(), anyString(), anyString(), anyString());
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(202L, 60L, 101L, 50L, 9L, "operator",
|
||||
anyString(), anyString(), anyString());
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(202L, 60L, 9L, "operator",
|
||||
"PDA-async-resume-60", "checked");
|
||||
verify(loadingListService).publishCompletedEvent(10L);
|
||||
verify(repository).insert(org.mockito.ArgumentMatchers.<LoadingPickOperationEntity>argThat(
|
||||
|
||||
Reference in New Issue
Block a user