fix: 锁定日计划后创建调度领取

This commit is contained in:
zhouz
2026-08-14 15:03:07 +08:00
parent c57023facb
commit b938547e75
5 changed files with 54 additions and 3 deletions

View File

@@ -66,4 +66,5 @@ public interface ErrorCodeConstants {
ErrorCode DAILY_PLAN_CLAIM_INVALID = new ErrorCode(41, "日计划领取标识无效、已结束或已过期");
ErrorCode DAILY_PLAN_CLAIM_MISMATCH = new ErrorCode(42, "日计划领取信息与进度确认不一致");
ErrorCode DAILY_PLAN_CLAIM_PROGRESS_CONFLICT = new ErrorCode(43, "日计划作业进度已变化,该领取无法确认");
ErrorCode DAILY_PLAN_CLAIM_CANDIDATE_CHANGED = new ErrorCode(44, "日计划候选状态已变化,请重新获取");
}

View File

@@ -51,6 +51,12 @@ public interface DailyPlanMapper extends BaseMapperX<DailyPlanDO> {
*/
DailyPlanDO selectDailyPlanById(@Param("dailyPlanId") Long dailyPlanId);
/**
* 锁定并重新校验可调度计划Claim 必须使用该锁内快照创建。
*/
DailyPlanDO selectSchedulableForUpdate(@Param("dailyPlanId") Long dailyPlanId,
@Param("strategyType") Integer strategyType);
/**
* 根据 ERP 订单号查询日计划。
*/

View File

@@ -24,6 +24,7 @@ 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_CLAIM_CONFLICT;
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_CLAIM_CANDIDATE_CHANGED;
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_CLAIM_INVALID;
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_CLAIM_MISMATCH;
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_CLAIM_PROGRESS_CONFLICT;
@@ -84,14 +85,16 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
}
if (mode == DailyPlanStrategyModeEnum.SEQUENCE) {
DailyPlanDO selected = candidates.get(0);
return claimCandidate(selected, remaining(selected, type), mode.getCode(), type, operator);
DailyPlanDO locked = lockSchedulablePlan(selected.getDailyPlanId(), type);
return claimCandidate(locked, remaining(locked, type), mode.getCode(), type, operator);
}
int selectedIndex = findCursorIndex(candidates, strategy.getCursorDailyPlanId());
DailyPlanDO selected = candidates.get(selectedIndex);
DailyPlanDO next = candidates.get((selectedIndex + 1) % candidates.size());
int quantity = Math.min(selected.getWeight(), remaining(selected, type));
DailyPlanScheduleCandidate candidate = claimCandidate(selected, quantity, mode.getCode(), type, operator);
DailyPlanDO locked = lockSchedulablePlan(selected.getDailyPlanId(), type);
int quantity = Math.min(locked.getWeight(), remaining(locked, type));
DailyPlanScheduleCandidate candidate = claimCandidate(locked, quantity, mode.getCode(), type, operator);
int affected = dailyPlanStrategyMapper.updateCursorWithVersion(strategy.getStrategyId(), next.getDailyPlanId(),
strategy.getVersion(), normalizedOperator(operator));
if (affected != 1) {
@@ -101,6 +104,14 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
return candidate;
}
private DailyPlanDO lockSchedulablePlan(Long dailyPlanId, DailyPlanStrategyTypeEnum type) {
DailyPlanDO locked = dailyPlanMapper.selectSchedulableForUpdate(dailyPlanId, type.getCode());
if (locked == null) {
throw exception(DAILY_PLAN_CLAIM_CANDIDATE_CHANGED);
}
return locked;
}
private int findCursorIndex(List<DailyPlanDO> candidates, Long cursorDailyPlanId) {
if (cursorDailyPlanId != null) {
for (int i = 0; i < candidates.size(); i++) {

View File

@@ -51,6 +51,28 @@
ORDER BY plan_date DESC, sort_seq ASC, daily_plan_id DESC
</select>
<select id="selectSchedulableForUpdate"
resultType="cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanDO">
SELECT <include refid="dailyPlanColumns"/>
FROM lms_daily_plan
WHERE daily_plan_id = #{dailyPlanId}
AND order_status = 1
AND deleted = b'0'
AND (
(#{strategyType} = 1 AND stocking_status = 1 AND stocked_qty &lt; plan_qty + append_qty)
OR (#{strategyType} = 2 AND sleeving_status = 1 AND sleeved_qty &lt; plan_qty + append_qty)
)
AND NOT EXISTS (
SELECT 1 FROM lms_daily_plan_claim claim
WHERE claim.daily_plan_id = lms_daily_plan.daily_plan_id
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() AND claim.deleted = b'0'
)
FOR UPDATE
</select>
<select id="selectWorkPage"
resultType="cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanDO">
SELECT <include refid="dailyPlanColumns"/>

View File

@@ -102,6 +102,9 @@ CREATE TABLE IF NOT EXISTS `lms_daily_plan_operation_log` (
) COMMENT='LMS日计划操作日志';
-- 兼容已执行过旧版脚本的数据库,幂等补充候选组合索引。
SET @lms_claim_progress_upgrade = NOT EXISTS(
SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE()
AND table_name = 'lms_daily_plan_claim' AND column_name = 'progress_qty');
SET @lms_daily_plan_ddl = IF(
EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE()
AND table_name = 'lms_daily_plan' AND index_name = 'idx_stocking_candidate'),
@@ -118,6 +121,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;
-- 旧版活跃 Claim 没有可靠的领取时进度快照,首次升级时统一释放,避免按默认 0 错误确认。
SET @lms_daily_plan_ddl = IF(
@lms_claim_progress_upgrade,
'UPDATE lms_daily_plan_claim SET claim_status = 2, active_flag = NULL, updater = ''SQL_UPGRADE'' WHERE claim_status = 0 AND active_flag = 1 AND deleted = b''0''',
'SELECT 1');
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 = 'version'),