feat: 增加日计划调度能力
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package cn.code.nl.module.lms.service.dailyplan;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 供日计划定时调度器使用的候选任务。 */
|
||||
@Data
|
||||
public class DailyPlanScheduleCandidate {
|
||||
|
||||
/** 日计划 ID。 */
|
||||
private Long dailyPlanId;
|
||||
/** 日计划编号。 */
|
||||
private String planCode;
|
||||
/** 物料 ID。 */
|
||||
private Long materialId;
|
||||
/** 物料编码。 */
|
||||
private String materialCode;
|
||||
/** 物料名称。 */
|
||||
private String materialName;
|
||||
/** 物料规格。 */
|
||||
private String materialSpec;
|
||||
/** 本轮建议处理数量。 */
|
||||
private Integer suggestedQuantity;
|
||||
/** 日计划乐观锁版本号。 */
|
||||
private Integer version;
|
||||
/** 本轮使用的策略模式。 */
|
||||
private Integer strategyMode;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.code.nl.module.lms.service.dailyplan;
|
||||
|
||||
/** 供后续定时器调用的日计划调度服务。 */
|
||||
public interface DailyPlanScheduleService {
|
||||
|
||||
/** 获取下一张待备货计划;无候选时返回 {@code null}。 */
|
||||
DailyPlanScheduleCandidate getNextStockingCandidate(String operator);
|
||||
|
||||
/** 获取下一张待套管计划;无候选时返回 {@code null}。 */
|
||||
DailyPlanScheduleCandidate getNextSleevingCandidate(String operator);
|
||||
|
||||
/** 原子增加已备货数量。 */
|
||||
void increaseStockedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator);
|
||||
|
||||
/** 原子增加已套管数量。 */
|
||||
void increaseSleevedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package cn.code.nl.module.lms.service.dailyplan;
|
||||
|
||||
import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanDO;
|
||||
import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanOperationLogDO;
|
||||
import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanStrategyDO;
|
||||
import cn.code.nl.module.lms.dal.mysql.dailyplan.DailyPlanMapper;
|
||||
import cn.code.nl.module.lms.dal.mysql.dailyplan.DailyPlanOperationLogMapper;
|
||||
import cn.code.nl.module.lms.dal.mysql.dailyplan.DailyPlanStrategyMapper;
|
||||
import cn.code.nl.module.lms.enums.DailyPlanOrderStatusEnum;
|
||||
import cn.code.nl.module.lms.enums.DailyPlanStrategyModeEnum;
|
||||
import cn.code.nl.module.lms.enums.DailyPlanStrategyTypeEnum;
|
||||
import cn.code.nl.module.lms.enums.DailyPlanWorkStatusEnum;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_COMPLETED_QTY_EXCEEDS_TOTAL;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_NOT_EXISTS;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_PROGRESS_QTY_NOT_POSITIVE;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_STATUS_OPERATION_NOT_ALLOWED;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_STRATEGY_NOT_EXISTS;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_STRATEGY_VERSION_CONFLICT;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_VERSION_CONFLICT;
|
||||
|
||||
/** 日计划调度服务实现。 */
|
||||
@Service
|
||||
public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
|
||||
|
||||
private static final String TARGET_STOCKING = "STOCKING";
|
||||
private static final String TARGET_SLEEVING = "SLEEVING";
|
||||
private static final String TYPE_PROGRESS = "PROGRESS";
|
||||
|
||||
@Resource
|
||||
private DailyPlanMapper dailyPlanMapper;
|
||||
@Resource
|
||||
private DailyPlanStrategyMapper dailyPlanStrategyMapper;
|
||||
@Resource
|
||||
private DailyPlanOperationLogMapper dailyPlanOperationLogMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DailyPlanScheduleCandidate getNextStockingCandidate(String operator) {
|
||||
return selectCandidate(DailyPlanStrategyTypeEnum.STOCKING, dailyPlanMapper.selectStockingCandidates(), operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DailyPlanScheduleCandidate getNextSleevingCandidate(String operator) {
|
||||
return selectCandidate(DailyPlanStrategyTypeEnum.SLEEVING, dailyPlanMapper.selectSleevingCandidates(), operator);
|
||||
}
|
||||
|
||||
private DailyPlanScheduleCandidate selectCandidate(DailyPlanStrategyTypeEnum type,
|
||||
List<DailyPlanDO> candidates, String operator) {
|
||||
DailyPlanStrategyDO strategy = dailyPlanStrategyMapper.selectByStrategyType(type.getCode());
|
||||
if (strategy == null) {
|
||||
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
||||
}
|
||||
if (candidates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
DailyPlanStrategyModeEnum mode = DailyPlanStrategyModeEnum.getByCode(strategy.getStrategyMode());
|
||||
if (mode == null) {
|
||||
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
||||
}
|
||||
if (mode == DailyPlanStrategyModeEnum.SEQUENCE) {
|
||||
DailyPlanDO selected = candidates.get(0);
|
||||
return convertCandidate(selected, remaining(selected, type), mode.getCode());
|
||||
}
|
||||
|
||||
int selectedIndex = findCursorIndex(candidates, strategy.getCursorDailyPlanId());
|
||||
DailyPlanDO selected = candidates.get(selectedIndex);
|
||||
DailyPlanDO next = candidates.get((selectedIndex + 1) % candidates.size());
|
||||
int affected = dailyPlanStrategyMapper.updateCursorWithVersion(strategy.getStrategyId(),
|
||||
next.getDailyPlanId(), strategy.getVersion(), normalizedOperator(operator));
|
||||
if (affected != 1) {
|
||||
// 本次候选不应在游标推进失败时被执行,调用方可重新获取最新候选。
|
||||
throw exception(DAILY_PLAN_STRATEGY_VERSION_CONFLICT);
|
||||
}
|
||||
int quantity = Math.min(selected.getWeight(), remaining(selected, type));
|
||||
return convertCandidate(selected, quantity, mode.getCode());
|
||||
}
|
||||
|
||||
private int findCursorIndex(List<DailyPlanDO> candidates, Long cursorDailyPlanId) {
|
||||
if (cursorDailyPlanId != null) {
|
||||
for (int i = 0; i < candidates.size(); i++) {
|
||||
if (cursorDailyPlanId.equals(candidates.get(i).getDailyPlanId())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int remaining(DailyPlanDO plan, DailyPlanStrategyTypeEnum type) {
|
||||
int completed = type == DailyPlanStrategyTypeEnum.STOCKING ? plan.getStockedQty() : plan.getSleevedQty();
|
||||
return plan.getPlanQty() + plan.getAppendQty() - completed;
|
||||
}
|
||||
|
||||
private DailyPlanScheduleCandidate convertCandidate(DailyPlanDO plan, int quantity, Integer strategyMode) {
|
||||
DailyPlanScheduleCandidate candidate = new DailyPlanScheduleCandidate();
|
||||
candidate.setDailyPlanId(plan.getDailyPlanId());
|
||||
candidate.setPlanCode(plan.getPlanCode());
|
||||
candidate.setMaterialId(plan.getMaterialId());
|
||||
candidate.setMaterialCode(plan.getMaterialCode());
|
||||
candidate.setMaterialName(plan.getMaterialName());
|
||||
candidate.setMaterialSpec(plan.getMaterialSpec());
|
||||
candidate.setSuggestedQuantity(quantity);
|
||||
candidate.setVersion(plan.getVersion());
|
||||
candidate.setStrategyMode(strategyMode);
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void increaseStockedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator) {
|
||||
increaseQuantity(dailyPlanId, quantity, version, operator, DailyPlanStrategyTypeEnum.STOCKING);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void increaseSleevedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator) {
|
||||
increaseQuantity(dailyPlanId, quantity, version, operator, DailyPlanStrategyTypeEnum.SLEEVING);
|
||||
}
|
||||
|
||||
private void increaseQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator,
|
||||
DailyPlanStrategyTypeEnum type) {
|
||||
if (quantity == null || quantity <= 0) {
|
||||
throw exception(DAILY_PLAN_PROGRESS_QTY_NOT_POSITIVE);
|
||||
}
|
||||
DailyPlanDO plan = dailyPlanMapper.selectDailyPlanById(dailyPlanId);
|
||||
if (plan == null) {
|
||||
throw exception(DAILY_PLAN_NOT_EXISTS);
|
||||
}
|
||||
if (!plan.getVersion().equals(version)) {
|
||||
throw exception(DAILY_PLAN_VERSION_CONFLICT);
|
||||
}
|
||||
Integer workStatus = type == DailyPlanStrategyTypeEnum.STOCKING
|
||||
? plan.getStockingStatus() : plan.getSleevingStatus();
|
||||
if (!DailyPlanOrderStatusEnum.IN_PROGRESS.getCode().equals(plan.getOrderStatus())
|
||||
|| !DailyPlanWorkStatusEnum.IN_PROGRESS.getCode().equals(workStatus)) {
|
||||
throw exception(DAILY_PLAN_STATUS_OPERATION_NOT_ALLOWED,
|
||||
plan.getOrderStatus() + "/" + workStatus, "更新作业进度");
|
||||
}
|
||||
int before = type == DailyPlanStrategyTypeEnum.STOCKING ? plan.getStockedQty() : plan.getSleevedQty();
|
||||
long total = (long) plan.getPlanQty() + plan.getAppendQty();
|
||||
if ((long) before + quantity > total) {
|
||||
throw exception(DAILY_PLAN_COMPLETED_QTY_EXCEEDS_TOTAL);
|
||||
}
|
||||
int affected = type == DailyPlanStrategyTypeEnum.STOCKING
|
||||
? dailyPlanMapper.increaseStockedQuantity(dailyPlanId, quantity, version, normalizedOperator(operator))
|
||||
: dailyPlanMapper.increaseSleevedQuantity(dailyPlanId, quantity, version, normalizedOperator(operator));
|
||||
if (affected != 1) {
|
||||
throw exception(DAILY_PLAN_VERSION_CONFLICT);
|
||||
}
|
||||
insertProgressLog(dailyPlanId, type, before, before + quantity, quantity, operator);
|
||||
}
|
||||
|
||||
private void insertProgressLog(Long dailyPlanId, DailyPlanStrategyTypeEnum type, int before, int after,
|
||||
int quantity, String operator) {
|
||||
DailyPlanOperationLogDO log = new DailyPlanOperationLogDO();
|
||||
log.setOperationLogId(IdWorker.getId());
|
||||
log.setDailyPlanId(dailyPlanId);
|
||||
log.setOperationTarget(type == DailyPlanStrategyTypeEnum.STOCKING ? TARGET_STOCKING : TARGET_SLEEVING);
|
||||
log.setOperationType(TYPE_PROGRESS);
|
||||
log.setBeforeValue(String.valueOf(before));
|
||||
log.setAfterValue(String.valueOf(after));
|
||||
log.setChangeQty(quantity);
|
||||
log.setOperator(normalizedOperator(operator));
|
||||
if (dailyPlanOperationLogMapper.insertOperationLog(log) != 1) {
|
||||
throw exception(DAILY_PLAN_VERSION_CONFLICT);
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizedOperator(String operator) {
|
||||
return StrUtil.blankToDefault(StrUtil.trim(operator), "SCHEDULER");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user