From ad9694f7c53d81dec76292b8d329e4dc06e90bc6 Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期四, 23 四月 2026 14:45:22 +0800
Subject: [PATCH] feat(production): 新增BOM复制功能
---
src/main/java/com/ruoyi/production/dto/ProductBomDto.java | 3
src/main/java/com/ruoyi/common/utils/BigDecimalUtils.java | 180 ++++++++++++++++++++++++++++++
src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java | 111 ++++++++++++++++++
src/main/java/com/ruoyi/production/controller/ProductBomController.java | 7 +
src/main/java/com/ruoyi/production/service/ProductBomService.java | 2
5 files changed, 303 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/ruoyi/common/utils/BigDecimalUtils.java b/src/main/java/com/ruoyi/common/utils/BigDecimalUtils.java
new file mode 100644
index 0000000..8d16cc3
--- /dev/null
+++ b/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锛宯ull瑙嗕负0
+ * @param number 鍙互鏄疦umber銆丼tring鎴朆igDecimal
+ * @return 瀵瑰簲鐨凚igDecimal锛宯ull杞负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;
+ }
+ }
+
+ /**
+ * 鍔犳硶杩愮畻锛堣嚜鍔ㄥ鐞唍ull鍊硷級
+ * @param values 澶氫釜鍔犳暟
+ * @return 鍜�
+ */
+ public static BigDecimal add(Object... values) {
+ return Arrays.stream(values)
+ .map(BigDecimalUtils::safe)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ /**
+ * 鍑忔硶杩愮畻锛堣嚜鍔ㄥ鐞唍ull鍊硷級
+ * @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;
+ }
+
+ /**
+ * 涔樻硶杩愮畻锛堣嚜鍔ㄥ鐞唍ull鍊硷紝浠讳綍鍙傛暟涓簄ull鍒欒繑鍥�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);
+ }
+
+ /**
+ * 闄ゆ硶杩愮畻锛堣嚜鍔ㄥ鐞唍ull鍊硷級
+ * @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));
+ }
+
+ /**
+ * 闄ゆ硶杩愮畻锛堣嚜鍔ㄥ鐞唍ull鍊硷級
+ * @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);
+ }
+
+ /**
+ * 姣旇緝涓や釜鏁扮殑澶у皬锛堣嚜鍔ㄥ鐞唍ull鍊硷紝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));
+ }
+
+ /**
+ * 鍥涜垗浜斿叆锛堣嚜鍔ㄥ鐞唍ull鍊硷級
+ * @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);
+ }
+
+ /**
+ * 鏍囧噯鍖朆igDecimal缁撴灉
+ * 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());
+ }
+}
diff --git a/src/main/java/com/ruoyi/production/controller/ProductBomController.java b/src/main/java/com/ruoyi/production/controller/ProductBomController.java
index 3925b5c..79eb6e0 100644
--- a/src/main/java/com/ruoyi/production/controller/ProductBomController.java
+++ b/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")
diff --git a/src/main/java/com/ruoyi/production/dto/ProductBomDto.java b/src/main/java/com/ruoyi/production/dto/ProductBomDto.java
index 30998d3..09ad814 100644
--- a/src/main/java/com/ruoyi/production/dto/ProductBomDto.java
+++ b/src/main/java/com/ruoyi/production/dto/ProductBomDto.java
@@ -17,4 +17,7 @@
//鐗╂枡缂栫爜
private String materialCode;
+
+ //鐗╂枡缂栫爜
+ private Long copyId;
}
diff --git a/src/main/java/com/ruoyi/production/service/ProductBomService.java b/src/main/java/com/ruoyi/production/service/ProductBomService.java
index 7ec6d9a..927f4c6 100644
--- a/src/main/java/com/ruoyi/production/service/ProductBomService.java
+++ b/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);
}
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java
index b7b67bb..b91ba31 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java
+++ b/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("澶嶅埗婧怋OM ID涓嶈兘涓虹┖");
+ }
+ ProductBom sourceBom = productBomMapper.selectById(copyId);
+ if (sourceBom == null) {
+ throw new ServiceException("澶嶅埗婧怋OM涓嶅瓨鍦�");
+ }
+
+
+ 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;
--
Gitblit v1.9.3