Files
klp-oa/klp-wms/src/main/java/com/klp/service/impl/WmsCoilPendingActionServiceImpl.java
Joshi 31bbdedb8f feat(wms): 获取消耗钢卷接口,用作报表或者导出
- 在IWmsCoilPendingActionService中新增queryActionIdCoilIdList方法
- 在WmsCoilPendingActionController中新增/actionCoilIdList端点
- 在WmsCoilPendingActionMapper中新增selectActionIdCoilIdList查询方法
- 在WmsCoilPendingActionMapper.xml中添加对应的SQL查询语句
- 在WmsCoilPendingActionServiceImpl中实现查询逻辑
- 创建WmsCoilPendingActionIdCoilVo数据传输对象,仅包含actionId和coilId字段
2026-03-25 14:36:53 +08:00

585 lines
23 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.klp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
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;
import com.klp.common.exception.ServiceException;
import com.klp.common.helper.LoginHelper;
import com.klp.common.utils.StringUtils;
import com.klp.domain.WmsCoilPendingAction;
import com.klp.domain.WmsMaterialCoil;
import com.klp.domain.bo.WmsCoilPendingActionBo;
import com.klp.domain.vo.TheoryCyclePointVo;
import com.klp.domain.vo.TheoryCycleRegressionResultVo;
import com.klp.domain.vo.TheoryCycleRegressionVo;
import com.klp.domain.vo.WmsCoilPendingActionVo;
import com.klp.domain.vo.WmsCoilPendingActionIdCoilVo;
import com.klp.mapper.WmsCoilPendingActionMapper;
import com.klp.mapper.WmsMaterialCoilMapper;
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;
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;
/**
* 钢卷待操作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";
/**
* 查询钢卷待操作
*/
@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()));
}
});
}
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());
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());
}
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());
// 根据创建人筛选
qw.eq(StringUtils.isNotBlank(bo.getCreateBy()), "wcpa.create_by", bo.getCreateBy());
// 加工后的钢卷ids
qw.like(StringUtils.isNotBlank(bo.getProcessedCoilIds()), "wcpa.processed_coil_ids", bo.getProcessedCoilIds());
//逻辑删除 - 支持查询已删除记录
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;
}
/**
* 查询钢卷待操作列表
*/
@Override
public List<WmsCoilPendingActionVo> queryList(WmsCoilPendingActionBo bo) {
LambdaQueryWrapper<WmsCoilPendingAction> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
@Override
public List<WmsCoilPendingActionIdCoilVo> queryActionIdCoilIdList(WmsCoilPendingActionBo bo) {
// 复用与 /list 相同的查询条件buildQueryWrapperPlus
QueryWrapper<WmsCoilPendingAction> lqw = buildQueryWrapperPlus(bo);
return baseMapper.selectActionIdCoilIdList(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());
if (bo.getActionTypes() != null && !bo.getActionTypes().isEmpty()) {
lqw.in(WmsCoilPendingAction::getActionType, bo.getActionTypes());
} else {
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());
lqw.orderByDesc(WmsCoilPendingAction::getCreateTime);
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("该钢卷为历史钢卷不能被操作");
}
}
// 设置默认值
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) {
// 先查询原记录,检查操作人是否为空
WmsCoilPendingAction oldAction = baseMapper.selectById(actionId);
if (oldAction == null) {
throw new RuntimeException("待操作记录不存在");
}
WmsCoilPendingAction action = new WmsCoilPendingAction();
action.setActionId(actionId);
action.setActionStatus(2); // 已完成
action.setCompleteTime(new Date());
// 如果操作人为空,设置当前登录用户为操作人
if (oldAction.getOperatorId() == null || oldAction.getOperatorName() == null) {
try {
action.setOperatorId(LoginHelper.getUserId());
action.setOperatorName(LoginHelper.getUsername());
action.setProcessTime(new Date());
} catch (Exception e) {
// 如果获取登录用户失败,不影响主流程
}
}
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) {
// 参数校验
if (actionId == null) {
throw new ServiceException("操作ID不能为空");
}
// 使用自定义查询方法检查记录是否存在且del_flag为1(已删除)
WmsCoilPendingAction oldAction = baseMapper.selectByActionIdAndDelFlag(actionId, 2);
if (oldAction == null) {
throw new ServiceException("待操作记录不存在或未被删除");
}
// 使用自定义更新方法更新del_flag绕过@TableLogic注解限制
int rows = baseMapper.updateDelFlag(actionId, 0);
return rows > 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();
}
}