feat: add outbound WCS retry dispatch
This commit is contained in:
@@ -86,13 +86,27 @@ public class WcsClient extends BaseExternalClient {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String body = objectMapper.writeValueAsString(job);
|
String body = objectMapper.writeValueAsString(job);
|
||||||
// 正式WCS作业对象仍使用统一接收入口;地址按出库作业所属仓库解析。
|
return post(jobUrl("sendRetrieveTask", job), body, "WCS-下发出库任务", this::parseWcsAck, MsgTypeEnum.JSON);
|
||||||
return post(jobUrl("sendPutawayTask", job), body, "WCS-下发出库任务", this::parseWcsAck, MsgTypeEnum.JSON);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.fail("SERIALIZE_FAIL", "出库作业JSON报文生成失败: " + e.getMessage());
|
return ApiResponse.fail("SERIALIZE_FAIL", "出库作业JSON报文生成失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 兼容统一出库命名,实际使用普通出库接口。 */
|
||||||
|
public ApiResponse<String> sendOutboundTask(MydoJob job) {
|
||||||
|
return sendRetrieveTask(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 下发指定货位出库任务。 */
|
||||||
|
public ApiResponse<String> sendLocationOutboundTask(MydoJob job) {
|
||||||
|
return sendJsonJob(job, "sendLocationOutboundTask", "WCS-下发指定货位出库任务", "MOCK_WCS_LOCATION_OUT_OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 下发密集库移库任务。 */
|
||||||
|
public ApiResponse<String> sendRelocationTask(MydoJob job) {
|
||||||
|
return sendJsonJob(job, "sendRelocationTask", "WCS-下发移库任务", "MOCK_WCS_RELOCATION_OK");
|
||||||
|
}
|
||||||
|
|
||||||
// ── 接口3:查询设备状态 ───────────────────────────────────
|
// ── 接口3:查询设备状态 ───────────────────────────────────
|
||||||
|
|
||||||
public ApiResponse<List<Map<String, String>>> queryDeviceStatus(String deviceType) {
|
public ApiResponse<List<Map<String, String>>> queryDeviceStatus(String deviceType) {
|
||||||
@@ -161,6 +175,21 @@ public class WcsClient extends BaseExternalClient {
|
|||||||
return config().getApiUrl(apiKey, job == null ? null : job.getWarehouseId());
|
return config().getApiUrl(apiKey, job == null ? null : job.getWarehouseId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ApiResponse<String> sendJsonJob(MydoJob job, String apiKey, String description, String mockResult) {
|
||||||
|
if (job == null) {
|
||||||
|
return ApiResponse.fail("PARAM_ERROR", "作业不能为空");
|
||||||
|
}
|
||||||
|
if (mockEnabled()) {
|
||||||
|
return ApiResponse.ok(mockResult);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String body = objectMapper.writeValueAsString(job);
|
||||||
|
return post(jobUrl(apiKey, job), body, description, this::parseWcsAck, MsgTypeEnum.JSON);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.fail("SERIALIZE_FAIL", "作业JSON报文生成失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String parseOkNg(String rawBody) {
|
private String parseOkNg(String rawBody) {
|
||||||
String[] parts = rawBody.trim().split("\\|", 2);
|
String[] parts = rawBody.trim().split("\\|", 2);
|
||||||
if ("OK".equals(parts[0])) {
|
if ("OK".equals(parts[0])) {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import com.ruoyi.in.jobdetail.mapper.MydoJobDetailMapper;
|
|||||||
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
||||||
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
||||||
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
|
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
@@ -78,6 +80,9 @@ public class MydoJobServiceImpl implements IMydoJobService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
|
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IOutboundDispatchService outboundDispatchService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询作业信息
|
* 查询作业信息
|
||||||
*
|
*
|
||||||
@@ -309,38 +314,7 @@ public class MydoJobServiceImpl implements IMydoJobService
|
|||||||
@Override
|
@Override
|
||||||
public AjaxResult pushOutJobToWcs(String jobId)
|
public AjaxResult pushOutJobToWcs(String jobId)
|
||||||
{
|
{
|
||||||
if (StringUtils.isEmpty(jobId)) {
|
return outboundDispatchService.dispatch(jobId, DispatchTrigger.INITIAL);
|
||||||
return AjaxResult.error("作业ID不能为空");
|
|
||||||
}
|
|
||||||
MydoJob job = mydoJobMapper.selectMydoJobByJobId(jobId);
|
|
||||||
if (job == null) {
|
|
||||||
return AjaxResult.error("出库作业不存在");
|
|
||||||
}
|
|
||||||
if (job.getInOrOut() == null || job.getInOrOut() < 20) {
|
|
||||||
return AjaxResult.error("请选择出库作业");
|
|
||||||
}
|
|
||||||
if (!"1".equals(job.getStatus())) {
|
|
||||||
if ("2".equals(job.getStatus()) || "3".equals(job.getStatus())) {
|
|
||||||
return AjaxResult.success("出库作业已下发,无需重复推送");
|
|
||||||
}
|
|
||||||
return AjaxResult.error("当前作业状态不允许下发WCS");
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiResponse<String> response = wcsClient.sendRetrieveTask(job);
|
|
||||||
if (response == null || !response.isSuccess()) {
|
|
||||||
String errorMessage = response == null ? "WCS接口无返回" : response.getMessage();
|
|
||||||
return AjaxResult.error("WCS出库作业下发失败:" + errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
MydoJob updateJob = new MydoJob();
|
|
||||||
updateJob.setJobId(job.getJobId());
|
|
||||||
updateJob.setStatus("3");
|
|
||||||
updateJob.setExecutionPhase("1");
|
|
||||||
updateJob.setStartTime(DateUtils.getTime());
|
|
||||||
if (mydoJobMapper.updateMydoJob(updateJob) <= 0) {
|
|
||||||
return AjaxResult.error("出库作业状态更新失败");
|
|
||||||
}
|
|
||||||
return AjaxResult.success("WCS出库作业下发成功:" + Convert.toStr(response.getData()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.ruoyi.out.dense.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库作业下发资格。
|
||||||
|
*/
|
||||||
|
public class DispatchEligibility
|
||||||
|
{
|
||||||
|
private final boolean eligible;
|
||||||
|
private final String reason;
|
||||||
|
|
||||||
|
private DispatchEligibility(boolean eligible, String reason)
|
||||||
|
{
|
||||||
|
this.eligible = eligible;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DispatchEligibility allowed()
|
||||||
|
{
|
||||||
|
return new DispatchEligibility(true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DispatchEligibility blocked(String reason)
|
||||||
|
{
|
||||||
|
return new DispatchEligibility(false, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEligible()
|
||||||
|
{
|
||||||
|
return eligible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason()
|
||||||
|
{
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.ruoyi.out.dense.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库通道查询 Mapper。
|
||||||
|
*/
|
||||||
|
public interface DenseLaneMapper
|
||||||
|
{
|
||||||
|
Integer isCurrentOutermost(@Param("cargoSpaceId") String cargoSpaceId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.ruoyi.out.dense.service;
|
||||||
|
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.out.dense.domain.DispatchEligibility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库通道服务。
|
||||||
|
*/
|
||||||
|
public interface IDenseLaneService
|
||||||
|
{
|
||||||
|
DispatchEligibility checkDispatchEligibility(MydoJob job);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.ruoyi.out.dense.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.out.dense.domain.DispatchEligibility;
|
||||||
|
import com.ruoyi.out.dense.mapper.DenseLaneMapper;
|
||||||
|
import com.ruoyi.out.dense.service.IDenseLaneService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库通道服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class DenseLaneServiceImpl implements IDenseLaneService
|
||||||
|
{
|
||||||
|
private final DenseLaneMapper denseLaneMapper;
|
||||||
|
|
||||||
|
public DenseLaneServiceImpl(DenseLaneMapper denseLaneMapper)
|
||||||
|
{
|
||||||
|
this.denseLaneMapper = denseLaneMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DispatchEligibility checkDispatchEligibility(MydoJob job)
|
||||||
|
{
|
||||||
|
if (job == null) {
|
||||||
|
return DispatchEligibility.blocked("出库作业不存在");
|
||||||
|
}
|
||||||
|
if (!"03".equals(job.getWarehouseId())) {
|
||||||
|
return DispatchEligibility.allowed();
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(job.getCargoSpaceId())) {
|
||||||
|
return DispatchEligibility.blocked("03仓出库作业未分配源货位");
|
||||||
|
}
|
||||||
|
Integer outermost = denseLaneMapper.isCurrentOutermost(job.getCargoSpaceId());
|
||||||
|
if (Integer.valueOf(1).equals(outermost)) {
|
||||||
|
return DispatchEligibility.allowed();
|
||||||
|
}
|
||||||
|
return DispatchEligibility.blocked("当前货位不是密集库通道最外侧货位");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.ruoyi.out.dispatch.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WCS 下发触发方式。
|
||||||
|
*/
|
||||||
|
public enum DispatchTrigger
|
||||||
|
{
|
||||||
|
INITIAL,
|
||||||
|
AUTO_RETRY,
|
||||||
|
MANUAL
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.out.dispatch.service;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出库作业 WCS 下发服务。
|
||||||
|
*/
|
||||||
|
public interface IOutboundDispatchService
|
||||||
|
{
|
||||||
|
AjaxResult dispatch(String jobId, DispatchTrigger trigger);
|
||||||
|
|
||||||
|
int retryFailedJobs();
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package com.ruoyi.out.dispatch.service.impl;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.ruoyi.api.client.WcsClient;
|
||||||
|
import com.ruoyi.api.domain.ApiResponse;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.text.Convert;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.in.job.mapper.MydoJobMapper;
|
||||||
|
import com.ruoyi.out.dense.domain.DispatchEligibility;
|
||||||
|
import com.ruoyi.out.dense.service.IDenseLaneService;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.domain.WcsJobDispatch;
|
||||||
|
import com.ruoyi.out.dispatch.mapper.WcsJobDispatchMapper;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出库作业 WCS 下发服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class OutboundDispatchServiceImpl implements IOutboundDispatchService
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(OutboundDispatchServiceImpl.class);
|
||||||
|
private static final int MAX_AUTO_RETRY_COUNT = 5;
|
||||||
|
private static final int RETRY_BATCH_SIZE = 100;
|
||||||
|
private static final long RETRY_DELAY_MILLIS = 60_000L;
|
||||||
|
|
||||||
|
private final MydoJobMapper jobMapper;
|
||||||
|
private final WcsJobDispatchMapper dispatchMapper;
|
||||||
|
private final IDenseLaneService denseLaneService;
|
||||||
|
private final WcsClient wcsClient;
|
||||||
|
|
||||||
|
public OutboundDispatchServiceImpl(MydoJobMapper jobMapper,
|
||||||
|
WcsJobDispatchMapper dispatchMapper,
|
||||||
|
IDenseLaneService denseLaneService,
|
||||||
|
WcsClient wcsClient)
|
||||||
|
{
|
||||||
|
this.jobMapper = jobMapper;
|
||||||
|
this.dispatchMapper = dispatchMapper;
|
||||||
|
this.denseLaneService = denseLaneService;
|
||||||
|
this.wcsClient = wcsClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult dispatch(String jobId, DispatchTrigger trigger)
|
||||||
|
{
|
||||||
|
if (!StringUtils.hasText(jobId)) {
|
||||||
|
return AjaxResult.error("作业ID不能为空");
|
||||||
|
}
|
||||||
|
if (trigger == null) {
|
||||||
|
return AjaxResult.error("WCS下发触发方式不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
MydoJob job = jobMapper.selectMydoJobByJobId(jobId.trim());
|
||||||
|
AjaxResult validation = validateJob(job);
|
||||||
|
if (validation != null) {
|
||||||
|
return validation;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchMapper.insertIfAbsent(job.getJobId());
|
||||||
|
WcsJobDispatch dispatch = dispatchMapper.selectByJobId(job.getJobId());
|
||||||
|
if (dispatch == null) {
|
||||||
|
return AjaxResult.error("WCS下发记录创建失败");
|
||||||
|
}
|
||||||
|
if (isActiveSending(dispatch)) {
|
||||||
|
return AjaxResult.success("作业正在下发WCS,无需重复处理");
|
||||||
|
}
|
||||||
|
if (trigger == DispatchTrigger.AUTO_RETRY
|
||||||
|
&& retryCount(dispatch) >= MAX_AUTO_RETRY_COUNT) {
|
||||||
|
if (!WcsJobDispatch.EXHAUSTED.equals(dispatch.getDispatchStatus())) {
|
||||||
|
Map<String, Object> exhaustedParams = markParams(
|
||||||
|
job.getJobId(), dispatch, "WCS自动重试次数已达上限", new Date());
|
||||||
|
exhaustedParams.put("autoRetryCount", retryCount(dispatch));
|
||||||
|
dispatchMapper.markExhausted(exhaustedParams);
|
||||||
|
}
|
||||||
|
return AjaxResult.success("WCS自动重试次数已达上限,等待人工下发");
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchEligibility eligibility = denseLaneService.checkDispatchEligibility(job);
|
||||||
|
if (eligibility == null || !eligibility.isEligible()) {
|
||||||
|
String reason = eligibility == null ? "密集库下发资格校验无返回" : eligibility.getReason();
|
||||||
|
dispatchMapper.markBlocked(markParams(job.getJobId(), dispatch, reason, new Date()));
|
||||||
|
return AjaxResult.success("作业暂未下发WCS:" + reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchTrigger attemptTrigger = resolveAttemptTrigger(trigger, dispatch);
|
||||||
|
Date attemptTime = new Date();
|
||||||
|
Map<String, Object> claimParams = new HashMap<>();
|
||||||
|
claimParams.put("jobId", job.getJobId());
|
||||||
|
claimParams.put("trigger", attemptTrigger.name());
|
||||||
|
claimParams.put("attemptTime", attemptTime);
|
||||||
|
claimParams.put("versionNo", version(dispatch));
|
||||||
|
if (dispatchMapper.claimForSend(claimParams) != 1) {
|
||||||
|
return AjaxResult.success("作业正在下发或状态已变化,无需重复处理");
|
||||||
|
}
|
||||||
|
int claimedVersion = version(dispatch) + 1;
|
||||||
|
|
||||||
|
ApiResponse<String> response;
|
||||||
|
try {
|
||||||
|
response = sendByType(job);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("WCS出库作业下发异常,jobId={}", job.getJobId(), e);
|
||||||
|
return handleFailure(job, dispatch, claimedVersion, attemptTrigger, e.getMessage());
|
||||||
|
}
|
||||||
|
if (response == null || !response.isSuccess()) {
|
||||||
|
String errorMessage = response == null ? "WCS接口无返回" : response.getMessage();
|
||||||
|
return handleFailure(job, dispatch, claimedVersion, attemptTrigger, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
MydoJob updateJob = new MydoJob();
|
||||||
|
updateJob.setJobId(job.getJobId());
|
||||||
|
updateJob.setStatus("3");
|
||||||
|
updateJob.setExecutionPhase("1");
|
||||||
|
updateJob.setStartTime(DateUtils.getTime());
|
||||||
|
if (jobMapper.updateMydoJob(updateJob) != 1) {
|
||||||
|
return handleFailure(job, dispatch, claimedVersion, attemptTrigger, "WCS已接收,但出库作业状态更新失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> successParams = new HashMap<>();
|
||||||
|
successParams.put("jobId", job.getJobId());
|
||||||
|
successParams.put("successTime", new Date());
|
||||||
|
successParams.put("versionNo", claimedVersion);
|
||||||
|
dispatchMapper.markSuccess(successParams);
|
||||||
|
return AjaxResult.success("WCS出库作业下发成功:" + Convert.toStr(response.getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int retryFailedJobs()
|
||||||
|
{
|
||||||
|
List<WcsJobDispatch> candidates = dispatchMapper.selectRetryCandidates(new Date(), RETRY_BATCH_SIZE);
|
||||||
|
int processed = 0;
|
||||||
|
for (WcsJobDispatch candidate : candidates) {
|
||||||
|
try {
|
||||||
|
dispatch(candidate.getJobId(), DispatchTrigger.AUTO_RETRY);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("WCS出库作业自动补偿异常,jobId={}", candidate.getJobId(), e);
|
||||||
|
}
|
||||||
|
processed++;
|
||||||
|
}
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AjaxResult validateJob(MydoJob job)
|
||||||
|
{
|
||||||
|
if (job == null) {
|
||||||
|
return AjaxResult.error("出库作业不存在");
|
||||||
|
}
|
||||||
|
if (job.getInOrOut() == null || job.getInOrOut() < 20) {
|
||||||
|
return AjaxResult.error("请选择出库作业");
|
||||||
|
}
|
||||||
|
if (!"1".equals(job.getStatus())) {
|
||||||
|
if ("2".equals(job.getStatus()) || "3".equals(job.getStatus())) {
|
||||||
|
return AjaxResult.success("出库作业已下发,无需重复推送");
|
||||||
|
}
|
||||||
|
return AjaxResult.error("当前作业状态不允许下发WCS");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiResponse<String> sendByType(MydoJob job)
|
||||||
|
{
|
||||||
|
if (Long.valueOf(20L).equals(job.getInOrOut())) {
|
||||||
|
return wcsClient.sendLocationOutboundTask(job);
|
||||||
|
}
|
||||||
|
if (Long.valueOf(23L).equals(job.getInOrOut())) {
|
||||||
|
return wcsClient.sendRelocationTask(job);
|
||||||
|
}
|
||||||
|
return wcsClient.sendRetrieveTask(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AjaxResult handleFailure(MydoJob job, WcsJobDispatch dispatch, int claimedVersion,
|
||||||
|
DispatchTrigger trigger, String errorMessage)
|
||||||
|
{
|
||||||
|
int autoRetryCount = retryCount(dispatch)
|
||||||
|
+ (trigger == DispatchTrigger.AUTO_RETRY ? 1 : 0);
|
||||||
|
String reason = StringUtils.hasText(errorMessage) ? errorMessage : "WCS下发失败";
|
||||||
|
Map<String, Object> params = markParams(job.getJobId(), dispatch, reason, new Date());
|
||||||
|
params.put("autoRetryCount", autoRetryCount);
|
||||||
|
params.put("versionNo", claimedVersion);
|
||||||
|
|
||||||
|
if (trigger == DispatchTrigger.MANUAL || autoRetryCount >= MAX_AUTO_RETRY_COUNT) {
|
||||||
|
dispatchMapper.markExhausted(params);
|
||||||
|
if (trigger == DispatchTrigger.MANUAL) {
|
||||||
|
return AjaxResult.error("WCS手动下发失败:" + reason);
|
||||||
|
}
|
||||||
|
return AjaxResult.success("WCS自动重试已达5次,等待人工下发:" + reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.put("nextRetryTime", new Date(System.currentTimeMillis() + RETRY_DELAY_MILLIS));
|
||||||
|
dispatchMapper.markRetryWait(params);
|
||||||
|
return AjaxResult.success("出库作业已生成,WCS下发失败,已进入补偿队列:" + reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> markParams(String jobId, WcsJobDispatch dispatch,
|
||||||
|
String lastError, Date updateTime)
|
||||||
|
{
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("jobId", jobId);
|
||||||
|
params.put("lastError", lastError);
|
||||||
|
params.put("updateTime", updateTime);
|
||||||
|
params.put("versionNo", version(dispatch));
|
||||||
|
params.put("autoRetryCount", retryCount(dispatch));
|
||||||
|
params.put("nextRetryTime", new Date(updateTime.getTime() + RETRY_DELAY_MILLIS));
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int retryCount(WcsJobDispatch dispatch)
|
||||||
|
{
|
||||||
|
return dispatch.getAutoRetryCount() == null ? 0 : dispatch.getAutoRetryCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DispatchTrigger resolveAttemptTrigger(DispatchTrigger requestedTrigger, WcsJobDispatch dispatch)
|
||||||
|
{
|
||||||
|
if (requestedTrigger == DispatchTrigger.AUTO_RETRY && dispatch.getLastAttemptTime() == null) {
|
||||||
|
return DispatchTrigger.INITIAL;
|
||||||
|
}
|
||||||
|
return requestedTrigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isActiveSending(WcsJobDispatch dispatch)
|
||||||
|
{
|
||||||
|
return WcsJobDispatch.SENDING.equals(dispatch.getDispatchStatus())
|
||||||
|
&& dispatch.getLastAttemptTime() != null
|
||||||
|
&& dispatch.getLastAttemptTime().getTime() > System.currentTimeMillis() - 5 * 60_000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int version(WcsJobDispatch dispatch)
|
||||||
|
{
|
||||||
|
return dispatch.getVersionNo() == null ? 0 : dispatch.getVersionNo();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ruoyi.out.dispatch.task;
|
||||||
|
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WCS 出库作业失败补偿任务。
|
||||||
|
*/
|
||||||
|
@Component("wcsOutboundRetryTask")
|
||||||
|
public class WcsOutboundRetryTask
|
||||||
|
{
|
||||||
|
private final IOutboundDispatchService outboundDispatchService;
|
||||||
|
|
||||||
|
public WcsOutboundRetryTask(IOutboundDispatchService outboundDispatchService)
|
||||||
|
{
|
||||||
|
this.outboundDispatchService = outboundDispatchService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int retryFailedJobs()
|
||||||
|
{
|
||||||
|
return outboundDispatchService.retryFailedJobs();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.in.job.domain.MydoJob;
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
import com.ruoyi.in.job.service.IMydoJobService;
|
import com.ruoyi.in.job.service.IMydoJobService;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -37,6 +39,9 @@ public class OutMydoJobController extends BaseController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IMydoJobService mydoJobService;
|
private IMydoJobService mydoJobService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IOutboundDispatchService outboundDispatchService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询作业信息列表
|
* 查询作业信息列表
|
||||||
*/
|
*/
|
||||||
@@ -147,11 +152,11 @@ public class OutMydoJobController extends BaseController
|
|||||||
/**
|
/**
|
||||||
* 推送出库作业到 WCS
|
* 推送出库作业到 WCS
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('out:job:edit')")
|
@PreAuthorize("@ss.hasPermi('out:job:pushWcs')")
|
||||||
@Log(title = "作业信息-WCS推送", businessType = BusinessType.UPDATE)
|
@Log(title = "作业信息-WCS推送", businessType = BusinessType.UPDATE)
|
||||||
@PutMapping("/pushWcs/{jobId}")
|
@PutMapping("/pushWcs/{jobId}")
|
||||||
public AjaxResult pushWcs(@PathVariable("jobId") String jobId)
|
public AjaxResult pushWcs(@PathVariable("jobId") String jobId)
|
||||||
{
|
{
|
||||||
return mydoJobService.pushOutJobToWcs(jobId);
|
return outboundDispatchService.dispatch(jobId, DispatchTrigger.MANUAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,8 +176,10 @@ external:
|
|||||||
'02': ${WCS_02_BASE_URL:${WCS_DEFAULT_BASE_URL:http://172.16.4.63:8084}}
|
'02': ${WCS_02_BASE_URL:${WCS_DEFAULT_BASE_URL:http://172.16.4.63:8084}}
|
||||||
'03': ${WCS_03_BASE_URL:${WCS_DEFAULT_BASE_URL:http://172.16.4.63:8084}}
|
'03': ${WCS_03_BASE_URL:${WCS_DEFAULT_BASE_URL:http://172.16.4.63:8084}}
|
||||||
apis:
|
apis:
|
||||||
sendPutawayTask: /wcs/api/receiveWmsJob
|
sendPutawayTask: ${WCS_PUTAWAY_PATH:/wcs/api/receiveWmsJob}
|
||||||
sendRetrieveTask: /wcs/api/receiveWmsJob
|
sendRetrieveTask: ${WCS_OUTBOUND_PATH:/wcs/api/receiveWmsOutJob}
|
||||||
|
sendLocationOutboundTask: ${WCS_LOCATION_OUTBOUND_PATH:/wcs/api/receiveWmsLocationOutJob}
|
||||||
|
sendRelocationTask: ${WCS_RELOCATION_PATH:/wcs/api/receiveWmsMoveJob}
|
||||||
queryDeviceStatus: /device/status
|
queryDeviceStatus: /device/status
|
||||||
sendCraneMove: /crane/move
|
sendCraneMove: /crane/move
|
||||||
cancelTask: /task/cancel
|
cancelTask: /task/cancel
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.out.dense.mapper.DenseLaneMapper">
|
||||||
|
|
||||||
|
<select id="isCurrentOutermost" resultType="java.lang.Integer">
|
||||||
|
select case
|
||||||
|
when source_space.warehouse_id != '03'
|
||||||
|
or source_space.group_coding is null
|
||||||
|
or ltrim(rtrim(source_space.group_coding)) = '' then 1
|
||||||
|
when not exists
|
||||||
|
(
|
||||||
|
select 1
|
||||||
|
from bas_cargo_space occupied
|
||||||
|
cross apply
|
||||||
|
(
|
||||||
|
select min(lane_space.the_column) as min_column,
|
||||||
|
max(lane_space.the_column) as max_column
|
||||||
|
from bas_cargo_space lane_space
|
||||||
|
where lane_space.warehouse_id = source_space.warehouse_id
|
||||||
|
and lane_space.group_coding = source_space.group_coding
|
||||||
|
and lane_space.the_floor = source_space.the_floor
|
||||||
|
) lane_boundary
|
||||||
|
where occupied.warehouse_id = source_space.warehouse_id
|
||||||
|
and occupied.group_coding = source_space.group_coding
|
||||||
|
and occupied.the_floor = source_space.the_floor
|
||||||
|
and occupied.cargo_space_id != source_space.cargo_space_id
|
||||||
|
and occupied.status in ('2', '3', '4')
|
||||||
|
and occupied.tray_code is not null
|
||||||
|
and ltrim(rtrim(occupied.tray_code)) != ''
|
||||||
|
and
|
||||||
|
(
|
||||||
|
(isnull(area_config.direction_type, '1') = '1'
|
||||||
|
and occupied.the_column > source_space.the_column)
|
||||||
|
or (area_config.direction_type = '2'
|
||||||
|
and occupied.the_column < source_space.the_column)
|
||||||
|
or (area_config.direction_type = '3'
|
||||||
|
and case
|
||||||
|
when occupied.the_column - lane_boundary.min_column
|
||||||
|
<= lane_boundary.max_column - occupied.the_column
|
||||||
|
then occupied.the_column - lane_boundary.min_column
|
||||||
|
else lane_boundary.max_column - occupied.the_column
|
||||||
|
end
|
||||||
|
< case
|
||||||
|
when source_space.the_column - lane_boundary.min_column
|
||||||
|
<= lane_boundary.max_column - source_space.the_column
|
||||||
|
then source_space.the_column - lane_boundary.min_column
|
||||||
|
else lane_boundary.max_column - source_space.the_column
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
) then 1
|
||||||
|
else 0
|
||||||
|
end
|
||||||
|
from bas_cargo_space source_space
|
||||||
|
left join bas_cargo_area_config area_config
|
||||||
|
on area_config.warehouse_id = source_space.warehouse_id
|
||||||
|
and area_config.cargo_area_code = source_space.this_cargo_area
|
||||||
|
and area_config.is_active = '1'
|
||||||
|
where source_space.cargo_space_id = #{cargoSpaceId}
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@@ -60,6 +60,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
where job_id = #{jobId}
|
where job_id = #{jobId}
|
||||||
and version_no = #{versionNo}
|
and version_no = #{versionNo}
|
||||||
and dispatch_status in ('PENDING', 'BLOCKED', 'RETRY_WAIT', 'EXHAUSTED', 'SENDING')
|
and dispatch_status in ('PENDING', 'BLOCKED', 'RETRY_WAIT', 'EXHAUSTED', 'SENDING')
|
||||||
|
and (dispatch_status != 'SENDING'
|
||||||
|
or last_attempt_time <= dateadd(minute, -5, #{attemptTime}))
|
||||||
and (#{trigger} != 'AUTO_RETRY' or auto_retry_count < 5)
|
and (#{trigger} != 'AUTO_RETRY' or auto_retry_count < 5)
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
@@ -67,27 +69,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
update wcs_job_dispatch
|
update wcs_job_dispatch
|
||||||
set dispatch_status = 'BLOCKED', last_error = #{lastError}, next_retry_time = #{nextRetryTime},
|
set dispatch_status = 'BLOCKED', last_error = #{lastError}, next_retry_time = #{nextRetryTime},
|
||||||
update_time = #{updateTime}, version_no = version_no + 1
|
update_time = #{updateTime}, version_no = version_no + 1
|
||||||
where job_id = #{jobId} and dispatch_status != 'SUCCESS'
|
where job_id = #{jobId}
|
||||||
|
and version_no = #{versionNo}
|
||||||
|
and dispatch_status != 'SUCCESS'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<update id="markRetryWait" parameterType="map">
|
<update id="markRetryWait" parameterType="map">
|
||||||
update wcs_job_dispatch
|
update wcs_job_dispatch
|
||||||
set dispatch_status = 'RETRY_WAIT', last_error = #{lastError}, next_retry_time = #{nextRetryTime},
|
set dispatch_status = 'RETRY_WAIT', last_error = #{lastError}, next_retry_time = #{nextRetryTime},
|
||||||
update_time = #{updateTime}, version_no = version_no + 1
|
update_time = #{updateTime}, version_no = version_no + 1
|
||||||
where job_id = #{jobId} and dispatch_status = 'SENDING'
|
where job_id = #{jobId}
|
||||||
|
and version_no = #{versionNo}
|
||||||
|
and dispatch_status = 'SENDING'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<update id="markSuccess" parameterType="map">
|
<update id="markSuccess" parameterType="map">
|
||||||
update wcs_job_dispatch
|
update wcs_job_dispatch
|
||||||
set dispatch_status = 'SUCCESS', last_error = null, next_retry_time = null,
|
set dispatch_status = 'SUCCESS', last_error = null, next_retry_time = null,
|
||||||
success_time = #{successTime}, update_time = #{successTime}, version_no = version_no + 1
|
success_time = #{successTime}, update_time = #{successTime}, version_no = version_no + 1
|
||||||
where job_id = #{jobId} and dispatch_status = 'SENDING'
|
where job_id = #{jobId}
|
||||||
|
and version_no = #{versionNo}
|
||||||
|
and dispatch_status = 'SENDING'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<update id="markExhausted" parameterType="map">
|
<update id="markExhausted" parameterType="map">
|
||||||
update wcs_job_dispatch
|
update wcs_job_dispatch
|
||||||
set dispatch_status = 'EXHAUSTED', last_error = #{lastError}, next_retry_time = null,
|
set dispatch_status = 'EXHAUSTED', last_error = #{lastError}, next_retry_time = null,
|
||||||
update_time = #{updateTime}, version_no = version_no + 1
|
update_time = #{updateTime}, version_no = version_no + 1
|
||||||
where job_id = #{jobId} and dispatch_status = 'SENDING'
|
where job_id = #{jobId}
|
||||||
|
and version_no = #{versionNo}
|
||||||
|
and dispatch_status in ('SENDING', 'BLOCKED', 'RETRY_WAIT')
|
||||||
</update>
|
</update>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ class WcsClientTest {
|
|||||||
.isEqualTo("MOCK_WCS_RETRIEVE_OK");
|
.isEqualTo("MOCK_WCS_RETRIEVE_OK");
|
||||||
assertThat(client.sendRetrieveTask(outboundJob).getData())
|
assertThat(client.sendRetrieveTask(outboundJob).getData())
|
||||||
.isEqualTo("MOCK_WCS_RETRIEVE_OK");
|
.isEqualTo("MOCK_WCS_RETRIEVE_OK");
|
||||||
|
assertThat(client.sendLocationOutboundTask(outboundJob).getData())
|
||||||
|
.isEqualTo("MOCK_WCS_LOCATION_OUT_OK");
|
||||||
|
assertThat(client.sendRelocationTask(outboundJob).getData())
|
||||||
|
.isEqualTo("MOCK_WCS_RELOCATION_OK");
|
||||||
assertThat(client.sendCraneMove("CR01", "A0101", "A0102", "MOVE").getData())
|
assertThat(client.sendCraneMove("CR01", "A0101", "A0102", "MOVE").getData())
|
||||||
.isEqualTo("MOCK_WCS_CRANE_MOVE_OK");
|
.isEqualTo("MOCK_WCS_CRANE_MOVE_OK");
|
||||||
assertThat(client.cancelTask("TASK001").getData())
|
assertThat(client.cancelTask("TASK001").getData())
|
||||||
@@ -64,6 +68,10 @@ class WcsClientTest {
|
|||||||
.isEqualTo("ACCEPTED");
|
.isEqualTo("ACCEPTED");
|
||||||
assertThat(client.sendRetrieveTask(outboundJob).getData())
|
assertThat(client.sendRetrieveTask(outboundJob).getData())
|
||||||
.isEqualTo("ACCEPTED");
|
.isEqualTo("ACCEPTED");
|
||||||
|
assertThat(client.sendLocationOutboundTask(outboundJob).getData())
|
||||||
|
.isEqualTo("ACCEPTED");
|
||||||
|
assertThat(client.sendRelocationTask(outboundJob).getData())
|
||||||
|
.isEqualTo("ACCEPTED");
|
||||||
assertThat(client.queryDeviceStatus("CRANE").getData().get(0).get("status"))
|
assertThat(client.queryDeviceStatus("CRANE").getData().get(0).get("status"))
|
||||||
.isEqualTo("ONLINE");
|
.isEqualTo("ONLINE");
|
||||||
assertThat(client.sendCraneMove("CR01", "A0101", "A0102", "MOVE").getData())
|
assertThat(client.sendCraneMove("CR01", "A0101", "A0102", "MOVE").getData())
|
||||||
@@ -75,7 +83,9 @@ class WcsClientTest {
|
|||||||
"http://wcs/wcs/api/receiveWmsJob",
|
"http://wcs/wcs/api/receiveWmsJob",
|
||||||
"http://wcs/wcs/api/receiveWmsJob",
|
"http://wcs/wcs/api/receiveWmsJob",
|
||||||
"http://wcs/task/dispatch",
|
"http://wcs/task/dispatch",
|
||||||
"http://wcs/wcs/api/receiveWmsJob",
|
"http://wcs/task/dispatch",
|
||||||
|
"http://wcs/location/dispatch",
|
||||||
|
"http://wcs/relocation/dispatch",
|
||||||
"http://wcs/device/status",
|
"http://wcs/device/status",
|
||||||
"http://wcs/crane/move",
|
"http://wcs/crane/move",
|
||||||
"http://wcs/task/cancel");
|
"http://wcs/task/cancel");
|
||||||
@@ -84,6 +94,8 @@ class WcsClientTest {
|
|||||||
MsgTypeEnum.JSON,
|
MsgTypeEnum.JSON,
|
||||||
MsgTypeEnum.TEXT,
|
MsgTypeEnum.TEXT,
|
||||||
MsgTypeEnum.JSON,
|
MsgTypeEnum.JSON,
|
||||||
|
MsgTypeEnum.JSON,
|
||||||
|
MsgTypeEnum.JSON,
|
||||||
MsgTypeEnum.TEXT,
|
MsgTypeEnum.TEXT,
|
||||||
MsgTypeEnum.TEXT,
|
MsgTypeEnum.TEXT,
|
||||||
MsgTypeEnum.TEXT);
|
MsgTypeEnum.TEXT);
|
||||||
@@ -219,6 +231,8 @@ class WcsClientTest {
|
|||||||
Map<String, String> apis = new HashMap<>();
|
Map<String, String> apis = new HashMap<>();
|
||||||
apis.put("sendPutawayTask", "/wcs/api/receiveWmsJob");
|
apis.put("sendPutawayTask", "/wcs/api/receiveWmsJob");
|
||||||
apis.put("sendRetrieveTask", "/task/dispatch");
|
apis.put("sendRetrieveTask", "/task/dispatch");
|
||||||
|
apis.put("sendLocationOutboundTask", "/location/dispatch");
|
||||||
|
apis.put("sendRelocationTask", "/relocation/dispatch");
|
||||||
apis.put("queryDeviceStatus", "/device/status");
|
apis.put("queryDeviceStatus", "/device/status");
|
||||||
apis.put("sendCraneMove", "/crane/move");
|
apis.put("sendCraneMove", "/crane/move");
|
||||||
apis.put("cancelTask", "/task/cancel");
|
apis.put("cancelTask", "/task/cancel");
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import com.ruoyi.in.stockin.domain.InStockin;
|
|||||||
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
||||||
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
||||||
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
|
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -213,6 +215,9 @@ class MydoJobServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
|
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IOutboundDispatchService outboundDispatchService;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private MydoJobServiceImpl service;
|
private MydoJobServiceImpl service;
|
||||||
|
|
||||||
@@ -257,57 +262,16 @@ class MydoJobServiceImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void pushOutJobToWcsRejectsInboundJob() {
|
void pushOutJobToWcsDelegatesInitialDispatchForExistingBusinessCallers() {
|
||||||
MydoJob job = new MydoJob();
|
AjaxResult expected = AjaxResult.success("WCS出库作业下发成功:OK");
|
||||||
job.setJobId("RK001");
|
when(outboundDispatchService.dispatch("CK002", DispatchTrigger.INITIAL)).thenReturn(expected);
|
||||||
job.setStatus("1");
|
|
||||||
job.setInOrOut(1L);
|
|
||||||
when(mydoJobMapper.selectMydoJobByJobId("RK001")).thenReturn(job);
|
|
||||||
|
|
||||||
AjaxResult result = service.pushOutJobToWcs("RK001");
|
|
||||||
|
|
||||||
assertThat(result.isError()).isTrue();
|
|
||||||
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("请选择出库作业");
|
|
||||||
verifyNoInteractions(wcsClient);
|
|
||||||
verify(mydoJobMapper, never()).updateMydoJob(any(MydoJob.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void pushOutJobToWcsDoesNotSendAlreadyPushedJobAgain() {
|
|
||||||
MydoJob job = new MydoJob();
|
|
||||||
job.setJobId("CK001");
|
|
||||||
job.setStatus("2");
|
|
||||||
job.setInOrOut(20L);
|
|
||||||
when(mydoJobMapper.selectMydoJobByJobId("CK001")).thenReturn(job);
|
|
||||||
|
|
||||||
AjaxResult result = service.pushOutJobToWcs("CK001");
|
|
||||||
|
|
||||||
assertThat(result.isSuccess()).isTrue();
|
|
||||||
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("出库作业已下发,无需重复推送");
|
|
||||||
verifyNoInteractions(wcsClient);
|
|
||||||
verify(mydoJobMapper, never()).updateMydoJob(any(MydoJob.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void pushOutJobToWcsMarksPendingOutJobAsPushedAfterWcsAcceptsIt() {
|
|
||||||
MydoJob job = new MydoJob();
|
|
||||||
job.setJobId("CK002");
|
|
||||||
job.setStatus("1");
|
|
||||||
job.setInOrOut(20L);
|
|
||||||
when(mydoJobMapper.selectMydoJobByJobId("CK002")).thenReturn(job);
|
|
||||||
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.ok("OK"));
|
|
||||||
when(mydoJobMapper.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
|
||||||
|
|
||||||
AjaxResult result = service.pushOutJobToWcs("CK002");
|
AjaxResult result = service.pushOutJobToWcs("CK002");
|
||||||
|
|
||||||
assertThat(result.isSuccess()).isTrue();
|
assertThat(result).isSameAs(expected);
|
||||||
ArgumentCaptor<MydoJob> updateCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
verify(outboundDispatchService).dispatch("CK002", DispatchTrigger.INITIAL);
|
||||||
verify(mydoJobMapper).updateMydoJob(updateCaptor.capture());
|
verifyNoInteractions(mydoJobMapper);
|
||||||
MydoJob update = updateCaptor.getValue();
|
verifyNoInteractions(wcsClient);
|
||||||
assertThat(update.getJobId()).isEqualTo("CK002");
|
|
||||||
assertThat(update.getStatus()).isEqualTo("3");
|
|
||||||
assertThat(update.getExecutionPhase()).isEqualTo("1");
|
|
||||||
assertThat(update.getStartTime()).isNotBlank();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class WcsJobDispatchMapperSqlTest
|
|||||||
.contains("EXHAUSTED")
|
.contains("EXHAUSTED")
|
||||||
.contains("auto_retry_count < 5")
|
.contains("auto_retry_count < 5")
|
||||||
.contains("dispatch_status = 'SENDING'")
|
.contains("dispatch_status = 'SENDING'")
|
||||||
|
.contains("last_attempt_time <= dateadd(minute, -5")
|
||||||
.contains("version_no = version_no + 1")
|
.contains("version_no = version_no + 1")
|
||||||
.contains("selectRetryCandidates")
|
.contains("selectRetryCandidates")
|
||||||
.contains("claimForSend")
|
.contains("claimForSend")
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
package com.ruoyi.out.dispatch.service.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.ruoyi.api.client.WcsClient;
|
||||||
|
import com.ruoyi.api.domain.ApiResponse;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.in.job.mapper.MydoJobMapper;
|
||||||
|
import com.ruoyi.out.dense.domain.DispatchEligibility;
|
||||||
|
import com.ruoyi.out.dense.service.IDenseLaneService;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.domain.WcsJobDispatch;
|
||||||
|
import com.ruoyi.out.dispatch.mapper.WcsJobDispatchMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
class OutboundDispatchServiceImplTest
|
||||||
|
{
|
||||||
|
private MydoJobMapper jobMapper;
|
||||||
|
private WcsJobDispatchMapper dispatchMapper;
|
||||||
|
private IDenseLaneService denseLaneService;
|
||||||
|
private WcsClient wcsClient;
|
||||||
|
private OutboundDispatchServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp()
|
||||||
|
{
|
||||||
|
jobMapper = mock(MydoJobMapper.class);
|
||||||
|
dispatchMapper = mock(WcsJobDispatchMapper.class);
|
||||||
|
denseLaneService = mock(IDenseLaneService.class);
|
||||||
|
wcsClient = mock(WcsClient.class);
|
||||||
|
service = new OutboundDispatchServiceImpl(jobMapper, dispatchMapper, denseLaneService, wcsClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initialFailureKeepsBusinessSuccessAndStartsRetryCountAtZero()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK1", 21L);
|
||||||
|
stubClaim(job, dispatch("CK1", WcsJobDispatch.PENDING, 0));
|
||||||
|
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.fail("WCS_ERROR", "连接失败"));
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK1", DispatchTrigger.INITIAL);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(dispatchMapper).markRetryWait(argThat(params ->
|
||||||
|
"CK1".equals(params.get("jobId"))
|
||||||
|
&& Integer.valueOf(0).equals(params.get("autoRetryCount"))
|
||||||
|
&& String.valueOf(params.get("lastError")).contains("连接失败")));
|
||||||
|
verify(jobMapper, never()).updateMydoJob(any(MydoJob.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fifthAutomaticRetryFailureBecomesExhausted()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK2", 21L);
|
||||||
|
stubClaim(job, dispatch("CK2", WcsJobDispatch.RETRY_WAIT, 4));
|
||||||
|
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.fail("WCS_ERROR", "仍然失败"));
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK2", DispatchTrigger.AUTO_RETRY);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(dispatchMapper).claimForSend(argThat(params ->
|
||||||
|
DispatchTrigger.AUTO_RETRY.name().equals(params.get("trigger"))));
|
||||||
|
verify(dispatchMapper).markExhausted(argThat(params ->
|
||||||
|
Integer.valueOf(5).equals(params.get("autoRetryCount"))));
|
||||||
|
verify(dispatchMapper, never()).markRetryWait(any(Map.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void automaticRetryBelowLimitReturnsToRetryWait()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK3", 21L);
|
||||||
|
stubClaim(job, dispatch("CK3", WcsJobDispatch.RETRY_WAIT, 3));
|
||||||
|
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.fail("WCS_ERROR", "暂时失败"));
|
||||||
|
|
||||||
|
service.dispatch("CK3", DispatchTrigger.AUTO_RETRY);
|
||||||
|
|
||||||
|
verify(dispatchMapper).markRetryWait(argThat(params ->
|
||||||
|
Integer.valueOf(4).equals(params.get("autoRetryCount"))));
|
||||||
|
verify(dispatchMapper, never()).markExhausted(any(Map.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void staleSendingRecordAtRetryLimitIsMarkedExhaustedWithoutCallingWcs()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK-STALE", 21L);
|
||||||
|
WcsJobDispatch dispatch = dispatch("CK-STALE", WcsJobDispatch.SENDING, 5);
|
||||||
|
dispatch.setLastAttemptTime(new java.util.Date(System.currentTimeMillis() - 6 * 60_000L));
|
||||||
|
when(jobMapper.selectMydoJobByJobId("CK-STALE")).thenReturn(job);
|
||||||
|
when(dispatchMapper.selectByJobId("CK-STALE")).thenReturn(dispatch);
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK-STALE", DispatchTrigger.AUTO_RETRY);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(dispatchMapper).markExhausted(argThat(params ->
|
||||||
|
Integer.valueOf(5).equals(params.get("autoRetryCount"))));
|
||||||
|
verify(wcsClient, never()).sendRetrieveTask(any(MydoJob.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void manualDispatchCanRecoverAnExhaustedJob()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK4", 21L);
|
||||||
|
stubClaim(job, dispatch("CK4", WcsJobDispatch.EXHAUSTED, 5));
|
||||||
|
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.ok("OK"));
|
||||||
|
when(jobMapper.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK4", DispatchTrigger.MANUAL);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(dispatchMapper).claimForSend(argThat(params ->
|
||||||
|
DispatchTrigger.MANUAL.name().equals(params.get("trigger"))));
|
||||||
|
verify(dispatchMapper).markSuccess(any(Map.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void successfulDispatchUpdatesBusinessJobOnlyAfterWcsAccepts()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK5", 21L);
|
||||||
|
stubClaim(job, dispatch("CK5", WcsJobDispatch.PENDING, 0));
|
||||||
|
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.ok("WCS-OK"));
|
||||||
|
when(jobMapper.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK5", DispatchTrigger.INITIAL);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
ArgumentCaptor<MydoJob> updateCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||||
|
verify(jobMapper).updateMydoJob(updateCaptor.capture());
|
||||||
|
assertThat(updateCaptor.getValue().getJobId()).isEqualTo("CK5");
|
||||||
|
assertThat(updateCaptor.getValue().getStatus()).isEqualTo("3");
|
||||||
|
assertThat(updateCaptor.getValue().getExecutionPhase()).isEqualTo("1");
|
||||||
|
assertThat(updateCaptor.getValue().getStartTime()).isNotBlank();
|
||||||
|
verify(dispatchMapper).markSuccess(any(Map.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectedAtomicClaimDoesNotCallWcs()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK6", 21L);
|
||||||
|
when(jobMapper.selectMydoJobByJobId("CK6")).thenReturn(job);
|
||||||
|
when(dispatchMapper.selectByJobId("CK6"))
|
||||||
|
.thenReturn(dispatch("CK6", WcsJobDispatch.RETRY_WAIT, 1));
|
||||||
|
when(denseLaneService.checkDispatchEligibility(job)).thenReturn(DispatchEligibility.allowed());
|
||||||
|
when(dispatchMapper.claimForSend(any(Map.class))).thenReturn(0);
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK6", DispatchTrigger.AUTO_RETRY);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(wcsClient, never()).sendRetrieveTask(any(MydoJob.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void activeSendingRecordCannotBeClaimedAgain()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK-SENDING", 21L);
|
||||||
|
WcsJobDispatch dispatch = dispatch("CK-SENDING", WcsJobDispatch.SENDING, 1);
|
||||||
|
dispatch.setLastAttemptTime(new java.util.Date());
|
||||||
|
when(jobMapper.selectMydoJobByJobId("CK-SENDING")).thenReturn(job);
|
||||||
|
when(dispatchMapper.selectByJobId("CK-SENDING")).thenReturn(dispatch);
|
||||||
|
when(denseLaneService.checkDispatchEligibility(job)).thenReturn(DispatchEligibility.allowed());
|
||||||
|
when(dispatchMapper.claimForSend(any(Map.class))).thenReturn(1);
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK-SENDING", DispatchTrigger.MANUAL);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(dispatchMapper, never()).claimForSend(any(Map.class));
|
||||||
|
verify(wcsClient, never()).sendRetrieveTask(any(MydoJob.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void blockedDenseJobDoesNotConsumeRetryOrCallWcs()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK7", 21L);
|
||||||
|
when(jobMapper.selectMydoJobByJobId("CK7")).thenReturn(job);
|
||||||
|
when(dispatchMapper.selectByJobId("CK7"))
|
||||||
|
.thenReturn(dispatch("CK7", WcsJobDispatch.RETRY_WAIT, 2));
|
||||||
|
when(denseLaneService.checkDispatchEligibility(job))
|
||||||
|
.thenReturn(DispatchEligibility.blocked("前方仍有2个托盘"));
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK7", DispatchTrigger.AUTO_RETRY);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(dispatchMapper).markBlocked(argThat(params ->
|
||||||
|
"前方仍有2个托盘".equals(params.get("lastError"))));
|
||||||
|
verify(dispatchMapper, never()).claimForSend(any(Map.class));
|
||||||
|
verify(wcsClient, never()).sendRetrieveTask(any(MydoJob.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void firstActualSendAfterBlockedStateDoesNotCountAsAutomaticRetry()
|
||||||
|
{
|
||||||
|
MydoJob job = jobOfType("CK-BLOCKED-FIRST", 21L);
|
||||||
|
WcsJobDispatch dispatch = dispatch("CK-BLOCKED-FIRST", WcsJobDispatch.BLOCKED, 0);
|
||||||
|
stubClaim(job, dispatch);
|
||||||
|
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.fail("WCS_ERROR", "首次调用失败"));
|
||||||
|
|
||||||
|
AjaxResult result = service.dispatch("CK-BLOCKED-FIRST", DispatchTrigger.AUTO_RETRY);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(dispatchMapper).claimForSend(argThat(params ->
|
||||||
|
DispatchTrigger.INITIAL.name().equals(params.get("trigger"))));
|
||||||
|
verify(dispatchMapper).markRetryWait(argThat(params ->
|
||||||
|
Integer.valueOf(0).equals(params.get("autoRetryCount"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(longs = {20L, 21L, 23L})
|
||||||
|
void routesOutboundByJobType(long jobType)
|
||||||
|
{
|
||||||
|
String jobId = "CK-TYPE-" + jobType;
|
||||||
|
MydoJob job = jobOfType(jobId, jobType);
|
||||||
|
stubClaim(job, dispatch(jobId, WcsJobDispatch.PENDING, 0));
|
||||||
|
when(jobMapper.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||||
|
when(wcsClient.sendLocationOutboundTask(job)).thenReturn(ApiResponse.ok("LOCATION-OK"));
|
||||||
|
when(wcsClient.sendRetrieveTask(job)).thenReturn(ApiResponse.ok("RETRIEVE-OK"));
|
||||||
|
when(wcsClient.sendRelocationTask(job)).thenReturn(ApiResponse.ok("MOVE-OK"));
|
||||||
|
|
||||||
|
service.dispatch(jobId, DispatchTrigger.INITIAL);
|
||||||
|
|
||||||
|
if (jobType == 20L) {
|
||||||
|
verify(wcsClient).sendLocationOutboundTask(job);
|
||||||
|
verify(wcsClient, never()).sendRetrieveTask(job);
|
||||||
|
verify(wcsClient, never()).sendRelocationTask(job);
|
||||||
|
} else if (jobType == 23L) {
|
||||||
|
verify(wcsClient).sendRelocationTask(job);
|
||||||
|
verify(wcsClient, never()).sendRetrieveTask(job);
|
||||||
|
verify(wcsClient, never()).sendLocationOutboundTask(job);
|
||||||
|
} else {
|
||||||
|
verify(wcsClient).sendRetrieveTask(job);
|
||||||
|
verify(wcsClient, never()).sendLocationOutboundTask(job);
|
||||||
|
verify(wcsClient, never()).sendRelocationTask(job);
|
||||||
|
}
|
||||||
|
reset(wcsClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void retryTaskDispatchesEveryCandidateAsAutomaticRetry()
|
||||||
|
{
|
||||||
|
WcsJobDispatch first = dispatch("CK8", WcsJobDispatch.RETRY_WAIT, 1);
|
||||||
|
WcsJobDispatch second = dispatch("CK9", WcsJobDispatch.BLOCKED, 0);
|
||||||
|
when(dispatchMapper.selectRetryCandidates(any(), any(Integer.class)))
|
||||||
|
.thenReturn(List.of(first, second));
|
||||||
|
MydoJob firstJob = jobOfType("CK8", 21L);
|
||||||
|
MydoJob secondJob = jobOfType("CK9", 21L);
|
||||||
|
when(jobMapper.selectMydoJobByJobId("CK8")).thenReturn(firstJob);
|
||||||
|
when(jobMapper.selectMydoJobByJobId("CK9")).thenReturn(secondJob);
|
||||||
|
when(dispatchMapper.selectByJobId("CK8")).thenReturn(first);
|
||||||
|
when(dispatchMapper.selectByJobId("CK9")).thenReturn(second);
|
||||||
|
when(denseLaneService.checkDispatchEligibility(any(MydoJob.class)))
|
||||||
|
.thenReturn(DispatchEligibility.allowed());
|
||||||
|
when(dispatchMapper.claimForSend(any(Map.class))).thenReturn(0);
|
||||||
|
|
||||||
|
int processed = service.retryFailedJobs();
|
||||||
|
|
||||||
|
assertThat(processed).isEqualTo(2);
|
||||||
|
verify(dispatchMapper).claimForSend(argThat(params -> "CK8".equals(params.get("jobId"))));
|
||||||
|
verify(dispatchMapper).claimForSend(argThat(params -> "CK9".equals(params.get("jobId"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stubClaim(MydoJob job, WcsJobDispatch dispatch)
|
||||||
|
{
|
||||||
|
when(jobMapper.selectMydoJobByJobId(job.getJobId())).thenReturn(job);
|
||||||
|
when(dispatchMapper.selectByJobId(job.getJobId())).thenReturn(dispatch);
|
||||||
|
when(denseLaneService.checkDispatchEligibility(job)).thenReturn(DispatchEligibility.allowed());
|
||||||
|
when(dispatchMapper.claimForSend(any(Map.class))).thenReturn(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MydoJob jobOfType(String jobId, Long inOrOut)
|
||||||
|
{
|
||||||
|
MydoJob job = new MydoJob();
|
||||||
|
job.setJobId(jobId);
|
||||||
|
job.setInOrOut(inOrOut);
|
||||||
|
job.setStatus("1");
|
||||||
|
job.setExecutionPhase("0");
|
||||||
|
job.setWarehouseId("03");
|
||||||
|
job.setCargoSpaceId("CS-" + jobId);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WcsJobDispatch dispatch(String jobId, String status, int autoRetryCount)
|
||||||
|
{
|
||||||
|
WcsJobDispatch dispatch = new WcsJobDispatch();
|
||||||
|
dispatch.setJobId(jobId);
|
||||||
|
dispatch.setDispatchStatus(status);
|
||||||
|
dispatch.setAutoRetryCount(autoRetryCount);
|
||||||
|
dispatch.setManualRetryCount(0);
|
||||||
|
dispatch.setVersionNo(0);
|
||||||
|
if (!WcsJobDispatch.PENDING.equals(status) && !WcsJobDispatch.BLOCKED.equals(status)) {
|
||||||
|
dispatch.setLastAttemptTime(new java.util.Date());
|
||||||
|
}
|
||||||
|
return dispatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user