feat(product): 添加产品型号向下复制功能并优化生产报工重量计算
- 在ProductController中新增downCopy接口实现产品型号批量复制功能
- 将ProductionProductMainDto中的bomInputQty字段重命名为inputWeight
- 在ProductionProductMainServiceImpl中添加JSON解析逻辑支持从otherData中提取投入重量
- 新增resolveInputWeight、findParameterValue、findFieldValue等工具方法处理复杂参数解析
- 为ProductModelDto添加targetProductId字段用于指定复制目标
- 修复销售台账按调度员ID和姓名分组的SQL查询问题
- 优化库存服务中剩余数量计算的空值处理逻辑
- 完善生产投料数量为空时的默认值处理机制
已修改8个文件
165 ■■■■■ 文件已修改
src/main/java/com/ruoyi/basic/controller/ProductController.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductModelService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -17,8 +17,6 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import com.ruoyi.sales.service.ISalesLedgerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
@@ -143,4 +141,14 @@
        ExcelUtil<ProductModelExportDto> excelUtil = new ExcelUtil<>(ProductModelExportDto.class);
        excelUtil.importTemplateExcel(response, "产品规格导入模板");
    }
    /**
     * 向下复制型号等
     */
    @Log(title = "向下复制型号等", businessType = BusinessType.INSERT)
    @PostMapping("/downCopy")
    public AjaxResult downCopy(@RequestBody ProductModelDto productModelDto) {
        return toAjax(productModelService.downCopy(productModelDto));
    }
}
src/main/java/com/ruoyi/basic/dto/ProductModelDto.java
@@ -9,4 +9,7 @@
@Data
public class ProductModelDto extends ProductModel {
    private List<ProductStructureDto> productStructureList;
    //复制的目标产品id
    private Long targetProductId;
}
src/main/java/com/ruoyi/basic/service/IProductModelService.java
@@ -35,4 +35,6 @@
    IPage<ProductModel> modelListPage(Page page , ProductDto productDto);
    AjaxResult importProductModel(MultipartFile file, Integer productId);
    int downCopy(ProductModelDto productModelDto);
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -155,4 +155,51 @@
            throw new ServiceException("导入失败");
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int downCopy(ProductModelDto productModelDto) {
        if (productModelDto == null || productModelDto.getProductId() == null || productModelDto.getTargetProductId() == null) {
            throw new ServiceException("源产品ID和目标产品ID不能为空");
        }
        Long sourceProductId = productModelDto.getProductId();
        Long targetProductId = productModelDto.getTargetProductId();
        if (sourceProductId.equals(targetProductId)) {
            throw new ServiceException("源产品和目标产品不能相同");
        }
        Product sourceProduct = productMapper.selectById(sourceProductId);
        if (sourceProduct == null) {
            throw new ServiceException("源产品不存在");
        }
        Product targetProduct = productMapper.selectById(targetProductId);
        if (targetProduct == null) {
            throw new ServiceException("目标产品不存在");
        }
        List<ProductModel> sourceModels = productModelMapper.selectList(
                new LambdaQueryWrapper<ProductModel>()
                        .eq(ProductModel::getProductId, sourceProductId)
                        .orderByAsc(ProductModel::getId)
        );
        if (CollectionUtils.isEmpty(sourceModels)) {
            throw new ServiceException("源产品下没有可复制的型号");
        }
        List<ProductModel> copyList = new ArrayList<>();
        for (ProductModel sourceModel : sourceModels) {
            ProductModel copy = new ProductModel();
            BeanUtils.copyProperties(sourceModel, copy);
            copy.setId(null);
            copy.setProductId(targetProductId);
            copy.setTenantId(null);
            copyList.add(copy);
        }
        saveBatch(copyList);
        return copyList.size();
    }
}
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java
@@ -88,5 +88,5 @@
    private String batchNo;
    @ApiModelProperty(value = "产品结构投入数量")
    private BigDecimal bomInputQty;
    private BigDecimal inputWeight;
}
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -1,5 +1,8 @@
package com.ruoyi.production.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -52,6 +55,8 @@
    private static final String PROCESS_VOLTAGE_SORT = "电压分选";
    private static final String PROCESS_OPTICAL_INSPECTION = "光检外观";
    private static final String PROCESS_PACKAGING = "包装";
    private static final String INPUT_WEIGHT_PARAMETER = "投入重量";
    private static final String INPUT_WEIGHT_FIELD = "inputWeight";
    private static final Object PRODUCT_MAIN_NO_LOCK = new Object();
    private IQualityInspectService qualityInspectService;
@@ -98,7 +103,10 @@
        BigDecimal reportQty = dto.getQuantity();
        BigDecimal scrapQty = dto.getScrapQty() == null ? BigDecimal.ZERO : dto.getScrapQty();
        BigDecimal bomInputQty = dto.getBomInputQty();
        BigDecimal bomInputQty = dto.getInputWeight();
        if (bomInputQty == null) {
            bomInputQty = resolveInputWeight(dto.getOtherData());
        }
        if (reportQty == null || reportQty.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("报工数量必须大于0");
        }
@@ -345,7 +353,7 @@
            ProductionProductInput productionProductInput = new ProductionProductInput();
            productionProductInput.setProductModelId(productStructureDto.getProductModelId());
            productionProductInput.setQuantity(needQty);
            productionProductInput.setQuantity(needQty == null ? BigDecimal.ZERO : needQty);
            productionProductInput.setProductMainId(productionProductMain.getId());
            productionProductInputMapper.insert(productionProductInput);
@@ -761,4 +769,87 @@
        return productionProductMainDtos;
    }
    private BigDecimal resolveInputWeight(String otherData) {
        if (StringUtils.isBlank(otherData)) {
            return null;
        }
        Object parsed;
        try {
            parsed = JSON.parse(otherData);
        } catch (Exception ex) {
            throw new ServiceException("报工参数格式错误,无法解析投入重量");
        }
        String inputWeight = StringUtils.trim(findParameterValue(parsed, INPUT_WEIGHT_PARAMETER));
        if (StringUtils.isBlank(inputWeight)) {
            inputWeight = StringUtils.trim(findFieldValue(parsed, INPUT_WEIGHT_FIELD));
        }
        if (StringUtils.isBlank(inputWeight)) {
            return null;
        }
        try {
            return new BigDecimal(inputWeight);
        } catch (NumberFormatException ex) {
            throw new ServiceException("报工参数中的投入重量格式错误");
        }
    }
    private String findParameterValue(Object node, String parameterItem) {
        if (node instanceof JSONArray) {
            JSONArray array = (JSONArray) node;
            for (Object item : array) {
                String value = findParameterValue(item, parameterItem);
                if (StringUtils.isNotBlank(value)) {
                    return value;
                }
            }
            return null;
        }
        if (node instanceof JSONObject) {
            JSONObject object = (JSONObject) node;
            if (parameterItem.equals(StringUtils.trim(object.getString("parameterItem")))) {
                String value = StringUtils.trim(object.getString("value"));
                if (StringUtils.isNotBlank(value)) {
                    return value;
                }
            }
            for (Object value : object.values()) {
                String matched = findParameterValue(value, parameterItem);
                if (StringUtils.isNotBlank(matched)) {
                    return matched;
                }
            }
        }
        return null;
    }
    private String findFieldValue(Object node, String fieldName) {
        if (node instanceof JSONArray) {
            JSONArray array = (JSONArray) node;
            for (Object item : array) {
                String value = findFieldValue(item, fieldName);
                if (StringUtils.isNotBlank(value)) {
                    return value;
                }
            }
            return null;
        }
        if (node instanceof JSONObject) {
            JSONObject object = (JSONObject) node;
            Object fieldValue = object.get(fieldName);
            if (fieldValue != null) {
                String value = StringUtils.trim(String.valueOf(fieldValue));
                if (StringUtils.isNotBlank(value)) {
                    return value;
                }
            }
            for (Object value : object.values()) {
                String matched = findFieldValue(value, fieldName);
                if (StringUtils.isNotBlank(matched)) {
                    return matched;
                }
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -298,7 +298,7 @@
                throw new RuntimeException("产品库存不存在");
            }
            BigDecimal remainingQty = stockInventoryDto.getQualitity();
            BigDecimal remainingQty = stockInventoryDto.getQualitity() == null ? BigDecimal.ZERO : stockInventoryDto.getQualitity();
            for (StockInventory stockInventory : stockInventories) {
                BigDecimal lockedQty = defaultDecimal(stockInventory.getLockedQuantity());
                BigDecimal availableQty = defaultDecimal(stockInventory.getQualitity()).subtract(lockedQty);
src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml
@@ -89,7 +89,7 @@
            </if>
        </where>
        GROUP BY slpa.scheduling_user_name
        GROUP BY slpa.scheduling_user_id, slpa.scheduling_user_name
    </select>