2025-11-03 17:03:03 +08:00
|
|
|
|
package com.klp.service.impl;
|
|
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
2025-11-18 14:44:52 +08:00
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
2026-03-12 17:24:14 +08:00
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
2026-01-28 18:40:53 +08:00
|
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
|
import com.klp.common.core.domain.PageQuery;
|
|
|
|
|
|
import com.klp.common.core.page.TableDataInfo;
|
2026-03-12 16:48:16 +08:00
|
|
|
|
import com.klp.common.exception.ServiceException;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
import com.klp.common.helper.LoginHelper;
|
2026-01-28 18:40:53 +08:00
|
|
|
|
import com.klp.common.utils.StringUtils;
|
|
|
|
|
|
import com.klp.domain.WmsCoilPendingAction;
|
2026-01-22 10:23:30 +08:00
|
|
|
|
import com.klp.domain.WmsMaterialCoil;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
import com.klp.domain.bo.WmsCoilPendingActionBo;
|
2026-01-28 18:40:53 +08:00
|
|
|
|
import com.klp.domain.vo.TheoryCyclePointVo;
|
|
|
|
|
|
import com.klp.domain.vo.TheoryCycleRegressionResultVo;
|
|
|
|
|
|
import com.klp.domain.vo.TheoryCycleRegressionVo;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
import com.klp.domain.vo.WmsCoilPendingActionVo;
|
2026-03-25 14:36:53 +08:00
|
|
|
|
import com.klp.domain.vo.WmsCoilPendingActionIdCoilVo;
|
2026-05-19 17:13:37 +08:00
|
|
|
|
import com.klp.domain.DrMillProductionPlan;
|
|
|
|
|
|
import com.klp.domain.WmsRawMaterial;
|
|
|
|
|
|
import com.klp.mapper.DrMillProductionPlanMapper;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
import com.klp.mapper.WmsCoilPendingActionMapper;
|
2026-01-28 18:40:53 +08:00
|
|
|
|
import com.klp.mapper.WmsMaterialCoilMapper;
|
2026-05-19 17:13:37 +08:00
|
|
|
|
import com.klp.mapper.WmsRawMaterialMapper;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
import com.klp.service.IWmsCoilPendingActionService;
|
2026-01-28 18:40:53 +08:00
|
|
|
|
import com.klp.system.service.ISysUserService;
|
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
|
|
|
import org.springframework.stereotype.Service;
|
2026-05-19 14:33:48 +08:00
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
|
2026-01-28 18:40:53 +08:00
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
|
import java.math.RoundingMode;
|
|
|
|
|
|
import java.time.Duration;
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
|
import java.time.ZoneId;
|
2025-12-05 13:50:29 +08:00
|
|
|
|
import java.util.*;
|
2026-01-28 18:40:53 +08:00
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
|
import java.util.regex.Pattern;
|
2025-12-05 13:50:29 +08:00
|
|
|
|
import java.util.stream.Collectors;
|
2025-11-03 17:03:03 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 钢卷待操作Service业务层处理
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author Joshi
|
|
|
|
|
|
* @date 2025-11-03
|
|
|
|
|
|
*/
|
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
|
@Service
|
|
|
|
|
|
public class WmsCoilPendingActionServiceImpl implements IWmsCoilPendingActionService {
|
|
|
|
|
|
|
|
|
|
|
|
private final WmsCoilPendingActionMapper baseMapper;
|
|
|
|
|
|
|
2025-12-05 09:20:30 +08:00
|
|
|
|
private final ISysUserService userService;
|
2026-01-22 10:23:30 +08:00
|
|
|
|
private final WmsMaterialCoilMapper materialCoilMapper;
|
2026-05-19 17:13:37 +08:00
|
|
|
|
private final WmsRawMaterialMapper rawMaterialMapper;
|
|
|
|
|
|
private final DrMillProductionPlanMapper drPlanMapper;
|
2026-01-28 18:40:53 +08:00
|
|
|
|
private final StringRedisTemplate stringRedisTemplate;
|
|
|
|
|
|
|
2026-05-19 17:13:37 +08:00
|
|
|
|
/** 双机架工序 / 修复的 actionType */
|
|
|
|
|
|
private static final int ACTION_TYPE_DR_NORMAL = 504;
|
|
|
|
|
|
private static final int ACTION_TYPE_DR_REPAIR = 524;
|
|
|
|
|
|
|
2026-01-28 18:40:53 +08:00
|
|
|
|
private static final String REDIS_KEY_IDEAL_CYCLE = "oee:ideal-cycle-time";
|
2025-12-05 09:20:30 +08:00
|
|
|
|
|
2025-11-03 17:03:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 查询钢卷待操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public WmsCoilPendingActionVo queryById(Long actionId){
|
|
|
|
|
|
return baseMapper.selectVoById(actionId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询钢卷待操作列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public TableDataInfo<WmsCoilPendingActionVo> queryPageList(WmsCoilPendingActionBo bo, PageQuery pageQuery) {
|
2025-11-18 14:44:52 +08:00
|
|
|
|
QueryWrapper<WmsCoilPendingAction> lqw = buildQueryWrapperPlus(bo);
|
|
|
|
|
|
Page<WmsCoilPendingActionVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), lqw);
|
2025-12-05 13:50:29 +08:00
|
|
|
|
List<WmsCoilPendingActionVo> records = result.getRecords();
|
|
|
|
|
|
Set<String> userNames = records.stream()
|
|
|
|
|
|
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getOperatorName()))
|
|
|
|
|
|
.filter(StringUtils::isNotBlank)
|
|
|
|
|
|
.collect(Collectors.toSet());
|
|
|
|
|
|
if (!userNames.isEmpty()) {
|
|
|
|
|
|
Map<String, String> nickMap = userService.selectNickNameMapByUserNames(records.stream()
|
|
|
|
|
|
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getOperatorName()))
|
|
|
|
|
|
.filter(StringUtils::isNotBlank)
|
|
|
|
|
|
.distinct()
|
|
|
|
|
|
.collect(Collectors.toList()));
|
|
|
|
|
|
records.forEach(item -> {
|
|
|
|
|
|
if (StringUtils.isNotBlank(item.getCreateBy())) {
|
|
|
|
|
|
item.setCreateByName(nickMap.getOrDefault(item.getCreateBy(), item.getCreateBy()));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (StringUtils.isNotBlank(item.getOperatorName())) {
|
|
|
|
|
|
item.setOperatorByName(nickMap.getOrDefault(item.getOperatorName(), item.getOperatorName()));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-11-03 17:03:03 +08:00
|
|
|
|
return TableDataInfo.build(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-18 14:44:52 +08:00
|
|
|
|
private QueryWrapper<WmsCoilPendingAction> buildQueryWrapperPlus(WmsCoilPendingActionBo bo) {
|
|
|
|
|
|
QueryWrapper<WmsCoilPendingAction> qw = Wrappers.query();
|
|
|
|
|
|
qw.eq(bo.getCoilId() != null, "wcpa.coil_id", bo.getCoilId());
|
2026-03-27 16:08:59 +08:00
|
|
|
|
if (StringUtils.isNotBlank(bo.getCoilIds())) {
|
|
|
|
|
|
List<Long> coilIdList = Arrays.stream(bo.getCoilIds().split(","))
|
|
|
|
|
|
.filter(StringUtils::isNotBlank)
|
|
|
|
|
|
.map(Long::parseLong)
|
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
if (!coilIdList.isEmpty()) {
|
|
|
|
|
|
qw.in("wcpa.coil_id", coilIdList);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-18 14:44:52 +08:00
|
|
|
|
qw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), "wcpa.current_coil_no", bo.getCurrentCoilNo());
|
2026-03-24 14:21:28 +08:00
|
|
|
|
if (bo.getActionTypes() != null && !bo.getActionTypes().isEmpty()) {
|
|
|
|
|
|
qw.in("wcpa.action_type", bo.getActionTypes());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
qw.eq(bo.getActionType() != null, "wcpa.action_type", bo.getActionType());
|
|
|
|
|
|
}
|
2026-03-05 09:55:44 +08:00
|
|
|
|
if (bo.getActionStatus() != null) {
|
|
|
|
|
|
if (bo.getActionStatus() == -1) {
|
|
|
|
|
|
qw.ne("wcpa.action_status", 2);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
qw.eq("wcpa.action_status", bo.getActionStatus());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-18 14:44:52 +08:00
|
|
|
|
qw.eq(bo.getWarehouseId() != null, "wcpa.warehouse_id", bo.getWarehouseId());
|
|
|
|
|
|
qw.eq(bo.getPriority() != null, "wcpa.priority", bo.getPriority());
|
|
|
|
|
|
qw.like(StringUtils.isNotBlank(bo.getSourceType()), "wcpa.source_type", bo.getSourceType());
|
|
|
|
|
|
qw.orderByDesc("wcpa.create_time");
|
|
|
|
|
|
qw.orderByDesc("wcpa.scan_time");
|
2026-01-09 18:26:58 +08:00
|
|
|
|
//根据开始时间和结束时间筛选修改时间
|
2026-03-11 11:15:01 +08:00
|
|
|
|
qw.ge(bo.getStartTime() != null, "wcpa.complete_time", bo.getStartTime());
|
|
|
|
|
|
qw.le(bo.getEndTime() != null, "wcpa.complete_time", bo.getEndTime());
|
2026-03-02 13:31:37 +08:00
|
|
|
|
// 根据更新人查询
|
|
|
|
|
|
qw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), "wcpa.update_by", bo.getUpdateBy());
|
2026-04-16 09:15:09 +08:00
|
|
|
|
// 根据创建人筛选(支持逗号分隔的多个创建人)
|
|
|
|
|
|
if (StringUtils.isNotBlank(bo.getCreateBys())) {
|
|
|
|
|
|
List<String> createByList = Arrays.stream(bo.getCreateBys().split(","))
|
|
|
|
|
|
.filter(StringUtils::isNotBlank)
|
|
|
|
|
|
.map(String::trim)
|
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
if (!createByList.isEmpty()) {
|
|
|
|
|
|
qw.in("wcpa.create_by", createByList);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-16 13:20:40 +08:00
|
|
|
|
// 加工后的钢卷ids
|
|
|
|
|
|
qw.like(StringUtils.isNotBlank(bo.getProcessedCoilIds()), "wcpa.processed_coil_ids", bo.getProcessedCoilIds());
|
2026-03-12 14:29:28 +08:00
|
|
|
|
//逻辑删除 - 支持查询已删除记录
|
|
|
|
|
|
if (bo.getIncludeDeleted() != null) {
|
|
|
|
|
|
if (bo.getIncludeDeleted() == 1) {
|
|
|
|
|
|
// 包含已删除记录:不添加del_flag过滤,查询所有记录
|
|
|
|
|
|
} else if (bo.getIncludeDeleted() == 2) {
|
|
|
|
|
|
// 仅查询已删除记录
|
2026-03-12 16:23:45 +08:00
|
|
|
|
qw.eq("wcpa.del_flag", 2);
|
2026-03-12 14:29:28 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 默认:仅查询正常记录
|
|
|
|
|
|
qw.eq("wcpa.del_flag", 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 未传参数时默认仅查询正常记录
|
|
|
|
|
|
qw.eq("wcpa.del_flag", 0);
|
|
|
|
|
|
}
|
2025-11-18 14:44:52 +08:00
|
|
|
|
return qw;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 17:03:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 查询钢卷待操作列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public List<WmsCoilPendingActionVo> queryList(WmsCoilPendingActionBo bo) {
|
|
|
|
|
|
LambdaQueryWrapper<WmsCoilPendingAction> lqw = buildQueryWrapper(bo);
|
|
|
|
|
|
return baseMapper.selectVoList(lqw);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 14:33:48 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 查询待操作记录中,关联钢卷已是历史钢卷(dataType=0)且操作未完成(actionStatus != 2)的记录
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
@Transactional(readOnly = true)
|
|
|
|
|
|
public TableDataInfo<WmsCoilPendingActionVo> queryStaleActionPageList(PageQuery pageQuery) {
|
|
|
|
|
|
QueryWrapper<WmsCoilPendingAction> lqw = Wrappers.query();
|
|
|
|
|
|
lqw.ne("wcpa.action_status", 2);
|
|
|
|
|
|
lqw.eq("wcpa.del_flag", 0);
|
2026-05-19 15:34:12 +08:00
|
|
|
|
lqw.orderByDesc("wcpa.create_time");
|
|
|
|
|
|
lqw.orderByDesc("wcpa.scan_time");
|
2026-05-19 14:33:48 +08:00
|
|
|
|
Page<WmsCoilPendingActionVo> result = baseMapper.selectStaleActionVoPagePlus(pageQuery.build(), lqw);
|
2026-05-19 15:34:12 +08:00
|
|
|
|
List<WmsCoilPendingActionVo> records = result.getRecords();
|
|
|
|
|
|
if (records != null && !records.isEmpty()) {
|
|
|
|
|
|
Set<String> userNames = records.stream()
|
|
|
|
|
|
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getOperatorName()))
|
|
|
|
|
|
.filter(StringUtils::isNotBlank)
|
|
|
|
|
|
.collect(Collectors.toSet());
|
|
|
|
|
|
if (!userNames.isEmpty()) {
|
|
|
|
|
|
Map<String, String> nickMap = userService.selectNickNameMapByUserNames(records.stream()
|
|
|
|
|
|
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getOperatorName()))
|
|
|
|
|
|
.filter(StringUtils::isNotBlank)
|
|
|
|
|
|
.distinct()
|
|
|
|
|
|
.collect(Collectors.toList()));
|
|
|
|
|
|
records.forEach(item -> {
|
|
|
|
|
|
if (StringUtils.isNotBlank(item.getCreateBy())) {
|
|
|
|
|
|
item.setCreateByName(nickMap.getOrDefault(item.getCreateBy(), item.getCreateBy()));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (StringUtils.isNotBlank(item.getOperatorName())) {
|
|
|
|
|
|
item.setOperatorByName(nickMap.getOrDefault(item.getOperatorName(), item.getOperatorName()));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-19 14:33:48 +08:00
|
|
|
|
return TableDataInfo.build(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 14:36:53 +08:00
|
|
|
|
@Override
|
|
|
|
|
|
public List<WmsCoilPendingActionIdCoilVo> queryActionIdCoilIdList(WmsCoilPendingActionBo bo) {
|
|
|
|
|
|
// 复用与 /list 相同的查询条件(buildQueryWrapperPlus)
|
|
|
|
|
|
QueryWrapper<WmsCoilPendingAction> lqw = buildQueryWrapperPlus(bo);
|
|
|
|
|
|
return baseMapper.selectActionIdCoilIdList(lqw);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 17:03:03 +08:00
|
|
|
|
private LambdaQueryWrapper<WmsCoilPendingAction> buildQueryWrapper(WmsCoilPendingActionBo bo) {
|
|
|
|
|
|
LambdaQueryWrapper<WmsCoilPendingAction> lqw = Wrappers.lambdaQuery();
|
|
|
|
|
|
lqw.eq(bo.getCoilId() != null, WmsCoilPendingAction::getCoilId, bo.getCoilId());
|
|
|
|
|
|
lqw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), WmsCoilPendingAction::getCurrentCoilNo, bo.getCurrentCoilNo());
|
2026-03-24 14:21:28 +08:00
|
|
|
|
if (bo.getActionTypes() != null && !bo.getActionTypes().isEmpty()) {
|
|
|
|
|
|
lqw.in(WmsCoilPendingAction::getActionType, bo.getActionTypes());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
lqw.eq(bo.getActionType() != null, WmsCoilPendingAction::getActionType, bo.getActionType());
|
|
|
|
|
|
}
|
2025-11-03 17:03:03 +08:00
|
|
|
|
lqw.eq(bo.getActionStatus() != null, WmsCoilPendingAction::getActionStatus, bo.getActionStatus());
|
|
|
|
|
|
lqw.eq(bo.getWarehouseId() != null, WmsCoilPendingAction::getWarehouseId, bo.getWarehouseId());
|
|
|
|
|
|
lqw.eq(bo.getPriority() != null, WmsCoilPendingAction::getPriority, bo.getPriority());
|
|
|
|
|
|
lqw.like(StringUtils.isNotBlank(bo.getSourceType()), WmsCoilPendingAction::getSourceType, bo.getSourceType());
|
2025-11-11 14:43:59 +08:00
|
|
|
|
lqw.orderByDesc(WmsCoilPendingAction::getCreateTime);
|
2025-11-03 17:03:03 +08:00
|
|
|
|
lqw.orderByDesc(WmsCoilPendingAction::getScanTime);
|
|
|
|
|
|
return lqw;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增钢卷待操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Boolean insertByBo(WmsCoilPendingActionBo bo) {
|
|
|
|
|
|
WmsCoilPendingAction add = BeanUtil.toBean(bo, WmsCoilPendingAction.class);
|
|
|
|
|
|
validEntityBeforeSave(add);
|
2026-01-22 10:23:30 +08:00
|
|
|
|
if (add.getCoilId() != null){
|
|
|
|
|
|
WmsMaterialCoil materialCoil = materialCoilMapper.selectById(add.getCoilId());
|
2026-01-26 13:03:34 +08:00
|
|
|
|
if (materialCoil.getDataType() == 0) {
|
|
|
|
|
|
throw new RuntimeException("该钢卷为历史钢卷不能被操作");
|
2026-01-22 10:23:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-03 17:03:03 +08:00
|
|
|
|
// 设置默认值
|
|
|
|
|
|
if (add.getActionStatus() == null) {
|
|
|
|
|
|
add.setActionStatus(0); // 默认待处理
|
|
|
|
|
|
}
|
|
|
|
|
|
if (StringUtils.isBlank(add.getSourceType())) {
|
|
|
|
|
|
add.setSourceType("manual"); // 默认手动创建
|
|
|
|
|
|
}
|
|
|
|
|
|
if (add.getScanTime() == null && "scan".equals(add.getSourceType())) {
|
|
|
|
|
|
add.setScanTime(new Date());
|
|
|
|
|
|
}
|
|
|
|
|
|
boolean flag = baseMapper.insert(add) > 0;
|
|
|
|
|
|
if (flag) {
|
|
|
|
|
|
bo.setActionId(add.getActionId());
|
2026-05-19 17:13:37 +08:00
|
|
|
|
// 双机架工序/修复:同步在 double-rack 数据库创建生产计划
|
|
|
|
|
|
if (ACTION_TYPE_DR_NORMAL == add.getActionType() || ACTION_TYPE_DR_REPAIR == add.getActionType()) {
|
|
|
|
|
|
autoCreateDrPlan(add);
|
|
|
|
|
|
}
|
2025-11-03 17:03:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
return flag;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 17:13:37 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 自动创建双机架生产计划(写入 double-rack 数据源)。
|
|
|
|
|
|
* 同时查询钢卷库(wms_material_coil)和原材料库(wms_raw_material),
|
|
|
|
|
|
* 将完整的钢卷/原料规格信息写入计划。
|
|
|
|
|
|
* planNo = "DR" + actionId,保证唯一且可追溯。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@com.baomidou.dynamic.datasource.annotation.DS("double-rack")
|
|
|
|
|
|
private void autoCreateDrPlan(WmsCoilPendingAction action) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
DrMillProductionPlan plan = new DrMillProductionPlan();
|
|
|
|
|
|
plan.setPlanNo("DR" + action.getActionId());
|
|
|
|
|
|
|
|
|
|
|
|
if (action.getCoilId() != null) {
|
|
|
|
|
|
// ① 查钢卷库
|
|
|
|
|
|
WmsMaterialCoil coil = materialCoilMapper.selectById(action.getCoilId());
|
|
|
|
|
|
if (coil != null) {
|
|
|
|
|
|
plan.setInMatNo(coil.getEnterCoilNo() != null ? coil.getEnterCoilNo() : coil.getCurrentCoilNo());
|
|
|
|
|
|
plan.setEnterCoilNo(coil.getEnterCoilNo());
|
|
|
|
|
|
plan.setCurrentCoilNo(coil.getCurrentCoilNo());
|
|
|
|
|
|
|
|
|
|
|
|
// 实测厚度(优先)
|
|
|
|
|
|
if (coil.getActualThickness() != null) {
|
|
|
|
|
|
try { plan.setInMatThick(new java.math.BigDecimal(coil.getActualThickness())); } catch (Exception ignored) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 实测宽度(优先)
|
|
|
|
|
|
plan.setInMatWidth(coil.getActualWidth());
|
|
|
|
|
|
// 净重 > 毛重
|
|
|
|
|
|
plan.setInMatWeight(coil.getNetWeight() != null ? coil.getNetWeight() : coil.getGrossWeight());
|
|
|
|
|
|
// 长度
|
2026-05-27 16:57:17 +08:00
|
|
|
|
plan.setInMatLength(coil.getLength() != null ? BigDecimal.valueOf(coil.getLength()) : null);
|
2026-05-19 17:13:37 +08:00
|
|
|
|
|
|
|
|
|
|
// ② 查原材料库(itemType='raw_material' 时通过 itemId 关联)
|
|
|
|
|
|
if ("raw_material".equals(coil.getItemType()) && coil.getItemId() != null) {
|
|
|
|
|
|
WmsRawMaterial rm = rawMaterialMapper.selectById(coil.getItemId());
|
|
|
|
|
|
if (rm != null) {
|
|
|
|
|
|
// 钢种/合金号
|
|
|
|
|
|
plan.setAlloyNo(rm.getSteelGrade());
|
|
|
|
|
|
// 标称厚度(实测没有时用标称补齐)
|
|
|
|
|
|
if (plan.getInMatThick() == null && rm.getThickness() != null) {
|
|
|
|
|
|
plan.setInMatThick(rm.getThickness());
|
|
|
|
|
|
}
|
|
|
|
|
|
// 标称宽度(实测没有时用标称补齐)
|
|
|
|
|
|
if (plan.getInMatWidth() == null && rm.getWidth() != null) {
|
|
|
|
|
|
plan.setInMatWidth(rm.getWidth());
|
|
|
|
|
|
}
|
|
|
|
|
|
// 卷重(WMS 净重没有时用原材料卷重补齐)
|
|
|
|
|
|
if (plan.getInMatWeight() == null && rm.getCoilWeight() != null) {
|
|
|
|
|
|
plan.setInMatWeight(rm.getCoilWeight());
|
|
|
|
|
|
}
|
|
|
|
|
|
// 目标冷轧厚度 / 宽度写入出口目标厚度
|
|
|
|
|
|
if (rm.getTargetColdThickness() != null) {
|
|
|
|
|
|
plan.setOutThick(rm.getTargetColdThickness());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (action.getCurrentCoilNo() != null) {
|
|
|
|
|
|
plan.setInMatNo(action.getCurrentCoilNo());
|
|
|
|
|
|
plan.setCurrentCoilNo(action.getCurrentCoilNo());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 修复工序在备注中标记
|
|
|
|
|
|
if (ACTION_TYPE_DR_REPAIR == action.getActionType()) {
|
|
|
|
|
|
plan.setRemark("[修复] " + (action.getRemark() != null ? action.getRemark() : ""));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
plan.setRemark(action.getRemark());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String user = LoginHelper.getUsername();
|
|
|
|
|
|
plan.setCreateBy(user);
|
|
|
|
|
|
plan.setUpdateBy(user);
|
|
|
|
|
|
plan.setPlanStatus("0");
|
|
|
|
|
|
plan.setProdStatus("Idle");
|
|
|
|
|
|
|
|
|
|
|
|
// 排到队列末尾
|
|
|
|
|
|
List<DrMillProductionPlan> all = drPlanMapper.selectList(new DrMillProductionPlan());
|
|
|
|
|
|
plan.setSortNo(all.size() + 1);
|
|
|
|
|
|
|
|
|
|
|
|
drPlanMapper.insert(plan);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
org.slf4j.LoggerFactory.getLogger(getClass())
|
|
|
|
|
|
.error("[双机架] 自动创建生产计划失败, actionId={}", action.getActionId(), e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 17:03:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 修改钢卷待操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Boolean updateByBo(WmsCoilPendingActionBo bo) {
|
|
|
|
|
|
WmsCoilPendingAction update = BeanUtil.toBean(bo, WmsCoilPendingAction.class);
|
|
|
|
|
|
validEntityBeforeSave(update);
|
|
|
|
|
|
return baseMapper.updateById(update) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 保存前的数据校验
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void validEntityBeforeSave(WmsCoilPendingAction entity){
|
|
|
|
|
|
// TODO 做一些数据校验,如唯一约束
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 批量删除钢卷待操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
|
|
|
|
|
if(isValid){
|
|
|
|
|
|
// TODO 做一些业务上的校验,判断是否需要校验
|
|
|
|
|
|
}
|
|
|
|
|
|
return baseMapper.deleteBatchIds(ids) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新操作状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Boolean updateStatus(Long actionId, Integer status) {
|
|
|
|
|
|
WmsCoilPendingAction action = new WmsCoilPendingAction();
|
|
|
|
|
|
action.setActionId(actionId);
|
|
|
|
|
|
action.setActionStatus(status);
|
|
|
|
|
|
return baseMapper.updateById(action) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 开始处理操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Boolean startProcess(Long actionId) {
|
|
|
|
|
|
WmsCoilPendingAction action = new WmsCoilPendingAction();
|
|
|
|
|
|
action.setActionId(actionId);
|
|
|
|
|
|
action.setActionStatus(1); // 处理中
|
|
|
|
|
|
action.setProcessTime(new Date());
|
|
|
|
|
|
try {
|
|
|
|
|
|
action.setOperatorId(LoginHelper.getUserId());
|
|
|
|
|
|
action.setOperatorName(LoginHelper.getUsername());
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
// 如果获取登录用户失败,不影响主流程
|
|
|
|
|
|
}
|
|
|
|
|
|
return baseMapper.updateById(action) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 完成操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
2026-04-15 14:31:27 +08:00
|
|
|
|
public Boolean completeAction(Long actionId, String newCoilIds) {
|
2025-11-11 10:01:42 +08:00
|
|
|
|
// 先查询原记录,检查操作人是否为空
|
|
|
|
|
|
WmsCoilPendingAction oldAction = baseMapper.selectById(actionId);
|
|
|
|
|
|
if (oldAction == null) {
|
|
|
|
|
|
throw new RuntimeException("待操作记录不存在");
|
|
|
|
|
|
}
|
2025-11-11 10:12:17 +08:00
|
|
|
|
|
2025-11-03 17:03:03 +08:00
|
|
|
|
WmsCoilPendingAction action = new WmsCoilPendingAction();
|
|
|
|
|
|
action.setActionId(actionId);
|
|
|
|
|
|
action.setActionStatus(2); // 已完成
|
|
|
|
|
|
action.setCompleteTime(new Date());
|
2026-04-16 09:10:39 +08:00
|
|
|
|
if(StringUtils.isNotBlank(newCoilIds) && !newCoilIds.equals("-")) {
|
|
|
|
|
|
action.setProcessedCoilIds(newCoilIds);
|
|
|
|
|
|
}
|
2025-11-11 10:01:42 +08:00
|
|
|
|
// 如果操作人为空,设置当前登录用户为操作人
|
|
|
|
|
|
if (oldAction.getOperatorId() == null || oldAction.getOperatorName() == null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
action.setOperatorId(LoginHelper.getUserId());
|
|
|
|
|
|
action.setOperatorName(LoginHelper.getUsername());
|
2025-11-11 10:12:17 +08:00
|
|
|
|
action.setProcessTime(new Date());
|
2025-11-11 10:01:42 +08:00
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
// 如果获取登录用户失败,不影响主流程
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-11 10:12:17 +08:00
|
|
|
|
|
2025-11-03 17:03:03 +08:00
|
|
|
|
return baseMapper.updateById(action) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 取消操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Boolean cancelAction(Long actionId) {
|
|
|
|
|
|
WmsCoilPendingAction action = new WmsCoilPendingAction();
|
|
|
|
|
|
action.setActionId(actionId);
|
|
|
|
|
|
action.setActionStatus(3); // 已取消
|
|
|
|
|
|
return baseMapper.updateById(action) > 0;
|
|
|
|
|
|
}
|
2026-01-28 18:40:53 +08:00
|
|
|
|
|
2026-03-12 16:23:45 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 还原操作(将已删除的记录恢复为正常状态)
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Boolean restoreAction(Long actionId) {
|
2026-03-12 16:48:16 +08:00
|
|
|
|
// 参数校验
|
|
|
|
|
|
if (actionId == null) {
|
|
|
|
|
|
throw new ServiceException("操作ID不能为空");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 17:24:14 +08:00
|
|
|
|
// 使用自定义查询方法检查记录是否存在且del_flag为1(已删除)
|
|
|
|
|
|
WmsCoilPendingAction oldAction = baseMapper.selectByActionIdAndDelFlag(actionId, 2);
|
2026-03-12 16:23:45 +08:00
|
|
|
|
if (oldAction == null) {
|
2026-03-12 16:48:16 +08:00
|
|
|
|
throw new ServiceException("待操作记录不存在或未被删除");
|
2026-03-12 16:23:45 +08:00
|
|
|
|
}
|
2026-03-12 16:48:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 使用自定义更新方法更新del_flag,绕过@TableLogic注解限制
|
|
|
|
|
|
int rows = baseMapper.updateDelFlag(actionId, 0);
|
|
|
|
|
|
return rows > 0;
|
2026-03-12 16:23:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 18:40:53 +08:00
|
|
|
|
@Override
|
|
|
|
|
|
public TheoryCycleRegressionResultVo calcTheoryCycleRegression(Date startTime, Date endTime) {
|
|
|
|
|
|
return calcTheoryCycleRegression(startTime, endTime, true, 2000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public TheoryCycleRegressionResultVo calcTheoryCycleRegression(Date startTime, Date endTime, Boolean includePoints, Integer maxPoints) {
|
|
|
|
|
|
LocalDateTime end = endTime == null ? LocalDateTime.now() : toLocalDateTime(endTime);
|
|
|
|
|
|
LocalDateTime start = startTime == null ? end.minusMonths(6) : toLocalDateTime(startTime);
|
|
|
|
|
|
|
|
|
|
|
|
boolean inc = includePoints != null && includePoints;
|
|
|
|
|
|
int limit = (maxPoints == null || maxPoints <= 0) ? 2000 : maxPoints;
|
|
|
|
|
|
TheoryCycleRegressionVo sy = buildRegression("SY", "酸轧线", 11, start, end, false, inc, limit);
|
|
|
|
|
|
TheoryCycleRegressionVo dx1 = buildRegression("DX1", "镀锌一线", 501, start, end, true, inc, limit);
|
|
|
|
|
|
|
|
|
|
|
|
cacheIdealCycle(sy);
|
|
|
|
|
|
cacheIdealCycle(dx1);
|
|
|
|
|
|
|
|
|
|
|
|
TheoryCycleRegressionResultVo result = new TheoryCycleRegressionResultVo();
|
|
|
|
|
|
result.setLines(Arrays.asList(sy, dx1));
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private TheoryCycleRegressionVo buildRegression(String lineId, String lineName, int actionType,
|
|
|
|
|
|
LocalDateTime start, LocalDateTime end,
|
|
|
|
|
|
boolean parseRemarkIds,
|
|
|
|
|
|
boolean includePoints, int maxPoints) {
|
|
|
|
|
|
LambdaQueryWrapper<WmsCoilPendingAction> lqw = Wrappers.lambdaQuery();
|
|
|
|
|
|
lqw.eq(WmsCoilPendingAction::getActionType, actionType)
|
|
|
|
|
|
.eq(WmsCoilPendingAction::getDelFlag, 0)
|
|
|
|
|
|
.eq(WmsCoilPendingAction::getActionStatus, 2)
|
|
|
|
|
|
.ge(WmsCoilPendingAction::getCreateTime, Date.from(start.atZone(ZoneId.systemDefault()).toInstant()))
|
|
|
|
|
|
.le(WmsCoilPendingAction::getCreateTime, Date.from(end.atZone(ZoneId.systemDefault()).toInstant()));
|
|
|
|
|
|
|
|
|
|
|
|
List<WmsCoilPendingAction> actions = baseMapper.selectList(lqw);
|
|
|
|
|
|
if (actions == null || actions.isEmpty()) {
|
|
|
|
|
|
TheoryCycleRegressionVo vo = new TheoryCycleRegressionVo();
|
|
|
|
|
|
vo.setLineId(lineId);
|
|
|
|
|
|
vo.setLineName(lineName);
|
|
|
|
|
|
vo.setStartTime(Date.from(start.atZone(ZoneId.systemDefault()).toInstant()));
|
|
|
|
|
|
vo.setEndTime(Date.from(end.atZone(ZoneId.systemDefault()).toInstant()));
|
|
|
|
|
|
vo.setSampleCount(0);
|
|
|
|
|
|
vo.setPoints(Collections.emptyList());
|
|
|
|
|
|
vo.setLinePoints(Collections.emptyList());
|
|
|
|
|
|
return vo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 预先收集所有需要的钢卷 ID,一次性批量查询,避免在循环中逐条访问数据库
|
|
|
|
|
|
Set<Long> allCoilIds = new HashSet<>();
|
|
|
|
|
|
for (WmsCoilPendingAction action : actions) {
|
|
|
|
|
|
if (action.getCreateTime() == null || action.getCompleteTime() == null) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (parseRemarkIds) {
|
|
|
|
|
|
List<Long> ids = parseIdsFromRemark(action.getRemark());
|
|
|
|
|
|
allCoilIds.addAll(ids);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (action.getCoilId() != null) {
|
|
|
|
|
|
allCoilIds.add(action.getCoilId());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<Long, Double> weightTonMap = new HashMap<>();
|
|
|
|
|
|
if (!allCoilIds.isEmpty()) {
|
|
|
|
|
|
List<WmsMaterialCoil> coils = materialCoilMapper.selectBatchIds(allCoilIds);
|
|
|
|
|
|
if (coils != null) {
|
|
|
|
|
|
for (WmsMaterialCoil coil : coils) {
|
|
|
|
|
|
if (coil == null || coil.getCoilId() == null) continue;
|
|
|
|
|
|
BigDecimal net = coil.getNetWeight();
|
|
|
|
|
|
BigDecimal gross = coil.getGrossWeight();
|
|
|
|
|
|
BigDecimal weightKg = net != null && net.compareTo(BigDecimal.ZERO) > 0 ? net : gross;
|
|
|
|
|
|
if (weightKg == null || weightKg.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
double ton = weightKg.divide(BigDecimal.valueOf(1000), 6, RoundingMode.HALF_UP).doubleValue();
|
|
|
|
|
|
weightTonMap.put(coil.getCoilId(), ton);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
List<TheoryCyclePointVo> points = new ArrayList<>();
|
|
|
|
|
|
for (WmsCoilPendingAction action : actions) {
|
|
|
|
|
|
if (action.getCreateTime() == null || action.getCompleteTime() == null) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
long minutes = Duration.between(
|
|
|
|
|
|
toLocalDateTime(action.getCreateTime()),
|
|
|
|
|
|
toLocalDateTime(action.getCompleteTime())
|
|
|
|
|
|
).toMinutes();
|
|
|
|
|
|
if (minutes <= 0) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
double weightTon = 0D;
|
|
|
|
|
|
if (parseRemarkIds) {
|
|
|
|
|
|
List<Long> ids = parseIdsFromRemark(action.getRemark());
|
|
|
|
|
|
for (Long id : ids) {
|
|
|
|
|
|
Double wt = weightTonMap.get(id);
|
|
|
|
|
|
if (wt != null && wt > 0D) {
|
|
|
|
|
|
weightTon += wt;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (action.getCoilId() != null) {
|
|
|
|
|
|
Double wt = weightTonMap.get(action.getCoilId());
|
|
|
|
|
|
if (wt != null && wt > 0D) {
|
|
|
|
|
|
weightTon = wt;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (weightTon <= 0D) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
TheoryCyclePointVo p = new TheoryCyclePointVo();
|
|
|
|
|
|
p.setActionId(action.getActionId());
|
|
|
|
|
|
p.setCreateTime(action.getCreateTime());
|
|
|
|
|
|
p.setDurationMin((double) minutes);
|
|
|
|
|
|
p.setWeightTon(weightTon);
|
|
|
|
|
|
points.add(p);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TheoryCycleRegressionVo vo = new TheoryCycleRegressionVo();
|
|
|
|
|
|
vo.setLineId(lineId);
|
|
|
|
|
|
vo.setLineName(lineName);
|
|
|
|
|
|
vo.setStartTime(Date.from(start.atZone(ZoneId.systemDefault()).toInstant()));
|
|
|
|
|
|
vo.setEndTime(Date.from(end.atZone(ZoneId.systemDefault()).toInstant()));
|
|
|
|
|
|
vo.setSampleCount(points.size());
|
|
|
|
|
|
if (includePoints) {
|
|
|
|
|
|
vo.setPoints(samplePoints(points, maxPoints));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
vo.setPoints(Collections.emptyList());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RegressionStat stat = linearRegression(points);
|
|
|
|
|
|
vo.setSlopeMinPerTon(stat.slope);
|
|
|
|
|
|
vo.setInterceptMin(stat.intercept);
|
|
|
|
|
|
vo.setR2(stat.r2);
|
|
|
|
|
|
vo.setLinePoints(stat.linePoints);
|
|
|
|
|
|
return vo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 散点抽样:避免返回体过大导致网络/序列化问题。
|
|
|
|
|
|
*/
|
|
|
|
|
|
private List<TheoryCyclePointVo> samplePoints(List<TheoryCyclePointVo> points, int maxPoints) {
|
|
|
|
|
|
if (points == null || points.isEmpty()) {
|
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (maxPoints <= 0 || points.size() <= maxPoints) {
|
|
|
|
|
|
return points;
|
|
|
|
|
|
}
|
|
|
|
|
|
int n = points.size();
|
|
|
|
|
|
double step = (double) n / (double) maxPoints;
|
|
|
|
|
|
List<TheoryCyclePointVo> sampled = new ArrayList<>(maxPoints);
|
|
|
|
|
|
for (int i = 0; i < maxPoints; i++) {
|
|
|
|
|
|
int idx = (int) Math.floor(i * step);
|
|
|
|
|
|
if (idx < 0) idx = 0;
|
|
|
|
|
|
if (idx >= n) idx = n - 1;
|
|
|
|
|
|
sampled.add(points.get(idx));
|
|
|
|
|
|
}
|
|
|
|
|
|
return sampled;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void cacheIdealCycle(TheoryCycleRegressionVo vo) {
|
|
|
|
|
|
if (vo == null || vo.getSlopeMinPerTon() == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
String field = vo.getLineId() == null ? "UNKNOWN" : vo.getLineId().toUpperCase();
|
|
|
|
|
|
stringRedisTemplate.opsForHash().put(REDIS_KEY_IDEAL_CYCLE, field, vo.getSlopeMinPerTon().toString());
|
|
|
|
|
|
if (vo.getInterceptMin() != null) {
|
|
|
|
|
|
stringRedisTemplate.opsForHash().put(REDIS_KEY_IDEAL_CYCLE + ":intercept", field, vo.getInterceptMin().toString());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private RegressionStat linearRegression(List<TheoryCyclePointVo> points) {
|
|
|
|
|
|
RegressionStat stat = new RegressionStat();
|
|
|
|
|
|
if (points == null || points.size() < 2) {
|
|
|
|
|
|
return stat;
|
|
|
|
|
|
}
|
|
|
|
|
|
double sumX = 0, sumY = 0, sumXX = 0, sumXY = 0;
|
|
|
|
|
|
for (TheoryCyclePointVo p : points) {
|
|
|
|
|
|
double x = p.getWeightTon();
|
|
|
|
|
|
double y = p.getDurationMin();
|
|
|
|
|
|
sumX += x;
|
|
|
|
|
|
sumY += y;
|
|
|
|
|
|
sumXX += x * x;
|
|
|
|
|
|
sumXY += x * y;
|
|
|
|
|
|
}
|
|
|
|
|
|
int n = points.size();
|
|
|
|
|
|
double denominator = n * sumXX - sumX * sumX;
|
|
|
|
|
|
if (denominator == 0) {
|
|
|
|
|
|
return stat;
|
|
|
|
|
|
}
|
|
|
|
|
|
double slope = (n * sumXY - sumX * sumY) / denominator;
|
|
|
|
|
|
double intercept = (sumY - slope * sumX) / n;
|
|
|
|
|
|
|
|
|
|
|
|
double ssTot = 0, ssRes = 0;
|
|
|
|
|
|
double meanY = sumY / n;
|
|
|
|
|
|
for (TheoryCyclePointVo p : points) {
|
|
|
|
|
|
double y = p.getDurationMin();
|
|
|
|
|
|
double yHat = slope * p.getWeightTon() + intercept;
|
|
|
|
|
|
ssTot += Math.pow(y - meanY, 2);
|
|
|
|
|
|
ssRes += Math.pow(y - yHat, 2);
|
|
|
|
|
|
}
|
|
|
|
|
|
double r2 = ssTot == 0 ? 0 : 1 - ssRes / ssTot;
|
|
|
|
|
|
|
|
|
|
|
|
stat.slope = slope;
|
|
|
|
|
|
stat.intercept = intercept;
|
|
|
|
|
|
stat.r2 = r2;
|
|
|
|
|
|
stat.linePoints = buildLinePoints(points, slope, intercept);
|
|
|
|
|
|
return stat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<TheoryCyclePointVo> buildLinePoints(List<TheoryCyclePointVo> points, double slope, double intercept) {
|
|
|
|
|
|
if (points == null || points.isEmpty()) {
|
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
|
}
|
|
|
|
|
|
double minX = points.stream().mapToDouble(TheoryCyclePointVo::getWeightTon).min().orElse(0D);
|
|
|
|
|
|
double maxX = points.stream().mapToDouble(TheoryCyclePointVo::getWeightTon).max().orElse(0D);
|
|
|
|
|
|
List<TheoryCyclePointVo> line = new ArrayList<>();
|
|
|
|
|
|
TheoryCyclePointVo p1 = new TheoryCyclePointVo();
|
|
|
|
|
|
p1.setWeightTon(minX);
|
|
|
|
|
|
p1.setDurationMin(slope * minX + intercept);
|
|
|
|
|
|
TheoryCyclePointVo p2 = new TheoryCyclePointVo();
|
|
|
|
|
|
p2.setWeightTon(maxX);
|
|
|
|
|
|
p2.setDurationMin(slope * maxX + intercept);
|
|
|
|
|
|
line.add(p1);
|
|
|
|
|
|
line.add(p2);
|
|
|
|
|
|
return line;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<Long> parseIdsFromRemark(String remark) {
|
|
|
|
|
|
if (StringUtils.isBlank(remark)) {
|
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
|
}
|
|
|
|
|
|
Matcher matcher = Pattern.compile("\\d+").matcher(remark);
|
|
|
|
|
|
List<Long> ids = new ArrayList<>();
|
|
|
|
|
|
while (matcher.find()) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
ids.add(Long.parseLong(matcher.group()));
|
|
|
|
|
|
} catch (NumberFormatException ignore) {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ids;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private LocalDateTime toLocalDateTime(Date date) {
|
|
|
|
|
|
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static class RegressionStat {
|
|
|
|
|
|
Double slope;
|
|
|
|
|
|
Double intercept;
|
|
|
|
|
|
Double r2;
|
|
|
|
|
|
List<TheoryCyclePointVo> linePoints = Collections.emptyList();
|
|
|
|
|
|
}
|
2025-11-03 17:03:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|