/* * Copyright (c) 2018-2025, ztt All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: ztt */ package com.chinaztt.mes.technology.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.chinaztt.mes.basic.entity.Part; import com.chinaztt.mes.basic.mapper.PartMapper; import com.chinaztt.mes.common.handler.StateMachineHandler; import com.chinaztt.mes.common.numgen.NumberGenerator; import com.chinaztt.mes.common.util.StateResult; import com.chinaztt.mes.technology.dto.BomDTO; import com.chinaztt.mes.technology.dto.StructureTree; import com.chinaztt.mes.technology.entity.*; import com.chinaztt.mes.technology.excel.BomData; import com.chinaztt.mes.technology.mapper.*; import com.chinaztt.mes.technology.service.BomService; import com.chinaztt.mes.technology.state.bom.BomStateMachineConfig; import com.chinaztt.mes.technology.state.bom.constant.BomEvents; import com.chinaztt.mes.technology.state.bom.constant.BomStateStringValues; import com.chinaztt.mes.technology.state.bom.constant.BomStates; import com.chinaztt.ztt.common.core.util.R; import lombok.AllArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.config.StateMachineFactory; import org.springframework.statemachine.persist.StateMachinePersister; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 完整bom树 * * @author zhangxy * @date 2021-05-07 15:08:17 */ @Service @AllArgsConstructor @Transactional(rollbackFor = Exception.class) public class BomServiceImpl extends ServiceImpl implements BomService { private NumberGenerator numberGenerator; /** * 随机ID,前端用 */ public volatile static long RANDOM_ID = 0; /** * 循环层级,最多10层防止出现死循环 */ public static final int CYCLE_DEEP = 10; private PartMapper partMapper; private OperationMapper operationMapper; private StructureMapper structureMapper; private BomComponentMapper bomComponentMapper; private StructureComponentMapper structureComponentMapper; private StateMachineFactory bomStateMachineFactory; private StateMachinePersister persister; @Override public void importExcel(List list) { if (CollectionUtil.isEmpty(list)) { return; } for (BomData data : list) { Bom bom = new Bom(); bom.setInsulationColor(data.getInsulationColor()); bom.setSheathColor(data.getSheathColor()); bom.setCharacteristicOne(data.getCharacteristicOne()); String partNo = data.getPartNo(); if (StringUtils.isBlank(data.getPartNo()) || StringUtils.isBlank(data.getVersion())) { log.error("bom导入:" + data + "零件号为空"); continue; } //零件号和版本号查询唯一 Part part = partMapper.selectOne(Wrappers.lambdaQuery() .eq(Part::getPartNo, partNo).eq(Part::getEngChgLevel, data.getVersion())); if (null == part) { log.error("bom导入 :" + data + "零件不存在"); continue; } bom.setPartId(part.getId()); bom.setVersion(data.getVersion()); bom.setNumber(numberGenerator.generateNumberWithPrefix(Bom.DIGIT, Bom.PREFIX, Bom::getNumber)); bom.setState(BomStateStringValues.DRAFT); baseMapper.insert(bom); //查询构建树数据 saveBomComp(getAllBomExt(bom, 999), bom); } } @Override public IPage> getPage(Page page, QueryWrapper ew) { return baseMapper.getBomPage(page, ew); } @Override public R saveBom(BomDTO bomDTO) { Bom bom = new Bom(); BeanUtils.copyProperties(bomDTO, bom); if (StringUtils.isBlank(bom.getNumber())) { bom.setNumber(numberGenerator.generateNumberWithPrefix(Bom.DIGIT, Bom.PREFIX, Bom::getNumber)); } bom.setState(BomStateStringValues.DRAFT); baseMapper.insert(bom); saveBomComp(bomDTO.getTree(), bom); return R.ok(getBomDtoById(bom.getId())); } /** * @Author: Hans * @Description: 根据产品结构保存所有BOM * @Date: 2022-06-01 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED) public R saveALLBom(List structures) { structures.forEach(structure -> { // Structure structure = structures.get(i); Bom bom = new Bom(); bom.setPartId(structure.getPartId()); bom.setBomTypeDb(structure.getBomTypeDb()); bom.setVersion(structure.getVersion()); bom.setAlternativeNo(structure.getAlternativeNo()); bom.setAlternativeDesc(structure.getAlternativeDesc()); StructureTree tree = getAllBomExt(bom, 998); BomDTO bomDTO = new BomDTO(); BeanUtils.copyProperties(bom, bomDTO); bomDTO.setTree(tree); saveBom(bomDTO); }); return R.ok(); } //插入头BomComp @Override public void saveBomComp(StructureTree structureTree, Bom bom) { BomComponent bomComp = new BomComponent(); bomComp.setParentStructureId(structureTree.getStructureId()); bomComp.setQpa(structureTree.getQpa()); bomComp.setOriginalQpa(structureTree.getOriginalQpa()); bomComp.setUnit(structureTree.getUnit()); bomComp.setPartId(structureTree.getPartId()); bomComp.setBomId(bom.getId()); bomComp.setPlanningMethod(structureTree.getPlanningMethod()); bomComp.setColor(structureTree.getColor()); bomComp.setOperationId(structureTree.getOperationId()); bomComponentMapper.insert(bomComp); saveChildren(structureTree.getChildren(), bomComp.getId(), bom, structureTree.getPartId()); } @Override public R updateBom(BomDTO bomDTO) { Bom bom = new Bom(); BeanUtils.copyProperties(bomDTO, bom); baseMapper.updateById(bom); bomComponentMapper.delete(Wrappers.lambdaQuery().eq(BomComponent::getBomId, bomDTO.getId())); saveBomComp(bomDTO.getTree(), bom); return R.ok(); } /** * @param children 子节点list * @param parentId 父节点id * @param bom 根BOM * @param parentPartId 父节点的零件号 */ private void saveChildren(List children, Long parentId, Bom bom, Long parentPartId) { if (CollectionUtil.isEmpty(children)) { return; } Part parentPart = partMapper.selectById(parentPartId); //"P" 虚拟件的意思 在一个BOM中,除根节点外,其余节点若不是虚拟件(零件【计划方法】不为P 和 K),则禁止为其增加子件。 if (!bom.getPartId().equals(parentPartId)) { if (!"P".equals(parentPart.getPlanningMethod()) && !"K".equals(parentPart.getPlanningMethod())) { throw new RuntimeException(parentPart.getPartNo() + parentPart.getPartName() + "为非虚拟件,不可添加子件"); } } for (StructureTree child : children) { BomComponent bomComp = new BomComponent(); bomComp.setQpa(child.getQpa()); bomComp.setParentStructureId(child.getStructureId()); bomComp.setOriginalQpa(child.getOriginalQpa()); bomComp.setUnit(child.getUnit()); bomComp.setPartId(child.getPartId()); bomComp.setParent(parentId); bomComp.setBomId(bom.getId()); bomComp.setOperationId(child.getOperationId()); bomComp.setPlanningMethod(child.getPlanningMethod()); bomComp.setDiscNum(child.getDiscNum()); bomComponentMapper.insert(bomComp); saveChildren(child.getChildren(), bomComp.getId(), bom, child.getPartId()); } } /** * @author hans * @date 2022-5-24 * 添加bomLayers,设置BOM默认递归查询层数 */ @Override public StructureTree getAllBomExt(Bom bom, int bomLayers) { Long partId = bom.getPartId(); String version = bom.getVersion(); String alternativeNo = bom.getAlternativeNo(); String bomType = bom.getBomTypeDb(); // 判断获取BOM的三个关键字段是否为空 if (null == partId || version == null || alternativeNo == null) { return null; } if (RANDOM_ID >= Integer.MAX_VALUE) { RANDOM_ID = 0; } // 获取BOM根节点 StructureTree root = getRootNode(partId); //通过产品结构查工序 Map cache = new HashMap<>(80); getStructureByPartId(root, partId, cache, version, alternativeNo, bomType, bomLayers); return root; } /** * @Author: Hans * @Description: 获取BOM根节点 * @Date: 2022-06-01 */ private StructureTree getRootNode(Long partId) { Part part = partMapper.selectById(partId); StructureTree root = new StructureTree(); root.setPartName(part.getPartName()); root.setPlanningMethod(part.getPlanningMethod()); root.setPartId(part.getId()); root.setQpa(BigDecimal.ONE); root.setOriginalQpa(BigDecimal.ONE); root.setId(++RANDOM_ID); root.setParentId(0L); root.setExpand(true); root.setStructureId(0L); root.setUnit(part.getUnit()); root.setChecked(false); return root; } /** * @Author: Hans * @Description: 通过零件ID查询所有版本和替代的BOM * @Date: 2022-06-02 */ @Override public StructureTree getAllBomByPartId(Long partId) { if (RANDOM_ID >= Integer.MAX_VALUE) { RANDOM_ID = 0; } Part part = partMapper.selectById(partId); StructureTree root = new StructureTree(); root.setPartName(part.getPartName()); root.setPartId(part.getId()); root.setPartNo(part.getPartNo()); root.setQpa(BigDecimal.ONE); root.setOriginalQpa(BigDecimal.ONE); root.setId(++RANDOM_ID); root.setParentId(0L); root.setExpand(true); root.setStructureId(0L); root.setUnit(part.getUnit()); root.setChecked(false); //通过产品结构查工序 Map cache = new HashMap<>(80); // 版本version为ALL:查询所有版本的产品结构,替代为ALL:查询所有替代的产品结构 getStructureByPartId(root, partId, cache, "ALL", "ALL", null, 999); return root; } public Bom copyBom(Long id) { Bom bom = baseMapper.selectById(id); bom.setNumber(numberGenerator.generateNumberWithPrefix(Bom.DIGIT, Bom.PREFIX, Bom::getNumber)); List bomCompList = bomComponentMapper.selectList(Wrappers.lambdaQuery().eq(BomComponent::getBomId, id)); baseMapper.insert(bom); if (CollectionUtil.isNotEmpty(bomCompList)) { for (BomComponent bomComponent : bomCompList) { bomComponent.setBomId(bom.getId()); bomComponentMapper.insert(bomComponent); } } return bom; } @Override public BomDTO getBomDtoById(Long id) { Bom bom = baseMapper.selectById(id); //如果存在未绑定工序的 工艺路线 则会为空 List nodes = baseMapper.getBomComponents4Tree(id); if (CollectionUtil.isEmpty(nodes)) { return null; } StructureTree root = nodes.stream().filter(e -> e.getParentId() == null || e.getParentId() == 0).findFirst().get(); setChildren(root, nodes); Part part = partMapper.selectById(bom.getPartId()); BomDTO bomDto = new BomDTO(); BeanUtils.copyProperties(bom, bomDto); bomDto.setTree(root); bomDto.setPartName(part.getPartName()); bomDto.setPartNo(part.getPartNo()); return bomDto; } private void setChildren(StructureTree node, List list) { List children = list.stream().filter(e -> e.getParentId() != null && e.getParentId().equals(node.getId())).collect(Collectors.toList()); if (CollectionUtil.isEmpty(children)) { return; } node.setChildren(children); children.forEach(e -> { setChildren(e, list); }); } @Override public R delete(List ids) { for (Long id : ids) { bomComponentMapper.delete(Wrappers.lambdaQuery().eq(BomComponent::getBomId, id)); } baseMapper.deleteBatchIds(ids); return R.ok(); } @Override public List getComponentsById(Long bomId) { return bomComponentMapper.selectList(Wrappers.lambdaQuery().eq(BomComponent::getBomId, bomId)); } @Override public boolean changeState(List ids, String event) { for (Long id : ids) { Bom bom = baseMapper.selectById(id); Message message = MessageBuilder.withPayload(BomEvents.valueOf(event)).setHeader("bom", bom).build(); StateMachineHandler handler = new StateMachineHandler(bomStateMachineFactory, persister, BomStateMachineConfig.MACHINE_ID, bom); StateResult res = handler.sendEvent(message, bom.getId()); if (!res.isSuccess()) { throw new RuntimeException(res.getMsg()); } } return true; } /*private void getStructureByPartId(StructureTree parent, Long partId, Map cache, int bomLayers) { Integer cycleCount = cache.get(partId) == null ? 0 : cache.get(partId); if (cycleCount >= CYCLE_DEEP) { return; } else { cache.put(partId, cycleCount + 1); } //根据零件找到结构主表 List structures = structureMapper.selectList(Wrappers.lambdaQuery().eq(Structure::getPartId, partId)); if (CollectionUtil.isNotEmpty(structures)) { for (Structure s : structures) { List components = structureComponentMapper.selectList(Wrappers.lambdaQuery().eq(StructureComponent::getStructureId, s.getId()) .orderByAsc(StructureComponent::getId)); if (CollectionUtil.isEmpty(components)) { return; } BigDecimal parentQpa = parent.getQpa(); List children = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren(); // 遍历一层减少一层 bomLayers--; for (StructureComponent c : components) { Part part = partMapper.selectById(c.getPartId()); Operation operation = operationMapper.selectById(c.getOperationId()); if (part == null) { continue; } StructureTree child = new StructureTree(); child.setId(++RANDOM_ID); child.setParentId(parent.getId()); child.setCompId(c.getId()); child.setStructureId(s.getId()); child.setPartId(c.getPartId()); child.setPartNo(part.getPartNo()); child.setPartName(part.getPartName()); child.setExpand(true); child.setUnit(part.getUnit()); child.setChecked(false); child.setQpa(c.getQpa().multiply(parentQpa).setScale(8, BigDecimal.ROUND_HALF_UP)); child.setOriginalQpa(c.getQpa()); child.setDiscNum(null == c.getDiscNum() ? null : c.getDiscNum()); if (null != operation) { child.setOperationId(operation.getId()); child.setOperationNo(operation.getOperationNo()); child.setOperationName(operation.getName()); } // 判断到第几层了 if (bomLayers > 0) getStructureByPartId(child, c.getPartId(), cache, bomLayers); children.add(child); } parent.setChildren(children); } } }*/ /** * @Author: Hans * @Description: 查询root节点 * @Date: 2022-06-02 */ private List getRootNodes(Long partId, String version, String alternativeNo, String bomType) { List structures = null; if (version.equals("ALL") && alternativeNo.equals("ALL")) { // 根据零件号查询 structures = structureMapper.selectList(Wrappers.lambdaQuery().eq(Structure::getPartId, partId).eq(StringUtils.isNotBlank(bomType), Structure::getBomTypeDb, bomType)); } else if (alternativeNo.equals("ALL")) { // 根据零件号、版本号查询 structures = structureMapper.selectList(Wrappers.lambdaQuery().eq(Structure::getPartId, partId). eq(Structure::getVersion, version).eq(StringUtils.isNotBlank(bomType), Structure::getBomTypeDb, bomType)); } else if (version.equals("ALL")) { // 根据零件号、替代号查询 structures = structureMapper.selectList(Wrappers.lambdaQuery().eq(Structure::getPartId, partId). eq(Structure::getAlternativeNo, alternativeNo).eq(StringUtils.isNotBlank(bomType), Structure::getBomTypeDb, bomType)); } else { // 根据零件号、版本号、替代号查询 structures = structureMapper.selectList(Wrappers.lambdaQuery() .eq(Structure::getPartId, partId) .eq(Structure::getVersion, version) .eq(Structure::getAlternativeNo, alternativeNo) .eq(StringUtils.isNotBlank(bomType), Structure::getBomTypeDb, bomType)); } return structures; } /** * @Author: Hans * @Description: 填充BOM树 * @Date: 2022-06-02 */ private void fillBomTree() { } /** * @Author: Hans * @Description: 填充child节点数据 * @Date: 2022-06-02 */ private StructureTree fillChildNode(StructureTree parent, StructureComponent component, Structure structure, Part part) { StructureTree child = new StructureTree(); Operation operation = operationMapper.selectById(component.getOperationId()); BigDecimal parentQpa = parent.getQpa(); child.setId(++RANDOM_ID); child.setParentId(parent.getId()); child.setCompId(component.getId()); child.setStructureId(structure.getId()); child.setPartId(component.getPartId()); child.setColor(component.getColor()); child.setPartNo(part.getPartNo()); child.setPartName(part.getPartName()); child.setExpand(true); child.setUnit(part.getUnit()); child.setPlanningMethod(component.getPlanningMethod()); child.setChecked(false); child.setQpa(component.getQpa().multiply(parentQpa).setScale(8, BigDecimal.ROUND_HALF_UP)); child.setOriginalQpa(component.getQpa()); child.setDiscNum(null == component.getDiscNum() ? null : component.getDiscNum()); if (null != operation) { child.setOperationId(operation.getId()); child.setOperationNo(operation.getOperationNo()); child.setOperationName(operation.getName()); } return child; } /** * @Author: Hans * @Description: 重写获取产品结构方法 * @Date: 2022-06-02 */ private void getStructureByPartId(StructureTree parent, Long partId, Map cache, String version, String alternativeNo, String bomType, int bomLayers) { Integer cycleCount = cache.get(partId) == null ? 0 : cache.get(partId); if (cycleCount >= CYCLE_DEEP) { return; } else { cache.put(partId, cycleCount + 1); } //根据零件找到结构主表 List structures = getRootNodes(partId, version, alternativeNo, bomType); if (CollectionUtil.isNotEmpty(structures)) { for (Structure structure : structures) { // 查询BOM子节点 List components = structureComponentMapper.selectList(Wrappers.lambdaQuery(). eq(StructureComponent::getStructureId, structure.getId()) .orderByAsc(StructureComponent::getId)); if (CollectionUtil.isEmpty(components)) { return; } List children = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren(); // 遍历一层减少一层,999代表不做限制可以一直递归 if (bomLayers < 999) { bomLayers--; } for (StructureComponent component : components) { Part part = partMapper.selectById(component.getPartId()); if (part == null) { continue; } // 填充child节点数据 StructureTree child = fillChildNode(parent, component, structure, part); if (bomLayers == 999) { getStructureByPartId(child, component.getPartId(), cache, "ALL", "ALL", bomType, bomLayers); // 判断到第几层了,制造类型为1-已制造,计划方法为P-虚拟件 } else if (bomLayers > 0 && (part.getMaterialType().equals("1") && !part.getPlanningMethod().equals( "A"))) { getStructureByPartId(child, component.getPartId(), cache, "ALL", "ALL", bomType, bomLayers); } children.add(child); } parent.setChildren(children); } } } }