From c57023facb14ae291591e0892801dc96e1ca44ba Mon Sep 17 00:00:00 2001 From: zhouz <> Date: Fri, 14 Aug 2026 14:59:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8=E9=A2=86=E5=8F=96?= =?UTF-8?q?=E4=BB=A4=E7=89=8C=E9=9A=94=E7=A6=BB=E6=97=A5=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-08-14-lms-daily-plan.md | 20 ++--- .../specs/2026-08-14-lms-daily-plan-design.md | 7 +- .../module/lms/enums/ErrorCodeConstants.java | 1 + .../dailyplan/DailyPlanClaimDO.java | 2 + .../mysql/dailyplan/DailyPlanClaimMapper.java | 8 +- .../dal/mysql/dailyplan/DailyPlanMapper.java | 16 ++-- .../dailyplan/DailyPlanScheduleService.java | 4 +- .../DailyPlanScheduleServiceImpl.java | 86 +++++++++++-------- .../mapper/dailyplan/DailyPlanClaimMapper.xml | 21 +++-- .../mapper/dailyplan/DailyPlanMapper.xml | 24 +++--- sql/mysql/lms_daily_plan.sql | 20 ++++- 11 files changed, 129 insertions(+), 80 deletions(-) diff --git a/docs/superpowers/plans/2026-08-14-lms-daily-plan.md b/docs/superpowers/plans/2026-08-14-lms-daily-plan.md index a8ad8964..d7c4f42a 100644 --- a/docs/superpowers/plans/2026-08-14-lms-daily-plan.md +++ b/docs/superpowers/plans/2026-08-14-lms-daily-plan.md @@ -225,25 +225,23 @@ List selectSleevingCandidates(); - [ ] **步骤 2:实现受控写入** -XML 提供 `insertDailyPlan`、`updateNotStartedPlan`、`updateStateWithVersion`、`updateSortWithVersion`、`updateWeightWithVersion` 和两个原子进度方法。版本更新必须使用: +XML 提供 `insertDailyPlan`、`updateNotStartedPlan`、`updateStateWithVersion`、`updateSortWithVersion`、`updateWeightWithVersion` 和两个基于 Claim 的原子进度方法。页面写操作使用日计划版本;调度进度以 Claim 令牌及领取时对应作业进度作为并发边界: ```sql UPDATE lms_daily_plan SET stocked_qty = stocked_qty + #{incrementQty}, stocking_status = CASE - WHEN stocked_qty + #{incrementQty} = plan_qty + append_qty THEN 2 + WHEN stocking_status = 1 AND stocked_qty + #{incrementQty} = plan_qty + append_qty THEN 2 ELSE stocking_status END, version = version + 1, updater = #{updater} WHERE daily_plan_id = #{dailyPlanId} - AND version = #{version} - AND order_status = 1 - AND stocking_status = 1 + AND stocked_qty = #{claimProgressQty} AND stocked_qty + #{incrementQty} <= plan_qty + append_qty ``` -套管方法使用同样约束。受影响行数不是 1 时由 Service 区分版本冲突或数量超限。 +套管方法使用独立的 `sleeved_qty` 快照约束。排序、权重、ERP 追加和另一作业进度引起的日计划版本变化不影响确认。取消或结束只阻止新领取,已有有效 Claim 仍允许累计数量,且 SQL 不覆盖用户选择的取消或结束状态。受影响行数不是 1 时由 Service 区分进度冲突或数量超限。 - [ ] **步骤 3:实现策略与日志 Mapper** @@ -359,7 +357,7 @@ POST /lms/daily-plan/erpPush - [ ] **步骤 1:定义调度返回对象** -创建 `DailyPlanScheduleCandidate`,包含日计划 ID、计划编号、物料信息、建议处理数量、版本号和策略模式。 +创建 `DailyPlanScheduleCandidate`,包含日计划 ID、计划编号、物料信息、建议处理数量、版本号、策略模式、排他 Claim 令牌和租约过期时间。版本号仅供展示审计,不作为进度确认并发边界。 - [ ] **步骤 2:实现顺序模式选择** @@ -367,18 +365,18 @@ POST /lms/daily-plan/erpPush - [ ] **步骤 3:实现权重轮转选择** -根据策略游标从有序候选列表定位下一张;建议数量为 `min(weight, remainingQty)`。完成选择后以版本号更新游标到下一张计划。游标无效时从顺序第一张开始。 +根据策略游标从有序候选列表定位下一张;建议数量为 `min(weight, remainingQty)`。先原子创建 Claim,成功后再以策略版本号更新游标到下一张计划,两者同事务提交。游标无效时从顺序第一张开始。 - [ ] **步骤 4:实现原子进度更新** 提供: ```java -void increaseStockedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator); -void increaseSleevedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator); +void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator); +void increaseSleevedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator); ``` -更新到需求总量时同步写完成状态和完成时间,并追加操作日志。版本冲突或数量超限抛明确业务异常。 +确认先锁定并原子消费 Claim,再按 Claim 保存的对应作业进度快照更新数量并追加日志。备货、套管可独立并发;同一 Claim 重复确认幂等成功。更新到需求总量时,仅当该作业仍在进行中才同步写完成状态和完成时间;若用户已取消或结束则保留终态。另提供 Claim 续租、主动释放和过期回收能力。 - [ ] **步骤 5:编译并提交调度能力** diff --git a/docs/superpowers/specs/2026-08-14-lms-daily-plan-design.md b/docs/superpowers/specs/2026-08-14-lms-daily-plan-design.md index 48f58f13..9ca74eb3 100644 --- a/docs/superpowers/specs/2026-08-14-lms-daily-plan-design.md +++ b/docs/superpowers/specs/2026-08-14-lms-daily-plan-design.md @@ -221,11 +221,14 @@ ERP 推送不允许直接修改顺序、权重、当前完成数量或作业状 ## 并发与事务 -- 页面操作和定时器数量更新使用版本号进行乐观锁校验。 +- 页面状态及资料操作使用日计划版本号进行乐观锁校验;调度数量确认不绑定整张日计划版本。 - 状态变更、数量变更和对应操作记录在同一事务中提交。 - 批量调整顺序在一个事务内完成;任一计划版本冲突时整体回滚。 - 策略切换和轮转游标更新使用版本号,避免两个调度线程重复选择同一轮计划。 -- 并发失败统一提示“日计划已发生变化,请刷新后重试”。 +- 每次调度先持久化排他 Claim,Claim 令牌、作业类型、预占数量、领取时作业进度和 Claim 自身版本共同构成确认并发边界。备货与套管 Claim 相互独立。 +- 进度确认先原子消费 Claim,再以领取时对应作业进度为条件累计数量;重复确认同一已完成 Claim 幂等成功,不重复计数。 +- 排序、权重、ERP 追加和另一作业进度不会阻断有效 Claim 确认。取消、结束后不再产生新 Claim,但已下发的有效 Claim 仍可确认;确认只累计数量,保留用户选择的取消或结束状态。 +- Claim 支持续租、主动释放和过期回收;过期或已释放 Claim 不得确认。 ## 错误处理 diff --git a/nl-module-lms/nl-module-lms-api/src/main/java/cn/code/nl/module/lms/enums/ErrorCodeConstants.java b/nl-module-lms/nl-module-lms-api/src/main/java/cn/code/nl/module/lms/enums/ErrorCodeConstants.java index 5f5c6eaa..cb6d1838 100644 --- a/nl-module-lms/nl-module-lms-api/src/main/java/cn/code/nl/module/lms/enums/ErrorCodeConstants.java +++ b/nl-module-lms/nl-module-lms-api/src/main/java/cn/code/nl/module/lms/enums/ErrorCodeConstants.java @@ -65,4 +65,5 @@ public interface ErrorCodeConstants { ErrorCode DAILY_PLAN_CLAIM_CONFLICT = new ErrorCode(40, "日计划已被其他调度器领取,请重新获取候选"); 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, "日计划作业进度已变化,该领取无法确认"); } diff --git a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/dataobject/dailyplan/DailyPlanClaimDO.java b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/dataobject/dailyplan/DailyPlanClaimDO.java index 2cba12ad..810e8571 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/dataobject/dailyplan/DailyPlanClaimDO.java +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/dataobject/dailyplan/DailyPlanClaimDO.java @@ -19,6 +19,8 @@ public class DailyPlanClaimDO extends TenantBaseDO { private Integer strategyType; private Integer claimedQty; private Integer planVersion; + private Integer progressQty; + private Integer version; private Integer claimStatus; private Integer activeFlag; private LocalDateTime expireTime; diff --git a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanClaimMapper.java b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanClaimMapper.java index 9087fcdc..79244ea0 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanClaimMapper.java +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanClaimMapper.java @@ -15,11 +15,15 @@ public interface DailyPlanClaimMapper extends BaseMapperX { DailyPlanClaimDO selectByToken(@Param("claimToken") String claimToken); - int confirmClaim(@Param("claimId") Long claimId, @Param("updater") String updater); + DailyPlanClaimDO selectByTokenForUpdate(@Param("claimToken") String claimToken); + + int confirmClaim(@Param("claimId") Long claimId, @Param("version") Integer version, + @Param("updater") String updater); int releaseClaim(@Param("claimToken") String claimToken, @Param("updater") String updater); - int renewClaim(@Param("claimToken") String claimToken, @Param("expireTime") LocalDateTime expireTime, + int renewClaim(@Param("claimToken") String claimToken, @Param("version") Integer version, + @Param("expireTime") LocalDateTime expireTime, @Param("updater") String updater); int expireClaims(@Param("now") LocalDateTime now, @Param("updater") String updater); diff --git a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanMapper.java b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanMapper.java index 0991df5f..db1436f9 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanMapper.java +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanMapper.java @@ -128,16 +128,16 @@ public interface DailyPlanMapper extends BaseMapperX { /** * 原子增加已备货数量,并在达到总量时完成备货。 */ - int increaseStockedQuantity(@Param("dailyPlanId") Long dailyPlanId, - @Param("incrementQty") Integer incrementQty, - @Param("version") Integer version, - @Param("updater") String updater); + int increaseStockedQuantityByClaim(@Param("dailyPlanId") Long dailyPlanId, + @Param("incrementQty") Integer incrementQty, + @Param("expectedQty") Integer expectedQty, + @Param("updater") String updater); /** * 原子增加已套管数量,并在达到总量时完成套管。 */ - int increaseSleevedQuantity(@Param("dailyPlanId") Long dailyPlanId, - @Param("incrementQty") Integer incrementQty, - @Param("version") Integer version, - @Param("updater") String updater); + int increaseSleevedQuantityByClaim(@Param("dailyPlanId") Long dailyPlanId, + @Param("incrementQty") Integer incrementQty, + @Param("expectedQty") Integer expectedQty, + @Param("updater") String updater); } diff --git a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleService.java b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleService.java index 117f786b..91e8932d 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleService.java +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleService.java @@ -10,10 +10,10 @@ public interface DailyPlanScheduleService { DailyPlanScheduleCandidate getNextSleevingCandidate(String operator); /** 原子增加已备货数量。 */ - void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, String operator); + void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator); /** 原子增加已套管数量。 */ - void increaseSleevedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, String operator); + void increaseSleevedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator); /** 延长仍在执行中的领取租约。 */ void renewClaim(String claimToken, String operator); diff --git a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleServiceImpl.java b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleServiceImpl.java index 5f126a5f..fd8272e0 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleServiceImpl.java +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleServiceImpl.java @@ -8,10 +8,8 @@ import cn.code.nl.module.lms.dal.mysql.dailyplan.DailyPlanClaimMapper; 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.IdUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.IdWorker; @@ -28,10 +26,10 @@ import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.ex 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_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; 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; @@ -44,6 +42,7 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { private static final String TARGET_SLEEVING = "SLEEVING"; private static final String TYPE_PROGRESS = "PROGRESS"; private static final int CLAIM_ACTIVE = 0; + private static final int CLAIM_CONFIRMED = 1; @Resource private DailyPlanMapper dailyPlanMapper; @@ -128,6 +127,7 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { claim.setStrategyType(type.getCode()); claim.setClaimedQty(quantity); claim.setPlanVersion(plan.getVersion()); + claim.setProgressQty(type == DailyPlanStrategyTypeEnum.STOCKING ? plan.getStockedQty() : plan.getSleevedQty()); claim.setExpireTime(expireTime); claim.setOperator(normalizedOperator(operator)); try { @@ -154,56 +154,53 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { @Override @Transactional(rollbackFor = Exception.class) - public void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, - String operator) { - increaseQuantity(claimToken, dailyPlanId, quantity, version, operator, DailyPlanStrategyTypeEnum.STOCKING); + public void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator) { + increaseQuantity(claimToken, dailyPlanId, quantity, operator, DailyPlanStrategyTypeEnum.STOCKING); } @Override @Transactional(rollbackFor = Exception.class) - public void increaseSleevedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, - String operator) { - increaseQuantity(claimToken, dailyPlanId, quantity, version, operator, DailyPlanStrategyTypeEnum.SLEEVING); + public void increaseSleevedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator) { + increaseQuantity(claimToken, dailyPlanId, quantity, operator, DailyPlanStrategyTypeEnum.SLEEVING); } - private void increaseQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, String operator, + private void increaseQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator, DailyPlanStrategyTypeEnum type) { if (quantity == null || quantity <= 0) { throw exception(DAILY_PLAN_PROGRESS_QTY_NOT_POSITIVE); } - DailyPlanClaimDO claim = validateActiveClaim(claimToken); + DailyPlanClaimDO claim = getClaimForUpdate(claimToken); if (!claim.getDailyPlanId().equals(dailyPlanId) || !claim.getStrategyType().equals(type.getCode()) - || !claim.getClaimedQty().equals(quantity) || !claim.getPlanVersion().equals(version)) { + || !claim.getClaimedQty().equals(quantity)) { throw exception(DAILY_PLAN_CLAIM_MISMATCH); } + if (Integer.valueOf(CLAIM_CONFIRMED).equals(claim.getClaimStatus())) { + return; // 同一 claim 的重复确认幂等成功,绝不重复累计。 + } + validateActiveClaim(claim); + if (dailyPlanClaimMapper.confirmClaim(claim.getClaimId(), claim.getVersion(), normalizedOperator(operator)) != 1) { + DailyPlanClaimDO latest = getClaim(claimToken); + if (Integer.valueOf(CLAIM_CONFIRMED).equals(latest.getClaimStatus())) { + return; + } + throw exception(DAILY_PLAN_CLAIM_INVALID); + } DailyPlanDO plan = dailyPlanMapper.selectDailyPlanById(dailyPlanId); if (plan == null) { throw exception(DAILY_PLAN_NOT_EXISTS); } - if (!plan.getVersion().equals(claim.getPlanVersion())) { - // 领取后的任何计划变更都会使旧领取失效;不确认 claim,调用方可处理冲突后主动释放或续租。 - 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) { + if ((long) claim.getProgressQty() + 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)); + ? dailyPlanMapper.increaseStockedQuantityByClaim(dailyPlanId, quantity, claim.getProgressQty(), + normalizedOperator(operator)) + : dailyPlanMapper.increaseSleevedQuantityByClaim(dailyPlanId, quantity, claim.getProgressQty(), + normalizedOperator(operator)); if (affected != 1) { - throw exception(DAILY_PLAN_VERSION_CONFLICT); - } - if (dailyPlanClaimMapper.confirmClaim(claim.getClaimId(), normalizedOperator(operator)) != 1) { - throw exception(DAILY_PLAN_CLAIM_INVALID); + throw exception(DAILY_PLAN_CLAIM_PROGRESS_CONFLICT); } insertProgressLog(dailyPlanId, type, before, before + quantity, quantity, operator); } @@ -211,9 +208,11 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { @Override @Transactional(rollbackFor = Exception.class) public void renewClaim(String claimToken, String operator) { - validateActiveClaim(claimToken); + DailyPlanClaimDO claim = getClaimForUpdate(claimToken); + validateActiveClaim(claim); LocalDateTime expireTime = nextClaimExpireTime(); - if (dailyPlanClaimMapper.renewClaim(claimToken, expireTime, normalizedOperator(operator)) != 1) { + if (dailyPlanClaimMapper.renewClaim(claimToken, claim.getVersion(), expireTime, + normalizedOperator(operator)) != 1) { throw exception(DAILY_PLAN_CLAIM_INVALID); } } @@ -236,18 +235,35 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { return dailyPlanClaimMapper.expireClaims(LocalDateTime.now(), normalizedOperator(operator)); } - private DailyPlanClaimDO validateActiveClaim(String claimToken) { + private DailyPlanClaimDO getClaim(String claimToken) { if (StrUtil.isBlank(claimToken)) { throw exception(DAILY_PLAN_CLAIM_INVALID); } DailyPlanClaimDO claim = dailyPlanClaimMapper.selectByToken(claimToken); - if (claim == null || !Integer.valueOf(CLAIM_ACTIVE).equals(claim.getClaimStatus()) - || claim.getActiveFlag() == null || !claim.getExpireTime().isAfter(LocalDateTime.now())) { + if (claim == null) { throw exception(DAILY_PLAN_CLAIM_INVALID); } return claim; } + private DailyPlanClaimDO getClaimForUpdate(String claimToken) { + if (StrUtil.isBlank(claimToken)) { + throw exception(DAILY_PLAN_CLAIM_INVALID); + } + DailyPlanClaimDO claim = dailyPlanClaimMapper.selectByTokenForUpdate(claimToken); + if (claim == null) { + throw exception(DAILY_PLAN_CLAIM_INVALID); + } + return claim; + } + + private void validateActiveClaim(DailyPlanClaimDO claim) { + if (!Integer.valueOf(CLAIM_ACTIVE).equals(claim.getClaimStatus()) || claim.getActiveFlag() == null + || !claim.getExpireTime().isAfter(LocalDateTime.now())) { + throw exception(DAILY_PLAN_CLAIM_INVALID); + } + } + private LocalDateTime nextClaimExpireTime() { return LocalDateTime.now().plusSeconds(Math.max(1L, claimTimeoutSeconds)); } diff --git a/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanClaimMapper.xml b/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanClaimMapper.xml index 75a63eb7..c72695a4 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanClaimMapper.xml +++ b/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanClaimMapper.xml @@ -3,18 +3,18 @@ - claim_id, claim_token, daily_plan_id, strategy_type, claimed_qty, plan_version, + 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, creator, create_time, updater, update_time, deleted, tenant_id INSERT INTO lms_daily_plan_claim - (claim_id, claim_token, daily_plan_id, strategy_type, claimed_qty, plan_version, + (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) VALUES (#{claim.claimId}, #{claim.claimToken}, #{claim.dailyPlanId}, #{claim.strategyType}, - #{claim.claimedQty}, #{claim.planVersion}, 0, 1, #{claim.expireTime}, + #{claim.claimedQty}, #{claim.planVersion}, #{claim.progressQty}, 0, 0, 1, #{claim.expireTime}, #{claim.operator}, #{claim.operator}, #{claim.operator}) @@ -24,10 +24,17 @@ WHERE claim_token = #{claimToken} AND deleted = b'0' + + UPDATE lms_daily_plan_claim - SET claim_status = 1, active_flag = NULL, confirmed_time = NOW(), updater = #{updater} - WHERE claim_id = #{claimId} AND claim_status = 0 AND active_flag = 1 + 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 expire_time > NOW() AND deleted = b'0' @@ -39,8 +46,8 @@ UPDATE lms_daily_plan_claim - SET expire_time = #{expireTime}, updater = #{updater} - WHERE claim_token = #{claimToken} AND claim_status = 0 AND active_flag = 1 + SET expire_time = #{expireTime}, updater = #{updater}, version = version + 1 + WHERE claim_token = #{claimToken} AND version = #{version} AND claim_status = 0 AND active_flag = 1 AND expire_time > NOW() AND deleted = b'0' diff --git a/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanMapper.xml b/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanMapper.xml index 085a7769..3d79b084 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanMapper.xml +++ b/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanMapper.xml @@ -236,42 +236,42 @@ WHERE daily_plan_id = #{dailyPlanId} AND version = #{version} AND deleted = b'0' - + UPDATE lms_daily_plan SET stocking_status = CASE - WHEN stocked_qty + #{incrementQty} = plan_qty + append_qty THEN 2 + WHEN stocking_status = 1 AND stocked_qty + #{incrementQty} = plan_qty + append_qty THEN 2 ELSE stocking_status END, stocking_complete_time = CASE - WHEN stocked_qty + #{incrementQty} = plan_qty + append_qty THEN NOW() + WHEN stocking_status = 1 AND stocked_qty + #{incrementQty} = plan_qty + append_qty THEN NOW() ELSE stocking_complete_time END, stocked_qty = stocked_qty + #{incrementQty}, updater = #{updater}, version = version + 1 WHERE daily_plan_id = #{dailyPlanId} - AND version = #{version} AND deleted = b'0' - AND order_status = 1 - AND stocking_status = 1 + AND order_status IN (1, 2, 3) + AND stocking_status IN (1, 2, 3) + AND stocked_qty = #{expectedQty} AND #{incrementQty} > 0 AND stocked_qty + #{incrementQty} <= plan_qty + append_qty - + UPDATE lms_daily_plan SET sleeving_status = CASE - WHEN sleeved_qty + #{incrementQty} = plan_qty + append_qty THEN 2 + WHEN sleeving_status = 1 AND sleeved_qty + #{incrementQty} = plan_qty + append_qty THEN 2 ELSE sleeving_status END, sleeving_complete_time = CASE - WHEN sleeved_qty + #{incrementQty} = plan_qty + append_qty THEN NOW() + WHEN sleeving_status = 1 AND sleeved_qty + #{incrementQty} = plan_qty + append_qty THEN NOW() ELSE sleeving_complete_time END, sleeved_qty = sleeved_qty + #{incrementQty}, updater = #{updater}, version = version + 1 WHERE daily_plan_id = #{dailyPlanId} - AND version = #{version} AND deleted = b'0' - AND order_status = 1 - AND sleeving_status = 1 + AND order_status IN (1, 2, 3) + AND sleeving_status IN (1, 2, 3) + AND sleeved_qty = #{expectedQty} AND #{incrementQty} > 0 AND sleeved_qty + #{incrementQty} <= plan_qty + append_qty diff --git a/sql/mysql/lms_daily_plan.sql b/sql/mysql/lms_daily_plan.sql index 31d5501d..834335cc 100644 --- a/sql/mysql/lms_daily_plan.sql +++ b/sql/mysql/lms_daily_plan.sql @@ -65,7 +65,9 @@ CREATE TABLE IF NOT EXISTS `lms_daily_plan_claim` ( `daily_plan_id` bigint NOT NULL COMMENT '日计划ID', `strategy_type` tinyint NOT NULL COMMENT '作业类型:1备货,2套管', `claimed_qty` int NOT NULL COMMENT '本次预占数量', - `plan_version` int NOT NULL COMMENT '领取时日计划版本号', + `plan_version` int NOT NULL COMMENT '领取时日计划版本号(仅审计,不作为确认并发边界)', + `progress_qty` int NOT NULL COMMENT '领取时对应作业已完成数量', + `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 '有效领取唯一标记,终态置空', `expire_time` datetime NOT NULL COMMENT '租约过期时间', @@ -108,6 +110,22 @@ 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 = 'progress_qty'), + 'SELECT 1', + 'ALTER TABLE lms_daily_plan_claim ADD COLUMN progress_qty INT NOT NULL DEFAULT 0 COMMENT ''领取时对应作业已完成数量'' AFTER plan_version'); +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'), + 'SELECT 1', + 'ALTER TABLE lms_daily_plan_claim ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT ''领取乐观锁版本号'' AFTER progress_qty'); +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.statistics WHERE table_schema = DATABASE() AND table_name = 'lms_daily_plan' AND index_name = 'idx_sleeving_candidate'),