package com.ruoyi.technology.service.impl; import cn.hutool.core.bean.BeanUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.OrderUtils; import com.ruoyi.technology.bean.dto.TechnologyRoutingDto; import com.ruoyi.technology.bean.vo.TechnologyRoutingVo; import com.ruoyi.production.mapper.ProductionOrderRoutingMapper; import com.ruoyi.production.pojo.ProductionOrderRouting; import com.ruoyi.technology.mapper.*; import com.ruoyi.technology.pojo.*; import com.ruoyi.technology.service.TechnologyRoutingService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) @RequiredArgsConstructor public class TechnologyRoutingServiceImpl extends ServiceImpl implements TechnologyRoutingService { private final TechnologyRoutingMapper technologyRoutingMapper; private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; private final TechnologyBomStructureMapper technologyBomStructureMapper; private final TechnologyOperationMapper technologyOperationMapper; private final TechnologyOperationParamMapper technologyOperationParamMapper; private final TechnologyParamMapper technologyParamMapper; private final TechnologyRoutingOperationParamMapper technologyRoutingOperationParamMapper; private final ProductionOrderRoutingMapper productionOrderRoutingMapper; @Override public IPage pageTechnologyRouting(Page page, TechnologyRoutingDto technologyRoutingDto) { return technologyRoutingMapper.pageTechnologyRouting(page, technologyRoutingDto); } @Override public TechnologyRoutingVo getTechnologyRoutingInfo(Long id) { TechnologyRouting technologyRouting = this.getById(id); if (technologyRouting == null) { return null; } return BeanUtil.copyProperties(technologyRouting, TechnologyRoutingVo.class); } @Override public Long saveTechnologyRouting(TechnologyRouting technologyRouting) { String code = OrderUtils.countTodayByCreateTime(technologyRoutingMapper, "GYLX", "process_route_code"); technologyRouting.setProcessRouteCode(code); technologyRoutingMapper.insert(technologyRouting); // 带入bom产品结构 syncRoutingOperationsByBom(technologyRouting); return technologyRouting.getId(); } @Override public boolean updateTechnologyRouting(TechnologyRouting technologyRouting) { boolean updated = this.updateById(technologyRouting); if (updated) { TechnologyRouting currentRouting = this.getById(technologyRouting.getId()); syncRoutingOperationsByBom(currentRouting); } return updated; } @Override public boolean removeTechnologyRouting(List ids) { if (ids == null || ids.isEmpty()) { return false; } // 判断是否有生产订单绑定该工艺路线 Long count = productionOrderRoutingMapper.selectCount( Wrappers.lambdaQuery() .in(ProductionOrderRouting::getTechnologyRoutingId, ids)); if (count > 0) { throw new ServiceException("该工艺路线已绑定生产订单,无法删除!"); } List routingOperationList = technologyRoutingOperationMapper.selectList( Wrappers.lambdaQuery() .in(TechnologyRoutingOperation::getTechnologyRoutingId, ids)); if (!routingOperationList.isEmpty()) { List routingOperationIdList = routingOperationList.stream().map(TechnologyRoutingOperation::getId).collect(Collectors.toList()); technologyRoutingOperationParamMapper.delete(Wrappers.lambdaQuery() .in(TechnologyRoutingOperationParam::getTechnologyRoutingOperationId, routingOperationIdList)); } technologyRoutingOperationMapper.delete(Wrappers.lambdaQuery() .in(TechnologyRoutingOperation::getTechnologyRoutingId, ids)); return this.removeByIds(ids); } /** * 按当前选择的 BOM 重建工艺路线工序: * 1. 先删除该工艺路线下已有工序 * 2. 读取 BOM 结构中的 operation_id * 3. 按 BOM 出现顺序去重后重新写入工艺路线工序表 */ private void syncRoutingOperationsByBom(TechnologyRouting technologyRouting) { List oldRoutingOperationList = technologyRoutingOperationMapper.selectList( Wrappers.lambdaQuery() .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId())); if (!oldRoutingOperationList.isEmpty()) { List oldRoutingOperationIdList = oldRoutingOperationList.stream().map(TechnologyRoutingOperation::getId).collect(Collectors.toList()); technologyRoutingOperationParamMapper.delete(Wrappers.lambdaQuery() .in(TechnologyRoutingOperationParam::getTechnologyRoutingOperationId, oldRoutingOperationIdList)); technologyRoutingOperationMapper.delete(Wrappers.lambdaQuery() .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId())); } if (technologyRouting.getBomId() == null) { return; } List bomStructures = technologyBomStructureMapper.selectList( Wrappers.lambdaQuery() .eq(TechnologyBomStructure::getBomId, technologyRouting.getBomId()) .orderByAsc(TechnologyBomStructure::getId) ); if (bomStructures.isEmpty()) { throw new ServiceException("bom产品结构为空!"); } Map structureById = new HashMap<>(); Map> childrenByParentId = new LinkedHashMap<>(); List rootList = new ArrayList<>(); for (TechnologyBomStructure bomStructure : bomStructures) { if (bomStructure == null) { continue; } if (bomStructure.getId() != null) { structureById.put(bomStructure.getId(), bomStructure); } if (bomStructure.getParentId() == null || bomStructure.getParentId() == 0L) { rootList.add(bomStructure); } else { childrenByParentId.computeIfAbsent(bomStructure.getParentId(), key -> new ArrayList<>()).add(bomStructure); } } // 后序遍历BOM List orderedOperationNodes = new ArrayList<>(); Set visitingIds = new HashSet<>(); for (TechnologyBomStructure rootNode : rootList) { collectOperationNodePostOrder(rootNode, childrenByParentId, orderedOperationNodes, visitingIds); } if (orderedOperationNodes.isEmpty()) { throw new ServiceException("bom产品结构未配置消耗工序!"); } // 同一层级(同一父节点)内相同工序合并为一道工序,例如同层的多个压接件合并成一道压接; // 合并后的位置取该工序最后一次出现的位置,确保其全部输入物料的工序都在它之前 Map lastPositionByKey = new HashMap<>(); Map operationNodeByKey = new LinkedHashMap<>(); for (int i = 0; i < orderedOperationNodes.size(); i++) { TechnologyBomStructure bomStructure = orderedOperationNodes.get(i); String dedupKey = buildBomOperationDedupKey(bomStructure); lastPositionByKey.put(dedupKey, i); operationNodeByKey.putIfAbsent(dedupKey, bomStructure); } List sortedDedupKeys = new ArrayList<>(operationNodeByKey.keySet()); sortedDedupKeys.sort(Comparator.comparingInt(lastPositionByKey::get)); int dragSort = 1; for (String dedupKey : sortedDedupKeys) { TechnologyBomStructure bomStructure = operationNodeByKey.get(dedupKey); TechnologyRoutingOperation routingOperation = new TechnologyRoutingOperation(); routingOperation.setTechnologyRoutingId(technologyRouting.getId()); routingOperation.setProductModelId(resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId())); routingOperation.setTechnologyOperationId(bomStructure.getOperationId()); routingOperation.setDragSort(dragSort++); TechnologyOperation technologyOperation = getOperation(bomStructure.getOperationId()); routingOperation.setIsQuality(technologyOperation != null ? technologyOperation.getIsQuality() : null); routingOperation.setIsProduction(technologyOperation != null ? technologyOperation.getIsProduction() : null); routingOperation.setType(technologyOperation != null ? technologyOperation.getType() : null); technologyRoutingOperationMapper.insert(routingOperation); syncRoutingOperationParams(routingOperation.getId(), bomStructure.getOperationId()); } } /** * 后序遍历 BOM */ private void collectOperationNodePostOrder(TechnologyBomStructure node, Map> childrenByParentId, List orderedOperationNodes, Set visitingIds) { if (node == null || node.getId() == null) { return; } if (!visitingIds.add(node.getId())) { return; } List children = childrenByParentId.get(node.getId()); if (children != null && !children.isEmpty()) { for (TechnologyBomStructure child : children) { collectOperationNodePostOrder(child, childrenByParentId, orderedOperationNodes, visitingIds); } } visitingIds.remove(node.getId()); if (node.getOperationId() != null) { orderedOperationNodes.add(node); } } /** * 去重键:同一父节点 + 相同工序(只在同一层级内合并,不同层级、不同分支的相同工序不会合并)。 */ private String buildBomOperationDedupKey(TechnologyBomStructure bomStructure) { Long operationId = bomStructure == null ? null : bomStructure.getOperationId(); Long parentId = bomStructure == null ? null : bomStructure.getParentId(); return operationId + "#" + (parentId == null ? "root" : String.valueOf(parentId)); } /** * 工序产出产品 = 父节点的产品(本行物料由父节点层级的消耗工序消耗,产出的是上一层产品, * 而不是本行物料自身);仅当父节点缺失或父节点没有产品时,才回退到本节点自身产品、再回退到工艺路线产品。 */ private Long resolveOutputProductModelId(TechnologyBomStructure bomStructure, Map structureById, Long routingProductModelId) { if (bomStructure == null) { return routingProductModelId; } Long parentId = bomStructure.getParentId(); if (parentId != null && parentId != 0L && structureById != null) { TechnologyBomStructure parent = structureById.get(parentId); if (parent != null && parent.getProductModelId() != null) { return parent.getProductModelId(); } } if (bomStructure.getProductModelId() != null) { return bomStructure.getProductModelId(); } return routingProductModelId; } private void syncRoutingOperationParams(Long technologyRoutingOperationId, Long technologyOperationId) { List operationParamList = technologyOperationParamMapper.selectList( Wrappers.lambdaQuery() .eq(TechnologyOperationParam::getTechnologyOperationId, technologyOperationId) .orderByAsc(TechnologyOperationParam::getId)); if (operationParamList.isEmpty()) { return; } for (TechnologyOperationParam operationParam : operationParamList) { TechnologyParam technologyParam = technologyParamMapper.selectById(operationParam.getTechnologyParamId()); if (technologyParam == null) { continue; } TechnologyRoutingOperationParam snapshot = new TechnologyRoutingOperationParam(); snapshot.setTechnologyRoutingOperationId(technologyRoutingOperationId); snapshot.setTechnologyOperationParamId(operationParam.getId()); snapshot.setTechnologyOperationId(operationParam.getTechnologyOperationId()); snapshot.setParamId(technologyParam.getId()); snapshot.setParamCode(technologyParam.getParamCode()); snapshot.setParamName(technologyParam.getParamName()); snapshot.setParamType(technologyParam.getParamType()); snapshot.setParamFormat(technologyParam.getParamFormat()); snapshot.setUnit(technologyParam.getUnit()); snapshot.setIsRequired(technologyParam.getIsRequired()); snapshot.setRemark(technologyParam.getRemark()); snapshot.setStandardValue(operationParam.getStandardValue()); technologyRoutingOperationParamMapper.insert(snapshot); } } private TechnologyOperation getOperation(Long operationId) { if (operationId == null) { return null; } return technologyOperationMapper.selectById(operationId); } private String buildProcessRouteCode(Long id) { String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String idStr = String.format("%06d", id); return "GYLX" + dateStr + idStr; } private boolean isNotBlank(String value) { return value != null && !value.trim().isEmpty(); } }