| | |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | | import com.ruoyi.basic.dto.ProductModelExcelDto; |
| | | import com.ruoyi.basic.dto.ProductModelExcelItemDto; |
| | | import com.ruoyi.common.utils.uuid.UUID; |
| | | import org.apache.poi.ss.formula.functions.T; |
| | | import org.springframework.stereotype.Component; |
| | |
| | | import java.time.LocalTime; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * @author :yys |
| | |
| | | // 拼接订单编号 preFix + 时间(yyyyMMdd) + 订单数量(001) |
| | | return preFix + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1)) + "-" + new Date().getTime(); |
| | | } |
| | | |
| | | /** |
| | | * 将平面列表转换为树形结构(含规格型号拆分功能) |
| | | * @param flatList 导入的平面数据列表 |
| | | * @return 树形结构的根节点列表 |
| | | */ |
| | | public static List<ProductModelExcelDto> buildTree(List<ProductModelExcelDto> flatList) { |
| | | // 1. 参数校验 |
| | | if (CollectionUtils.isEmpty(flatList)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | // 2. 预处理:过滤无效数据 + 规格型号拆分 + 初始化子节点列表 |
| | | List<ProductModelExcelDto> validList = preprocessData(flatList); |
| | | if (CollectionUtils.isEmpty(validList)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | // 3. 按层级分组,便于逐层处理 |
| | | Map<Integer, List<ProductModelExcelDto>> levelGroupMap = groupByLevel(validList); |
| | | |
| | | // 4. 构建节点映射(序号 -> 节点),提高查询效率 |
| | | Map<String, ProductModelExcelDto> nodeMap = buildNodeMap(validList); |
| | | |
| | | // 5. 逐层构建父子关系 |
| | | buildParentChildRelation(levelGroupMap, nodeMap); |
| | | |
| | | // 6. 获取根节点(层级1)并排序 |
| | | List<ProductModelExcelDto> rootNodes = getRootNodes(levelGroupMap); |
| | | sortTreeNodes(rootNodes); |
| | | |
| | | return rootNodes; |
| | | } |
| | | |
| | | /** |
| | | * 数据预处理: |
| | | * 1. 过滤无效数据 |
| | | * 2. 规格型号拆分并填充到items集合 |
| | | * 3. 初始化子节点列表 |
| | | */ |
| | | private static List<ProductModelExcelDto> preprocessData(List<ProductModelExcelDto> flatList) { |
| | | return flatList.stream() |
| | | // 过滤核心字段无效的节点 |
| | | .filter(node -> isValidNode(node)) |
| | | // 处理规格型号拆分和items填充 |
| | | .peek(node -> { |
| | | // 拆分规格型号并填充到items |
| | | splitModelAndFillItems(node); |
| | | // 初始化子节点列表(避免空指针) |
| | | if (node.getChildren() == null) { |
| | | node.setChildren(new ArrayList<>()); |
| | | } |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | /** |
| | | * 验证节点是否有效(核心字段校验) |
| | | */ |
| | | private static boolean isValidNode(ProductModelExcelDto node) { |
| | | if (node == null) { |
| | | return false; |
| | | } |
| | | // 序号必须有效(基础校验) |
| | | if (!isValidSequence(node.getNumber())) { |
| | | return false; |
| | | } |
| | | // 产品名称非空(业务校验,可根据实际需求调整) |
| | | return StringUtils.isNotBlank(node.getProductName()); |
| | | } |
| | | |
| | | /** |
| | | * 规格型号拆分并填充到items集合 |
| | | * @param node 待处理的节点 |
| | | */ |
| | | private static void splitModelAndFillItems(ProductModelExcelDto node) { |
| | | // 1. 获取原始规格型号 |
| | | String originalModel = node.getModel(); |
| | | List<ProductModelExcelItemDto> items = new ArrayList<>(); |
| | | |
| | | if (StringUtils.isNotBlank(originalModel)) { |
| | | // 2. 按"+"拆分规格型号(如果有多个),处理每个片段 |
| | | List<String> splitModels = Arrays.stream(originalModel.split("\\+")) |
| | | .map(String::trim) |
| | | .filter(StringUtils::isNotBlank) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 3. 为每个规格创建Item(即使只有一个,也生成一个Item) |
| | | items = splitModels.stream() |
| | | .map(model -> createExcelItem(node, model)) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | // 4. 填充到节点的items集合(覆盖原有数据) |
| | | node.setItems(items); |
| | | } |
| | | |
| | | /** |
| | | * 创建ProductModelExcelItemDto对象(继承原节点的公共属性) |
| | | * @param node 原节点 |
| | | * @param model 拆分后的规格型号 |
| | | * @return 构建好的Item对象 |
| | | */ |
| | | private static ProductModelExcelItemDto createExcelItem(ProductModelExcelDto node, String model) { |
| | | ProductModelExcelItemDto item = new ProductModelExcelItemDto(); |
| | | // 1. 拆分后的规格型号 |
| | | item.setModel(model); |
| | | // 2. 继承原节点的单位(空值处理) |
| | | item.setUnit(StringUtils.defaultIfBlank(node.getUnit(), "")); |
| | | // 3. 继承原节点的产品类型(空值处理,默认0表示未知) |
| | | item.setProductType(Optional.ofNullable(node.getProductType()).orElse(0)); |
| | | // 4. 继承原节点的图纸编号(空值处理) |
| | | item.setDrawingNumber(StringUtils.defaultIfBlank(node.getDrawingNumber(), "")); |
| | | return item; |
| | | } |
| | | |
| | | /** |
| | | * 验证序号格式是否有效(支持1、1.1、1.1.1等格式) |
| | | */ |
| | | private static boolean isValidSequence(String sequence) { |
| | | if (StringUtils.isBlank(sequence)) { |
| | | return false; |
| | | } |
| | | // 正则表达式:匹配数字开头,后续可跟.和数字 |
| | | return sequence.trim().matches("^\\d+(\\.\\d+)*$"); |
| | | } |
| | | |
| | | /** |
| | | * 按节点层级分组 |
| | | */ |
| | | private static Map<Integer, List<ProductModelExcelDto>> groupByLevel(List<ProductModelExcelDto> validList) { |
| | | return validList.stream() |
| | | .collect(Collectors.groupingBy( |
| | | node -> getNodeLevel(node.getNumber()), // 计算层级 |
| | | TreeMap::new, // 按层级升序排序 |
| | | Collectors.toList() |
| | | )); |
| | | } |
| | | |
| | | /** |
| | | * 计算节点层级(序号中.的数量 + 1) |
| | | * 例:1 -> 1级,1.1 -> 2级,1.1.1 -> 3级 |
| | | */ |
| | | private static int getNodeLevel(String sequence) { |
| | | if (StringUtils.isBlank(sequence)) { |
| | | return 0; |
| | | } |
| | | String trimmedSeq = sequence.trim(); |
| | | return trimmedSeq.split("\\.").length; |
| | | } |
| | | |
| | | /** |
| | | * 构建节点映射表(序号 -> 节点) |
| | | */ |
| | | private static Map<String, ProductModelExcelDto> buildNodeMap(List<ProductModelExcelDto> validList) { |
| | | return validList.stream() |
| | | .collect(Collectors.toMap( |
| | | node -> node.getNumber().trim(), // 键:序号(去空格) |
| | | node -> node, // 值:节点对象 |
| | | (oldVal, newVal) -> oldVal // 处理重复序号:保留第一个 |
| | | )); |
| | | } |
| | | |
| | | /** |
| | | * 构建父子节点关系 |
| | | */ |
| | | private static void buildParentChildRelation(Map<Integer, List<ProductModelExcelDto>> levelGroupMap, |
| | | Map<String, ProductModelExcelDto> nodeMap) { |
| | | // 从层级2开始处理(层级1是根节点,无父节点) |
| | | for (Map.Entry<Integer, List<ProductModelExcelDto>> entry : levelGroupMap.entrySet()) { |
| | | int currentLevel = entry.getKey(); |
| | | if (currentLevel <= 1) { |
| | | continue; // 跳过根节点层级 |
| | | } |
| | | |
| | | List<ProductModelExcelDto> currentLevelNodes = entry.getValue(); |
| | | for (ProductModelExcelDto currentNode : currentLevelNodes) { |
| | | // 计算父节点序号 |
| | | String parentSequence = getParentSequence(currentNode.getNumber()); |
| | | if (StringUtils.isBlank(parentSequence)) { |
| | | continue; |
| | | } |
| | | |
| | | // 查找父节点 |
| | | ProductModelExcelDto parentNode = nodeMap.get(parentSequence); |
| | | if (parentNode != null && parentNode.getChildren() != null) { |
| | | // 添加到父节点的子节点列表 |
| | | parentNode.getChildren().add(currentNode); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据当前节点序号获取父节点序号 |
| | | * 例:1.1.2 -> 1.1,1.2 -> 1,1 -> null |
| | | */ |
| | | private static String getParentSequence(String currentSequence) { |
| | | if (StringUtils.isBlank(currentSequence)) { |
| | | return null; |
| | | } |
| | | |
| | | String trimmedSeq = currentSequence.trim(); |
| | | String[] seqParts = trimmedSeq.split("\\."); |
| | | if (seqParts.length <= 1) { |
| | | return null; // 根节点无父节点 |
| | | } |
| | | |
| | | // 去掉最后一部分,拼接父节点序号 |
| | | String[] parentParts = Arrays.copyOfRange(seqParts, 0, seqParts.length - 1); |
| | | return String.join(".", parentParts); |
| | | } |
| | | |
| | | /** |
| | | * 获取根节点列表(层级1的节点) |
| | | */ |
| | | private static List<ProductModelExcelDto> getRootNodes(Map<Integer, List<ProductModelExcelDto>> levelGroupMap) { |
| | | return levelGroupMap.getOrDefault(1, new ArrayList<>()); |
| | | } |
| | | |
| | | /** |
| | | * 递归排序所有节点(按序号数字顺序) |
| | | */ |
| | | private static void sortTreeNodes(List<ProductModelExcelDto> nodeList) { |
| | | if (CollectionUtils.isEmpty(nodeList)) { |
| | | return; |
| | | } |
| | | |
| | | // 排序当前层级节点 |
| | | nodeList.sort(Comparator.comparing( |
| | | ProductModelExcelDto::getNumber, |
| | | new SequenceComparator() // 自定义序号比较器 |
| | | )); |
| | | |
| | | // 递归排序子节点 |
| | | for (ProductModelExcelDto node : nodeList) { |
| | | sortTreeNodes(node.getChildren()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 序号比较器:按数字顺序排序(支持1.10在1.2之后) |
| | | */ |
| | | private static class SequenceComparator implements Comparator<String> { |
| | | @Override |
| | | public int compare(String seq1, String seq2) { |
| | | if (StringUtils.isBlank(seq1) && StringUtils.isBlank(seq2)) { |
| | | return 0; |
| | | } |
| | | if (StringUtils.isBlank(seq1)) { |
| | | return -1; |
| | | } |
| | | if (StringUtils.isBlank(seq2)) { |
| | | return 1; |
| | | } |
| | | |
| | | // 分割序号为数字数组 |
| | | String[] parts1 = seq1.trim().split("\\."); |
| | | String[] parts2 = seq2.trim().split("\\."); |
| | | |
| | | // 逐段比较数字 |
| | | int minLength = Math.min(parts1.length, parts2.length); |
| | | for (int i = 0; i < minLength; i++) { |
| | | int num1 = parseSequencePart(parts1[i]); |
| | | int num2 = parseSequencePart(parts2[i]); |
| | | if (num1 != num2) { |
| | | return num1 - num2; |
| | | } |
| | | } |
| | | |
| | | // 长度不同时,短的在前(如1.1在1.1.1之前) |
| | | return parts1.length - parts2.length; |
| | | } |
| | | |
| | | /** |
| | | * 解析序号片段为数字(处理异常情况) |
| | | */ |
| | | private int parseSequencePart(String part) { |
| | | try { |
| | | return Integer.parseInt(part.trim()); |
| | | } catch (NumberFormatException e) { |
| | | return 0; // 异常情况按0处理 |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 产品类型映射(1=物料,2=产品,0=未知) |
| | | */ |
| | | private static String mapProductType(Integer productType) { |
| | | return Optional.ofNullable(productType) |
| | | .map(type -> type == 1 ? "物料" : (type == 2 ? "产品" : "未知")) |
| | | .orElse("未知"); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | } |