feat: 增加日计划管芯现场调度

This commit is contained in:
zhouz
2026-08-17 10:47:32 +08:00
parent 1bb22e9930
commit 5e70feeb27
20 changed files with 736 additions and 11 deletions

View File

@@ -23,6 +23,7 @@ public class DailyPlanClaimDO extends TenantBaseDO {
private Integer version;
private Integer claimStatus;
private Integer activeFlag;
private Boolean taskCreated;
private LocalDateTime expireTime;
private LocalDateTime confirmedTime;
private String operator;

View File

@@ -21,4 +21,42 @@ public interface CoreTubeMapper extends BaseMapperX<CoreTubeDO> {
/** 插入管芯实例。 */
int insertCoreTube(@Param("tube") CoreTubeDO tube);
/** 查询对接位上一根可用的目标管芯。 */
CoreTubeDO selectAvailableDockTube(@Param("materialId") Long materialId);
/** 查询管芯库中一根可用的目标管芯。 */
CoreTubeDO selectAvailableWarehouseTube(@Param("materialId") Long materialId);
/** 原子预占一根管芯。 */
int reserveTube(@Param("coreTubeId") Long coreTubeId, @Param("expectedStage") Integer expectedStage,
@Param("newStage") Integer newStage, @Param("dailyPlanId") Long dailyPlanId,
@Param("claimToken") String claimToken, @Param("expireTime") java.time.LocalDateTime expireTime,
@Param("updater") String updater);
/** 绑定现场任务。 */
int bindTask(@Param("coreTubeId") Long coreTubeId, @Param("taskId") Long taskId,
@Param("updater") String updater);
/** 释放尚未落任务的管芯预占。 */
int releaseReservation(@Param("coreTubeId") Long coreTubeId, @Param("claimToken") String claimToken,
@Param("stage") Integer stage, @Param("updater") String updater);
/** 完成管芯位置转换。 */
int completeTubeTask(@Param("coreTubeId") Long coreTubeId, @Param("taskId") Long taskId,
@Param("stage") Integer stage, @Param("status") Integer status,
@Param("locationType") Integer locationType,
@Param("locationId") Long locationId, @Param("locationCode") String locationCode,
@Param("clearVehicle") Boolean clearVehicle, @Param("updater") String updater);
/** 取消管芯任务并恢复可用。 */
int cancelTubeTask(@Param("coreTubeId") Long coreTubeId, @Param("taskId") Long taskId,
@Param("stage") Integer stage, @Param("updater") String updater);
/** 按托盘更新全部管芯当前位置。 */
int updateVehicleLocation(@Param("vehicleCode") String vehicleCode,
@Param("locationType") Integer locationType,
@Param("locationId") Long locationId,
@Param("locationCode") String locationCode,
@Param("updater") String updater);
}

View File

@@ -22,6 +22,8 @@ public interface DailyPlanClaimMapper extends BaseMapperX<DailyPlanClaimDO> {
int releaseClaim(@Param("claimToken") String claimToken, @Param("updater") String updater);
int markTaskCreated(@Param("claimToken") String claimToken, @Param("updater") String updater);
int renewClaim(@Param("claimToken") String claimToken, @Param("version") Integer version,
@Param("expireTime") LocalDateTime expireTime,
@Param("updater") String updater);

View File

@@ -78,4 +78,26 @@ public interface StockingIvtMapper extends BaseMapperX<StockingIvtDO> {
* 新增管芯托盘格位。
*/
int insertPaperVehicleCoreTube(@Param("tube") cn.code.nl.module.lms.dal.dataobject.coretube.CoreTubeDO tube);
/** 管芯离开托盘后清理格位绑定。 */
int clearPaperVehicleCoreTube(@Param("coreTubeId") Long coreTubeId, @Param("updater") String updater);
/** 查询当前有托盘的对接位。 */
StockingIvtDO selectOccupiedDockPoint();
/** 查询当前空闲的对接位。 */
StockingIvtDO selectEmptyDockPoint();
/** 查询当前空闲的缓存位。 */
StockingIvtDO selectEmptyCachePoint();
/** 为目标物料选择最合适的缓存位托盘。 */
StockingIvtDO selectBestCacheVehicle(@Param("materialId") Long materialId,
@Param("requiredQty") Integer requiredQty);
/** 完成托盘点位转移。 */
int completeVehicleMove(@Param("sourcePointId") Long sourcePointId,
@Param("targetPointId") Long targetPointId,
@Param("vehicleCode") String vehicleCode,
@Param("updater") String updater);
}

View File

@@ -0,0 +1,7 @@
package cn.code.nl.module.lms.enums;
/** 管芯任务回调处理器编码。 */
public interface CoreTubeHandleCodeConstants {
String CORE_TUBE_STOCKING_TASK = "CORE_TUBE_STOCKING_TASK";
String CORE_TUBE_SLEEVING_TASK = "CORE_TUBE_SLEEVING_TASK";
}

View File

@@ -0,0 +1,9 @@
package cn.code.nl.module.lms.enums;
/** 管芯现场任务业务类型。 */
public enum CoreTubeTaskBizTypeEnum {
CORE_TUBE_PALLET_IN,
CORE_TUBE_PALLET_OUT,
CORE_TUBE_STOCKING_PICK,
CORE_TUBE_SLEEVING_OUT
}

View File

@@ -0,0 +1,38 @@
package cn.code.nl.module.lms.job;
import cn.code.nl.framework.tenant.core.job.TenantJob;
import cn.code.nl.module.lms.service.coretube.CoreTubeTaskDispatchService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/** 日计划管芯备货与套管定时调度。 */
@Slf4j
@Component
public class DailyPlanCoreTubeJob {
@Resource
private CoreTubeTaskDispatchService dispatchService;
/** 每次每租户最多创建一条备货现场任务。 */
@XxlJob("dailyPlanStockingJob")
@TenantJob
public void dailyPlanStockingJob() {
String result = dispatchService.dispatchStockingOnce();
log.info("日计划备货调度完成:{}", result);
XxlJobHelper.log(result);
XxlJobHelper.handleSuccess(result);
}
/** 每次每租户最多创建一条单根管芯出库任务。 */
@XxlJob("dailyPlanSleevingJob")
@TenantJob
public void dailyPlanSleevingJob() {
String result = dispatchService.dispatchSleevingOnce();
log.info("日计划套管调度完成:{}", result);
XxlJobHelper.log(result);
XxlJobHelper.handleSuccess(result);
}
}

View File

@@ -0,0 +1,18 @@
package cn.code.nl.module.lms.service.coretube;
import lombok.Data;
/** 管芯现场任务回调上下文。 */
@Data
public class CoreTubeTaskContext {
private String bizType;
private Long dailyPlanId;
private String claimToken;
private Long coreTubeId;
private String coreTubeCode;
private String vehicleCode;
private Long sourcePointId;
private String sourcePointCode;
private Long targetPointId;
private String targetPointCode;
}

View File

@@ -0,0 +1,17 @@
package cn.code.nl.module.lms.service.coretube;
/** 管芯日计划现场任务调度服务。 */
public interface CoreTubeTaskDispatchService {
/** 执行一轮备货调度。 */
String dispatchStockingOnce();
/** 执行一轮套管调度。 */
String dispatchSleevingOnce();
/** 处理任务完成。 */
void handleFinished(Long taskId, boolean sleeving);
/** 处理任务取消。 */
void handleCancelled(Long taskId, boolean sleeving);
}

View File

@@ -0,0 +1,330 @@
package cn.code.nl.module.lms.service.coretube;
import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.code.nl.module.lms.dal.dataobject.coretube.CoreTubeDO;
import cn.code.nl.module.lms.dal.dataobject.stockingivt.StockingIvtDO;
import cn.code.nl.module.lms.dal.mysql.coretube.CoreTubeMapper;
import cn.code.nl.module.lms.dal.mysql.stockingivt.StockingIvtMapper;
import cn.code.nl.module.lms.enums.CoreTubeHandleCodeConstants;
import cn.code.nl.module.lms.enums.CoreTubeLocationTypeEnum;
import cn.code.nl.module.lms.enums.CoreTubeStageEnum;
import cn.code.nl.module.lms.enums.CoreTubeStatusEnum;
import cn.code.nl.module.lms.enums.CoreTubeTaskBizTypeEnum;
import cn.code.nl.module.lms.service.dailyplan.DailyPlanScheduleCandidate;
import cn.code.nl.module.lms.service.dailyplan.DailyPlanScheduleService;
import cn.code.nl.module.task.api.TransportTaskApi;
import cn.code.nl.module.task.dto.TaskInfoDTO;
import cn.code.nl.module.task.dto.TransportTaskCreateReqDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** 管芯日计划现场任务调度服务实现。 */
@Slf4j
@Service
public class CoreTubeTaskDispatchServiceImpl implements CoreTubeTaskDispatchService {
private static final String OPERATOR = "SCHEDULER";
private static final String OWNER_SERVICE = "LMS";
@Resource
private DailyPlanScheduleService dailyPlanScheduleService;
@Resource
private CoreTubeMapper coreTubeMapper;
@Resource
private StockingIvtMapper stockingIvtMapper;
@Resource
private TransportTaskApi transportTaskApi;
@Value("${lms.core-tube.agv-system-type:AGV}")
private String agvSystemType;
@Value("${lms.core-tube.pallet-in-acs-type:CORE_PALLET_IN}")
private String palletInAcsType;
@Value("${lms.core-tube.pallet-out-acs-type:CORE_PALLET_OUT}")
private String palletOutAcsType;
@Value("${lms.core-tube.stocking-pick-acs-type:CORE_STOCKING_PICK}")
private String stockingPickAcsType;
@Value("${lms.core-tube.sleeving-out-acs-type:CORE_SLEEVING_OUT}")
private String sleevingOutAcsType;
@Value("${lms.core-tube.stocking-target-point:CORE_WAREHOUSE_IN}")
private String stockingTargetPoint;
@Value("${lms.core-tube.sleeving-target-point:SLEEVING_IN}")
private String sleevingTargetPoint;
/** 执行一轮备货调度。 */
@Override
@Transactional(rollbackFor = Exception.class)
public String dispatchStockingOnce() {
List<DailyPlanScheduleCandidate> candidates = dailyPlanScheduleService.previewStockingCandidates();
if (candidates.isEmpty()) {
return "没有待备货日计划";
}
Set<Long> checkedMaterials = new HashSet<>();
for (DailyPlanScheduleCandidate candidate : candidates) {
if (!checkedMaterials.add(candidate.getMaterialId())) {
continue;
}
CoreTubeDO tube = coreTubeMapper.selectAvailableDockTube(candidate.getMaterialId());
if (tube != null) {
Long taskId = createSingleTubeTask(candidate, tube, false);
return "已创建管芯抓取任务taskId=" + taskId + ",管芯=" + tube.getCoreTubeCode();
}
}
return createPalletTask(candidates, checkedMaterials);
}
/** 执行一轮套管调度。 */
@Override
@Transactional(rollbackFor = Exception.class)
public String dispatchSleevingOnce() {
List<DailyPlanScheduleCandidate> candidates = dailyPlanScheduleService.previewSleevingCandidates();
if (candidates.isEmpty()) {
return "没有待套管日计划";
}
Set<Long> checkedMaterials = new HashSet<>();
for (DailyPlanScheduleCandidate candidate : candidates) {
if (!checkedMaterials.add(candidate.getMaterialId())) {
continue;
}
CoreTubeDO tube = coreTubeMapper.selectAvailableWarehouseTube(candidate.getMaterialId());
if (tube != null) {
Long taskId = createSingleTubeTask(candidate, tube, true);
return "已创建管芯出库任务taskId=" + taskId + ",管芯=" + tube.getCoreTubeCode();
}
if (hasStockingTask(candidate.getMaterialId())) {
log.info("{}管芯正在备货,跳过日计划{}", candidate.getMaterialSpec(), candidate.getPlanCode());
} else {
log.info("备货区{}管芯库存不足,跳过日计划{}", candidate.getMaterialSpec(), candidate.getPlanCode());
}
}
return "全部待套管规格当前均无可出库管芯";
}
/** 创建单根抓取或出库任务。 */
protected Long createSingleTubeTask(DailyPlanScheduleCandidate preview, CoreTubeDO tube, boolean sleeving) {
DailyPlanScheduleCandidate claim = sleeving
? dailyPlanScheduleService.claimSleevingCandidate(preview.getDailyPlanId(), OPERATOR)
: dailyPlanScheduleService.claimStockingCandidate(preview.getDailyPlanId(), OPERATOR);
int oldStage = sleeving ? CoreTubeStageEnum.CORE_WAREHOUSE.getCode() : CoreTubeStageEnum.STOCKING_AREA.getCode();
int newStage = sleeving ? CoreTubeStageEnum.CORE_OUTBOUND.getCode() : CoreTubeStageEnum.STOCKING_PICKING.getCode();
if (coreTubeMapper.reserveTube(tube.getCoreTubeId(), oldStage, newStage, claim.getDailyPlanId(),
claim.getClaimToken(), claim.getClaimExpireTime(), OPERATOR) != 1) {
dailyPlanScheduleService.releaseClaim(claim.getClaimToken(), OPERATOR);
throw new IllegalStateException("管芯已被其他任务占用");
}
CoreTubeTaskContext context = new CoreTubeTaskContext();
context.setBizType((sleeving ? CoreTubeTaskBizTypeEnum.CORE_TUBE_SLEEVING_OUT
: CoreTubeTaskBizTypeEnum.CORE_TUBE_STOCKING_PICK).name());
context.setDailyPlanId(claim.getDailyPlanId());
context.setClaimToken(claim.getClaimToken());
context.setCoreTubeId(tube.getCoreTubeId());
context.setCoreTubeCode(tube.getCoreTubeCode());
context.setVehicleCode(tube.getVehicleCode());
context.setSourcePointId(tube.getCurrentLocationId());
context.setSourcePointCode(tube.getCurrentLocationCode());
context.setTargetPointCode(sleeving ? sleevingTargetPoint : stockingTargetPoint);
String bizType = context.getBizType();
String bizId = tube.getCoreTubeCode();
Long taskId = createTask(buildTubeTask(claim, tube, context, sleeving), bizType, bizId);
if (coreTubeMapper.bindTask(tube.getCoreTubeId(), taskId, OPERATOR) != 1) {
throw new IllegalStateException("管芯任务绑定失败");
}
dailyPlanScheduleService.confirmTaskCreated(claim.getClaimToken(), OPERATOR);
return taskId;
}
/** 所有规格不能直接抓取时创建一条托盘任务。 */
private String createPalletTask(List<DailyPlanScheduleCandidate> candidates, Set<Long> checkedMaterials) {
StockingIvtDO occupiedDock = stockingIvtMapper.selectOccupiedDockPoint();
if (occupiedDock != null) {
StockingIvtDO target = stockingIvtMapper.selectEmptyCachePoint();
if (target == null) {
return "对接位存在非目标托盘,但没有空缓存位可移出";
}
Long taskId = createPalletMoveTask(occupiedDock, target, occupiedDock.getVehicleCode(), true, null);
return "已创建托盘移出任务taskId=" + taskId;
}
StockingIvtDO dock = stockingIvtMapper.selectEmptyDockPoint();
if (dock == null) {
return "没有可用的备货对接位";
}
for (DailyPlanScheduleCandidate candidate : candidates) {
if (!checkedMaterials.contains(candidate.getMaterialId())) {
continue;
}
String materialBizId = "MATERIAL:" + candidate.getMaterialId();
if (getUnfinished(CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_IN.name(), materialBizId) != null) {
continue;
}
StockingIvtDO source = stockingIvtMapper.selectBestCacheVehicle(candidate.getMaterialId(),
candidate.getRemainingQuantity());
if (source != null) {
Long taskId = createPalletMoveTask(source, dock, source.getVehicleCode(), false, candidate);
return "已创建托盘移入任务taskId=" + taskId;
}
log.info("备货区没有可用的{}管芯,跳过日计划{}", candidate.getMaterialSpec(), candidate.getPlanCode());
}
return "备货区没有任何候选规格的可用管芯托盘";
}
/** 创建托盘点位转移任务。 */
private Long createPalletMoveTask(StockingIvtDO source, StockingIvtDO target, String vehicleCode,
boolean moveOut, DailyPlanScheduleCandidate candidate) {
CoreTubeTaskContext context = new CoreTubeTaskContext();
context.setBizType((moveOut ? CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_OUT
: CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_IN).name());
context.setDailyPlanId(candidate == null ? null : candidate.getDailyPlanId());
context.setVehicleCode(vehicleCode);
context.setSourcePointId(source.getIvtId());
context.setSourcePointCode(source.getPointCode());
context.setTargetPointId(target.getIvtId());
context.setTargetPointCode(target.getPointCode());
TransportTaskCreateReqDTO request = baseTask(context, moveOut ? "管芯托盘移出" : "管芯托盘移入",
moveOut ? palletOutAcsType : palletInAcsType, CoreTubeHandleCodeConstants.CORE_TUBE_STOCKING_TASK);
request.setVehicleCode(vehicleCode);
request.setPointCode1(source.getPointCode());
request.setPointCode2(target.getPointCode());
request.setMaterialId(candidate == null ? null : candidate.getMaterialId());
request.setMaterialCode(candidate == null ? null : candidate.getMaterialCode());
String bizId = moveOut ? "DOCK:" + source.getPointCode() : "MATERIAL:" + candidate.getMaterialId();
return createTask(request, context.getBizType(), bizId);
}
/** 构造单根管芯任务。 */
private TransportTaskCreateReqDTO buildTubeTask(DailyPlanScheduleCandidate candidate, CoreTubeDO tube,
CoreTubeTaskContext context, boolean sleeving) {
TransportTaskCreateReqDTO request = baseTask(context, sleeving ? "日计划管芯出库" : "日计划管芯抓取",
sleeving ? sleevingOutAcsType : stockingPickAcsType,
sleeving ? CoreTubeHandleCodeConstants.CORE_TUBE_SLEEVING_TASK
: CoreTubeHandleCodeConstants.CORE_TUBE_STOCKING_TASK);
request.setPointCode1(tube.getCurrentLocationCode());
request.setPointCode2(context.getTargetPointCode());
request.setVehicleCode(tube.getCoreTubeCode());
request.setMaterialId(candidate.getMaterialId());
request.setMaterialCode(candidate.getMaterialCode());
return request;
}
/** 构造任务公共字段。 */
private TransportTaskCreateReqDTO baseTask(CoreTubeTaskContext context, String taskName, String acsType,
String handleCode) {
TransportTaskCreateReqDTO request = new TransportTaskCreateReqDTO();
request.setTaskName(taskName);
request.setOwnerService(OWNER_SERVICE);
request.setBizType(context.getBizType());
request.setHandleCode(handleCode);
request.setAcsTaskType(acsType);
request.setAgvSystemType(agvSystemType);
request.setIsAutoIssue("1");
request.setIsCreateFinish(true);
request.setRequestParam(JsonUtils.toJsonString(context));
return request;
}
/** 创建任务并处理响应不确定场景。 */
private Long createTask(TransportTaskCreateReqDTO request, String bizType, String bizId) {
request.setBizType(bizType);
request.setBizId(bizId);
CommonResult<Long> result = transportTaskApi.createTransportTask(request);
if (result != null && result.isSuccess() && result.getData() != null) {
return result.getData();
}
TaskInfoDTO existing = getUnfinished(bizType, bizId);
if (existing != null) {
return existing.getTaskId();
}
throw new IllegalStateException("创建现场任务失败:" + (result == null ? "无响应" : result.getMsg()));
}
/** 查询未完成业务任务。 */
private TaskInfoDTO getUnfinished(String bizType, String bizId) {
CommonResult<TaskInfoDTO> result = transportTaskApi.getUnfinishedTaskByBiz(OWNER_SERVICE, bizType, bizId);
return result != null && result.isSuccess() ? result.getData() : null;
}
/** 判断目标物料是否正在备货。 */
private boolean hasStockingTask(Long materialId) {
if (getUnfinished(CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_IN.name(), "MATERIAL:" + materialId) != null) {
return true;
}
CommonResult<List<TaskInfoDTO>> result = transportTaskApi.getRunningTaskByMaterialId(materialId, OWNER_SERVICE);
return result != null && result.isSuccess() && result.getData() != null
&& result.getData().stream().anyMatch(task ->
CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_IN.name().equals(task.getBizType())
|| CoreTubeTaskBizTypeEnum.CORE_TUBE_STOCKING_PICK.name().equals(task.getBizType()));
}
/** 处理任务完成。 */
@Override
@Transactional(rollbackFor = Exception.class)
public void handleFinished(Long taskId, boolean sleeving) {
TaskInfoDTO task = getTask(taskId);
CoreTubeTaskContext context = JsonUtils.parseObject(task.getRequestParam(), CoreTubeTaskContext.class);
if (CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_IN.name().equals(context.getBizType())
|| CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_OUT.name().equals(context.getBizType())) {
int affected = stockingIvtMapper.completeVehicleMove(context.getSourcePointId(),
context.getTargetPointId(), context.getVehicleCode(), OPERATOR);
if (affected == 0) {
return;
}
if (affected != 2) {
throw new IllegalStateException("托盘点位转移状态不完整taskId=" + taskId);
}
int locationType = CoreTubeTaskBizTypeEnum.CORE_TUBE_PALLET_IN.name().equals(context.getBizType())
? CoreTubeLocationTypeEnum.STOCKING_DOCK.getCode()
: CoreTubeLocationTypeEnum.STOCKING_CACHE.getCode();
coreTubeMapper.updateVehicleLocation(context.getVehicleCode(), locationType,
context.getTargetPointId(), context.getTargetPointCode(), OPERATOR);
return;
}
int stage = sleeving ? CoreTubeStageEnum.SLEEVING.getCode() : CoreTubeStageEnum.CORE_WAREHOUSE.getCode();
int locationType = sleeving ? CoreTubeLocationTypeEnum.EQUIPMENT.getCode()
: CoreTubeLocationTypeEnum.CORE_WAREHOUSE.getCode();
if (coreTubeMapper.completeTubeTask(context.getCoreTubeId(), taskId, stage,
sleeving ? CoreTubeStatusEnum.PROCESSING.getCode() : CoreTubeStatusEnum.AVAILABLE.getCode(),
locationType, context.getTargetPointId(), context.getTargetPointCode(), true, OPERATOR) == 1) {
if (sleeving) {
dailyPlanScheduleService.increaseSleevedQuantity(context.getClaimToken(),
context.getDailyPlanId(), 1, OPERATOR);
} else {
stockingIvtMapper.clearPaperVehicleCoreTube(context.getCoreTubeId(), OPERATOR);
dailyPlanScheduleService.increaseStockedQuantity(context.getClaimToken(),
context.getDailyPlanId(), 1, OPERATOR);
}
}
}
/** 处理任务取消。 */
@Override
@Transactional(rollbackFor = Exception.class)
public void handleCancelled(Long taskId, boolean sleeving) {
TaskInfoDTO task = getTask(taskId);
CoreTubeTaskContext context = JsonUtils.parseObject(task.getRequestParam(), CoreTubeTaskContext.class);
if (context.getCoreTubeId() == null) {
return;
}
int affected = coreTubeMapper.cancelTubeTask(context.getCoreTubeId(), taskId,
sleeving ? CoreTubeStageEnum.CORE_WAREHOUSE.getCode() : CoreTubeStageEnum.STOCKING_AREA.getCode(),
OPERATOR);
if (affected == 1) {
dailyPlanScheduleService.releaseClaim(context.getClaimToken(), OPERATOR);
}
}
/** 查询任务详情。 */
private TaskInfoDTO getTask(Long taskId) {
CommonResult<TaskInfoDTO> result = transportTaskApi.getTaskById(taskId);
if (result == null || !result.isSuccess() || result.getData() == null) {
throw new IllegalStateException("管芯任务不存在taskId=" + taskId);
}
return result.getData();
}
}

View File

@@ -22,6 +22,8 @@ public class DailyPlanScheduleCandidate {
private String materialSpec;
/** 本轮建议处理数量。 */
private Integer suggestedQuantity;
/** 当前作业尚需处理数量,用于整托盘择优。 */
private Integer remainingQuantity;
/** 日计划乐观锁版本号。 */
private Integer version;
/** 本轮使用的策略模式。 */

View File

@@ -107,7 +107,7 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
}
List<DailyPlanScheduleCandidate> result = new ArrayList<>(ordered.size());
for (DailyPlanDO plan : ordered) {
result.add(convertCandidate(plan, mode.getCode()));
result.add(convertCandidate(plan, mode.getCode(), type));
}
return result;
}
@@ -176,7 +176,7 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
} catch (DuplicateKeyException ex) {
throw exception(DAILY_PLAN_CLAIM_CONFLICT);
}
DailyPlanScheduleCandidate candidate = convertCandidate(plan, strategyMode);
DailyPlanScheduleCandidate candidate = convertCandidate(plan, strategyMode, type);
candidate.setSuggestedQuantity(quantity);
candidate.setClaimToken(claim.getClaimToken());
candidate.setClaimExpireTime(expireTime);
@@ -184,7 +184,8 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
}
/** 将日计划转换为调度候选。 */
private DailyPlanScheduleCandidate convertCandidate(DailyPlanDO plan, Integer strategyMode) {
private DailyPlanScheduleCandidate convertCandidate(DailyPlanDO plan, Integer strategyMode,
DailyPlanStrategyTypeEnum type) {
DailyPlanScheduleCandidate candidate = new DailyPlanScheduleCandidate();
candidate.setDailyPlanId(plan.getDailyPlanId());
candidate.setPlanCode(plan.getPlanCode());
@@ -193,6 +194,9 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
candidate.setMaterialName(plan.getMaterialName());
candidate.setMaterialSpec(plan.getMaterialSpec());
candidate.setSuggestedQuantity(1);
int total = Math.addExact(plan.getPlanQty(), plan.getAppendQty());
int progress = type == DailyPlanStrategyTypeEnum.STOCKING ? plan.getStockedQty() : plan.getSleevedQty();
candidate.setRemainingQuantity(total - progress);
candidate.setVersion(plan.getVersion());
candidate.setStrategyMode(strategyMode);
return candidate;
@@ -204,6 +208,10 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
public void confirmTaskCreated(String claimToken, String operator) {
DailyPlanClaimDO claim = getClaimForUpdate(claimToken);
validateActiveClaim(claim);
if (!claim.getTaskCreated()
&& dailyPlanClaimMapper.markTaskCreated(claimToken, normalizedOperator(operator)) != 1) {
throw exception(DAILY_PLAN_CLAIM_INVALID);
}
DailyPlanStrategyTypeEnum type = DailyPlanStrategyTypeEnum.getByCode(claim.getStrategyType());
if (type == null) {
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
@@ -353,7 +361,7 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
private void validateActiveClaim(DailyPlanClaimDO claim) {
if (!Integer.valueOf(CLAIM_ACTIVE).equals(claim.getClaimStatus()) || claim.getActiveFlag() == null
|| !claim.getExpireTime().isAfter(LocalDateTime.now())) {
|| !claim.getTaskCreated() && !claim.getExpireTime().isAfter(LocalDateTime.now())) {
throw exception(DAILY_PLAN_CLAIM_INVALID);
}
}

View File

@@ -0,0 +1,26 @@
package cn.code.nl.module.lms.task;
import cn.code.nl.framework.execute.core.AbstractTask;
import cn.code.nl.framework.execute.core.dto.TaskExecuteDTO;
import cn.code.nl.module.lms.enums.CoreTubeHandleCodeConstants;
import cn.code.nl.module.lms.service.coretube.CoreTubeTaskDispatchService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/** 管芯库单根出库任务回调。 */
@Component(CoreTubeHandleCodeConstants.CORE_TUBE_SLEEVING_TASK)
public class CoreTubeSleevingTask extends AbstractTask {
@Resource
private CoreTubeTaskDispatchService dispatchService;
@Override
public void doHandleFinish(TaskExecuteDTO taskExecuteDTO) {
dispatchService.handleFinished(taskExecuteDTO.getTaskId(), true);
}
@Override
public void doHandleCancel(TaskExecuteDTO taskExecuteDTO) {
dispatchService.handleCancelled(taskExecuteDTO.getTaskId(), true);
}
}

View File

@@ -0,0 +1,26 @@
package cn.code.nl.module.lms.task;
import cn.code.nl.framework.execute.core.AbstractTask;
import cn.code.nl.framework.execute.core.dto.TaskExecuteDTO;
import cn.code.nl.module.lms.enums.CoreTubeHandleCodeConstants;
import cn.code.nl.module.lms.service.coretube.CoreTubeTaskDispatchService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/** 备货区托盘搬运及管芯抓取任务回调。 */
@Component(CoreTubeHandleCodeConstants.CORE_TUBE_STOCKING_TASK)
public class CoreTubeStockingTask extends AbstractTask {
@Resource
private CoreTubeTaskDispatchService dispatchService;
@Override
public void doHandleFinish(TaskExecuteDTO taskExecuteDTO) {
dispatchService.handleFinished(taskExecuteDTO.getTaskId(), false);
}
@Override
public void doHandleCancel(TaskExecuteDTO taskExecuteDTO) {
dispatchService.handleCancelled(taskExecuteDTO.getTaskId(), false);
}
}

View File

@@ -48,4 +48,88 @@
#{tube.currentLocationType}, #{tube.currentLocationId}, #{tube.currentLocationCode},
0, #{tube.creator}, #{tube.updater})
</insert>
<select id="selectAvailableDockTube"
resultType="cn.code.nl.module.lms.dal.dataobject.coretube.CoreTubeDO">
SELECT tube.*
FROM lms_core_tube tube
JOIN lms_ivt_stockingivt point ON point.ivt_id = tube.current_location_id
WHERE tube.material_id = #{materialId}
AND tube.stage = 1 AND tube.status = 1
AND tube.reserved_task_id IS NULL AND tube.deleted = b'0'
AND point.point_type = '3' AND point.is_used = '0'
ORDER BY point.sort_seq, tube.row_num, tube.col_num, tube.core_tube_id
LIMIT 1
</select>
<select id="selectAvailableWarehouseTube"
resultType="cn.code.nl.module.lms.dal.dataobject.coretube.CoreTubeDO">
SELECT <include refid="coreTubeColumns"/>
FROM lms_core_tube
WHERE material_id = #{materialId}
AND stage = 3 AND status = 1
AND reserved_task_id IS NULL AND deleted = b'0'
ORDER BY current_location_code, core_tube_id
LIMIT 1
</select>
<update id="reserveTube">
UPDATE lms_core_tube
SET stage = #{newStage}, status = 2,
reserved_daily_plan_id = #{dailyPlanId}, reserved_claim_token = #{claimToken},
reserve_expire_time = #{expireTime}, updater = #{updater}, version = version + 1
WHERE core_tube_id = #{coreTubeId}
AND stage = #{expectedStage} AND status = 1
AND reserved_task_id IS NULL AND deleted = b'0'
</update>
<update id="bindTask">
UPDATE lms_core_tube
SET reserved_task_id = #{taskId}, status = 3, updater = #{updater}, version = version + 1
WHERE core_tube_id = #{coreTubeId}
AND status = 2 AND reserved_task_id IS NULL AND deleted = b'0'
</update>
<update id="releaseReservation">
UPDATE lms_core_tube
SET stage = #{stage}, status = 1,
reserved_daily_plan_id = NULL, reserved_claim_token = NULL,
reserve_expire_time = NULL, updater = #{updater}, version = version + 1
WHERE core_tube_id = #{coreTubeId}
AND reserved_claim_token = #{claimToken}
AND reserved_task_id IS NULL AND deleted = b'0'
</update>
<update id="completeTubeTask">
UPDATE lms_core_tube
SET stage = #{stage}, status = #{status},
current_location_type = #{locationType}, current_location_id = #{locationId},
current_location_code = #{locationCode},
vehicle_code = CASE WHEN #{clearVehicle} THEN NULL ELSE vehicle_code END,
row_num = CASE WHEN #{clearVehicle} THEN NULL ELSE row_num END,
col_num = CASE WHEN #{clearVehicle} THEN NULL ELSE col_num END,
active_slot = CASE WHEN #{clearVehicle} THEN NULL ELSE active_slot END,
reserved_daily_plan_id = NULL, reserved_claim_token = NULL,
reserved_task_id = NULL, reserve_expire_time = NULL,
updater = #{updater}, version = version + 1
WHERE core_tube_id = #{coreTubeId}
AND reserved_task_id = #{taskId} AND deleted = b'0'
</update>
<update id="cancelTubeTask">
UPDATE lms_core_tube
SET stage = #{stage}, status = 1,
reserved_daily_plan_id = NULL, reserved_claim_token = NULL,
reserved_task_id = NULL, reserve_expire_time = NULL,
updater = #{updater}, version = version + 1
WHERE core_tube_id = #{coreTubeId}
AND reserved_task_id = #{taskId} AND deleted = b'0'
</update>
<update id="updateVehicleLocation">
UPDATE lms_core_tube
SET current_location_type = #{locationType}, current_location_id = #{locationId},
current_location_code = #{locationCode}, updater = #{updater}, version = version + 1
WHERE vehicle_code = #{vehicleCode} AND active_slot = 1 AND deleted = b'0'
</update>
</mapper>

View File

@@ -4,17 +4,17 @@
<sql id="claimColumns">
claim_id, claim_token, daily_plan_id, strategy_type, claimed_qty, plan_version, progress_qty, version,
claim_status, active_flag, expire_time, confirmed_time, operator,
claim_status, active_flag, task_created, expire_time, confirmed_time, operator,
creator, create_time, updater, update_time, deleted, tenant_id
</sql>
<insert id="insertClaim">
INSERT INTO lms_daily_plan_claim
(claim_id, claim_token, daily_plan_id, strategy_type, claimed_qty, plan_version, progress_qty, version,
claim_status, active_flag, expire_time, operator, creator, updater)
claim_status, active_flag, task_created, expire_time, operator, creator, updater)
VALUES
(#{claim.claimId}, #{claim.claimToken}, #{claim.dailyPlanId}, #{claim.strategyType},
#{claim.claimedQty}, #{claim.planVersion}, #{claim.progressQty}, 0, 0, 1, #{claim.expireTime},
#{claim.claimedQty}, #{claim.planVersion}, #{claim.progressQty}, 0, 0, 1, b'0', #{claim.expireTime},
#{claim.operator}, #{claim.operator}, #{claim.operator})
</insert>
@@ -35,6 +35,13 @@
UPDATE lms_daily_plan_claim
SET claim_status = 1, active_flag = NULL, confirmed_time = NOW(), updater = #{updater}, version = version + 1
WHERE claim_id = #{claimId} AND version = #{version} AND claim_status = 0 AND active_flag = 1
AND (task_created = b'1' OR expire_time &gt; NOW(6)) AND deleted = b'0'
</update>
<update id="markTaskCreated">
UPDATE lms_daily_plan_claim
SET task_created = b'1', updater = #{updater}, version = version + 1
WHERE claim_token = #{claimToken} AND claim_status = 0 AND active_flag = 1
AND expire_time &gt; NOW(6) AND deleted = b'0'
</update>
@@ -54,7 +61,8 @@
<update id="expireClaims">
UPDATE lms_daily_plan_claim
SET claim_status = 3, active_flag = NULL, updater = #{updater}
WHERE claim_status = 0 AND active_flag = 1 AND expire_time &lt;= #{now} AND deleted = b'0'
WHERE claim_status = 0 AND active_flag = 1 AND task_created = b'0'
AND expire_time &lt;= #{now} AND deleted = b'0'
</update>
</mapper>

View File

@@ -68,7 +68,7 @@
AND claim.tenant_id = lms_daily_plan.tenant_id
AND claim.strategy_type = #{strategyType}
AND claim.claim_status = 0 AND claim.active_flag = 1
AND claim.expire_time &gt; NOW(6) AND claim.deleted = b'0'
AND (claim.task_created = b'1' OR claim.expire_time &gt; NOW(6)) AND claim.deleted = b'0'
)
FOR UPDATE
</select>
@@ -124,7 +124,8 @@
WHERE claim.daily_plan_id = lms_daily_plan.daily_plan_id
AND claim.tenant_id = lms_daily_plan.tenant_id
AND claim.strategy_type = 1 AND claim.claim_status = 0
AND claim.active_flag = 1 AND claim.expire_time &gt; NOW(6) AND claim.deleted = b'0'
AND claim.active_flag = 1
AND (claim.task_created = b'1' OR claim.expire_time &gt; NOW(6)) AND claim.deleted = b'0'
)
ORDER BY sort_seq ASC, daily_plan_id ASC
</select>
@@ -142,7 +143,8 @@
WHERE claim.daily_plan_id = lms_daily_plan.daily_plan_id
AND claim.tenant_id = lms_daily_plan.tenant_id
AND claim.strategy_type = 2 AND claim.claim_status = 0
AND claim.active_flag = 1 AND claim.expire_time &gt; NOW(6) AND claim.deleted = b'0'
AND claim.active_flag = 1
AND (claim.task_created = b'1' OR claim.expire_time &gt; NOW(6)) AND claim.deleted = b'0'
)
ORDER BY sort_seq ASC, daily_plan_id ASC
</select>

View File

@@ -120,4 +120,73 @@
#{tube.materialCode}, #{tube.materialName}, 1, #{tube.creator}, #{tube.updater})
</insert>
<update id="clearPaperVehicleCoreTube">
UPDATE lms_base_papervehicle
SET core_tube_id = NULL,
core_tube_code = NULL,
material_code = NULL,
material_name = NULL,
qty = 0,
updater = #{updater}
WHERE core_tube_id = #{coreTubeId}
</update>
<select id="selectOccupiedDockPoint"
resultType="cn.code.nl.module.lms.dal.dataobject.stockingivt.StockingIvtDO">
SELECT <include refid="stockingIvtColumns"/>
FROM lms_ivt_stockingivt
WHERE point_type = '3' AND is_used = '0'
AND ivt_status = '2' AND vehicle_code IS NOT NULL AND vehicle_code != ''
ORDER BY sort_seq, ivt_id
LIMIT 1
</select>
<select id="selectEmptyDockPoint"
resultType="cn.code.nl.module.lms.dal.dataobject.stockingivt.StockingIvtDO">
SELECT <include refid="stockingIvtColumns"/>
FROM lms_ivt_stockingivt
WHERE point_type = '3' AND is_used = '0' AND ivt_status = '1'
ORDER BY sort_seq, ivt_id
LIMIT 1
</select>
<select id="selectEmptyCachePoint"
resultType="cn.code.nl.module.lms.dal.dataobject.stockingivt.StockingIvtDO">
SELECT <include refid="stockingIvtColumns"/>
FROM lms_ivt_stockingivt
WHERE point_type = '2' AND is_used = '0' AND ivt_status = '1'
ORDER BY sort_seq, ivt_id
LIMIT 1
</select>
<select id="selectBestCacheVehicle"
resultType="cn.code.nl.module.lms.dal.dataobject.stockingivt.StockingIvtDO">
SELECT point.ivt_id, point.point_code, point.point_name, point.vehicle_code,
point.product_area, point.point_type, point.ivt_status, point.point_location,
point.sort_seq, point.is_used, point.remark,
point.creator, point.create_time, point.updater, point.update_time
FROM lms_ivt_stockingivt point
JOIN lms_core_tube tube ON tube.current_location_id = point.ivt_id
WHERE point.point_type = '2' AND point.is_used = '0' AND point.ivt_status = '2'
AND tube.material_id = #{materialId} AND tube.stage = 1 AND tube.status = 1
AND tube.reserved_task_id IS NULL AND tube.deleted = b'0'
GROUP BY point.ivt_id, point.point_code, point.point_name, point.vehicle_code,
point.product_area, point.point_type, point.ivt_status, point.point_location,
point.sort_seq, point.is_used, point.remark,
point.creator, point.create_time, point.updater, point.update_time
ORDER BY CASE WHEN COUNT(*) &gt;= #{requiredQty} THEN 0 ELSE 1 END,
CASE WHEN COUNT(*) &gt;= #{requiredQty} THEN COUNT(*) END ASC,
COUNT(*) DESC, point.sort_seq, point.ivt_id
LIMIT 1
</select>
<update id="completeVehicleMove">
UPDATE lms_ivt_stockingivt
SET vehicle_code = CASE WHEN ivt_id = #{sourcePointId} THEN NULL ELSE #{vehicleCode} END,
ivt_status = CASE WHEN ivt_id = #{sourcePointId} THEN '1' ELSE '2' END,
updater = #{updater}
WHERE (ivt_id = #{sourcePointId} AND vehicle_code = #{vehicleCode})
OR (ivt_id = #{targetPointId} AND ivt_status = '1')
</update>
</mapper>

View File

@@ -48,6 +48,15 @@ PREPARE lms_core_tube_stmt FROM @lms_core_tube_ddl;
EXECUTE lms_core_tube_stmt;
DEALLOCATE PREPARE lms_core_tube_stmt;
SET @lms_core_tube_ddl = IF(
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE()
AND table_name = 'lms_daily_plan_claim' AND column_name = 'task_created'),
'SELECT 1',
'ALTER TABLE lms_daily_plan_claim ADD COLUMN task_created BIT(1) NOT NULL DEFAULT b''0'' COMMENT ''是否已创建现场任务'' AFTER active_flag');
PREPARE lms_core_tube_stmt FROM @lms_core_tube_ddl;
EXECUTE lms_core_tube_stmt;
DEALLOCATE PREPARE lms_core_tube_stmt;
SET @lms_core_tube_ddl = IF(
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE()
AND table_name = 'lms_base_papervehicle' AND column_name = 'core_tube_code'),

View File

@@ -81,6 +81,7 @@ CREATE TABLE IF NOT EXISTS `lms_daily_plan_claim` (
`version` int NOT NULL DEFAULT 0 COMMENT '领取乐观锁版本号',
`claim_status` tinyint NOT NULL DEFAULT 0 COMMENT '领取状态0执行中1已确认2已释放3已过期',
`active_flag` tinyint DEFAULT 1 COMMENT '有效领取唯一标记,终态置空',
`task_created` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已创建现场任务',
`expire_time` datetime(6) NOT NULL COMMENT '租约过期时间',
`confirmed_time` datetime DEFAULT NULL COMMENT '确认时间',
`operator` varchar(64) NOT NULL COMMENT '领取人/执行器',
@@ -125,6 +126,14 @@ SET @lms_daily_plan_ddl = IF(
PREPARE lms_daily_plan_stmt FROM @lms_daily_plan_ddl;
EXECUTE lms_daily_plan_stmt;
DEALLOCATE PREPARE lms_daily_plan_stmt;
SET @lms_daily_plan_ddl = IF(
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE()
AND table_name = 'lms_daily_plan_claim' AND column_name = 'task_created'),
'SELECT 1',
'ALTER TABLE lms_daily_plan_claim ADD COLUMN task_created BIT(1) NOT NULL DEFAULT b''0'' COMMENT ''是否已创建现场任务'' AFTER active_flag');
PREPARE lms_daily_plan_stmt FROM @lms_daily_plan_ddl;
EXECUTE lms_daily_plan_stmt;
DEALLOCATE PREPARE lms_daily_plan_stmt;
SET @lms_daily_plan_ddl = IF(
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE()
AND table_name = 'lms_daily_plan_claim' AND column_name = 'expire_time'