src/main/java/com/ruoyi/technology/service/impl/TechnologyBomStructureServiceImpl.java
@@ -1,20 +1,150 @@
package com.ruoyi.technology.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.technology.bean.dto.TechnologyBomStructureDto;
import com.ruoyi.technology.bean.vo.TechnologyBomStructureVo;
import com.ruoyi.technology.mapper.TechnologyBomMapper;
import com.ruoyi.technology.mapper.TechnologyBomStructureMapper;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
import com.ruoyi.technology.pojo.TechnologyBom;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import com.ruoyi.technology.service.TechnologyBomStructureService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * <p>
 * BOM产品结构表 服务实现类
 * </p>
 *
 * @author 芯导软件(江苏)有限公司
 * @since 2026-04-20 10:06:17
 */
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
@RequiredArgsConstructor
public class TechnologyBomStructureServiceImpl extends ServiceImpl<TechnologyBomStructureMapper, TechnologyBomStructure> implements TechnologyBomStructureService {
    private final TechnologyBomStructureMapper technologyBomStructureMapper;
    private final TechnologyBomMapper technologyBomMapper;
    private final ProductModelMapper productModelMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    /**
     * 全量同步 BOM 的扁平物料明细:一行只表示“某个物料规格在某道工序被消耗”。
     * 不再有父子层级、单位用量、需求数量和单位配置。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addTechnologyBomStructure(TechnologyBomStructureDto dto) {
        Long bomId = dto.getBomId();
        if (bomId == null) {
            throw new ServiceException("BOM ID不能为空");
        }
        TechnologyBom bom = technologyBomMapper.selectById(bomId);
        if (bom == null) {
            throw new ServiceException("BOM不存在");
        }
        List<TechnologyBomStructureDto> detailList = dto.getDetailList() == null
                ? Collections.emptyList()
                : dto.getDetailList();
        List<TechnologyBomStructure> dbList = this.list(new LambdaQueryWrapper<TechnologyBomStructure>()
                .eq(TechnologyBomStructure::getBomId, bomId));
        Set<Long> dbIds = new HashSet<>();
        for (TechnologyBomStructure dbItem : dbList) {
            dbIds.add(dbItem.getId());
        }
        Set<Long> submittedIds = new HashSet<>();
        Set<String> relationKeys = new HashSet<>();
        List<TechnologyBomStructure> insertList = new ArrayList<>();
        List<TechnologyBomStructure> updateList = new ArrayList<>();
        for (TechnologyBomStructureDto item : detailList) {
            Long productModelId = item.getProductModelId();
            Long operationId = item.getOperationId();
            validateMaterial(bom, productModelId);
            validateOperation(operationId);
            if (!relationKeys.add(productModelId + "#" + operationId)) {
                throw new ServiceException("同一物料规格在同一工序下不能重复配置");
            }
            TechnologyBomStructure entity = new TechnologyBomStructure();
            entity.setBomId(bomId);
            entity.setProductModelId(productModelId);
            entity.setOperationId(operationId);
            // 扁平 BOM 不再使用层级和数量语义,历史列统一置空。
            entity.setParentId(null);
            entity.setUnitQuantity(null);
            entity.setDemandedQuantity(null);
            entity.setUnit(null);
            if (item.getId() == null) {
                insertList.add(entity);
            } else {
                if (!dbIds.contains(item.getId())) {
                    throw new ServiceException("BOM物料明细不存在或不属于当前BOM");
                }
                entity.setId(item.getId());
                submittedIds.add(item.getId());
                updateList.add(entity);
            }
        }
        Set<Long> deleteIds = new HashSet<>(dbIds);
        deleteIds.removeAll(submittedIds);
        if (!deleteIds.isEmpty()) {
            this.removeByIds(deleteIds);
        }
        if (!updateList.isEmpty()) {
            this.updateBatchById(updateList);
        }
        if (!insertList.isEmpty()) {
            this.saveBatch(insertList);
        }
        return true;
    }
    /**
     * 根据BOM查询扁平物料明细,按稳定顺序返回。
     */
    @Override
    public List<TechnologyBomStructureVo> listByBomId(Long bomId) {
        if (bomId == null) {
            return new ArrayList<>();
        }
        return technologyBomStructureMapper.listByBomId(bomId);
    }
    /**
     * 校验物料规格:必须存在,且不能是 BOM 自身的成品规格。
     */
    private void validateMaterial(TechnologyBom bom, Long productModelId) {
        if (productModelId == null) {
            throw new ServiceException("请选择物料规格");
        }
        ProductModel productModel = productModelMapper.selectById(productModelId);
        if (productModel == null) {
            throw new ServiceException("物料规格不存在");
        }
        if (productModelId.equals(bom.getProductModelId())) {
            throw new ServiceException("成品规格不能作为自身的物料");
        }
    }
    /**
     * 校验消耗工序必填且存在。
     */
    private void validateOperation(Long operationId) {
        if (operationId == null) {
            throw new ServiceException("请选择消耗工序");
        }
        if (technologyOperationMapper.selectById(operationId) == null) {
            throw new ServiceException("消耗工序不存在");
        }
    }
}