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;
/**
*
* 生产订单BOM产品结构 服务实现类
*
*
* @author 芯导软件(江苏)有限公司
* @since 2026-04-21 03:55:52
*/
@Service
@RequiredArgsConstructor()
public class ProductionBomStructureServiceImpl extends ServiceImpl 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 listByBomId(Long bomId) {
// 按BOMID查询生产结构数据
List list = productionBomStructureMapper.listByBomId(bomId);
Map map = new HashMap<>();
for (ProductionBomStructureVo node : list) {
node.setChildren(new ArrayList<>());
map.put(node.getId(), node);
}
List 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 flatDtoList = new ArrayList<>();
flattenTree(dto.getChildren(), flatDtoList);
// 查询数据库已有结构,用于后续做增删改对比
List dbList = this.list(new LambdaQueryWrapper()
.eq(ProductionBomStructure::getProductionOrderBomId, orderBomId));
// 收集前端仍然存在的节点ID
Set frontendIds = new HashSet<>();
for (ProductionBomStructureDto item : flatDtoList) {
if (item.getId() != null) {
frontendIds.add(item.getId());
}
}
// 计算需要删除的节点(数据库有、前端已删除)
Set 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 insertList = new ArrayList<>();
List updateList = new ArrayList<>();
Map 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 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 structureList = this.list(
Wrappers.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 structureList, BigDecimal orderQuantity) {
if (structureList == null || structureList.isEmpty()) {
return;
}
List 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 structureList,
BigDecimal orderQuantity,
Long rootProductModelId) {
List taskList = productionOperationTaskMapper.selectList(
Wrappers.lambdaQuery()
.eq(ProductionOperationTask::getProductionOrderId, productionOrderId)
.orderByAsc(ProductionOperationTask::getId));
if (taskList == null || taskList.isEmpty()) {
return;
}
Set routingOperationIds = taskList.stream()
.map(ProductionOperationTask::getProductionOrderRoutingOperationId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (routingOperationIds.isEmpty()) {
return;
}
Map routingOperationMap = productionOrderRoutingOperationMapper
.selectBatchIds(routingOperationIds)
.stream()
.filter(item -> item != null && item.getId() != null)
.collect(Collectors.toMap(ProductionOrderRoutingOperation::getId, item -> item, (left, right) -> left));
Map 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 buildOperationDemandedQuantityMap(List structureList,
Long rootProductModelId,
BigDecimal orderQuantity) {
if (structureList == null || structureList.isEmpty()) {
return Collections.emptyMap();
}
Map structureById = structureList.stream()
.filter(item -> item != null && item.getId() != null)
.collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
Map 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 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 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 source, List result) {
// 扁平化处理树
if (source == null) {
return;
}
for (ProductionBomStructureDto node : source) {
result.add(node);
flattenTree(node.getChildren(), result);
}
}
}