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 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 getRouteProcessListByRouteId(Long routeId) { return routeProcessMapper.selectListByRouteId(routeId); } @Override public List getRouteProcessListByRouteIds(Collection 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 getRouteProcessListByProductId(Long productId) { // 1. 根据产品查找关联的工艺路线产品记录 MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(productId); if (routeProduct == null) { return Collections.emptyList(); } // 2. 返回该工艺路线的工序列表 List list = routeProcessMapper.selectListByRouteId(routeProduct.getRouteId()); // 3. 按产出归属过滤:通用产出 + 当前产品专属产出 Map> outputMap = routeProcessOutputMapper .selectListByRouteId(routeProduct.getRouteId()).stream() .collect(Collectors.groupingBy(MesProRouteProcessOutputDO::getRouteProcessId)); for (MesProRouteProcessDO process : list) { List 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 getOutputItemIdsByRouteProcessId(Long routeProcessId, Long productId) { List 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 getRouteProcessOutputListByRouteProcessIds(Collection routeProcessIds) { if (CollUtil.isEmpty(routeProcessIds)) { return Collections.emptyList(); } return routeProcessOutputMapper.selectListByRouteProcessIds(routeProcessIds); } /** * 计算工序产出物料的聚合串(逗号分隔,去重) * * 优先使用产出归属配置(outputs),未传时兼容 outputItemIds。 */ private String resolveOutputItemId(MesProRouteProcessSaveReqVO reqVO) { List 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 routeProductIds = routeProductService.getRouteProductListByRouteId(routeProcess.getRouteId()) .stream().map(MesProRouteProductDO::getItemId).filter(Objects::nonNull).collect(Collectors.toSet()); List outputs = new ArrayList<>(); Set 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 list, MesProRouteProductDO routeProduct) { if (CollUtil.isEmpty(list) || routeProduct.getItemId() == null) { return; } Set 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> getRouteProcessBomMapByProductId(Long productId) { MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(productId); if (routeProduct == null) { return Collections.emptyMap(); } List bomList = routeProductBomService .getRouteProductBomListByRouteIdAndProductId(routeProduct.getRouteId(), productId); if (CollUtil.isEmpty(bomList)) { return Collections.emptyMap(); } return bomList.stream().collect(Collectors.groupingBy(MesProRouteProductBomDO::getProcessId)); } @Override public List getRouteProcessListByProcessId(Long processId) { return routeProcessMapper.selectListByProcessId(processId); } @Override public void deleteRouteProcessByRouteId(Long routeId) { routeProcessMapper.deleteByRouteId(routeId); routeProcessOutputMapper.deleteByRouteId(routeId); } }