| | |
| | | package com.ruoyi.production.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.production.bean.dto.ProductionBomStructureDto; |
| | | import com.ruoyi.production.bean.vo.ProductionBomStructureVo; |
| | | import com.ruoyi.production.mapper.ProductionBomStructureMapper; |
| | | import com.ruoyi.production.mapper.ProductionOperationTaskMapper; |
| | | import com.ruoyi.production.mapper.ProductionOrderBomMapper; |
| | | import com.ruoyi.production.mapper.ProductionOrderMapper; |
| | | import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper; |
| | | import com.ruoyi.production.pojo.ProductionBomStructure; |
| | | import com.ruoyi.production.pojo.ProductionOperationTask; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.pojo.ProductionOrderBom; |
| | | import com.ruoyi.production.pojo.ProductionOrderRoutingOperation; |
| | | import com.ruoyi.production.service.ProductionBomStructureService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.math.BigDecimal; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | @RequiredArgsConstructor() |
| | | public class ProductionBomStructureServiceImpl extends ServiceImpl<ProductionBomStructureMapper, ProductionBomStructure> implements ProductionBomStructureService { |
| | | |
| | | private final ProductionBomStructureMapper productionBomStructureMapper; |
| | | private final ProductionBomStructureMapper productionBomStructureMapper; |
| | | private final ProductionOrderBomMapper productionOrderBomMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | |
| | | /** |
| | | * 根据BOM查询并组装结构树。 |
| | | */ |
| | | @Override |
| | | public List<ProductionBomStructureVo> listByBomId(Long bomId) { |
| | | // 按BOMID查询生产结构数据 |
| | | List<ProductionBomStructureVo> list = productionBomStructureMapper.listByBomId(bomId); |
| | | Map<Long, ProductionBomStructureVo> map = new HashMap<>(); |
| | | for (ProductionBomStructureVo node : list) { |
| | |
| | | return tree; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean addProductionBomStructure(ProductionBomStructureDto dto) { |
| | | // 新增生产BOM结构 |
| | | // 读取当前订单BOM主键,并把前端树结构拍平成列表 |
| | | Long orderBomId = dto.getProductionOrderBomId(); |
| | | List<ProductionBomStructureDto> flatDtoList = new ArrayList<>(); |
| | | flattenTree(dto.getChildren(), flatDtoList); |
| | | |
| | | // 查询数据库已有结构,用于后续做增删改对比 |
| | | List<ProductionBomStructure> dbList = this.list(new LambdaQueryWrapper<ProductionBomStructure>() |
| | | .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId)); |
| | | |
| | | // 收集前端仍然存在的节点ID |
| | | Set<Long> frontendIds = new HashSet<>(); |
| | | for (ProductionBomStructureDto item : flatDtoList) { |
| | | if (item.getId() != null) { |
| | | frontendIds.add(item.getId()); |
| | | } |
| | | } |
| | | |
| | | // 计算需要删除的节点(数据库有、前端已删除) |
| | | Set<Long> deleteIds = new HashSet<>(); |
| | | for (ProductionBomStructure dbItem : dbList) { |
| | | if (!frontendIds.contains(dbItem.getId())) { |
| | | deleteIds.add(dbItem.getId()); |
| | | } |
| | | } |
| | | // 先删掉前端已经移除的节点 |
| | | if (!deleteIds.isEmpty()) { |
| | | this.removeByIds(deleteIds); |
| | | } |
| | | |
| | | // 按是否有ID拆分为新增和更新,同时缓存新增节点的临时ID映射 |
| | | List<ProductionBomStructure> insertList = new ArrayList<>(); |
| | | List<ProductionBomStructure> updateList = new ArrayList<>(); |
| | | Map<String, ProductionBomStructure> tempEntityMap = new HashMap<>(); |
| | | |
| | | for (ProductionBomStructureDto item : flatDtoList) { |
| | | ProductionBomStructure entity = new ProductionBomStructure(); |
| | | BeanUtils.copyProperties(item, entity); |
| | | entity.setProductionOrderBomId(orderBomId); |
| | | if (item.getId() == null) { |
| | | entity.setParentId(null); |
| | | insertList.add(entity); |
| | | tempEntityMap.put(item.getTempId(), entity); |
| | | } else { |
| | | updateList.add(entity); |
| | | } |
| | | } |
| | | |
| | | // 批量新增,拿到数据库生成的真实ID |
| | | if (!insertList.isEmpty()) { |
| | | this.saveBatch(insertList); |
| | | } |
| | | |
| | | // 新增节点二次回写父ID(前端传的是临时父ID) |
| | | List<ProductionBomStructure> parentFixList = new ArrayList<>(); |
| | | for (ProductionBomStructureDto item : flatDtoList) { |
| | | if (item.getId() == null && item.getParentTempId() != null) { |
| | | ProductionBomStructure child = tempEntityMap.get(item.getTempId()); |
| | | if (child == null) { |
| | | continue; |
| | | } |
| | | ProductionBomStructure parent = tempEntityMap.get(item.getParentTempId()); |
| | | // 父节点是本次新增时,直接用新增后的真实ID;否则回退为前端传入父ID |
| | | Long realParentId = parent != null ? parent.getId() : Long.valueOf(item.getParentTempId()); |
| | | child.setParentId(realParentId); |
| | | parentFixList.add(child); |
| | | } |
| | | } |
| | | |
| | | // 回写新增节点的父子关系 |
| | | if (!parentFixList.isEmpty()) { |
| | | this.updateBatchById(parentFixList); |
| | | } |
| | | // 批量更新已有节点 |
| | | if (!updateList.isEmpty()) { |
| | | this.updateBatchById(updateList); |
| | | } |
| | | syncDemandedQuantityAndTaskPlanQuantity(orderBomId, dto.getProductionOrderId()); |
| | | return true; |
| | | } |
| | | |
| | | private void syncDemandedQuantityAndTaskPlanQuantity(Long orderBomId, Long productionOrderId) { |
| | | if (orderBomId == null) { |
| | | return; |
| | | } |
| | | ProductionOrderBom orderBom = productionOrderBomMapper.selectById(orderBomId); |
| | | if (orderBom == null) { |
| | | return; |
| | | } |
| | | Long currentProductionOrderId = productionOrderId != null ? productionOrderId : orderBom.getProductionOrderId(); |
| | | if (currentProductionOrderId == null) { |
| | | return; |
| | | } |
| | | ProductionOrder productionOrder = productionOrderMapper.selectById(currentProductionOrderId); |
| | | if (productionOrder == null) { |
| | | return; |
| | | } |
| | | |
| | | BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity()); |
| | | List<ProductionBomStructure> structureList = this.list( |
| | | Wrappers.<ProductionBomStructure>lambdaQuery() |
| | | .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId) |
| | | .orderByAsc(ProductionBomStructure::getId)); |
| | | syncStructureDemandedQuantity(structureList, orderQuantity); |
| | | syncTaskPlanQuantity( |
| | | currentProductionOrderId, |
| | | structureList, |
| | | orderQuantity, |
| | | orderBom.getProductModelId() != null ? orderBom.getProductModelId() : productionOrder.getProductModelId()); |
| | | } |
| | | |
| | | private void syncStructureDemandedQuantity(List<ProductionBomStructure> structureList, BigDecimal orderQuantity) { |
| | | if (structureList == null || structureList.isEmpty()) { |
| | | return; |
| | | } |
| | | List<ProductionBomStructure> updateList = new ArrayList<>(); |
| | | for (ProductionBomStructure structure : structureList) { |
| | | if (structure == null || structure.getId() == null) { |
| | | continue; |
| | | } |
| | | BigDecimal demandedQuantity = defaultDecimal(structure.getUnitQuantity()).multiply(orderQuantity); |
| | | if (compareDecimal(structure.getDemandedQuantity(), demandedQuantity) == 0) { |
| | | continue; |
| | | } |
| | | ProductionBomStructure update = new ProductionBomStructure(); |
| | | update.setId(structure.getId()); |
| | | update.setDemandedQuantity(demandedQuantity); |
| | | updateList.add(update); |
| | | structure.setDemandedQuantity(demandedQuantity); |
| | | } |
| | | if (!updateList.isEmpty()) { |
| | | this.updateBatchById(updateList); |
| | | } |
| | | } |
| | | |
| | | private void syncTaskPlanQuantity(Long productionOrderId, |
| | | List<ProductionBomStructure> structureList, |
| | | BigDecimal orderQuantity, |
| | | Long rootProductModelId) { |
| | | List<ProductionOperationTask> taskList = productionOperationTaskMapper.selectList( |
| | | Wrappers.<ProductionOperationTask>lambdaQuery() |
| | | .eq(ProductionOperationTask::getProductionOrderId, productionOrderId) |
| | | .orderByAsc(ProductionOperationTask::getId)); |
| | | if (taskList == null || taskList.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | Set<Long> routingOperationIds = taskList.stream() |
| | | .map(ProductionOperationTask::getProductionOrderRoutingOperationId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | if (routingOperationIds.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | Map<Long, ProductionOrderRoutingOperation> routingOperationMap = productionOrderRoutingOperationMapper |
| | | .selectBatchIds(routingOperationIds) |
| | | .stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(ProductionOrderRoutingOperation::getId, item -> item, (left, right) -> left)); |
| | | Map<String, BigDecimal> demandedQuantityMap = buildOperationDemandedQuantityMap(structureList, rootProductModelId, orderQuantity); |
| | | |
| | | for (ProductionOperationTask task : taskList) { |
| | | if (task == null || task.getId() == null || task.getProductionOrderRoutingOperationId() == null) { |
| | | continue; |
| | | } |
| | | ProductionOrderRoutingOperation routingOperation = routingOperationMap.get(task.getProductionOrderRoutingOperationId()); |
| | | if (routingOperation == null || routingOperation.getTechnologyRoutingOperationId() == null) { |
| | | continue; |
| | | } |
| | | BigDecimal planQuantity = resolveTaskPlanQuantity(routingOperation, demandedQuantityMap, orderQuantity); |
| | | if (compareDecimal(task.getPlanQuantity(), planQuantity) == 0) { |
| | | continue; |
| | | } |
| | | ProductionOperationTask update = new ProductionOperationTask(); |
| | | update.setId(task.getId()); |
| | | update.setPlanQuantity(planQuantity); |
| | | productionOperationTaskMapper.updateById(update); |
| | | } |
| | | } |
| | | |
| | | private Map<String, BigDecimal> buildOperationDemandedQuantityMap(List<ProductionBomStructure> structureList, |
| | | Long rootProductModelId, |
| | | BigDecimal orderQuantity) { |
| | | if (structureList == null || structureList.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | Map<Long, ProductionBomStructure> structureById = structureList.stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left)); |
| | | Map<String, BigDecimal> demandedQuantityMap = new HashMap<>(); |
| | | for (ProductionBomStructure bomStructure : structureList) { |
| | | if (bomStructure == null || bomStructure.getTechnologyOperationId() == null || bomStructure.getUnitQuantity() == null) { |
| | | continue; |
| | | } |
| | | Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, rootProductModelId); |
| | | String key = buildOperationDemandedQuantityKey(bomStructure.getTechnologyOperationId(), outputProductModelId); |
| | | demandedQuantityMap.merge(key, bomStructure.getUnitQuantity().multiply(orderQuantity), BigDecimal::add); |
| | | } |
| | | return demandedQuantityMap; |
| | | } |
| | | |
| | | private BigDecimal resolveTaskPlanQuantity(ProductionOrderRoutingOperation routingOperation, |
| | | Map<String, BigDecimal> demandedQuantityMap, |
| | | BigDecimal orderQuantity) { |
| | | if (routingOperation == null || demandedQuantityMap == null || demandedQuantityMap.isEmpty()) { |
| | | return orderQuantity; |
| | | } |
| | | String key = buildOperationDemandedQuantityKey( |
| | | routingOperation.getTechnologyOperationId(), |
| | | routingOperation.getProductModelId()); |
| | | BigDecimal planQuantity = demandedQuantityMap.get(key); |
| | | return planQuantity != null ? planQuantity : orderQuantity; |
| | | } |
| | | |
| | | private String buildOperationDemandedQuantityKey(Long operationId, Long outputProductModelId) { |
| | | return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId); |
| | | } |
| | | |
| | | private Long resolveOutputProductModelId(ProductionBomStructure bomStructure, |
| | | Map<Long, ProductionBomStructure> structureById, |
| | | Long rootProductModelId) { |
| | | if (bomStructure == null) { |
| | | return rootProductModelId; |
| | | } |
| | | Long parentId = bomStructure.getParentId(); |
| | | if (parentId == null) { |
| | | return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId(); |
| | | } |
| | | ProductionBomStructure parent = structureById.get(parentId); |
| | | if (parent != null && parent.getProductModelId() != null) { |
| | | return parent.getProductModelId(); |
| | | } |
| | | return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId(); |
| | | } |
| | | |
| | | private BigDecimal defaultDecimal(BigDecimal value) { |
| | | return value == null ? BigDecimal.ZERO : value; |
| | | } |
| | | |
| | | private int compareDecimal(BigDecimal left, BigDecimal right) { |
| | | return defaultDecimal(left).compareTo(defaultDecimal(right)); |
| | | } |
| | | |
| | | /** |
| | | * 将树形结构拍平成列表,便于统一保存。 |
| | | */ |
| | | private void flattenTree(List<ProductionBomStructureDto> source, List<ProductionBomStructureDto> result) { |
| | | // 扁平化处理树 |
| | | if (source == null) { |
| | | return; |
| | | } |
| | | for (ProductionBomStructureDto node : source) { |
| | | result.add(node); |
| | | flattenTree(node.getChildren(), result); |
| | | } |
| | | } |
| | | |
| | | } |