refactor: remove WCS tidy task-state recovery
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package com.ruoyi.algorithm.integration;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = "fortucky.algorithm")
|
||||
public class AlgorithmProperties {
|
||||
private SchedulingMode mode = SchedulingMode.SHADOW;
|
||||
private String warehouseId = "01";
|
||||
private int pageSize = 100;
|
||||
private boolean databaseWritesEnabled = true;
|
||||
private boolean legacyFallbackOnTechnicalError;
|
||||
private long tidyAutoScanMs = 600_000L;
|
||||
private long tidyReconcileMs = 5_000L;
|
||||
private int tidyClaimLeaseSeconds = 60;
|
||||
private String tidyWcsSnapshotUrl = "http://localhost:8081/wcs2Wms/api/tidyVehicleSnapshot";
|
||||
private String replayRunId = "";
|
||||
private String replayDatabase = "";
|
||||
|
||||
public SchedulingMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(SchedulingMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getWarehouseId() {
|
||||
return warehouseId;
|
||||
}
|
||||
|
||||
public void setWarehouseId(String warehouseId) {
|
||||
this.warehouseId = warehouseId;
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(int pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
public boolean isDatabaseWritesEnabled() {
|
||||
return databaseWritesEnabled;
|
||||
}
|
||||
|
||||
public void setDatabaseWritesEnabled(boolean databaseWritesEnabled) {
|
||||
this.databaseWritesEnabled = databaseWritesEnabled;
|
||||
}
|
||||
|
||||
public boolean isLegacyFallbackOnTechnicalError() {
|
||||
return legacyFallbackOnTechnicalError;
|
||||
}
|
||||
|
||||
public void setLegacyFallbackOnTechnicalError(boolean legacyFallbackOnTechnicalError) {
|
||||
this.legacyFallbackOnTechnicalError = legacyFallbackOnTechnicalError;
|
||||
}
|
||||
|
||||
public long getTidyAutoScanMs() { return tidyAutoScanMs; }
|
||||
public void setTidyAutoScanMs(long value) { this.tidyAutoScanMs = value; }
|
||||
public long getTidyReconcileMs() { return tidyReconcileMs; }
|
||||
public void setTidyReconcileMs(long value) { this.tidyReconcileMs = value; }
|
||||
public int getTidyClaimLeaseSeconds() { return tidyClaimLeaseSeconds; }
|
||||
public void setTidyClaimLeaseSeconds(int value) { this.tidyClaimLeaseSeconds = value; }
|
||||
public String getTidyWcsSnapshotUrl() { return tidyWcsSnapshotUrl; }
|
||||
public void setTidyWcsSnapshotUrl(String value) { this.tidyWcsSnapshotUrl = value; }
|
||||
public String getReplayRunId() { return replayRunId; }
|
||||
public void setReplayRunId(String value) { this.replayRunId = value; }
|
||||
public String getReplayDatabase() { return replayDatabase; }
|
||||
public void setReplayDatabase(String value) { this.replayDatabase = value; }
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.algorithm.wms.tidy.TidyWorkflowState;
|
||||
import com.ruoyi.algorithm.wms.tidy.TidyRecoveryEvidence;
|
||||
|
||||
@Mapper
|
||||
@@ -59,7 +58,6 @@ public interface WmsAlgorithmMapper {
|
||||
int pauseTidyWorkflow(@Param("workflowId") String workflowId, @Param("statusReason") String statusReason);
|
||||
int pauseTidyStep(@Param("workflowId") String workflowId, @Param("stepNo") int stepNo,
|
||||
@Param("statusReason") String statusReason);
|
||||
List<TidyWorkflowState> selectActiveTidyWorkflows(@Param("warehouseId") String warehouseId);
|
||||
List<TidyRecoveryEvidence> selectTidyRecoveryEvidence(@Param("warehouseId") String warehouseId);
|
||||
TidyModeSnapshot selectTidyMode(@Param("warehouseId") String warehouseId);
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.ruoyi.algorithm.wms.tidy;
|
||||
|
||||
import com.ruoyi.algorithm.tidy.TidyCandidate;
|
||||
import com.ruoyi.algorithm.tidy.TidyMove;
|
||||
import com.ruoyi.algorithm.wms.WmsAlgorithmMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class TidyWorkflowTransaction implements TidyWorkflowStarter {
|
||||
private final WmsAlgorithmMapper mapper;
|
||||
private final ExecutionAdmissionService admission;
|
||||
|
||||
public TidyWorkflowTransaction(WmsAlgorithmMapper mapper, ExecutionAdmissionService admission) {
|
||||
this.mapper = mapper;
|
||||
this.admission = admission;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public UUID startAutomatic(TidyLaneClaim claim, TidyCandidate candidate) {
|
||||
return start(TidyOrigin.AUTO_TIDY, claim.warehouseId(), candidate, claim);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public UUID start(TidyOrigin origin, String warehouseId, TidyCandidate candidate) {
|
||||
return start(origin, warehouseId, candidate, null);
|
||||
}
|
||||
|
||||
private UUID start(TidyOrigin origin, String warehouseId, TidyCandidate candidate, TidyLaneClaim claim) {
|
||||
admission.assertAllowed(warehouseId, origin == TidyOrigin.AUTO_TIDY
|
||||
? ExecutionWorkType.AUTO_TIDY : ExecutionWorkType.MANUAL_TIDY);
|
||||
if (candidate == null || candidate.moves().isEmpty()) {
|
||||
throw new IllegalArgumentException("TIDY_CANDIDATE_EMPTY");
|
||||
}
|
||||
UUID workflowId = UUID.randomUUID();
|
||||
String laneCode = candidate.releasesLaneCode().orElse("");
|
||||
String token = claim == null ? UUID.randomUUID().toString() : claim.claimToken().toString();
|
||||
long dirtyVersion = claim == null ? 0 : claim.dirtyVersion();
|
||||
mapper.insertTidyWorkflow(workflowId.toString(), warehouseId, laneCode, origin.name(),
|
||||
candidate.type().name(), candidate.moves().get(0).fromSpaceId(),
|
||||
candidate.moves().get(candidate.moves().size() - 1).toSpaceId(),
|
||||
candidate.moves().get(0).trayNo(), "CLAIMED", dirtyVersion, token);
|
||||
int stepNo = 0;
|
||||
String dependency = null;
|
||||
for (TidyMove move : candidate.moves()) {
|
||||
String stepId = workflowId + "-" + (++stepNo);
|
||||
mapper.insertTidyStep(stepId, workflowId.toString(), stepNo, move.fromSpaceId(),
|
||||
move.toSpaceId(), move.trayNo(), "PENDING", dependency);
|
||||
mapper.lockTidyResource(UUID.randomUUID(), workflowId.toString(), warehouseId,
|
||||
"SPACE", move.fromSpaceId());
|
||||
mapper.lockTidyResource(UUID.randomUUID(), workflowId.toString(), warehouseId,
|
||||
"SPACE", move.toSpaceId());
|
||||
dependency = stepId;
|
||||
}
|
||||
mapper.insertTidyMoveJob(workflowId.toString(), workflowId + "-1");
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean onJobFinished(String jobId, String callbackStage) {
|
||||
if (jobId == null || jobId.isBlank()) return false;
|
||||
String workflowId = mapper.selectTidyWorkflowIdByJob(jobId);
|
||||
if (workflowId == null) return false;
|
||||
String stepId = mapper.selectTidyStepIdByJob(jobId);
|
||||
if (mapper.insertTidyCallbackEventIfAbsent(UUID.randomUUID(), workflowId, stepId,
|
||||
jobId, callbackStage, "SUCCESS") == 0) return false;
|
||||
mapper.completeTidyStep(stepId);
|
||||
String next = mapper.selectNextTidyStepId(workflowId);
|
||||
if (next == null) {
|
||||
mapper.completeTidyWorkflow(workflowId);
|
||||
mapper.releaseTidyResources(workflowId);
|
||||
} else {
|
||||
mapper.insertTidyMoveJob(workflowId, next);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -159,46 +159,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and status in ('PENDING', 'DISPATCHED', 'RUNNING')
|
||||
</update>
|
||||
|
||||
<resultMap id="tidyWorkflowStateMap" type="com.ruoyi.algorithm.wms.tidy.TidyWorkflowState">
|
||||
<constructor>
|
||||
<arg column="workflowId" javaType="java.lang.String" />
|
||||
<arg column="origin" javaType="com.ruoyi.algorithm.wms.tidy.TidyOrigin" />
|
||||
<arg column="status" javaType="java.lang.String" />
|
||||
<arg column="laneCode" javaType="java.lang.String" />
|
||||
<arg column="sourceSpaceId" javaType="java.lang.String" />
|
||||
<arg column="targetSpaceId" javaType="java.lang.String" />
|
||||
<arg column="trayCode" javaType="java.lang.String" />
|
||||
<arg column="wcsTaskId" javaType="java.lang.String" />
|
||||
<arg column="activeStep" javaType="_int" />
|
||||
</constructor>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectActiveTidyWorkflows" resultMap="tidyWorkflowStateMap">
|
||||
select workflow.workflow_id as workflowId,
|
||||
workflow.origin as origin,
|
||||
workflow.status as status,
|
||||
workflow.lane_code as laneCode,
|
||||
workflow.source_space_id as sourceSpaceId,
|
||||
workflow.target_space_id as targetSpaceId,
|
||||
workflow.tray_code as trayCode,
|
||||
coalesce(workflow.wcs_task_id, job.task_no) as wcsTaskId,
|
||||
active_step.step_no as activeStep
|
||||
from alg.tidy_workflow workflow
|
||||
cross apply (
|
||||
select top (1) step.step_no, step.step_id
|
||||
from alg.tidy_step step
|
||||
where step.workflow_id = workflow.workflow_id
|
||||
and step.status in ('PENDING', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY')
|
||||
order by step.step_no
|
||||
) active_step
|
||||
left join dbo.mydo_job job
|
||||
on job.job_id = concat('TDY-', workflow.workflow_id, '-', active_step.step_id)
|
||||
where workflow.warehouse_id = #{warehouseId}
|
||||
and workflow.status in (
|
||||
'CLAIMED', 'WAITING_DISPATCH', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY'
|
||||
)
|
||||
</select>
|
||||
|
||||
<sql id="tidyModeProjection">
|
||||
mode.warehouse_id as warehouseId,
|
||||
mode.requested_enabled as requestedEnabled,
|
||||
|
||||
@@ -161,17 +161,6 @@ class WmsAlgorithmMapperWorkflowSqlTest {
|
||||
.doesNotContain("foreach");
|
||||
}
|
||||
|
||||
@Test
|
||||
void activeTidyWorkflowUsesAnExplicitDatabaseShapedConstructorMapping() throws IOException {
|
||||
assertThat(statement("resultmap", "tidyWorkflowStateMap"))
|
||||
.contains("<constructor>")
|
||||
.contains("column=\"workflowid\" javatype=\"java.lang.string\"")
|
||||
.contains("column=\"origin\" javatype=\"com.ruoyi.algorithm.wms.tidy.tidyorigin\"")
|
||||
.contains("column=\"activestep\" javatype=\"_int\"");
|
||||
assertThat(statement("select", "selectActiveTidyWorkflows"))
|
||||
.contains("resultmap=\"tidyworkflowstatemap\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void tidyWorkflowCompletionDoesNotRewriteTerminalRows() throws IOException {
|
||||
assertThat(statement("update", "completeTidyWorkflow"))
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.ruoyi.algorithm.wms.tidy;
|
||||
|
||||
import com.ruoyi.algorithm.tidy.TidyCandidate;
|
||||
import com.ruoyi.algorithm.tidy.TidyCandidateType;
|
||||
import com.ruoyi.algorithm.tidy.TidyMove;
|
||||
import com.ruoyi.algorithm.wms.WmsAlgorithmMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class TidyWorkflowTransactionTest {
|
||||
@Test
|
||||
void startsOrderedMovesAsSingleWorkflowWithFirstMoveJob() {
|
||||
WmsAlgorithmMapper mapper = mock(WmsAlgorithmMapper.class);
|
||||
ExecutionAdmissionService admission = mock(ExecutionAdmissionService.class);
|
||||
TidyWorkflowTransaction tx = new TidyWorkflowTransaction(mapper, admission);
|
||||
|
||||
UUID workflow = tx.start(TidyOrigin.MANUAL_TIDY, "01", candidate());
|
||||
|
||||
assertThat(workflow).isNotNull();
|
||||
verify(admission).assertAllowed("01", ExecutionWorkType.MANUAL_TIDY);
|
||||
verify(mapper).insertTidyWorkflow(any(), eq("01"), any(), eq("MANUAL_TIDY"),
|
||||
eq("CONSOLIDATE_INCUBATION"), eq("SPACE-A"), eq("SPACE-C"), eq("TRAY-1"),
|
||||
eq("CLAIMED"), eq(0L), anyString());
|
||||
verify(mapper).insertTidyStep(any(), any(), eq(1), eq("SPACE-A"), eq("SPACE-B"),
|
||||
eq("TRAY-1"), eq("PENDING"), eq(null));
|
||||
verify(mapper).insertTidyStep(any(), any(), eq(2), eq("SPACE-B"), eq("SPACE-C"),
|
||||
eq("TRAY-2"), eq("PENDING"), any());
|
||||
verify(mapper).insertTidyMoveJob(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateCallbackDoesNotCompleteOrDispatchAgain() {
|
||||
WmsAlgorithmMapper mapper = mock(WmsAlgorithmMapper.class);
|
||||
ExecutionAdmissionService admission = mock(ExecutionAdmissionService.class);
|
||||
TidyWorkflowTransaction tx = new TidyWorkflowTransaction(mapper, admission);
|
||||
when(mapper.selectTidyWorkflowIdByJob("JOB-1")).thenReturn("WF-1");
|
||||
when(mapper.selectTidyStepIdByJob("JOB-1")).thenReturn("STEP-1");
|
||||
when(mapper.insertTidyCallbackEventIfAbsent(any(), eq("WF-1"), eq("STEP-1"),
|
||||
eq("JOB-1"), eq("FINISHED"), eq("SUCCESS"))).thenReturn(0);
|
||||
|
||||
assertThat(tx.onJobFinished("JOB-1", "FINISHED")).isFalse();
|
||||
verify(mapper, never()).completeTidyStep(any());
|
||||
verify(mapper, never()).insertTidyMoveJob(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void recoveryFinishedUsesTheNormalIdempotentAdvancementPath() {
|
||||
WmsAlgorithmMapper mapper = mock(WmsAlgorithmMapper.class);
|
||||
ExecutionAdmissionService admission = mock(ExecutionAdmissionService.class);
|
||||
TidyWorkflowTransaction tx = new TidyWorkflowTransaction(mapper, admission);
|
||||
when(mapper.selectTidyWorkflowIdByJob("JOB-1")).thenReturn("WF-1");
|
||||
when(mapper.selectTidyStepIdByJob("JOB-1")).thenReturn("STEP-1");
|
||||
when(mapper.insertTidyCallbackEventIfAbsent(any(), eq("WF-1"), eq("STEP-1"),
|
||||
eq("JOB-1"), eq("RECOVERY_FINISHED"), eq("SUCCESS"))).thenReturn(1);
|
||||
when(mapper.selectNextTidyStepId("WF-1")).thenReturn(null);
|
||||
|
||||
assertThat(tx.onJobFinished("JOB-1", "RECOVERY_FINISHED")).isTrue();
|
||||
|
||||
verify(mapper).completeTidyStep("STEP-1");
|
||||
verify(mapper).completeTidyWorkflow("WF-1");
|
||||
verify(mapper).releaseTidyResources("WF-1");
|
||||
verify(mapper, never()).pauseTidyWorkflow(anyString(), anyString());
|
||||
}
|
||||
|
||||
private TidyCandidate candidate() {
|
||||
return new TidyCandidate(TidyCandidateType.CONSOLIDATE_INCUBATION, "P-1",
|
||||
List.of(new TidyMove("TRAY-1", "SPACE-A", "SPACE-B"),
|
||||
new TidyMove("TRAY-2", "SPACE-B", "SPACE-C")),
|
||||
Optional.of("LANE-1"), "test");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.algorithm.wms.tidy;
|
||||
|
||||
import com.ruoyi.algorithm.integration.AlgorithmProperties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class WcsTaskStateDependencyRemovalTest {
|
||||
@Test
|
||||
void taskStateClassesPropertiesAndConfigurationAreAbsent() throws Exception {
|
||||
assertThatThrownBy(() -> Class.forName(
|
||||
"com.ruoyi.algorithm.wms.tidy.WcsTaskStateClient"))
|
||||
.isInstanceOf(ClassNotFoundException.class);
|
||||
assertThatThrownBy(() -> Class.forName(
|
||||
"com.ruoyi.algorithm.wms.tidy.WcsTaskState"))
|
||||
.isInstanceOf(ClassNotFoundException.class);
|
||||
|
||||
assertThat(Arrays.stream(AlgorithmProperties.class.getMethods())
|
||||
.map(Method::getName))
|
||||
.noneMatch(name -> name.contains("TidyWcsTaskState"));
|
||||
|
||||
try (InputStream stream = getClass().getResourceAsStream("/application.yml")) {
|
||||
assertThat(stream).isNotNull();
|
||||
String yaml = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertThat(yaml).doesNotContain("tidy-wcs-task-state");
|
||||
assertThat(yaml).contains("tidy-wcs-snapshot-url");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user