fix: 收紧日计划排程与租户策略边界
This commit is contained in:
@@ -76,7 +76,7 @@
|
||||
- 备货策略。
|
||||
- 套管策略。
|
||||
|
||||
每条配置保存业务类型、当前模式、修改人、修改时间和版本号。当前模式包括“顺序模式”和“权重轮转模式”。数据库唯一约束保证同一租户、同一业务类型只有一条有效配置。
|
||||
每条配置保存业务类型、当前模式、修改人、修改时间和版本号。当前模式包括“顺序模式”和“权重轮转模式”。数据库唯一约束保证同一租户、同一业务类型只有一条有效配置;查询或调度时若当前租户尚无配置,则并发安全地懒初始化备货、套管两类默认顺序策略,已逻辑删除的历史配置不自动恢复或重复插入。
|
||||
|
||||
### 操作记录表 `lms_daily_plan_operation_log`
|
||||
|
||||
@@ -135,7 +135,7 @@ ERP 推送不允许直接修改顺序、权重、当前完成数量或作业状
|
||||
|
||||
## 人工创建和维护
|
||||
|
||||
人工创建字段包括计划日期、人工订单号、管芯物料、计划数量、装箱信息、顺序和权重。人工订单号允许手填;未填写时使用 LMS 日计划编号作为订单号。
|
||||
人工创建字段包括计划日期、人工订单号、管芯物料、计划数量、装箱信息、顺序和权重。人工订单号统一去除首尾空白;未填写、全空白或修改时清空,均使用 LMS 日计划编号作为订单号。
|
||||
|
||||
只有未开始的人工来源日计划允许编辑基本信息。ERP 来源计划通过 ERP 幂等推送维护,管理页面只读。开始订单时 ERP 和人工来源均允许再次确认计划数量,并可填写追加数量;计划数量、累计追加数量和开始状态在同一事务中原子更新,开始成功后进入日计划作业页面。
|
||||
|
||||
@@ -238,7 +238,7 @@ ERP 推送不允许直接修改顺序、权重、当前完成数量或作业状
|
||||
- 未开始订单不能执行备货、套管、追加和结束作业操作。
|
||||
- 已结束订单不能取消、恢复、追加或重新开始。
|
||||
- 完成数量不能为负数,也不能超过需求总量。
|
||||
- 顺序号和权重必须为正整数。
|
||||
- 顺序号和权重必须为正整数,且只允许对订单状态为“进行中”的计划修改;批量排序任一计划状态不符时整批回滚。
|
||||
- 取消和恢复未填写原因时由参数校验直接拒绝。
|
||||
- 非法状态流转返回业务异常,不做静默幂等成功。
|
||||
- 请求失败沿用全局错误提示,不在前端吞掉异常。
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package cn.code.nl.module.lms.controller.admin.dailyplan.vo;
|
||||
|
||||
import cn.code.nl.module.system.api.user.AdminUserApi;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
import org.dromara.core.trans.anno.Trans;
|
||||
import org.dromara.core.trans.constant.TransType;
|
||||
import org.dromara.core.trans.vo.VO;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@@ -47,7 +44,6 @@ public class DailyPlanRespVO implements VO {
|
||||
private Integer version;
|
||||
private String creator;
|
||||
private LocalDateTime createTime;
|
||||
@Trans(type = TransType.AUTO_TRANS, key = AdminUserApi.PREFIX, fields = "nickname", ref = "updaterName")
|
||||
private String updater;
|
||||
private String updaterName;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@@ -16,6 +16,9 @@ public interface DailyPlanStrategyMapper extends BaseMapperX<DailyPlanStrategyDO
|
||||
*/
|
||||
DailyPlanStrategyDO selectByStrategyType(@Param("strategyType") Integer strategyType);
|
||||
|
||||
/** 查询包含逻辑删除记录的策略,用于避免唯一键冲突时误补。 */
|
||||
DailyPlanStrategyDO selectAnyByStrategyType(@Param("strategyType") Integer strategyType);
|
||||
|
||||
/**
|
||||
* 乐观锁更新策略模式并清空游标。
|
||||
*/
|
||||
|
||||
@@ -53,6 +53,8 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
|
||||
private DailyPlanStrategyMapper dailyPlanStrategyMapper;
|
||||
@Resource
|
||||
private DailyPlanOperationLogMapper dailyPlanOperationLogMapper;
|
||||
@Resource
|
||||
private DailyPlanStrategyInitializer strategyInitializer;
|
||||
@Value("${lms.daily-plan.claim-timeout-seconds:300}")
|
||||
private long claimTimeoutSeconds;
|
||||
|
||||
@@ -72,10 +74,7 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
|
||||
|
||||
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);
|
||||
}
|
||||
DailyPlanStrategyDO strategy = strategyInitializer.getOrCreate(type);
|
||||
if (candidates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
@Resource
|
||||
private DailyPlanOperationLogMapper dailyPlanOperationLogMapper;
|
||||
@Resource
|
||||
private DailyPlanStrategyInitializer strategyInitializer;
|
||||
@Resource
|
||||
private CodeGenApi codeGenApi;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@@ -207,6 +209,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
for (DailyPlanDO plan : page.getList()) {
|
||||
result.add(convertPlan(plan));
|
||||
}
|
||||
resolvePlanUpdaterNames(result);
|
||||
return new PageResult<>(result, page.getTotal());
|
||||
}
|
||||
|
||||
@@ -222,6 +225,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
for (DailyPlanDO plan : page.getList()) {
|
||||
result.add(convertPlan(plan));
|
||||
}
|
||||
resolvePlanUpdaterNames(result);
|
||||
return new PageResult<>(result, page.getTotal());
|
||||
}
|
||||
|
||||
@@ -232,7 +236,9 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
if (plan == null) {
|
||||
throw exception(DAILY_PLAN_NOT_EXISTS);
|
||||
}
|
||||
return convertPlan(plan);
|
||||
DailyPlanRespVO result = convertPlan(plan);
|
||||
resolvePlanUpdaterNames(List.of(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 查询日计划操作日志。 */
|
||||
@@ -273,9 +279,12 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
if (ERP_OPERATOR.equalsIgnoreCase(operator)) {
|
||||
return "ERP";
|
||||
}
|
||||
if ("SCHEDULER".equalsIgnoreCase(operator) || "系统调度".equals(operator)) {
|
||||
return "系统调度";
|
||||
}
|
||||
Long operatorId = parseOperatorId(operator);
|
||||
if (operatorId == null) {
|
||||
return "系统";
|
||||
return StrUtil.blankToDefault(operator, "系统");
|
||||
}
|
||||
AdminUserRespDTO user = userMap.get(operatorId);
|
||||
return user == null ? "未知用户" : user.getNickname();
|
||||
@@ -297,10 +306,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
public List<DailyPlanStrategyRespVO> getStrategies() {
|
||||
List<DailyPlanStrategyRespVO> result = new ArrayList<>(2);
|
||||
for (DailyPlanStrategyTypeEnum type : DailyPlanStrategyTypeEnum.values()) {
|
||||
DailyPlanStrategyDO strategy = dailyPlanStrategyMapper.selectByStrategyType(type.getCode());
|
||||
if (strategy == null) {
|
||||
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
||||
}
|
||||
DailyPlanStrategyDO strategy = strategyInitializer.getOrCreate(type);
|
||||
result.add(convertStrategy(strategy));
|
||||
}
|
||||
return result;
|
||||
@@ -316,7 +322,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
plan.setDailyPlanId(IdWorker.getId());
|
||||
plan.setPlanCode(planCode);
|
||||
plan.setSourceType(DailyPlanSourceTypeEnum.MANUAL.getCode());
|
||||
plan.setManualOrderCode(StrUtil.isEmpty(reqVO.getManualOrderCode()) ? planCode : reqVO.getManualOrderCode());
|
||||
plan.setManualOrderCode(StrUtil.blankToDefault(StrUtil.trim(reqVO.getManualOrderCode()), planCode));
|
||||
setEditableFields(plan, reqVO);
|
||||
plan.setCreator(userId);
|
||||
plan.setUpdater(userId);
|
||||
@@ -353,7 +359,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
DailyPlanDO update = new DailyPlanDO();
|
||||
update.setDailyPlanId(reqVO.getDailyPlanId());
|
||||
update.setVersion(reqVO.getVersion());
|
||||
update.setManualOrderCode(reqVO.getManualOrderCode());
|
||||
update.setManualOrderCode(StrUtil.blankToDefault(StrUtil.trim(reqVO.getManualOrderCode()), current.getPlanCode()));
|
||||
setEditableFields(update, reqVO);
|
||||
update.setUpdater(String.valueOf(getLoginUserId()));
|
||||
checkAffected(dailyPlanMapper.updateNotStartedPlan(update));
|
||||
@@ -528,7 +534,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
throw exception(DAILY_PLAN_STATUS_OPERATION_NOT_ALLOWED, item.getId(), "重复修改排序");
|
||||
}
|
||||
DailyPlanDO current = validateVersion(item.getId(), item.getVersion());
|
||||
validateNotFinished(current, "修改排序");
|
||||
validateOrderStatus(current, DailyPlanOrderStatusEnum.IN_PROGRESS, "修改排序");
|
||||
checkAffected(dailyPlanMapper.updateSortWithVersion(current.getDailyPlanId(), item.getSortSeq(),
|
||||
item.getVersion(), String.valueOf(getLoginUserId())));
|
||||
insertLog(current.getDailyPlanId(), TARGET_SORT, TYPE_UPDATE, String.valueOf(current.getSortSeq()),
|
||||
@@ -541,7 +547,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateWeight(DailyPlanWeightUpdateReqVO reqVO) {
|
||||
DailyPlanDO current = validateVersion(reqVO.getId(), reqVO.getVersion());
|
||||
validateNotFinished(current, "修改权重");
|
||||
validateOrderStatus(current, DailyPlanOrderStatusEnum.IN_PROGRESS, "修改权重");
|
||||
checkAffected(dailyPlanMapper.updateWeightWithVersion(current.getDailyPlanId(), reqVO.getWeight(),
|
||||
reqVO.getVersion(), String.valueOf(getLoginUserId())));
|
||||
insertLog(current.getDailyPlanId(), TARGET_WEIGHT, TYPE_UPDATE, String.valueOf(current.getWeight()),
|
||||
@@ -722,6 +728,14 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
checkAffected(dailyPlanOperationLogMapper.insertOperationLog(log));
|
||||
}
|
||||
|
||||
/** 批量解析列表修改人,数字用户走用户服务,系统标识保留可靠的显示名称。 */
|
||||
private void resolvePlanUpdaterNames(List<DailyPlanRespVO> plans) {
|
||||
Set<Long> userIds = plans.stream().map(DailyPlanRespVO::getUpdater)
|
||||
.map(DailyPlanServiceImpl::parseOperatorId).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
Map<Long, AdminUserRespDTO> userMap = userIds.isEmpty() ? Map.of() : adminUserApi.getUserMap(userIds);
|
||||
plans.forEach(plan -> plan.setUpdaterName(resolveOperatorName(plan.getUpdater(), userMap)));
|
||||
}
|
||||
|
||||
/** 将日计划数据对象逐字段转换为响应对象。 */
|
||||
private DailyPlanRespVO convertPlan(DailyPlanDO plan) {
|
||||
DailyPlanRespVO response = new DailyPlanRespVO();
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.code.nl.module.lms.service.dailyplan;
|
||||
|
||||
import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanStrategyDO;
|
||||
import cn.code.nl.module.lms.dal.mysql.dailyplan.DailyPlanStrategyMapper;
|
||||
import cn.code.nl.module.lms.enums.DailyPlanStrategyModeEnum;
|
||||
import cn.code.nl.module.lms.enums.DailyPlanStrategyTypeEnum;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_STRATEGY_NOT_EXISTS;
|
||||
|
||||
/** 按当前租户幂等补齐默认日计划策略。 */
|
||||
@Component
|
||||
public class DailyPlanStrategyInitializer {
|
||||
|
||||
private static final String SYSTEM_OPERATOR = "SCHEDULER";
|
||||
|
||||
@Resource
|
||||
private DailyPlanStrategyMapper strategyMapper;
|
||||
|
||||
public DailyPlanStrategyDO getOrCreate(DailyPlanStrategyTypeEnum type) {
|
||||
DailyPlanStrategyDO current = strategyMapper.selectByStrategyType(type.getCode());
|
||||
if (current != null) {
|
||||
return current;
|
||||
}
|
||||
// 唯一键不包含 deleted;存在已删除记录时不能另插一条,也不能擅自恢复历史配置。
|
||||
if (strategyMapper.selectAnyByStrategyType(type.getCode()) != null) {
|
||||
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
||||
}
|
||||
DailyPlanStrategyDO strategy = new DailyPlanStrategyDO();
|
||||
strategy.setStrategyId(IdWorker.getId());
|
||||
strategy.setStrategyType(type.getCode());
|
||||
strategy.setStrategyMode(DailyPlanStrategyModeEnum.SEQUENCE.getCode());
|
||||
strategy.setVersion(0);
|
||||
strategy.setCreator(SYSTEM_OPERATOR);
|
||||
strategy.setUpdater(SYSTEM_OPERATOR);
|
||||
try {
|
||||
strategyMapper.insert(strategy);
|
||||
return strategy;
|
||||
} catch (DuplicateKeyException ignored) {
|
||||
DailyPlanStrategyDO concurrent = strategyMapper.selectByStrategyType(type.getCode());
|
||||
if (concurrent != null) {
|
||||
return concurrent;
|
||||
}
|
||||
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,13 +263,13 @@
|
||||
<update id="updateSortWithVersion">
|
||||
UPDATE lms_daily_plan
|
||||
SET sort_seq = #{sortSeq}, updater = #{updater}, version = version + 1
|
||||
WHERE daily_plan_id = #{dailyPlanId} AND version = #{version} AND deleted = b'0'
|
||||
WHERE daily_plan_id = #{dailyPlanId} AND version = #{version} AND order_status = 1 AND deleted = b'0'
|
||||
</update>
|
||||
|
||||
<update id="updateWeightWithVersion">
|
||||
UPDATE lms_daily_plan
|
||||
SET weight = #{weight}, updater = #{updater}, version = version + 1
|
||||
WHERE daily_plan_id = #{dailyPlanId} AND version = #{version} AND deleted = b'0'
|
||||
WHERE daily_plan_id = #{dailyPlanId} AND version = #{version} AND order_status = 1 AND deleted = b'0'
|
||||
</update>
|
||||
|
||||
<update id="increaseStockedQuantityByClaim">
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
AND deleted = b'0'
|
||||
</select>
|
||||
|
||||
<select id="selectAnyByStrategyType"
|
||||
resultType="cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanStrategyDO">
|
||||
SELECT <include refid="strategyColumns"/>
|
||||
FROM lms_daily_plan_strategy
|
||||
WHERE strategy_type = #{strategyType}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<update id="updateModeWithVersion">
|
||||
UPDATE lms_daily_plan_strategy
|
||||
SET strategy_mode = #{strategyMode},
|
||||
|
||||
Reference in New Issue
Block a user