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/mapper/ProductStructureMapper.java
@@ -8,7 +8,9 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.Collection; import java.util.List; import java.util.Set; @Mapper public interface ProductStructureMapper extends BaseMapper<ProductStructure> { @@ -16,4 +18,6 @@ List<ProductStructureDto> listBybomId(@Param("bomId") Integer bomId); List<ProductStructureDto> listBybomAndProcess(@Param("bomId") Integer bomId, @Param("processId") Long processId); List<ProductStructureDto> selectByIds(@Param("parentIds") Set<Long> parentIds); } src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java
@@ -23,6 +23,9 @@ @ApiModelProperty(value = "æ°é") private BigDecimal quantity; @ApiModelProperty(value = "batchNo") private String batchNo; @ApiModelProperty(value = "å建æ¶é´") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; 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; src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -18,6 +18,7 @@ import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.procurementrecord.utils.StockUtils; import com.ruoyi.production.dto.ProductStructureDto; import com.ruoyi.production.dto.ProductionProductMainDto; import com.ruoyi.production.mapper.*; import com.ruoyi.production.pojo.*; @@ -33,12 +34,12 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @Service @@ -175,23 +176,48 @@ productionProductMain.setStatus(0); productionProductMainMapper.insert(productionProductMain); /*æ°å¢æ¥å·¥æå ¥è¡¨*/ // List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(productProcessRoute.getBomId(), productProcess.getId()); // if (productStructureDtos.isEmpty()) { // //å¦æè¯¥å·¥åºæ²¡æäº§åç»æçæå ¥å,é£è¿ä¸ªæå ¥åå产åºåæ¯åä¸ä¸ª // ProductStructureDto productStructureDto = new ProductStructureDto(); // productStructureDto.setProductModelId(productProcessRouteItem.getProductModelId()); // productStructureDto.setUnitQuantity(BigDecimal.ONE); // productStructureDtos.add(productStructureDto); // } // for (ProductStructureDto productStructureDto : productStructureDtos) { // System.out.println(productStructureDto.getProductModelId()); // ProductionProductInput productionProductInput = new ProductionProductInput(); // productionProductInput.setProductModelId(productStructureDto.getProductModelId()); List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(productProcessRoute.getBomId(), productProcess.getId()); if (productStructureDtos.isEmpty()) { //å¦æè¯¥å·¥åºæ²¡æäº§åç»æçæå ¥å,é£è¿ä¸ªæå ¥åå产åºåæ¯åä¸ä¸ª ProductStructureDto productStructureDto = new ProductStructureDto(); productStructureDto.setProductModelId(productProcessRouteItem.getProductModelId()); productStructureDto.setUnitQuantity(BigDecimal.ONE); productStructureDtos.add(productStructureDto); } Set<Long> parentIds = productStructureDtos.stream() .map(ProductStructureDto::getParentId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map<Long, ProductStructureDto> parentMap = productStructureMapper.selectByIds(parentIds) .stream() .collect(Collectors.toMap( ProductStructureDto::getId, Function.identity() )); for (ProductStructureDto productStructureDto : productStructureDtos) { ProductionProductInput productionProductInput = new ProductionProductInput(); productionProductInput.setProductModelId(productStructureDto.getProductModelId()); // productionProductInput.setQuantity(productStructureDto.getUnitQuantity().multiply(dto.getQuantity())); // productionProductInput.setProductMainId(productionProductMain.getId()); // productionProductInputMapper.insert(productionProductInput); // stockUtils.substractStock(productStructureDto.getProductModelId(), productionProductInput.getQuantity(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId(),dto.getBatchNo()); // } BigDecimal childQty = productStructureDto.getUnitQuantity(); BigDecimal parentQty = BigDecimal.ONE; if (productStructureDto.getParentId() != null) { ProductStructureDto parent = parentMap.get(productStructureDto.getParentId()); if (parent != null) { parentQty = parent.getUnitQuantity(); } } // æ ¸å¿è®¡ç® BigDecimal needQty = childQty.divide(parentQty, 6, RoundingMode.HALF_UP).multiply(dto.getQuantity()); productionProductInput.setQuantity(needQty); productionProductInput.setProductMainId(productionProductMain.getId()); productionProductInputMapper.insert(productionProductInput); stockUtils.substractStock(productStructureDto.getProductModelId(), productionProductInput.getQuantity(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId(), null); } /*æ°å¢æ¥å·¥äº§åºè¡¨*/ ProductionProductOutput productionProductOutput = new ProductionProductOutput(); productionProductOutput.setProductMainId(productionProductMain.getId()); @@ -448,4 +474,4 @@ return productionProductMainDtos; } } } src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; import com.ruoyi.common.exception.base.BaseException; import com.ruoyi.common.utils.EnumUtil; import com.ruoyi.common.utils.OrderUtils; @@ -61,7 +60,7 @@ int insertRows = stockInRecordMapper.insert(stockInRecord); Long insertId = stockInRecord.getId(); // æ¿æ¢ä¸ºä½ çå®é 主é®å段ï¼getRecordId()/getInboundId() ç Long insertId = stockInRecord.getId(); return insertRows > 0 ? insertId : null; } src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -37,6 +37,7 @@ import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; @@ -251,6 +252,38 @@ stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId()); stockOutRecordDto.setType("0"); stockOutRecordService.add(stockOutRecordDto); if (ObjectUtils.isEmpty(stockInventoryDto.getBatchNo())) { List<StockInventory> stockInventories = stockInventoryMapper.selectList(new QueryWrapper<StockInventory>().lambda() .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId()) .orderByAsc(StockInventory::getId)); if (ObjectUtils.isEmpty(stockInventories)) { throw new RuntimeException("产ååºåä¸åå¨"); } BigDecimal remainingQty = stockInventoryDto.getQualitity(); for (StockInventory stockInventory : stockInventories) { BigDecimal lockedQty = stockInventory.getLockedQuantity() == null ? BigDecimal.ZERO : stockInventory.getLockedQuantity(); BigDecimal availableQty = stockInventory.getQualitity().subtract(lockedQty); if (availableQty.compareTo(BigDecimal.ZERO) <= 0) { continue; } BigDecimal deductQty = remainingQty.min(availableQty); stockInventory.setQualitity(stockInventory.getQualitity().subtract(deductQty)); stockInventory.setVersion(stockInventory.getVersion() == null ? 1 : stockInventory.getVersion() + 1); stockInventory.setUpdateTime(LocalDateTime.now()); stockInventoryMapper.updateById(stockInventory); remainingQty = remainingQty.subtract(deductQty); if (remainingQty.compareTo(BigDecimal.ZERO) <= 0) { return true; } } ProductModel productModel = productModelMapper.selectById(stockInventoryDto.getProductModelId()); Product product = productMapper.selectById(productModel.getProductId()); throw new RuntimeException(product.getProductName() + "/" + productModel.getModel() + "åºåä¸è¶³æ æ³åºåº"); } StockInventory oldStockInventory = stockInventoryMapper.selectOne(new QueryWrapper<StockInventory>().lambda().eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId()) .eq(StockInventory::getBatchNo, stockInventoryDto.getBatchNo())); if (ObjectUtils.isEmpty(oldStockInventory)) { src/main/resources/mapper/production/ProductStructureMapper.xml
@@ -41,4 +41,12 @@ and ps.process_id=#{processId} order by ps.id </select> <select id="selectByIds" resultType="com.ruoyi.production.dto.ProductStructureDto"> select * from product_structure where id in <foreach item="item" collection="parentIds" separator="," open="(" close=")" index=""> #{item} </foreach> </select> </mapper> src/main/resources/mapper/production/ProductionProductInputMapper.xml
@@ -7,6 +7,7 @@ <result property="productMainId" column="product_main_id"/> <result property="productModelId" column="product_model_id"/> <result property="quantity" column="quantity"/> <result property="batchNo" column="batch_no"/> <result property="tenantId" column="tenant_id"/> <result property="createTime" column="create_time"/> </resultMap>