Files
klp-oa/klp-wms/src/main/java/com/klp/service/impl/WmsCoilPendingActionServiceImpl.java

563 lines
22 KiB
Java
Raw Normal View History

2025-11-03 17:03:03 +08:00
package com.klp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
2025-11-03 17:03:03 +08:00
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.klp.common.core.domain.PageQuery;
import com.klp.common.core.page.TableDataInfo;
2025-11-03 17:03:03 +08:00
import com.klp.common.helper.LoginHelper;
import com.klp.common.utils.StringUtils;
import com.klp.domain.WmsCoilPendingAction;
import com.klp.domain.WmsMaterialCoil;
2025-11-03 17:03:03 +08:00
import com.klp.domain.bo.WmsCoilPendingActionBo;
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;
import com.klp.mapper.WmsCoilPendingActionMapper;
import com.klp.mapper.WmsMaterialCoilMapper;
2025-11-03 17:03:03 +08:00
import com.klp.service.IWmsCoilPendingActionService;
import com.klp.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
2025-11-03 17:03:03 +08:00
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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;
private final ISysUserService userService;
private final WmsMaterialCoilMapper materialCoilMapper;
private final StringRedisTemplate stringRedisTemplate;
private static final String REDIS_KEY_IDEAL_CYCLE = "oee:ideal-cycle-time";
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) {
QueryWrapper<WmsCoilPendingAction> lqw = buildQueryWrapperPlus(bo);
Page<WmsCoilPendingActionVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), lqw);
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);
}
private QueryWrapper<WmsCoilPendingAction> buildQueryWrapperPlus(WmsCoilPendingActionBo bo) {
QueryWrapper<WmsCoilPendingAction> qw = Wrappers.query();
qw.eq(bo.getCoilId() != null, "wcpa.coil_id", bo.getCoilId());
qw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), "wcpa.current_coil_no", bo.getCurrentCoilNo());
qw.eq(bo.getActionType() != null, "wcpa.action_type", bo.getActionType());
if (bo.getActionStatus() != null) {
if (bo.getActionStatus() == -1) {
qw.ne("wcpa.action_status", 2);
} else {
qw.eq("wcpa.action_status", bo.getActionStatus());
}
}
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");
//根据开始时间和结束时间筛选修改时间
qw.ge(bo.getStartTime() != null, "wcpa.complete_time", bo.getStartTime());
qw.le(bo.getEndTime() != null, "wcpa.complete_time", bo.getEndTime());
// 根据更新人查询
qw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), "wcpa.update_by", bo.getUpdateBy());
//逻辑删除 - 支持查询已删除记录
if (bo.getIncludeDeleted() != null) {
if (bo.getIncludeDeleted() == 1) {
// 包含已删除记录不添加del_flag过滤查询所有记录
} else if (bo.getIncludeDeleted() == 2) {
// 仅查询已删除记录
qw.eq("wcpa.del_flag", 2);
} else {
// 默认:仅查询正常记录
qw.eq("wcpa.del_flag", 0);
}
} else {
// 未传参数时默认仅查询正常记录
qw.eq("wcpa.del_flag", 0);
}
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);
}
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());
lqw.eq(bo.getActionType() != null, WmsCoilPendingAction::getActionType, bo.getActionType());
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);
if (add.getCoilId() != null){
WmsMaterialCoil materialCoil = materialCoilMapper.selectById(add.getCoilId());
if (materialCoil.getDataType() == 0) {
throw new RuntimeException("该钢卷为历史钢卷不能被操作");
}
}
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());
}
return flag;
}
/**
* 修改钢卷待操作
*/
@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
public Boolean completeAction(Long actionId) {
2025-11-11 10:01:42 +08:00
// 先查询原记录,检查操作人是否为空
WmsCoilPendingAction oldAction = baseMapper.selectById(actionId);
if (oldAction == null) {
throw new RuntimeException("待操作记录不存在");
}
2025-11-03 17:03:03 +08:00
WmsCoilPendingAction action = new WmsCoilPendingAction();
action.setActionId(actionId);
action.setActionStatus(2); // 已完成
action.setCompleteTime(new Date());
2025-11-11 10:01:42 +08:00
// 如果操作人为空,设置当前登录用户为操作人
if (oldAction.getOperatorId() == null || oldAction.getOperatorName() == null) {
try {
action.setOperatorId(LoginHelper.getUserId());
action.setOperatorName(LoginHelper.getUsername());
action.setProcessTime(new Date());
2025-11-11 10:01:42 +08:00
} catch (Exception e) {
// 如果获取登录用户失败,不影响主流程
}
}
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;
}
/**
* 还原操作将已删除的记录恢复为正常状态
*/
@Override
public Boolean restoreAction(Long actionId) {
// 使用QueryWrapper查询不受逻辑删除影响检查记录是否存在且del_flag为2
WmsCoilPendingAction oldAction = baseMapper.selectOne(new QueryWrapper<WmsCoilPendingAction>()
.eq("action_id", actionId)
.eq("del_flag", 2)
.last("LIMIT 1"));
if (oldAction == null) {
throw new RuntimeException("待操作记录不存在或未被删除");
}
// 将del_flag改为0
WmsCoilPendingAction action = new WmsCoilPendingAction();
action.setActionId(actionId);
action.setDelFlag(0); // 恢复为正常状态
return baseMapper.updateById(action) > 0;
}
@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
}