| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.production; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.basic.mapper.ProductModelMapper; |
| | | import com.ruoyi.basic.pojo.ProductModel; |
| | | import com.ruoyi.production.mapper.ProductBomMapper; |
| | | import com.ruoyi.production.mapper.ProductStructureMapper; |
| | | import com.ruoyi.production.pojo.ProductBom; |
| | | import com.ruoyi.production.pojo.ProductStructure; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.boot.test.context.SpringBootTest; |
| | | import org.springframework.test.annotation.Rollback; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * BOMç»æè¡¥é½æµè¯ã |
| | | */ |
| | | @SpringBootTest |
| | | public class BomStructureCopyTest { |
| | | |
| | | @Autowired |
| | | private ProductModelMapper productModelMapper; |
| | | |
| | | @Autowired |
| | | private ProductBomMapper productBomMapper; |
| | | |
| | | @Autowired |
| | | private ProductStructureMapper productStructureMapper; |
| | | |
| | | @Test |
| | | @Rollback(false) |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void copyAllProductBomStructureByProductId() { |
| | | List<ProductModel> allProductModels = productModelMapper.selectList( |
| | | Wrappers.<ProductModel>lambdaQuery() |
| | | .isNotNull(ProductModel::getProductId) |
| | | .orderByAsc(ProductModel::getProductId) |
| | | .orderByAsc(ProductModel::getId) |
| | | ); |
| | | if (CollectionUtils.isEmpty(allProductModels)) { |
| | | throw new IllegalStateException("æªæ¥è¯¢å°ä»»ä½äº§åè§æ ¼"); |
| | | } |
| | | |
| | | Map<Long, List<ProductModel>> productModelGroups = allProductModels.stream() |
| | | .collect(Collectors.groupingBy(ProductModel::getProductId, LinkedHashMap::new, Collectors.toList())); |
| | | |
| | | List<CopySummary> summaries = new ArrayList<>(); |
| | | for (Map.Entry<Long, List<ProductModel>> entry : productModelGroups.entrySet()) { |
| | | CopySummary summary = copyBomStructure(entry.getKey(), entry.getValue()); |
| | | summaries.add(summary); |
| | | printProductSummary(summary); |
| | | } |
| | | |
| | | printBatchSummary(summaries); |
| | | } |
| | | |
| | | private CopySummary copyBomStructure(Long productId, List<ProductModel> productModels) { |
| | | if (CollectionUtils.isEmpty(productModels)) { |
| | | return CopySummary.skipped(productId, 0, 0, "æªæ¥è¯¢å°äº§å大类对åºçè§æ ¼"); |
| | | } |
| | | |
| | | Map<Long, ProductModel> productModelMap = productModels.stream() |
| | | .collect(Collectors.toMap(ProductModel::getId, item -> item, (left, right) -> left, LinkedHashMap::new)); |
| | | List<Long> productModelIds = new ArrayList<>(productModelMap.keySet()); |
| | | |
| | | List<ProductBom> bomList = productBomMapper.selectList( |
| | | Wrappers.<ProductBom>lambdaQuery() |
| | | .in(ProductBom::getProductModelId, productModelIds) |
| | | .orderByAsc(ProductBom::getId) |
| | | ); |
| | | if (CollectionUtils.isEmpty(bomList)) { |
| | | return CopySummary.skipped(productId, productModels.size(), 0, "æªæ¥è¯¢å°äº§å大类对åºçBOM"); |
| | | } |
| | | |
| | | Map<Integer, List<ProductStructure>> structureMap = loadStructureMap(bomList); |
| | | ProductBom templateBom = findTemplateBomOrNull(bomList, structureMap); |
| | | if (templateBom == null) { |
| | | return CopySummary.skipped(productId, productModels.size(), bomList.size(), "å½å产åå¤§ç±»ä¸æ²¡æå¯ä½ä¸ºæ¨¡æ¿ç宿´BOMç»æ"); |
| | | } |
| | | |
| | | List<ProductStructure> templateStructures = structureMap.get(templateBom.getId()); |
| | | ProductStructure templateRoot = requireSingleRoot(templateBom, templateStructures); |
| | | List<ProductStructure> templateChildren = templateStructures.stream() |
| | | .filter(item -> !isRoot(item)) |
| | | .sorted(Comparator.comparing(ProductStructure::getId)) |
| | | .collect(Collectors.toList()); |
| | | if (templateChildren.isEmpty()) { |
| | | throw new IllegalStateException("模æ¿BOM没æå¯å¤å¶çåç»æï¼bomId=" + templateBom.getId()); |
| | | } |
| | | |
| | | int copiedBomCount = 0; |
| | | int skippedBomCount = 0; |
| | | int insertedStructureCount = 0; |
| | | |
| | | for (ProductBom targetBom : bomList) { |
| | | if (Objects.equals(templateBom.getId(), targetBom.getId())) { |
| | | skippedBomCount++; |
| | | continue; |
| | | } |
| | | |
| | | List<ProductStructure> targetStructures = structureMap.getOrDefault(targetBom.getId(), Collections.emptyList()); |
| | | if (hasChildNodes(targetStructures)) { |
| | | skippedBomCount++; |
| | | continue; |
| | | } |
| | | |
| | | ProductModel targetModel = productModelMap.get(targetBom.getProductModelId()); |
| | | if (targetModel == null) { |
| | | throw new IllegalStateException("BOMå
³èçè§æ ¼ä¸å±äºå½å产å大类ï¼bomId=" + targetBom.getId() |
| | | + "ï¼productModelId=" + targetBom.getProductModelId()); |
| | | } |
| | | |
| | | RootNode targetRoot = ensureTargetRoot(targetBom, targetModel, templateRoot, targetStructures); |
| | | int copiedChildren = copyTemplateChildren(templateBom, templateRoot, templateChildren, targetBom, targetRoot.getNode()); |
| | | |
| | | copiedBomCount++; |
| | | insertedStructureCount += copiedChildren + (targetRoot.isInserted() ? 1 : 0); |
| | | } |
| | | |
| | | return CopySummary.copied( |
| | | productId, |
| | | productModels.size(), |
| | | templateBom.getId(), |
| | | templateBom.getBomNo(), |
| | | bomList.size(), |
| | | copiedBomCount, |
| | | skippedBomCount, |
| | | insertedStructureCount |
| | | ); |
| | | } |
| | | |
| | | private Map<Integer, List<ProductStructure>> loadStructureMap(List<ProductBom> bomList) { |
| | | List<Integer> bomIds = bomList.stream() |
| | | .map(ProductBom::getId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | if (bomIds.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | List<ProductStructure> structures = productStructureMapper.selectList( |
| | | Wrappers.<ProductStructure>lambdaQuery() |
| | | .in(ProductStructure::getBomId, bomIds) |
| | | .orderByAsc(ProductStructure::getId) |
| | | ); |
| | | |
| | | Map<Integer, List<ProductStructure>> structureMap = new HashMap<>(); |
| | | for (ProductStructure structure : structures) { |
| | | structureMap.computeIfAbsent(structure.getBomId(), key -> new ArrayList<>()).add(structure); |
| | | } |
| | | return structureMap; |
| | | } |
| | | |
| | | private ProductBom findTemplateBomOrNull(List<ProductBom> bomList, Map<Integer, List<ProductStructure>> structureMap) { |
| | | return bomList.stream() |
| | | .filter(item -> hasChildNodes(structureMap.getOrDefault(item.getId(), Collections.emptyList()))) |
| | | .max(Comparator.comparingInt(item -> childNodeCount(structureMap.getOrDefault(item.getId(), Collections.emptyList())))) |
| | | .orElse(null); |
| | | } |
| | | |
| | | private int childNodeCount(List<ProductStructure> structures) { |
| | | if (structures == null) { |
| | | return 0; |
| | | } |
| | | int count = 0; |
| | | for (ProductStructure structure : structures) { |
| | | if (!isRoot(structure)) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private boolean hasChildNodes(List<ProductStructure> structures) { |
| | | return childNodeCount(structures) > 0; |
| | | } |
| | | |
| | | private ProductStructure requireSingleRoot(ProductBom bom, List<ProductStructure> structures) { |
| | | List<ProductStructure> roots = findRoots(structures); |
| | | if (roots.size() != 1) { |
| | | throw new IllegalStateException("BOMæ ¹èç¹æ°éå¼å¸¸ï¼bomId=" + bom.getId() + "ï¼rootCount=" + roots.size()); |
| | | } |
| | | return roots.get(0); |
| | | } |
| | | |
| | | private RootNode ensureTargetRoot(ProductBom targetBom, |
| | | ProductModel targetModel, |
| | | ProductStructure templateRoot, |
| | | List<ProductStructure> targetStructures) { |
| | | List<ProductStructure> roots = findRoots(targetStructures); |
| | | if (roots.size() > 1) { |
| | | throw new IllegalStateException("ç®æ BOMæ ¹èç¹æ°éå¼å¸¸ï¼bomId=" + targetBom.getId() + "ï¼rootCount=" + roots.size()); |
| | | } |
| | | if (roots.size() == 1) { |
| | | return new RootNode(roots.get(0), false); |
| | | } |
| | | |
| | | ProductStructure targetRoot = new ProductStructure(); |
| | | targetRoot.setParentId(null); |
| | | targetRoot.setBomId(targetBom.getId()); |
| | | targetRoot.setProductModelId(targetBom.getProductModelId()); |
| | | targetRoot.setProcessId(templateRoot.getProcessId()); |
| | | targetRoot.setUnitQuantity(templateRoot.getUnitQuantity() == null ? BigDecimal.ONE : templateRoot.getUnitQuantity()); |
| | | targetRoot.setDemandedQuantity(templateRoot.getDemandedQuantity()); |
| | | targetRoot.setUnit(targetModel.getUnit()); |
| | | targetRoot.setTenantId(firstNotNull(targetBom.getTenantId(), targetModel.getTenantId(), templateRoot.getTenantId())); |
| | | productStructureMapper.insert(targetRoot); |
| | | return new RootNode(targetRoot, true); |
| | | } |
| | | |
| | | private int copyTemplateChildren(ProductBom templateBom, |
| | | ProductStructure templateRoot, |
| | | List<ProductStructure> templateChildren, |
| | | ProductBom targetBom, |
| | | ProductStructure targetRoot) { |
| | | Map<Long, Long> idMapping = new HashMap<>(); |
| | | idMapping.put(templateRoot.getId(), targetRoot.getId()); |
| | | |
| | | List<NodePair> insertedNodes = new ArrayList<>(); |
| | | for (ProductStructure sourceNode : templateChildren) { |
| | | ProductStructure copiedNode = new ProductStructure(); |
| | | copiedNode.setParentId(null); |
| | | copiedNode.setProductModelId(sourceNode.getProductModelId()); |
| | | copiedNode.setProcessId(sourceNode.getProcessId()); |
| | | copiedNode.setUnitQuantity(sourceNode.getUnitQuantity()); |
| | | copiedNode.setDemandedQuantity(sourceNode.getDemandedQuantity()); |
| | | copiedNode.setUnit(sourceNode.getUnit()); |
| | | copiedNode.setTenantId(firstNotNull(targetBom.getTenantId(), sourceNode.getTenantId())); |
| | | copiedNode.setBomId(targetBom.getId()); |
| | | |
| | | productStructureMapper.insert(copiedNode); |
| | | idMapping.put(sourceNode.getId(), copiedNode.getId()); |
| | | insertedNodes.add(new NodePair(sourceNode, copiedNode)); |
| | | } |
| | | |
| | | for (NodePair insertedNode : insertedNodes) { |
| | | Long sourceParentId = insertedNode.getSourceNode().getParentId(); |
| | | Long newParentId = idMapping.get(sourceParentId); |
| | | if (newParentId == null) { |
| | | throw new IllegalStateException("模æ¿BOMå卿 æ³æ å°çç¶èç¹ï¼templateBomId=" + templateBom.getId() |
| | | + "ï¼sourceNodeId=" + insertedNode.getSourceNode().getId() |
| | | + "ï¼sourceParentId=" + sourceParentId); |
| | | } |
| | | |
| | | ProductStructure copiedNode = insertedNode.getCopiedNode(); |
| | | copiedNode.setParentId(newParentId); |
| | | productStructureMapper.updateById(copiedNode); |
| | | } |
| | | |
| | | return insertedNodes.size(); |
| | | } |
| | | |
| | | private List<ProductStructure> findRoots(List<ProductStructure> structures) { |
| | | if (structures == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return structures.stream() |
| | | .filter(this::isRoot) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | private boolean isRoot(ProductStructure structure) { |
| | | return structure.getParentId() == null || Long.valueOf(0L).equals(structure.getParentId()); |
| | | } |
| | | |
| | | private Long firstNotNull(Long first, Long second) { |
| | | return first != null ? first : second; |
| | | } |
| | | |
| | | private Long firstNotNull(Long first, Long second, Long third) { |
| | | if (first != null) { |
| | | return first; |
| | | } |
| | | return second != null ? second : third; |
| | | } |
| | | |
| | | private void printProductSummary(CopySummary summary) { |
| | | if (summary.isProductSkipped()) { |
| | | System.out.printf( |
| | | "è·³è¿äº§ååç±»ï¼productId=%sï¼è§æ ¼æ°=%sï¼BOMæ»æ°=%sï¼åå =%s%n", |
| | | summary.getProductId(), |
| | | summary.getProductModelCount(), |
| | | summary.getTotalBomCount(), |
| | | summary.getSkipReason() |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | System.out.printf( |
| | | "å¤ç产ååç±»ï¼productId=%sï¼æ¨¡æ¿BOM=%s(%s)ï¼è§æ ¼æ°=%sï¼BOMæ»æ°=%sï¼å·²è¡¥é½BOM=%sï¼è·³è¿BOM=%sï¼æ°å¢ç»æèç¹=%s%n", |
| | | summary.getProductId(), |
| | | summary.getTemplateBomNo(), |
| | | summary.getTemplateBomId(), |
| | | summary.getProductModelCount(), |
| | | summary.getTotalBomCount(), |
| | | summary.getCopiedBomCount(), |
| | | summary.getSkippedBomCount(), |
| | | summary.getInsertedStructureCount() |
| | | ); |
| | | } |
| | | |
| | | private void printBatchSummary(List<CopySummary> summaries) { |
| | | int productCount = summaries.size(); |
| | | int skippedProductCount = 0; |
| | | int totalBomCount = 0; |
| | | int copiedBomCount = 0; |
| | | int skippedBomCount = 0; |
| | | int insertedStructureCount = 0; |
| | | |
| | | for (CopySummary summary : summaries) { |
| | | if (summary.isProductSkipped()) { |
| | | skippedProductCount++; |
| | | } |
| | | totalBomCount += summary.getTotalBomCount(); |
| | | copiedBomCount += summary.getCopiedBomCount(); |
| | | skippedBomCount += summary.getSkippedBomCount(); |
| | | insertedStructureCount += summary.getInsertedStructureCount(); |
| | | } |
| | | |
| | | System.out.printf( |
| | | "BOMç»ææ¹éå¤å¶å®æï¼äº§ååç±»æ°=%sï¼å·²å¤çåç±»=%sï¼è·³è¿åç±»=%sï¼BOMæ»æ°=%sï¼å·²è¡¥é½BOM=%sï¼è·³è¿BOM=%sï¼æ°å¢ç»æèç¹=%s%n", |
| | | productCount, |
| | | productCount - skippedProductCount, |
| | | skippedProductCount, |
| | | totalBomCount, |
| | | copiedBomCount, |
| | | skippedBomCount, |
| | | insertedStructureCount |
| | | ); |
| | | } |
| | | |
| | | private static class RootNode { |
| | | private final ProductStructure node; |
| | | private final boolean inserted; |
| | | |
| | | private RootNode(ProductStructure node, boolean inserted) { |
| | | this.node = node; |
| | | this.inserted = inserted; |
| | | } |
| | | |
| | | private ProductStructure getNode() { |
| | | return node; |
| | | } |
| | | |
| | | private boolean isInserted() { |
| | | return inserted; |
| | | } |
| | | } |
| | | |
| | | private static class NodePair { |
| | | private final ProductStructure sourceNode; |
| | | private final ProductStructure copiedNode; |
| | | |
| | | private NodePair(ProductStructure sourceNode, ProductStructure copiedNode) { |
| | | this.sourceNode = sourceNode; |
| | | this.copiedNode = copiedNode; |
| | | } |
| | | |
| | | private ProductStructure getSourceNode() { |
| | | return sourceNode; |
| | | } |
| | | |
| | | private ProductStructure getCopiedNode() { |
| | | return copiedNode; |
| | | } |
| | | } |
| | | |
| | | private static class CopySummary { |
| | | private final Long productId; |
| | | private final int productModelCount; |
| | | private final Integer templateBomId; |
| | | private final String templateBomNo; |
| | | private final int totalBomCount; |
| | | private final int copiedBomCount; |
| | | private final int skippedBomCount; |
| | | private final int insertedStructureCount; |
| | | private final String skipReason; |
| | | |
| | | private CopySummary(Long productId, |
| | | int productModelCount, |
| | | Integer templateBomId, |
| | | String templateBomNo, |
| | | int totalBomCount, |
| | | int copiedBomCount, |
| | | int skippedBomCount, |
| | | int insertedStructureCount, |
| | | String skipReason) { |
| | | this.productId = productId; |
| | | this.productModelCount = productModelCount; |
| | | this.templateBomId = templateBomId; |
| | | this.templateBomNo = templateBomNo; |
| | | this.totalBomCount = totalBomCount; |
| | | this.copiedBomCount = copiedBomCount; |
| | | this.skippedBomCount = skippedBomCount; |
| | | this.insertedStructureCount = insertedStructureCount; |
| | | this.skipReason = skipReason; |
| | | } |
| | | |
| | | private static CopySummary copied(Long productId, |
| | | int productModelCount, |
| | | Integer templateBomId, |
| | | String templateBomNo, |
| | | int totalBomCount, |
| | | int copiedBomCount, |
| | | int skippedBomCount, |
| | | int insertedStructureCount) { |
| | | return new CopySummary( |
| | | productId, |
| | | productModelCount, |
| | | templateBomId, |
| | | templateBomNo, |
| | | totalBomCount, |
| | | copiedBomCount, |
| | | skippedBomCount, |
| | | insertedStructureCount, |
| | | null |
| | | ); |
| | | } |
| | | |
| | | private static CopySummary skipped(Long productId, int productModelCount, int totalBomCount, String skipReason) { |
| | | return new CopySummary( |
| | | productId, |
| | | productModelCount, |
| | | null, |
| | | null, |
| | | totalBomCount, |
| | | 0, |
| | | totalBomCount, |
| | | 0, |
| | | skipReason |
| | | ); |
| | | } |
| | | |
| | | private Long getProductId() { |
| | | return productId; |
| | | } |
| | | |
| | | private int getProductModelCount() { |
| | | return productModelCount; |
| | | } |
| | | |
| | | private Integer getTemplateBomId() { |
| | | return templateBomId; |
| | | } |
| | | |
| | | private String getTemplateBomNo() { |
| | | return templateBomNo; |
| | | } |
| | | |
| | | private int getTotalBomCount() { |
| | | return totalBomCount; |
| | | } |
| | | |
| | | private int getCopiedBomCount() { |
| | | return copiedBomCount; |
| | | } |
| | | |
| | | private int getSkippedBomCount() { |
| | | return skippedBomCount; |
| | | } |
| | | |
| | | private int getInsertedStructureCount() { |
| | | return insertedStructureCount; |
| | | } |
| | | |
| | | private String getSkipReason() { |
| | | return skipReason; |
| | | } |
| | | |
| | | private boolean isProductSkipped() { |
| | | return skipReason != null; |
| | | } |
| | | } |
| | | } |