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 78bfaaa0..5f5c6eaa 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 @@ -62,4 +62,7 @@ public interface ErrorCodeConstants { ErrorCode DAILY_PLAN_CODE_GENERATE_FAILED = new ErrorCode(37, "日计划编码生成失败"); ErrorCode DAILY_PLAN_STRATEGY_VERSION_CONFLICT = new ErrorCode(38, "日计划策略已变化,请重新获取调度候选"); ErrorCode DAILY_PLAN_PROGRESS_QTY_NOT_POSITIVE = new ErrorCode(39, "日计划作业进度数量必须为正整数"); + ErrorCode DAILY_PLAN_CLAIM_CONFLICT = new ErrorCode(40, "日计划已被其他调度器领取,请重新获取候选"); + ErrorCode DAILY_PLAN_CLAIM_INVALID = new ErrorCode(41, "日计划领取标识无效、已结束或已过期"); + ErrorCode DAILY_PLAN_CLAIM_MISMATCH = new ErrorCode(42, "日计划领取信息与进度确认不一致"); } 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 new file mode 100644 index 00000000..2cba12ad --- /dev/null +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/dataobject/dailyplan/DailyPlanClaimDO.java @@ -0,0 +1,27 @@ +package cn.code.nl.module.lms.dal.dataobject.dailyplan; + +import cn.code.nl.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** LMS 日计划调度领取 DO。 */ +@TableName("lms_daily_plan_claim") +@Data +public class DailyPlanClaimDO extends TenantBaseDO { + + @TableId + private Long claimId; + private String claimToken; + private Long dailyPlanId; + private Integer strategyType; + private Integer claimedQty; + private Integer planVersion; + private Integer claimStatus; + private Integer activeFlag; + private LocalDateTime expireTime; + private LocalDateTime confirmedTime; + private String operator; +} 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 new file mode 100644 index 00000000..9087fcdc --- /dev/null +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/dailyplan/DailyPlanClaimMapper.java @@ -0,0 +1,26 @@ +package cn.code.nl.module.lms.dal.mysql.dailyplan; + +import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX; +import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanClaimDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +/** LMS 日计划调度领取 Mapper。 */ +@Mapper +public interface DailyPlanClaimMapper extends BaseMapperX { + + int insertClaim(@Param("claim") DailyPlanClaimDO claim); + + DailyPlanClaimDO selectByToken(@Param("claimToken") String claimToken); + + int confirmClaim(@Param("claimId") Long claimId, @Param("updater") String updater); + + int releaseClaim(@Param("claimToken") String claimToken, @Param("updater") String updater); + + int renewClaim(@Param("claimToken") String claimToken, @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/service/dailyplan/DailyPlanScheduleCandidate.java b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleCandidate.java index bcfe72d2..395e4c66 100644 --- a/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleCandidate.java +++ b/nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/dailyplan/DailyPlanScheduleCandidate.java @@ -2,6 +2,8 @@ package cn.code.nl.module.lms.service.dailyplan; import lombok.Data; +import java.time.LocalDateTime; + /** 供日计划定时调度器使用的候选任务。 */ @Data public class DailyPlanScheduleCandidate { @@ -24,4 +26,8 @@ public class DailyPlanScheduleCandidate { private Integer version; /** 本轮使用的策略模式。 */ private Integer strategyMode; + /** 本次排他领取标识,确认进度时必须原样传回。 */ + private String claimToken; + /** 领取租约过期时间。 */ + private LocalDateTime claimExpireTime; } 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 61184869..117f786b 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,8 +10,17 @@ public interface DailyPlanScheduleService { DailyPlanScheduleCandidate getNextSleevingCandidate(String operator); /** 原子增加已备货数量。 */ - void increaseStockedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator); + void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, String operator); /** 原子增加已套管数量。 */ - void increaseSleevedQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator); + void increaseSleevedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, String operator); + + /** 延长仍在执行中的领取租约。 */ + void renewClaim(String claimToken, String operator); + + /** 主动释放未执行的领取,使计划可重新参与调度。 */ + void releaseClaim(String claimToken, String operator); + + /** 回收超时领取,返回回收数量;供后续定时器调用。 */ + int releaseExpiredClaims(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 1ba30512..6253fd8a 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 @@ -1,8 +1,10 @@ package cn.code.nl.module.lms.service.dailyplan; +import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanClaimDO; 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.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; @@ -10,15 +12,22 @@ 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; import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; 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_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_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; @@ -34,23 +43,30 @@ 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"; + private static final int CLAIM_ACTIVE = 0; @Resource private DailyPlanMapper dailyPlanMapper; @Resource + private DailyPlanClaimMapper dailyPlanClaimMapper; + @Resource private DailyPlanStrategyMapper dailyPlanStrategyMapper; @Resource private DailyPlanOperationLogMapper dailyPlanOperationLogMapper; + @Value("${lms.daily-plan.claim-timeout-seconds:300}") + private long claimTimeoutSeconds; @Override @Transactional(rollbackFor = Exception.class) public DailyPlanScheduleCandidate getNextStockingCandidate(String operator) { + expireClaims(operator); return selectCandidate(DailyPlanStrategyTypeEnum.STOCKING, dailyPlanMapper.selectStockingCandidates(), operator); } @Override @Transactional(rollbackFor = Exception.class) public DailyPlanScheduleCandidate getNextSleevingCandidate(String operator) { + expireClaims(operator); return selectCandidate(DailyPlanStrategyTypeEnum.SLEEVING, dailyPlanMapper.selectSleevingCandidates(), operator); } @@ -69,20 +85,21 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { } if (mode == DailyPlanStrategyModeEnum.SEQUENCE) { DailyPlanDO selected = candidates.get(0); - return convertCandidate(selected, remaining(selected, type), mode.getCode()); + return claimCandidate(selected, remaining(selected, 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 affected = dailyPlanStrategyMapper.updateCursorWithVersion(strategy.getStrategyId(), - next.getDailyPlanId(), strategy.getVersion(), normalizedOperator(operator)); + int quantity = Math.min(selected.getWeight(), remaining(selected, type)); + DailyPlanScheduleCandidate candidate = claimCandidate(selected, quantity, mode.getCode(), type, operator); + 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()); + return candidate; } private int findCursorIndex(List candidates, Long cursorDailyPlanId) { @@ -101,7 +118,25 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { return plan.getPlanQty() + plan.getAppendQty() - completed; } - private DailyPlanScheduleCandidate convertCandidate(DailyPlanDO plan, int quantity, Integer strategyMode) { + private DailyPlanScheduleCandidate claimCandidate(DailyPlanDO plan, int quantity, Integer strategyMode, + DailyPlanStrategyTypeEnum type, String operator) { + LocalDateTime expireTime = nextClaimExpireTime(); + DailyPlanClaimDO claim = new DailyPlanClaimDO(); + claim.setClaimId(IdWorker.getId()); + claim.setClaimToken(IdUtil.fastSimpleUUID()); + claim.setDailyPlanId(plan.getDailyPlanId()); + claim.setStrategyType(type.getCode()); + claim.setClaimedQty(quantity); + claim.setPlanVersion(plan.getVersion()); + claim.setExpireTime(expireTime); + claim.setOperator(normalizedOperator(operator)); + try { + if (dailyPlanClaimMapper.insertClaim(claim) != 1) { + throw exception(DAILY_PLAN_CLAIM_CONFLICT); + } + } catch (DuplicateKeyException ex) { + throw exception(DAILY_PLAN_CLAIM_CONFLICT); + } DailyPlanScheduleCandidate candidate = new DailyPlanScheduleCandidate(); candidate.setDailyPlanId(plan.getDailyPlanId()); candidate.setPlanCode(plan.getPlanCode()); @@ -112,33 +147,39 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { candidate.setSuggestedQuantity(quantity); candidate.setVersion(plan.getVersion()); candidate.setStrategyMode(strategyMode); + candidate.setClaimToken(claim.getClaimToken()); + candidate.setClaimExpireTime(expireTime); 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); + public void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, + String operator) { + increaseQuantity(claimToken, 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); + public void increaseSleevedQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, + String operator) { + increaseQuantity(claimToken, dailyPlanId, quantity, version, operator, DailyPlanStrategyTypeEnum.SLEEVING); } - private void increaseQuantity(Long dailyPlanId, Integer quantity, Integer version, String operator, + private void increaseQuantity(String claimToken, Long dailyPlanId, Integer quantity, Integer version, String operator, DailyPlanStrategyTypeEnum type) { if (quantity == null || quantity <= 0) { throw exception(DAILY_PLAN_PROGRESS_QTY_NOT_POSITIVE); } + DailyPlanClaimDO claim = validateActiveClaim(claimToken); + if (!claim.getDailyPlanId().equals(dailyPlanId) || !claim.getStrategyType().equals(type.getCode()) + || !claim.getClaimedQty().equals(quantity) || !claim.getPlanVersion().equals(version)) { + throw exception(DAILY_PLAN_CLAIM_MISMATCH); + } 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()) @@ -152,14 +193,61 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService { 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.increaseStockedQuantity(dailyPlanId, quantity, plan.getVersion(), normalizedOperator(operator)) + : dailyPlanMapper.increaseSleevedQuantity(dailyPlanId, quantity, plan.getVersion(), 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); + } insertProgressLog(dailyPlanId, type, before, before + quantity, quantity, operator); } + @Override + @Transactional(rollbackFor = Exception.class) + public void renewClaim(String claimToken, String operator) { + validateActiveClaim(claimToken); + LocalDateTime expireTime = nextClaimExpireTime(); + if (dailyPlanClaimMapper.renewClaim(claimToken, expireTime, normalizedOperator(operator)) != 1) { + throw exception(DAILY_PLAN_CLAIM_INVALID); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void releaseClaim(String claimToken, String operator) { + if (dailyPlanClaimMapper.releaseClaim(claimToken, normalizedOperator(operator)) != 1) { + throw exception(DAILY_PLAN_CLAIM_INVALID); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int releaseExpiredClaims(String operator) { + return expireClaims(operator); + } + + private int expireClaims(String operator) { + return dailyPlanClaimMapper.expireClaims(LocalDateTime.now(), normalizedOperator(operator)); + } + + private DailyPlanClaimDO validateActiveClaim(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())) { + throw exception(DAILY_PLAN_CLAIM_INVALID); + } + return claim; + } + + private LocalDateTime nextClaimExpireTime() { + return LocalDateTime.now().plusSeconds(Math.max(1L, claimTimeoutSeconds)); + } + private void insertProgressLog(Long dailyPlanId, DailyPlanStrategyTypeEnum type, int before, int after, int quantity, String operator) { DailyPlanOperationLogDO log = new DailyPlanOperationLogDO(); 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 new file mode 100644 index 00000000..75a63eb7 --- /dev/null +++ b/nl-module-lms/nl-module-lms-server/src/main/resources/mapper/dailyplan/DailyPlanClaimMapper.xml @@ -0,0 +1,53 @@ + + + + + + claim_id, claim_token, daily_plan_id, strategy_type, claimed_qty, plan_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_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.operator}, #{claim.operator}, #{claim.operator}) + + + + + + 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 + AND expire_time > NOW() AND deleted = b'0' + + + + UPDATE lms_daily_plan_claim + SET claim_status = 2, active_flag = NULL, updater = #{updater} + WHERE claim_token = #{claimToken} AND claim_status = 0 AND active_flag = 1 AND deleted = b'0' + + + + UPDATE lms_daily_plan_claim + SET expire_time = #{expireTime}, updater = #{updater} + WHERE claim_token = #{claimToken} AND claim_status = 0 AND active_flag = 1 + AND expire_time > NOW() AND deleted = b'0' + + + + 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 <= #{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 c95a48c4..085a7769 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 @@ -93,6 +93,13 @@ AND stocking_status = 1 AND stocked_qty < plan_qty + append_qty AND deleted = b'0' + 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 = 1 AND claim.claim_status = 0 + AND claim.active_flag = 1 AND claim.expire_time > NOW() AND claim.deleted = b'0' + ) ORDER BY sort_seq ASC, daily_plan_id ASC @@ -104,6 +111,13 @@ AND sleeving_status = 1 AND sleeved_qty < plan_qty + append_qty AND deleted = b'0' + 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 = 2 AND claim.claim_status = 0 + AND claim.active_flag = 1 AND claim.expire_time > NOW() AND claim.deleted = b'0' + ) ORDER BY sort_seq ASC, daily_plan_id ASC diff --git a/sql/mysql/lms_daily_plan.sql b/sql/mysql/lms_daily_plan.sql index 7bf4d66e..31d5501d 100644 --- a/sql/mysql/lms_daily_plan.sql +++ b/sql/mysql/lms_daily_plan.sql @@ -38,7 +38,9 @@ CREATE TABLE IF NOT EXISTS `lms_daily_plan` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`daily_plan_id`), UNIQUE KEY `uk_plan_code_tenant` (`plan_code`, `tenant_id`), - UNIQUE KEY `uk_erp_order_code_tenant` (`erp_order_code`, `tenant_id`) + UNIQUE KEY `uk_erp_order_code_tenant` (`erp_order_code`, `tenant_id`), + KEY `idx_stocking_candidate` (`tenant_id`, `deleted`, `order_status`, `stocking_status`, `sort_seq`, `daily_plan_id`), + KEY `idx_sleeving_candidate` (`tenant_id`, `deleted`, `order_status`, `sleeving_status`, `sort_seq`, `daily_plan_id`) ) COMMENT='LMS日计划'; CREATE TABLE IF NOT EXISTS `lms_daily_plan_strategy` ( @@ -57,6 +59,30 @@ CREATE TABLE IF NOT EXISTS `lms_daily_plan_strategy` ( UNIQUE KEY `uk_strategy_type_tenant` (`strategy_type`, `tenant_id`) ) COMMENT='LMS日计划调度策略'; +CREATE TABLE IF NOT EXISTS `lms_daily_plan_claim` ( + `claim_id` bigint NOT NULL COMMENT '领取ID', + `claim_token` varchar(64) NOT NULL COMMENT '对外领取标识', + `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 '领取时日计划版本号', + `claim_status` tinyint NOT NULL DEFAULT 0 COMMENT '领取状态:0执行中,1已确认,2已释放,3已过期', + `active_flag` tinyint DEFAULT 1 COMMENT '有效领取唯一标记,终态置空', + `expire_time` datetime NOT NULL COMMENT '租约过期时间', + `confirmed_time` datetime DEFAULT NULL COMMENT '确认时间', + `operator` varchar(64) NOT NULL COMMENT '领取人/执行器', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`claim_id`), + UNIQUE KEY `uk_claim_token_tenant` (`claim_token`, `tenant_id`), + UNIQUE KEY `uk_active_plan_type_tenant` (`daily_plan_id`, `strategy_type`, `active_flag`, `tenant_id`), + KEY `idx_claim_expire` (`tenant_id`, `claim_status`, `expire_time`) +) COMMENT='LMS日计划调度领取'; + CREATE TABLE IF NOT EXISTS `lms_daily_plan_operation_log` ( `operation_log_id` bigint NOT NULL COMMENT '操作日志ID', `daily_plan_id` bigint DEFAULT NULL COMMENT '日计划ID', @@ -73,6 +99,24 @@ CREATE TABLE IF NOT EXISTS `lms_daily_plan_operation_log` ( PRIMARY KEY (`operation_log_id`) ) COMMENT='LMS日计划操作日志'; +-- 兼容已执行过旧版脚本的数据库,幂等补充候选组合索引。 +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'), + 'SELECT 1', + 'ALTER TABLE lms_daily_plan ADD INDEX idx_stocking_candidate (tenant_id, deleted, order_status, stocking_status, sort_seq, daily_plan_id)'); +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'), + 'SELECT 1', + 'ALTER TABLE lms_daily_plan ADD INDEX idx_sleeving_candidate (tenant_id, deleted, order_status, sleeving_status, sort_seq, daily_plan_id)'); +PREPARE lms_daily_plan_stmt FROM @lms_daily_plan_ddl; +EXECUTE lms_daily_plan_stmt; +DEALLOCATE PREPARE lms_daily_plan_stmt; + -- 2093号段用于 LMS 日计划固定基础数据;编码格式:DP + yyyyMMdd + 4位日流水。 INSERT INTO base_code_rule (id, rule_name, rule_code, segment_config, code_separator, letter_case, total_length,