yuan
2026-04-23 ad9694f7c53d81dec76292b8d329e4dc06e90bc6
feat(production): 新增BOM复制功能
已添加1个文件
已修改4个文件
303 ■■■■■ 文件已修改
src/main/java/com/ruoyi/common/utils/BigDecimalUtils.java 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductBomController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductBomDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductBomService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/BigDecimalUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,180 @@
package com.ruoyi.common.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
/**
 * @author yuan
 * @date 2026-04-23 13:10
 * @description BigDecimal工具类
 */
public class BigDecimalUtils {
    public static BigDecimal getBigDecimalValue(BigDecimal val) {
        return Optional.ofNullable(val).orElse(BigDecimal.ZERO);
    }
    public static BigDecimal of(Object val) {
        if (val == null) {
            return null;
        }
        try {
            if (val instanceof Double || val instanceof Float) {
                return BigDecimal.valueOf(((Number) val).doubleValue());
            }
            if (val instanceof Number) {
                return new BigDecimal(val.toString());
            }
            return new BigDecimal(val.toString().trim());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("无法转换 " + val + " ä¸º BigDecimal类型 ", e);
        }
    }
    /**
     * å®‰å…¨è½¬æ¢æ–¹æ³•,将对象转为BigDecimal,null视为0
     * @param number å¯ä»¥æ˜¯Number、String或BigDecimal
     * @return å¯¹åº”çš„BigDecimal,null转为0
     */
    public static BigDecimal safe(Object number) {
        if (number == null) return BigDecimal.ZERO;
        if (number instanceof BigDecimal) return (BigDecimal) number;
        if (number instanceof Number) return BigDecimal.valueOf(((Number) number).doubleValue());
        try {
            return new BigDecimal(number.toString());
        } catch (NumberFormatException e) {
            return BigDecimal.ZERO;
        }
    }
    /**
     * åŠ æ³•è¿ç®—ï¼ˆè‡ªåŠ¨å¤„ç†null值)
     * @param values å¤šä¸ªåŠ æ•°
     * @return å’Œ
     */
    public static BigDecimal add(Object... values) {
        return Arrays.stream(values)
                .map(BigDecimalUtils::safe)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    /**
     * å‡æ³•运算(自动处理null值)
     * @param minuend è¢«å‡æ•°
     * @param subtrahends å‡æ•°
     * @return å·®
     */
    public static BigDecimal subtract(Object minuend, Object... subtrahends) {
        BigDecimal result = safe(minuend);
        for (Object subtrahend : subtrahends) {
            result = result.subtract(safe(subtrahend));
        }
        return result;
    }
    /**
     * ä¹˜æ³•运算(自动处理null值,任何参数为null则返回0)
     * @param values å¤šä¸ªä¹˜æ•°
     * @return ç§¯
     */
    public static BigDecimal multiply(Object... values) {
        if (values == null || values.length == 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal result = Arrays.stream(values)
                .map(BigDecimalUtils::safe)
                .reduce(BigDecimal.ONE, BigDecimal::multiply);
        // æ ‡å‡†åŒ–结果:如果是0,去除所有小数位;否则保留原有小数位
        return stripTrailingZeros(result);
    }
    /**
     * é™¤æ³•运算(自动处理null值)
     * @param dividend è¢«é™¤æ•°
     * @param divisor é™¤æ•°
     * @param scale å°æ•°ä½æ•°
     * @param roundingMode èˆå…¥æ¨¡å¼
     * @return å•†
     */
    public static BigDecimal divide(Object dividend, Object divisor,
                                    int scale, RoundingMode roundingMode) {
        BigDecimal d1 = safe(dividend);
        BigDecimal d2 = safe(divisor);
        if (d2.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return    stripTrailingZeros(d1.divide(d2, scale, roundingMode));
    }
    /**
     * é™¤æ³•运算(自动处理null值)
     * @param dividend è¢«é™¤æ•°
     * @param divisor é™¤æ•°
     * @param scale å°æ•°ä½æ•°
     * @param roundingMode èˆå…¥æ¨¡å¼
     * @return å•†
     */
    public static BigDecimal dividePercentage(Object dividend, Object divisor,
                                              int scale, RoundingMode roundingMode) {
        BigDecimal d1 = safe(dividend);
        BigDecimal d2 = safe(divisor);
        if (d2.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal multiplied = d1.multiply(new BigDecimal("100"));
        BigDecimal result = multiplied.divide(d2, scale, roundingMode);
        return stripTrailingZeros(result);
    }
    /**
     * æ¯”较两个数的大小(自动处理null值,null视为0)
     * @param num1 ç¬¬ä¸€ä¸ªæ•°
     * @param num2 ç¬¬äºŒä¸ªæ•°
     * @return 1: num1 > num2; 0: num1 = num2; -1: num1 < num2
     */
    public static int compare(Object num1, Object num2) {
        return safe(num1).compareTo(safe(num2));
    }
    /**
     * å››èˆäº”入(自动处理null值)
     * @param value åŽŸå§‹å€¼
     * @param scale ä¿ç•™å°æ•°ä½æ•°
     * @return å››èˆäº”入后的值
     */
    public static BigDecimal round(Object value, int scale) {
        return safe(value).setScale(scale, RoundingMode.HALF_UP);
    }
    /**
     * æ±‚和(流式处理版)
     * @param numbers æ•°å­—集合
     * @return å’Œ
     */
    public static BigDecimal sum(Collection<?> numbers) {
        return numbers.stream()
                .map(BigDecimalUtils::safe)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    /**
     * æ ‡å‡†åŒ–BigDecimal结果
     * 1. å¦‚果是0,去除所有小数位
     * 2. å¦‚果不是0,去除末尾无意义的0
     */
    public static BigDecimal stripTrailingZeros(BigDecimal value) {
        if (value.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return new BigDecimal(value.stripTrailingZeros().toPlainString());
    }
}
src/main/java/com/ruoyi/production/controller/ProductBomController.java
@@ -69,6 +69,13 @@
        return productBomService.add(productBom);
    }
    @ApiModelProperty("复制BOM")
    @PostMapping("/copy")
    @Log(title = "复制BOM", businessType = BusinessType.INSERT)
    public AjaxResult copy(@RequestBody ProductBomDto productBom) {
        return productBomService.copy(productBom);
    }
    @ApiOperation("更新BOM")
    @Log(title = "修改", businessType = BusinessType.UPDATE)
    @PutMapping("/update")
src/main/java/com/ruoyi/production/dto/ProductBomDto.java
@@ -17,4 +17,7 @@
    //物料编码
    private String materialCode;
    //物料编码
    private Long copyId;
}
src/main/java/com/ruoyi/production/service/ProductBomService.java
@@ -29,4 +29,6 @@
    void exportBom(HttpServletResponse response, Integer bomId);
    AjaxResult update(ProductBom productBom);
    AjaxResult copy(ProductBomDto productBom);
}
src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
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.ruoyi.basic.pojo.Product;
@@ -20,6 +21,7 @@
import com.ruoyi.production.service.ProductBomService;
import com.ruoyi.production.service.ProductProcessService;
import com.ruoyi.production.service.ProductStructureService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -188,6 +190,115 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult copy(ProductBomDto productBom) {
        Long copyId = productBom.getCopyId();
        if (copyId == null) {
            throw new ServiceException("复制源BOM ID不能为空");
        }
        ProductBom sourceBom = productBomMapper.selectById(copyId);
        if (sourceBom == null) {
            throw new ServiceException("复制源BOM不存在");
        }
        ProductBom newBom = getProductBom(productBom, sourceBom);
        productBomMapper.insert(newBom);
        newBom.setBomNo("BM." + String.format("%05d", newBom.getId()));
        productBomMapper.updateById(newBom);
        ProductModel productModel = productModelService.getById(newBom.getProductModelId());
        if (productModel == null) {
            throw new ServiceException("选择的产品模型不存在");
        }
        ProductStructure newRoot = getProductStructure(newBom, productModel);
        productStructureService.save(newRoot);
        List<ProductStructure> sourceStructures = productStructureMapper.selectList(
                        Wrappers.<ProductStructure>lambdaQuery()
                                .eq(ProductStructure::getBomId, copyId.intValue()));
        if (sourceStructures == null || sourceStructures.isEmpty()) {
            return AjaxResult.success();
        }
        ProductStructure oldRoot = sourceStructures.stream()
                .filter(s -> s.getParentId() == null)
                .findFirst().orElse(new ProductStructure());
        List<ProductStructure> children = sourceStructures
                .stream()
                .filter(s -> !s.getId().equals(oldRoot.getId()))
                .collect(Collectors.toList());
        Map<Long, Long> oldNewIdMap = new HashMap<>();
        oldNewIdMap.put(oldRoot.getId(), newRoot.getId());
        List<ProductStructure> insertList = children
                .stream()
                .map(item -> getProductStructures(item, newBom))
                .collect(Collectors.toList());
        productStructureService.saveBatch(insertList);
        for (int i = 0; i < children.size(); i++) {
            oldNewIdMap.put(
                    children.get(i).getId(),
                    insertList.get(i).getId()
            );
        }
        List<ProductStructure> updateList = new ArrayList<>();
        for (int i = 0; i < children.size(); i++) {
            ProductStructure source = children.get(i);
            ProductStructure inserted = insertList.get(i);
            Long newParentId = oldNewIdMap.get(source.getParentId());
            if (newParentId != null) {
                inserted.setParentId(newParentId);
                updateList.add(inserted);
            }
        }
        if (!updateList.isEmpty()) {
            productStructureService.updateBatchById(updateList);
        }
        return AjaxResult.success();
    }
    @NotNull
    private static ProductStructure getProductStructures(ProductStructure item, ProductBom newBom) {
        ProductStructure copy = new ProductStructure();
        copy.setProductModelId(item.getProductModelId());
        copy.setProcessId(item.getProcessId());
        copy.setUnitQuantity(item.getUnitQuantity());
        copy.setDemandedQuantity(item.getDemandedQuantity());
        copy.setUnit(item.getUnit());
        copy.setBomId(newBom.getId());
        return copy;
    }
    @NotNull
    private static ProductStructure getProductStructure(ProductBom newBom, ProductModel productModel) {
        ProductStructure newRoot = new ProductStructure();
        newRoot.setProductModelId(newBom.getProductModelId());
        newRoot.setUnitQuantity(BigDecimal.valueOf(1));
        newRoot.setUnit(productModel.getUnit());
        newRoot.setBomId(newBom.getId());
        return newRoot;
    }
    @NotNull
    private static ProductBom getProductBom(ProductBomDto productBom, ProductBom sourceBom) {
        ProductBom newBom = new ProductBom();
        newBom.setProductModelId(productBom.getProductModelId() != null ? productBom.getProductModelId() : sourceBom.getProductModelId());
        newBom.setRemark(productBom.getRemark());
        newBom.setVersion(productBom.getVersion() != null ? productBom.getVersion() : sourceBom.getVersion());
        return newBom;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult uploadBom(MultipartFile file) {
        ExcelUtil<BomImportDto> util = new ExcelUtil<>(BomImportDto.class);
        List<BomImportDto> list;