package cn.iocoder.yudao.module.mes.service.pro.route;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.NumberUtil;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProRouteProcessSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessOutputDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProcessMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProcessOutputMapper;
|
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
|
import jakarta.annotation.Resource;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collection;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
|
|
/**
|
* MES 工艺路线工序 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
public class MesProRouteProcessServiceImpl implements MesProRouteProcessService {
|
|
@Resource
|
private MesProRouteProcessMapper routeProcessMapper;
|
@Resource
|
private MesProRouteProcessOutputMapper routeProcessOutputMapper;
|
|
@Resource
|
@Lazy
|
private MesProRouteProductService routeProductService;
|
@Resource
|
@Lazy
|
private MesProProcessService processService;
|
@Resource
|
@Lazy
|
private MesProRouteService routeService;
|
@Resource
|
@Lazy
|
private MesProRouteProductBomService routeProductBomService;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createRouteProcess(MesProRouteProcessSaveReqVO createReqVO) {
|
// 1.0 已启用的工艺路线,不允许操作
|
routeService.validateRouteNotEnable(createReqVO.getRouteId());
|
// 1.1 校验工艺路线、工序存在
|
validateRouteAndProcessExists(createReqVO.getRouteId(), createReqVO.getProcessId());
|
// 1.2 校验唯一性
|
validateSortUnique(null, createReqVO.getRouteId(), createReqVO.getSort());
|
validateProcessUnique(null, createReqVO.getRouteId(), createReqVO.getProcessId());
|
validateKeyProcessUnique(null, createReqVO.getRouteId(), createReqVO.getKeyFlag());
|
|
// 2. 插入
|
MesProRouteProcessDO routeProcess = BeanUtils.toBean(createReqVO, MesProRouteProcessDO.class);
|
routeProcess.setOutputItemId(resolveOutputItemId(createReqVO));
|
routeProcessMapper.insert(routeProcess);
|
// 2.1 保存产出归属配置
|
saveRouteProcessOutputs(routeProcess, createReqVO);
|
|
// 3. 更新工序链
|
rebuildProcessChain(createReqVO.getRouteId());
|
return routeProcess.getId();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateRouteProcess(MesProRouteProcessSaveReqVO updateReqVO) {
|
// 1.0 已启用的工艺路线,不允许操作
|
routeService.validateRouteNotEnable(updateReqVO.getRouteId());
|
// 1.1 校验存在;兼容只调整产出配置的部分更新请求
|
MesProRouteProcessDO existing = validateRouteProcessExists(updateReqVO.getId());
|
if (updateReqVO.getProcessId() == null) {
|
updateReqVO.setProcessId(existing.getProcessId());
|
}
|
if (updateReqVO.getSort() == null) {
|
updateReqVO.setSort(existing.getSort());
|
}
|
// 1.2 校验工艺路线、工序存在
|
validateRouteAndProcessExists(updateReqVO.getRouteId(), updateReqVO.getProcessId());
|
// 1.3 校验唯一性
|
validateSortUnique(updateReqVO.getId(), updateReqVO.getRouteId(), updateReqVO.getSort());
|
validateProcessUnique(updateReqVO.getId(), updateReqVO.getRouteId(), updateReqVO.getProcessId());
|
validateKeyProcessUnique(updateReqVO.getId(), updateReqVO.getRouteId(), updateReqVO.getKeyFlag());
|
|
// 2. 更新
|
MesProRouteProcessDO updateObj = BeanUtils.toBean(updateReqVO, MesProRouteProcessDO.class);
|
// 空数组也要显式写入空串,否则 MyBatis-Plus 默认忽略 null,用户清空产出品后旧配置仍会残留。
|
updateObj.setOutputItemId(resolveOutputItemId(updateReqVO));
|
routeProcessMapper.updateById(updateObj);
|
// 2.1 覆盖产出归属配置
|
saveRouteProcessOutputs(updateObj, updateReqVO);
|
|
// 3. 重建工序链
|
rebuildProcessChain(updateReqVO.getRouteId());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void deleteRouteProcess(Long id) {
|
// 1.1 校验存在
|
MesProRouteProcessDO routeProcess = validateRouteProcessExists(id);
|
// 1.2 已启用的工艺路线,不允许操作
|
routeService.validateRouteNotEnable(routeProcess.getRouteId());
|
|
// 2. 删除(含产出归属配置)
|
routeProcessMapper.deleteById(id);
|
routeProcessOutputMapper.deleteByRouteProcessId(id);
|
|
// 3. 重建工序链
|
rebuildProcessChain(routeProcess.getRouteId());
|
}
|
|
/**
|
* 重建工序链(更新所有工序的 nextProcessId)
|
*/
|
private void rebuildProcessChain(Long routeId) {
|
List<MesProRouteProcessDO> list = routeProcessMapper.selectListByRouteId(routeId);
|
if (CollUtil.isEmpty(list)) {
|
return;
|
}
|
// 按 sort 排序
|
list.sort(Comparator.comparing(MesProRouteProcessDO::getSort));
|
for (int i = 0; i < list.size(); i++) {
|
MesProRouteProcessDO item = list.get(i);
|
if (i + 1 < list.size()) {
|
item.setNextProcessId(list.get(i + 1).getProcessId());
|
} else {
|
item.setNextProcessId(null);
|
}
|
routeProcessMapper.updateById(item);
|
}
|
}
|
|
private MesProRouteProcessDO validateRouteProcessExists(Long id) {
|
MesProRouteProcessDO routeProcess = routeProcessMapper.selectById(id);
|
if (routeProcess == null) {
|
throw exception(PRO_ROUTE_PROCESS_NOT_EXISTS);
|
}
|
return routeProcess;
|
}
|
|
private void validateRouteAndProcessExists(Long routeId, Long processId) {
|
if (routeService.getRoute(routeId) == null) {
|
throw exception(PRO_ROUTE_NOT_EXISTS);
|
}
|
if (processService.getProcess(processId) == null) {
|
throw exception(PRO_ROUTE_PROCESS_NOT_EXISTS);
|
}
|
}
|
|
private void validateSortUnique(Long id, Long routeId, Integer sort) {
|
MesProRouteProcessDO existing = routeProcessMapper.selectByRouteIdAndSort(routeId, sort);
|
if (existing != null && !existing.getId().equals(id)) {
|
throw exception(PRO_ROUTE_PROCESS_SORT_DUPLICATE);
|
}
|
}
|
|
private void validateProcessUnique(Long id, Long routeId, Long processId) {
|
MesProRouteProcessDO existing = routeProcessMapper.selectByRouteIdAndProcessId(routeId, processId);
|
if (existing != null && !existing.getId().equals(id)) {
|
throw exception(PRO_ROUTE_PROCESS_DUPLICATE);
|
}
|
}
|
|
private void validateKeyProcessUnique(Long id, Long routeId, Boolean keyFlag) {
|
if (!Boolean.TRUE.equals(keyFlag)) {
|
return;
|
}
|
MesProRouteProcessDO existing = routeProcessMapper.selectKeyProcessByRouteId(routeId);
|
if (existing != null && !existing.getId().equals(id)) {
|
throw exception(PRO_ROUTE_PROCESS_KEY_DUPLICATE);
|
}
|
}
|
|
@Override
|
public MesProRouteProcessDO getRouteProcess(Long id) {
|
return routeProcessMapper.selectById(id);
|
}
|
|
@Override
|
public List<MesProRouteProcessDO> getRouteProcessListByRouteId(Long routeId) {
|
return routeProcessMapper.selectListByRouteId(routeId);
|
}
|
|
@Override
|
public List<MesProRouteProcessDO> getRouteProcessListByRouteIds(Collection<Long> routeIds) {
|
if (CollUtil.isEmpty(routeIds)) {
|
return Collections.emptyList();
|
}
|
return routeProcessMapper.selectListByRouteIds(routeIds);
|
}
|
|
@Override
|
public MesProRouteProcessDO getRouteProcessByRouteIdAndProcessId(Long routeId, Long processId) {
|
return routeProcessMapper.selectByRouteIdAndProcessId(routeId, processId);
|
}
|
|
@Override
|
public List<MesProRouteProcessDO> getRouteProcessListByProductId(Long productId) {
|
// 1. 根据产品查找关联的工艺路线产品记录
|
MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(productId);
|
if (routeProduct == null) {
|
return Collections.emptyList();
|
}
|
// 2. 返回该工艺路线的工序列表
|
List<MesProRouteProcessDO> list = routeProcessMapper.selectListByRouteId(routeProduct.getRouteId());
|
// 3. 按产出归属过滤:通用产出 + 当前产品专属产出
|
Map<Long, List<MesProRouteProcessOutputDO>> outputMap = routeProcessOutputMapper
|
.selectListByRouteId(routeProduct.getRouteId()).stream()
|
.collect(Collectors.groupingBy(MesProRouteProcessOutputDO::getRouteProcessId));
|
for (MesProRouteProcessDO process : list) {
|
List<MesProRouteProcessOutputDO> outputs = outputMap.get(process.getId());
|
if (CollUtil.isEmpty(outputs)) {
|
// 存量数据未拆分归属时,退回旧的“剔除同路线其它产品”逻辑
|
filterOtherProductOutputs(Collections.singletonList(process), routeProduct);
|
continue;
|
}
|
String filtered = outputs.stream()
|
.filter(output -> output.getProductId() == null || output.getProductId().equals(productId))
|
.map(MesProRouteProcessOutputDO::getItemId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.map(String::valueOf)
|
.collect(Collectors.joining(","));
|
process.setOutputItemId(filtered.isEmpty() ? null : filtered);
|
}
|
return list;
|
}
|
|
@Override
|
public List<Long> getOutputItemIdsByRouteProcessId(Long routeProcessId, Long productId) {
|
List<MesProRouteProcessOutputDO> outputs = routeProcessOutputMapper.selectListByRouteProcessId(routeProcessId);
|
if (CollUtil.isEmpty(outputs)) {
|
// 未配置产出归属(存量数据)返回 null,便于调用方回退聚合产出
|
return null;
|
}
|
return outputs.stream()
|
.filter(output -> output.getProductId() == null || output.getProductId().equals(productId))
|
.map(MesProRouteProcessOutputDO::getItemId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
}
|
|
@Override
|
public List<MesProRouteProcessOutputDO> getRouteProcessOutputListByRouteProcessIds(Collection<Long> routeProcessIds) {
|
if (CollUtil.isEmpty(routeProcessIds)) {
|
return Collections.emptyList();
|
}
|
return routeProcessOutputMapper.selectListByRouteProcessIds(routeProcessIds);
|
}
|
|
/**
|
* 计算工序产出物料的聚合串(逗号分隔,去重)
|
*
|
* 优先使用产出归属配置(outputs),未传时兼容 outputItemIds。
|
*/
|
private String resolveOutputItemId(MesProRouteProcessSaveReqVO reqVO) {
|
List<Long> itemIds;
|
if (CollUtil.isNotEmpty(reqVO.getOutputs())) {
|
itemIds = reqVO.getOutputs().stream()
|
.filter(Objects::nonNull)
|
.sorted(Comparator.comparing(MesProRouteProcessSaveReqVO.OutputItem::getSort,
|
Comparator.nullsLast(Comparator.naturalOrder())))
|
.map(MesProRouteProcessSaveReqVO.OutputItem::getItemId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
} else if (CollUtil.isNotEmpty(reqVO.getOutputItemIds())) {
|
itemIds = reqVO.getOutputItemIds().stream()
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
} else {
|
itemIds = Collections.emptyList();
|
}
|
return itemIds.stream().map(String::valueOf).collect(Collectors.joining(","));
|
}
|
|
/**
|
* 覆盖保存工序产出归属配置
|
*/
|
private void saveRouteProcessOutputs(MesProRouteProcessDO routeProcess,
|
MesProRouteProcessSaveReqVO reqVO) {
|
routeProcessOutputMapper.deleteByRouteProcessId(routeProcess.getId());
|
if (CollUtil.isEmpty(reqVO.getOutputs()) && CollUtil.isEmpty(reqVO.getOutputItemIds())) {
|
return;
|
}
|
Set<Long> routeProductIds = routeProductService.getRouteProductListByRouteId(routeProcess.getRouteId())
|
.stream().map(MesProRouteProductDO::getItemId).filter(Objects::nonNull).collect(Collectors.toSet());
|
List<MesProRouteProcessOutputDO> outputs = new ArrayList<>();
|
Set<String> keys = new HashSet<>();
|
int index = 0;
|
if (CollUtil.isNotEmpty(reqVO.getOutputs())) {
|
for (MesProRouteProcessSaveReqVO.OutputItem output : reqVO.getOutputs()) {
|
if (output == null || output.getItemId() == null) {
|
continue;
|
}
|
index++;
|
if (!keys.add(output.getProductId() + "_" + output.getItemId())) {
|
continue;
|
}
|
outputs.add(buildOutput(routeProcess, output.getProductId(), output.getItemId(),
|
output.getSort() != null ? output.getSort() : index, output.getRemark()));
|
}
|
} else {
|
// 兼容只传 outputItemIds 的调用方:产出是路线产品时归属到自身,否则视为通用
|
for (Long itemId : reqVO.getOutputItemIds()) {
|
if (itemId == null) {
|
continue;
|
}
|
index++;
|
Long productId = routeProductIds.contains(itemId) ? itemId : null;
|
if (!keys.add(productId + "_" + itemId)) {
|
continue;
|
}
|
outputs.add(buildOutput(routeProcess, productId, itemId, index, null));
|
}
|
}
|
if (CollUtil.isNotEmpty(outputs)) {
|
routeProcessOutputMapper.insertBatch(outputs);
|
}
|
}
|
|
private MesProRouteProcessOutputDO buildOutput(MesProRouteProcessDO routeProcess, Long productId,
|
Long itemId, Integer sort, String remark) {
|
return MesProRouteProcessOutputDO.builder()
|
.routeProcessId(routeProcess.getId())
|
.routeId(routeProcess.getRouteId())
|
.processId(routeProcess.getProcessId())
|
.productId(productId)
|
.itemId(itemId)
|
.sort(sort)
|
.remark(remark)
|
.build();
|
}
|
|
/**
|
* 过滤产出物料:剔除同一工艺路线下其它产品的产出,只保留当前产品的产出和半成品产出
|
*/
|
private void filterOtherProductOutputs(List<MesProRouteProcessDO> list, MesProRouteProductDO routeProduct) {
|
if (CollUtil.isEmpty(list) || routeProduct.getItemId() == null) {
|
return;
|
}
|
Set<Long> otherProductIds = routeProductService.getRouteProductListByRouteId(routeProduct.getRouteId())
|
.stream()
|
.map(MesProRouteProductDO::getItemId)
|
.filter(itemId -> itemId != null && !itemId.equals(routeProduct.getItemId()))
|
.collect(Collectors.toSet());
|
if (otherProductIds.isEmpty()) {
|
return;
|
}
|
for (MesProRouteProcessDO process : list) {
|
String outputItemId = process.getOutputItemId();
|
if (outputItemId == null || outputItemId.isBlank()) {
|
continue;
|
}
|
String filtered = Arrays.stream(outputItemId.split(","))
|
.map(String::trim)
|
.filter(s -> !s.isEmpty())
|
.filter(s -> !otherProductIds.contains(NumberUtil.parseLong(s, -1L)))
|
.collect(Collectors.joining(","));
|
process.setOutputItemId(filtered.isEmpty() ? null : filtered);
|
}
|
}
|
|
@Override
|
public Map<Long, List<MesProRouteProductBomDO>> getRouteProcessBomMapByProductId(Long productId) {
|
MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(productId);
|
if (routeProduct == null) {
|
return Collections.emptyMap();
|
}
|
List<MesProRouteProductBomDO> bomList = routeProductBomService
|
.getRouteProductBomListByRouteIdAndProductId(routeProduct.getRouteId(), productId);
|
if (CollUtil.isEmpty(bomList)) {
|
return Collections.emptyMap();
|
}
|
return bomList.stream().collect(Collectors.groupingBy(MesProRouteProductBomDO::getProcessId));
|
}
|
|
@Override
|
public List<MesProRouteProcessDO> getRouteProcessListByProcessId(Long processId) {
|
return routeProcessMapper.selectListByProcessId(processId);
|
}
|
|
@Override
|
public void deleteRouteProcessByRouteId(Long routeId) {
|
routeProcessMapper.deleteByRouteId(routeId);
|
routeProcessOutputMapper.deleteByRouteId(routeId);
|
}
|
|
}
|