feat(firstleg): converge phase3 loading models and pda
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PdaConvergenceOpenApiContractTest {
|
||||
@Test
|
||||
void publishesClaimAssignWorkAndReviewRoutes() throws Exception {
|
||||
String yaml = new ClassPathResource("openapi/phase3-pda-convergence-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/first-leg/pda-loading/tasks:")
|
||||
.contains("/tasks/{taskId}/claim:")
|
||||
.contains("/tasks/{taskId}/assign:")
|
||||
.contains("/tasks/{taskId}/items/{itemId}/pick:")
|
||||
.contains("/tasks/{taskId}/items/{itemId}/partial:")
|
||||
.contains("/tasks/{taskId}/items/{itemId}/skip:")
|
||||
.contains("/tasks/{taskId}/review:")
|
||||
.contains("/tasks/{taskId}/operations:")
|
||||
.contains("expectedAssignmentVersion")
|
||||
.contains("expectedTaskVersion");
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PlatformLoadingPdaControllerContractTest {
|
||||
@Test
|
||||
void exposesAssignmentWorkAndReviewWithDedicatedAuthorities() throws Exception {
|
||||
authority("task", "platform:firstleg:pda:view", AuthenticatedUser.class, String.class, long.class);
|
||||
authority("claim", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, ClaimRequest.class);
|
||||
authority("assign", "platform:firstleg:pda:assign", AuthenticatedUser.class, long.class, AssignRequest.class);
|
||||
authority("pick", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, long.class, PickRequest.class);
|
||||
authority("partial", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, long.class, PartialPickRequest.class);
|
||||
authority("skip", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, long.class, SkipRequest.class);
|
||||
authority("review", "platform:firstleg:pda:review", AuthenticatedUser.class, long.class, ReviewRequest.class);
|
||||
authority("operations", "platform:firstleg:pda:view", AuthenticatedUser.class, long.class);
|
||||
}
|
||||
|
||||
private void authority(String name, String expected, Class<?>... parameterTypes) throws Exception {
|
||||
Method method = PlatformLoadingPdaController.class.getDeclaredMethod(name, parameterTypes);
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('" + expected + "')");
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.baiyue.firstleg.loading.bridge;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LoadingModelBridgeContractTest {
|
||||
@Test
|
||||
void moduleSqlUsesExplicitManyToManyLinksAndRepeatableBackfill() throws Exception {
|
||||
String sql = new ClassPathResource("db/phase3-loading-model-bridge-schema.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(sql)
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_preload_loading_plan_link")
|
||||
.contains("UNIQUE KEY uk_preload_loading_pair (tenant_id, inbound_id, loading_plan_id)")
|
||||
.contains("FOREIGN KEY (preload_plan_id) REFERENCES fl_preload_plan (id)")
|
||||
.contains("FOREIGN KEY (loading_plan_id) REFERENCES fl_loading_plan (id)")
|
||||
.contains("INSERT IGNORE INTO fl_preload_loading_plan_link")
|
||||
.doesNotContain("ALTER TABLE fl_preload_plan ADD COLUMN loading_plan_id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void openApiDocumentsAtomicBindAndBothDirections() throws Exception {
|
||||
String yaml = new ClassPathResource("openapi/phase3-loading-model-bridge-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/preload/loading-plans:")
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/preload/loading-plans/{loadingPlanId}:")
|
||||
.contains("/api/platform/v1/first-leg/smart-loading/plans/{loadingPlanId}/preloads:")
|
||||
.contains("required: [expectedPreloadVersion, expectedLoadingPlanVersion]")
|
||||
.contains("either conflict rolls back the whole link operation");
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.baiyue.firstleg.loading.bridge.service;
|
||||
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.BindRequest;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper.LoadingPlanSnapshot;
|
||||
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper.PreloadSnapshot;
|
||||
import com.baiyue.firstleg.loading.bridge.repository.LoadingModelBridgeRepository;
|
||||
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 java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LoadingModelBridgeServiceTest {
|
||||
@Mock LoadingModelBridgeRepository repository;
|
||||
@InjectMocks LoadingModelBridgeService service;
|
||||
|
||||
@Test
|
||||
void bindsManyToManyModelsWithBothOptimisticLocks() {
|
||||
when(repository.preload(10, 20)).thenReturn(new PreloadSnapshot(30L, 10L, 20L, "PENDING", 2));
|
||||
when(repository.loadingPlan(40)).thenReturn(new LoadingPlanSnapshot(40L, 50L, "C1", "EDITING", 3));
|
||||
when(repository.allocatedPackages(10, 20, 40)).thenReturn(8);
|
||||
when(repository.touchPreload(30, 10, 20, 2, 7, "admin")).thenReturn(1);
|
||||
when(repository.touchLoadingPlan(40, 3, 7, "admin")).thenReturn(1);
|
||||
Link linked = link("PENDING", 3, "EDITING", 4, 8);
|
||||
when(repository.byInbound(10, 20)).thenReturn(List.of(linked));
|
||||
|
||||
Link result = service.bind(10, 20, 40, 7, "admin", "bridge-1", new BindRequest(2, 3));
|
||||
|
||||
assertThat(result.loadingPlanId()).isEqualTo(40);
|
||||
verify(repository).insert(any());
|
||||
verify(repository).touchPreload(30, 10, 20, 2, 7, "admin");
|
||||
verify(repository).touchLoadingPlan(40, 3, 7, "admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondVersionConflictDoesNotProceedAsSuccess() {
|
||||
when(repository.preload(10, 20)).thenReturn(new PreloadSnapshot(30L, 10L, 20L, "PENDING", 2));
|
||||
when(repository.loadingPlan(40)).thenReturn(new LoadingPlanSnapshot(40L, 50L, "C1", "EDITING", 3));
|
||||
when(repository.allocatedPackages(10, 20, 40)).thenReturn(8);
|
||||
when(repository.touchPreload(30, 10, 20, 2, 7, "admin")).thenReturn(1);
|
||||
when(repository.touchLoadingPlan(40, 3, 7, "admin")).thenReturn(0);
|
||||
|
||||
assertThatThrownBy(() -> service.bind(10, 20, 40, 7, "admin", "bridge-2", new BindRequest(2, 3)))
|
||||
.hasMessageContaining("刷新后重试");
|
||||
verify(repository, never()).byInbound(10, 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
void terminalGateRequiresEveryLinkedContainerPlanCompleted() {
|
||||
when(repository.byInbound(10, 20)).thenReturn(List.of(
|
||||
link("CONFIRMED", 4, "COMPLETED", 7, 5),
|
||||
new Link(2L, 10L, 20L, 30L, "CONFIRMED", 4, 41L, 51L, "C2",
|
||||
"LOADING", 6, 3, LocalDateTime.now())));
|
||||
|
||||
assertThatThrownBy(() -> service.requireCompleted(10, 20))
|
||||
.hasMessageContaining("尚未全部完成装柜");
|
||||
}
|
||||
|
||||
private static Link link(String preloadStatus, int preloadVersion, String loadingStatus,
|
||||
int loadingVersion, int packages) {
|
||||
return new Link(1L, 10L, 20L, 30L, preloadStatus, preloadVersion, 40L, 50L, "C1",
|
||||
loadingStatus, loadingVersion, packages, LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -21,6 +21,8 @@ class LoadingDeliveryOpenApiContractTest {
|
||||
.contains("required: [expectedVersion, evidenceType, fileId]")
|
||||
.contains("enum: [PHOTO_STEP_1, PHOTO_STEP_2, PHOTO_STEP_3, PHOTO_STEP_4, SEAL]")
|
||||
.contains("Required when evidenceType is SEAL")
|
||||
.contains("Complete a started PDA picking task");
|
||||
.contains("compatibility projection")
|
||||
.contains("must exactly match the server-derived container-task aggregate")
|
||||
.contains("cannot complete canonical picking");
|
||||
}
|
||||
}
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.baiyue.firstleg.loading.repository;
|
||||
|
||||
import com.baiyue.firstleg.mapper.LoadingPickItemMapper;
|
||||
import com.baiyue.firstleg.loading.mapper.InboundLoadingPickTaskMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PdaConvergenceSqlContractTest {
|
||||
@Test
|
||||
void hasIndependentAssignmentReviewOperationAuditAndPermissionGrants() throws Exception {
|
||||
String sql = new ClassPathResource("db/phase3-pda-convergence-schema.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(sql)
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_assignment")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_review")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_operation")
|
||||
.contains("UNIQUE KEY uk_loading_pick_operation_id")
|
||||
.contains("trg_loading_pick_review_before_update")
|
||||
.contains("trg_loading_pick_operation_before_delete")
|
||||
.contains("5102190, 0, 'PLATFORM'")
|
||||
.contains("5102193, 0, 'PLATFORM'")
|
||||
.contains("role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')")
|
||||
.contains("platform:firstleg:pda:view")
|
||||
.contains("platform:firstleg:pda:assign")
|
||||
.contains("platform:firstleg:pda:work")
|
||||
.contains("platform:firstleg:pda:review");
|
||||
}
|
||||
|
||||
@Test
|
||||
void derivesInboundProjectionFromCanonicalContainerItems() throws Exception {
|
||||
String aggregate = String.join(" ", LoadingPickItemMapper.class
|
||||
.getDeclaredMethod("selectInboundProgress", long.class, long.class)
|
||||
.getAnnotation(Select.class).value());
|
||||
String upsert = InboundLoadingPickTaskMapper.class.getDeclaredMethod("upsertProjection",
|
||||
long.class, long.class, long.class, String.class, int.class, int.class, long.class, String.class)
|
||||
.getAnnotation(Insert.class).value()[0];
|
||||
assertThat(aggregate)
|
||||
.contains("fl_loading_plan_item")
|
||||
.contains("fl_loading_pick_item")
|
||||
.contains("fl_loading_pick_task")
|
||||
.contains("plan_item.account_id=#{tenantId}")
|
||||
.contains("pick_task.task_status='COMPLETED'");
|
||||
assertThat(upsert)
|
||||
.contains("INSERT INTO fl_inbound_loading_pick_task")
|
||||
.contains("ON DUPLICATE KEY UPDATE")
|
||||
.contains("柜次PDA自动汇总");
|
||||
}
|
||||
}
|
||||
+22
-10
@@ -8,8 +8,10 @@ import com.baiyue.firstleg.loading.dto.LoadingRequests.PickingRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingRequests.UpsertEvidenceRequest;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingResponses.LoadingActionResponse;
|
||||
import com.baiyue.firstleg.loading.entity.LoadingEvidenceEntity;
|
||||
import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
|
||||
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper.Snapshot;
|
||||
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.repository.LoadingRepository;
|
||||
import com.baiyue.firstleg.mapper.InboundStatusHistoryMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -30,6 +32,8 @@ import static org.mockito.Mockito.*;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LoadingDeliveryServiceTest {
|
||||
@Mock LoadingRepository repository;
|
||||
@Mock LoadingModelBridgeService loadingModelBridge;
|
||||
@Mock LoadingPickRepository loadingPicks;
|
||||
@Mock InboundStatusHistoryMapper histories;
|
||||
@Mock LoadingEvidenceFileClient files;
|
||||
@InjectMocks LoadingDeliveryService service;
|
||||
@@ -48,9 +52,13 @@ class LoadingDeliveryServiceTest {
|
||||
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));
|
||||
InboundLoadingPickTaskEntity pick = new InboundLoadingPickTaskEntity();
|
||||
pick.setStatus("COMPLETED"); pick.setExpectedCount(5); pick.setPickedCount(5);
|
||||
when(repository.findPickTask(10, 20)).thenReturn(pick);
|
||||
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),
|
||||
@@ -64,6 +72,7 @@ class LoadingDeliveryServiceTest {
|
||||
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(histories).insert(any(InboundStatusHistoryEntity.class));
|
||||
}
|
||||
|
||||
@@ -71,9 +80,13 @@ class LoadingDeliveryServiceTest {
|
||||
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));
|
||||
InboundLoadingPickTaskEntity pick = new InboundLoadingPickTaskEntity();
|
||||
pick.setStatus("COMPLETED"); pick.setExpectedCount(5); pick.setPickedCount(5);
|
||||
when(repository.findPickTask(10, 20)).thenReturn(pick);
|
||||
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",
|
||||
@@ -111,13 +124,12 @@ class LoadingDeliveryServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCompletingPickingBeforeTaskStarts() {
|
||||
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));
|
||||
when(repository.findPickTask(10, 20)).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> service.completePicking(10, 20, 7, "admin", "k-picking",
|
||||
new PickingRequest(0, 1, 1, null))).hasMessageContaining("尚未开始");
|
||||
new PickingRequest(0, 1, 1, null))).hasMessageContaining("真实柜次");
|
||||
verify(repository, never()).createPickTask(any());
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -23,10 +23,15 @@ class Phase3FinalUnifiedSqlContractTest {
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_customs_receipt")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_port_schedule")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_port_schedule_history")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_preload_loading_plan_link")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_assignment")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_review")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_operation")
|
||||
.contains("uk_loading_evidence_seal (tenant_id,seal_no)")
|
||||
.contains("CALL assert_phase3_final_schema()")
|
||||
.contains("trg_port_schedule_history_before_update")
|
||||
.contains("trg_port_schedule_history_before_delete");
|
||||
.contains("trg_port_schedule_history_before_delete")
|
||||
.contains("trg_loading_pick_operation_before_delete");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -36,10 +41,12 @@ class Phase3FinalUnifiedSqlContractTest {
|
||||
for (int id = 5102160; id <= 5102164; id++) assertThat(sql).contains(Integer.toString(id));
|
||||
for (int id = 5102170; id <= 5102175; id++) assertThat(sql).contains(Integer.toString(id));
|
||||
for (int id = 5102180; id <= 5102183; id++) assertThat(sql).contains(Integer.toString(id));
|
||||
for (int id = 5102190; id <= 5102193; id++) assertThat(sql).contains(Integer.toString(id));
|
||||
assertThat(sql)
|
||||
.contains("platform:firstleg:outbound-dispatch:%")
|
||||
.contains("platform:firstleg:customs:%")
|
||||
.contains("platform:firstleg:port-schedule:%")
|
||||
.contains("platform:firstleg:pda:%")
|
||||
.contains("role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR')")
|
||||
.contains("角色授权未完整写入");
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package com.baiyue.firstleg.service;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PartialPickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ReviewRequest;
|
||||
import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickOperationEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
@@ -34,9 +37,12 @@ class LoadingPickServiceTest {
|
||||
LoadingPickItemEntity item = item();
|
||||
when(repository.history("pick-1")).thenReturn(null);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.item(20L, 30L, 101L)).thenReturn(item);
|
||||
when(repository.pick(30L, 2, 0, 101L)).thenReturn(1);
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(false);
|
||||
when(repository.touchTask(20L, 0)).thenReturn(1);
|
||||
when(repository.items(20L, 101L)).thenReturn(List.of(item));
|
||||
|
||||
service.pick(20L, 30L, new PickRequest(101L, 2, List.of(), 0, "pick-1"), 9L, "operator");
|
||||
@@ -50,14 +56,16 @@ class LoadingPickServiceTest {
|
||||
LoadingPickItemEntity item = item();
|
||||
when(repository.history("pick-2")).thenReturn(null);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.item(20L, 30L, 101L)).thenReturn(item);
|
||||
when(repository.pick(30L, 2, 0, 101L)).thenReturn(1);
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(true);
|
||||
when(repository.items(20L, 101L)).thenReturn(List.of(item));
|
||||
when(repository.touchTask(20L, 0)).thenReturn(1);
|
||||
|
||||
service.pick(20L, 30L, new PickRequest(101L, 2, List.of(), 0, "pick-2"), 9L, "operator");
|
||||
|
||||
verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(loadingListService, never()).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(repository).insert(any(LoadingPickHistoryEntity.class));
|
||||
}
|
||||
|
||||
@@ -67,14 +75,16 @@ class LoadingPickServiceTest {
|
||||
LoadingPickItemEntity item = item();
|
||||
when(repository.history("partial-1")).thenReturn(null);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.item(20L, 30L, 101L)).thenReturn(item);
|
||||
when(repository.partial(30L, 1, 0, 101L)).thenReturn(1);
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(true);
|
||||
when(repository.items(20L, 101L)).thenReturn(List.of(item));
|
||||
when(repository.touchTask(20L, 0)).thenReturn(1);
|
||||
|
||||
service.partial(20L, 30L, new PartialPickRequest(101L, 1, List.of(), 0, "partial-1"), 9L, "operator");
|
||||
|
||||
verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(loadingListService, never()).finishFromPda(10L, 20L, 9L, "operator");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,14 +93,37 @@ class LoadingPickServiceTest {
|
||||
LoadingPickItemEntity item = item();
|
||||
when(repository.history("skip-1")).thenReturn(null);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.item(20L, 30L, 101L)).thenReturn(item);
|
||||
when(repository.skip(30L, 0, 101L)).thenReturn(1);
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(true);
|
||||
when(repository.items(20L, 101L)).thenReturn(List.of(item));
|
||||
when(repository.touchTask(20L, 0)).thenReturn(1);
|
||||
|
||||
service.skip(20L, 30L, new SkipRequest(101L, 0, "skip-1", "fixture unavailable"), 9L, "operator");
|
||||
|
||||
verify(loadingListService, never()).finishFromPda(10L, 20L, 9L, "operator");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reviewSettlesPartialItemsBeforeCompletingPlan() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
LoadingPickItemEntity item = item();
|
||||
item.setPlannedPackageCount(5);
|
||||
item.setPickedPackageCount(3);
|
||||
item.setItemStatus("PARTIAL");
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.items(20L)).thenReturn(List.of(item));
|
||||
when(repository.settlePartial(40L, 30L, 3, 5, 9L, "operator")).thenReturn(1);
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(true);
|
||||
when(repository.approve(20L, 9L, "operator", "checked")).thenReturn(1);
|
||||
|
||||
service.review(20L, new ReviewRequest(0, "review-1", "checked"), 9L, "operator");
|
||||
|
||||
verify(repository).insert(any(LoadingPickOperationEntity.class));
|
||||
verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(repository).syncInboundProjections(20L, 9L, "operator");
|
||||
}
|
||||
|
||||
private static LoadingPickTaskEntity task() {
|
||||
@@ -116,4 +149,13 @@ class LoadingPickServiceTest {
|
||||
item.setVersion(0);
|
||||
return item;
|
||||
}
|
||||
|
||||
private static LoadingPickAssignmentEntity assignment() {
|
||||
LoadingPickAssignmentEntity assignment = new LoadingPickAssignmentEntity();
|
||||
assignment.setPickTaskId(20L);
|
||||
assignment.setAssigneeId(9L);
|
||||
assignment.setAssigneeName("operator");
|
||||
assignment.setVersion(0);
|
||||
return assignment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.firstleg.split.service;
|
||||
|
||||
import com.baiyue.firstleg.mapper.InboundStatusHistoryMapper;
|
||||
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
|
||||
import com.baiyue.firstleg.split.dto.SplitRequests.ConfirmPreloadRequest;
|
||||
import com.baiyue.firstleg.split.dto.SplitRequests.PreloadItem;
|
||||
import com.baiyue.firstleg.split.dto.SplitRequests.SmartSplitRequest;
|
||||
@@ -30,6 +31,7 @@ import static org.mockito.Mockito.verify;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SplitPreloadServiceTest {
|
||||
@Mock SplitRepository repository;
|
||||
@Mock LoadingModelBridgeService loadingModelBridge;
|
||||
@Mock InboundStatusHistoryMapper histories;
|
||||
@InjectMocks SplitPreloadService service;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
@@ -61,11 +63,13 @@ class SplitPreloadServiceTest {
|
||||
plan.setTenantId(10L); plan.setInboundId(20L); plan.setStatus("CONFIRMING"); plan.setVersion(1);
|
||||
plan.setItemsJson("[]");
|
||||
when(repository.findPlan(10, 20)).thenReturn(plan);
|
||||
org.mockito.Mockito.doNothing().when(loadingModelBridge).requireStarted(10, 20);
|
||||
when(repository.changePlanStatus(10, 20, "CONFIRMING", "CONFIRMED", 7, "admin", 1)).thenReturn(1);
|
||||
when(repository.beginLoading(10, 20, 7, 4)).thenReturn(1);
|
||||
SplitActionResponse result = service.confirmPreload(10, 20, 7, "admin", "k3",
|
||||
new ConfirmPreloadRequest(1));
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADING");
|
||||
verify(loadingModelBridge).requireStarted(10, 20);
|
||||
verify(repository).beginLoading(10, 20, 7, 4);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user