| | |
| | | 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.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; |
| | |
| | | |
| | | @Resource |
| | | private MesProRouteProcessMapper routeProcessMapper; |
| | | @Resource |
| | | private MesProRouteProcessOutputMapper routeProcessOutputMapper; |
| | | |
| | | @Resource |
| | | @Lazy |
| | |
| | | |
| | | // 2. 插入 |
| | | MesProRouteProcessDO routeProcess = BeanUtils.toBean(createReqVO, MesProRouteProcessDO.class); |
| | | if (CollUtil.isNotEmpty(createReqVO.getOutputItemIds())) { |
| | | routeProcess.setOutputItemId( |
| | | createReqVO.getOutputItemIds().stream() |
| | | .map(String::valueOf) |
| | | .collect(Collectors.joining(","))); |
| | | } |
| | | routeProcess.setOutputItemId(resolveOutputItemId(createReqVO)); |
| | | routeProcessMapper.insert(routeProcess); |
| | | // 2.1 保存产出归属配置 |
| | | saveRouteProcessOutputs(routeProcess, createReqVO); |
| | | |
| | | // 3. 更新工序链 |
| | | rebuildProcessChain(createReqVO.getRouteId()); |
| | |
| | | public void updateRouteProcess(MesProRouteProcessSaveReqVO updateReqVO) { |
| | | // 1.0 已启用的工艺路线,不允许操作 |
| | | routeService.validateRouteNotEnable(updateReqVO.getRouteId()); |
| | | // 1.1 校验存在;部分更新(如仅调整产出产品)时未传的必填字段用现有值回填 |
| | | // 1.1 校验存在;兼容只调整产出配置的部分更新请求 |
| | | MesProRouteProcessDO existing = validateRouteProcessExists(updateReqVO.getId()); |
| | | if (updateReqVO.getProcessId() == null) { |
| | | updateReqVO.setProcessId(existing.getProcessId()); |
| | |
| | | |
| | | // 2. 更新 |
| | | MesProRouteProcessDO updateObj = BeanUtils.toBean(updateReqVO, MesProRouteProcessDO.class); |
| | | if (CollUtil.isNotEmpty(updateReqVO.getOutputItemIds())) { |
| | | updateObj.setOutputItemId( |
| | | updateReqVO.getOutputItemIds().stream() |
| | | .map(String::valueOf) |
| | | .collect(Collectors.joining(","))); |
| | | } |
| | | // 空数组也要显式写入空串,否则 MyBatis-Plus 默认忽略 null,用户清空产出品后旧配置仍会残留。 |
| | | updateObj.setOutputItemId(resolveOutputItemId(updateReqVO)); |
| | | routeProcessMapper.updateById(updateObj); |
| | | // 2.1 覆盖产出归属配置 |
| | | saveRouteProcessOutputs(updateObj, updateReqVO); |
| | | |
| | | // 3. 重建工序链 |
| | | rebuildProcessChain(updateReqVO.getRouteId()); |
| | |
| | | // 1.2 已启用的工艺路线,不允许操作 |
| | | routeService.validateRouteNotEnable(routeProcess.getRouteId()); |
| | | |
| | | // 2. 删除 |
| | | // 2. 删除(含产出归属配置) |
| | | routeProcessMapper.deleteById(id); |
| | | routeProcessOutputMapper.deleteByRouteProcessId(id); |
| | | |
| | | // 3. 重建工序链 |
| | | rebuildProcessChain(routeProcess.getRouteId()); |
| | |
| | | return Collections.emptyList(); |
| | | } |
| | | // 2. 返回该工艺路线的工序列表 |
| | | return routeProcessMapper.selectListByRouteId(routeProduct.getRouteId()); |
| | | 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 |
| | |
| | | @Override |
| | | public void deleteRouteProcessByRouteId(Long routeId) { |
| | | routeProcessMapper.deleteByRouteId(routeId); |
| | | routeProcessOutputMapper.deleteByRouteId(routeId); |
| | | } |
| | | |
| | | } |