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.math.BigDecimal;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* <p>
|
* 生产订单BOM产品结构 服务实现类
|
* </p>
|
*
|
* @author 芯导软件(江苏)有限公司
|
* @since 2026-04-21 03:55:52
|
*/
|
@Service
|
@RequiredArgsConstructor()
|
public class ProductionBomStructureServiceImpl extends ServiceImpl<ProductionBomStructureMapper, ProductionBomStructure> implements ProductionBomStructureService {
|
|
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) {
|
node.setChildren(new ArrayList<>());
|
map.put(node.getId(), node);
|
}
|
|
List<ProductionBomStructureVo> tree = new ArrayList<>();
|
for (ProductionBomStructureVo node : list) {
|
Long parentId = node.getParentId();
|
if (parentId == null || parentId == 0L) {
|
tree.add(node);
|
continue;
|
}
|
ProductionBomStructureVo parent = map.get(parentId);
|
if (parent != null) {
|
parent.getChildren().add(node);
|
}
|
}
|
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);
|
}
|
}
|
|
}
|