feat: 采购台账产品层级自动注册与材质字段、大类删除限制、审批模板启用后仅可修改状态
已修改8个文件
199 ■■■■■ 文件已修改
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java
@@ -85,9 +85,16 @@
    @Override
    public Boolean updateApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto) {
        // 检查模板是否被审批实例引用
        if (isTemplateReferenced(approvalTemplateDto.getId())) {
            throw new RuntimeException("该审批模板已被审批流程引用,无法编辑");
        ApprovalTemplate existingTemplate = approvalTemplateMapper.selectById(approvalTemplateDto.getId());
        if (existingTemplate == null) {
            throw new IllegalArgumentException("模板不存在");
        }
        // 启用中的模板只能修改启用状态,其他项需先停用后再编辑
        if (existingTemplate.getEnabled() != null && existingTemplate.getEnabled() == 1) {
            ApprovalTemplate updateEntity = new ApprovalTemplate();
            updateEntity.setId(approvalTemplateDto.getId());
            updateEntity.setEnabled(approvalTemplateDto.getEnabled());
            return approvalTemplateMapper.updateById(updateEntity) > 0;
        }
        approvalTemplateMapper.updateById(approvalTemplateDto);
        approvalTemplateNodeService.remove(
src/main/java/com/ruoyi/basic/service/IProductService.java
@@ -20,4 +20,11 @@
    List<ProductTreeDto> selectProductList(ProductDto productDto);
    IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, ProductModel productModel);
    /**
     * 台账手工录入品名/材质/规格型号时,自动注册产品大类(品名)-子类(材质)-规格型号,已存在则复用
     *
     * @return 规格型号记录,未注册时返回 null
     */
    ProductModel ensureProductHierarchy(String productName, String material, String modelName, String unit);
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -55,7 +55,8 @@
        String productCode = StringUtils.trim(productModelDto.getProductCode());
        productModelDto.setModel(model);
        productModelDto.setProductCode(productCode);
        checkModelAndProductCodeUnique(model, productCode, productModelDto.getId());
        // 产品编号非必填,规格型号在同一产品下唯一
        checkModelUnique(productModelDto.getProductId(), model, productModelDto.getId());
        if (productModelDto.getId() == null) {
            ProductModel productModel = new ProductModel();
@@ -66,18 +67,18 @@
        }
    }
    private void checkModelAndProductCodeUnique(String model, String productCode, Long currentId) {
        if (StringUtils.isEmpty(model) || StringUtils.isEmpty(productCode)) {
    private void checkModelUnique(Long productId, String model, Long currentId) {
        if (StringUtils.isEmpty(model)) {
            return;
        }
        LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductModel::getModel, model)
                .eq(ProductModel::getProductCode, productCode)
        queryWrapper.eq(ProductModel::getProductId, productId)
                .eq(ProductModel::getModel, model)
                .ne(currentId != null, ProductModel::getId, currentId)
                .last("limit 1");
        ProductModel duplicateProductModel = productModelMapper.selectOne(queryWrapper);
        if (duplicateProductModel != null) {
            throw new ServiceException("对应的型号" + model + "的产品编码" + productCode + "已经存在");
            throw new ServiceException("对应的型号" + model + "已经存在");
        }
    }
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
@@ -89,21 +89,27 @@
    @Override
    public int addOrEditProduct(ProductDto productDto) {
        if (ObjectUtils.isEmpty(productDto.getParentId())) {
            throw new IllegalArgumentException("请选择父节点");
        }
        String productName = StringUtils.trim(productDto.getProductName());
        if (StringUtils.isEmpty(productName)) {
            throw new IllegalArgumentException("产品名称不能为空");
        }
        // 父节点为空表示新增一级大类
        if (ObjectUtils.isEmpty(productDto.getParentId())) {
            if (productDto.getId() != null) {
                throw new IllegalArgumentException("请选择父节点");
            }
            productDto.setParentId(null);
        }
        productDto.setProductName(productName);
        checkProductNameUnique(productDto.getParentId(), productName, productDto.getId());
        if (productDto.getId() == null) {
            // 新增产品逻辑
            // 检查父节点是否存在(可选,根据业务需求)
            Product parent = productMapper.selectById(productDto.getParentId());
            if (parent == null) {
                throw new IllegalArgumentException("父节点不存在,无法添加子产品");
            // 检查父节点是否存在(一级大类无父节点)
            if (productDto.getParentId() != null) {
                Product parent = productMapper.selectById(productDto.getParentId());
                if (parent == null) {
                    throw new IllegalArgumentException("父节点不存在,无法添加子产品");
                }
            }
            return productMapper.insert(productDto);
        } else {
@@ -119,8 +125,12 @@
    private void checkProductNameUnique(Long parentId, String productName, Long currentId) {
        LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Product::getParentId, parentId)
                .eq(Product::getProductName, productName)
        if (parentId == null) {
            queryWrapper.isNull(Product::getParentId);
        } else {
            queryWrapper.eq(Product::getParentId, parentId);
        }
        queryWrapper.eq(Product::getProductName, productName)
                .ne(currentId != null, Product::getId, currentId)
                .last("limit 1");
        Product duplicateProduct = productMapper.selectOne(queryWrapper);
@@ -131,6 +141,26 @@
    @Override
    public int delProductByIds(Long[] ids) {
        // 存在子类时不允许删除
        LambdaQueryWrapper<Product> childWrapper = new LambdaQueryWrapper<>();
        childWrapper.in(Product::getParentId, ids).last("limit 1");
        Product childProduct = productMapper.selectOne(childWrapper);
        if (childProduct != null) {
            throw new IllegalArgumentException("该大类下存在子类,不能删除");
        }
        // 大类下存在规格型号数据时不允许删除
        for (Product product : productMapper.selectBatchIds(Arrays.asList(ids))) {
            if (product.getParentId() != null) {
                continue;
            }
            LambdaQueryWrapper<ProductModel> modelWrapper = new LambdaQueryWrapper<>();
            modelWrapper.eq(ProductModel::getProductId, product.getId()).last("limit 1");
            if (productModelMapper.selectOne(modelWrapper) != null) {
                throw new IllegalArgumentException("该大类下存在数据,不能删除");
            }
        }
        // 1. 删除子表 product_model 中关联的数据
        LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ProductModel::getProductId, ids);
@@ -142,4 +172,54 @@
        return deleteCount;
    }
    @Override
    public ProductModel ensureProductHierarchy(String productName, String material, String modelName, String unit) {
        productName = StringUtils.trim(productName);
        material = StringUtils.trim(material);
        modelName = StringUtils.trim(modelName);
        if (StringUtils.isEmpty(productName) || StringUtils.isEmpty(material) || StringUtils.isEmpty(modelName)) {
            return null;
        }
        // 品名:一级大类(parentId 为空)
        LambdaQueryWrapper<Product> categoryWrapper = new LambdaQueryWrapper<>();
        categoryWrapper.isNull(Product::getParentId)
                .eq(Product::getProductName, productName)
                .last("limit 1");
        Product category = productMapper.selectOne(categoryWrapper);
        if (category == null) {
            category = new Product();
            category.setProductName(productName);
            productMapper.insert(category);
        }
        // 材质:大类下的子类
        LambdaQueryWrapper<Product> materialWrapper = new LambdaQueryWrapper<>();
        materialWrapper.eq(Product::getParentId, category.getId())
                .eq(Product::getProductName, material)
                .last("limit 1");
        Product materialProduct = productMapper.selectOne(materialWrapper);
        if (materialProduct == null) {
            materialProduct = new Product();
            materialProduct.setParentId(category.getId());
            materialProduct.setProductName(material);
            productMapper.insert(materialProduct);
        }
        // 规格型号:子类下的规格
        LambdaQueryWrapper<ProductModel> modelWrapper = new LambdaQueryWrapper<>();
        modelWrapper.eq(ProductModel::getProductId, materialProduct.getId())
                .eq(ProductModel::getModel, modelName)
                .last("limit 1");
        ProductModel productModel = productModelMapper.selectOne(modelWrapper);
        if (productModel == null) {
            productModel = new ProductModel();
            productModel.setProductId(materialProduct.getId());
            productModel.setModel(modelName);
            productModel.setUnit(unit);
            productModelMapper.insert(productModel);
        }
        return productModel;
    }
}
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -22,6 +22,7 @@
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.ApprovalStatusEnum;
import com.ruoyi.common.enums.ReviewStatusEnum;
@@ -103,6 +104,7 @@
    private final SupplierManageMapper supplierManageMapper;
    private final ProductMapper productMapper;
    private final ProductModelMapper productModelMapper;
    private final IProductService productService;
    private final SysUserMapper sysUserMapper;
    private final StringRedisTemplate redisTemplate;
    private final QualityInspectMapper qualityInspectMapper;
@@ -631,6 +633,11 @@
        }
        Integer ledgerType = type == null ? 2 : type;
        // 手工录入「品名+材质+规格型号」时,自动注册到产品维护
        for (SalesLedgerProduct product : products) {
            registerProductFromLedgerProduct(product);
        }
        // 提前收集所有需要查询的ID
        Set<Long> productIds = products.stream()
                .map(SalesLedgerProduct::getProductId)
@@ -658,6 +665,11 @@
        // 设置字段
        for (SalesLedgerProduct product : products) {
            product.setSalesLedgerId(salesLedgerId);
            // 手工录入品名/材质/规格型号的行已在注册时写入,不再用产品树节点名称覆盖
            if (StringUtils.isNotEmpty(product.getMaterial())) {
                continue;
            }
            Long productId = product.getProductId();
            if (productId != null && productMap.containsKey(productId)) {
@@ -710,6 +722,28 @@
        }
    }
    /**
     * 手工录入「品名+材质+规格型号」时,在产品维护自动注册:
     * 品名=一级大类,材质=大类下的子类,规格型号=子类下的规格
     */
    private void registerProductFromLedgerProduct(SalesLedgerProduct product) {
        String productName = StringUtils.trim(product.getProductCategory());
        String material = StringUtils.trim(product.getMaterial());
        String modelName = StringUtils.trim(product.getSpecificationModel());
        if (StringUtils.isEmpty(productName) || StringUtils.isEmpty(material) || StringUtils.isEmpty(modelName)) {
            return;
        }
        product.setProductCategory(productName);
        product.setMaterial(material);
        product.setSpecificationModel(modelName);
        ProductModel productModel = productService.ensureProductHierarchy(productName, material, modelName, product.getUnit());
        if (productModel != null) {
            product.setProductId(productModel.getProductId());
            product.setProductModelId(productModel.getId());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deletePurchaseLedgerByIds(Long[] ids) {
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -44,12 +44,18 @@
    private BigDecimal warnNum;
    /**
     * 产品大类
     * 产品大类(品名)
     */
    @Excel(name = "产品大类")
    private String productCategory;
    /**
     * 材质
     */
    @Excel(name = "材质")
    private String material;
    /**
     * 规格型号
     */
    @Excel(name = "规格型号")
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -4,6 +4,8 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.production.mapper.*;
@@ -26,6 +28,7 @@
import com.ruoyi.technology.mapper.TechnologyRoutingMapper;
import com.ruoyi.technology.pojo.TechnologyRouting;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -86,6 +89,8 @@
    private StockUtils stockUtils;
    @Autowired
    private StockInventoryMapper stockInventoryMapper;
    @Autowired
    private IProductService productService;
    @Override
    public SalesLedgerProduct selectSalesLedgerProductById(Long id) {
@@ -178,6 +183,20 @@
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedgerProduct(SalesLedgerProduct salesLedgerProduct) {
        // 采购台账(type=2)手工录入品名/材质/规格型号时,自动注册到产品维护
        if (Integer.valueOf(2).equals(salesLedgerProduct.getType())
                && StringUtils.isNotBlank(salesLedgerProduct.getMaterial())) {
            ProductModel productModel = productService.ensureProductHierarchy(
                    salesLedgerProduct.getProductCategory(),
                    salesLedgerProduct.getMaterial(),
                    salesLedgerProduct.getSpecificationModel(),
                    salesLedgerProduct.getUnit());
            if (productModel != null) {
                salesLedgerProduct.setProductId(productModel.getProductId());
                salesLedgerProduct.setProductModelId(productModel.getId());
            }
        }
        int result;
        Long salesLedgerId = salesLedgerProduct.getSalesLedgerId();
        if (salesLedgerProduct.getId() == null) {
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -26,9 +26,10 @@
        T1.is_production,
        T1.create_user,
        T1.dept_id,
        p.product_name as product_category,
        pm.model as specification_model,
        pm.unit as unit,
        COALESCE(NULLIF(T1.product_category, ''), p.product_name) as product_category,
        T1.material,
        COALESCE(NULLIF(T1.specification_model, ''), pm.model) as specification_model,
        COALESCE(NULLIF(T1.unit, ''), pm.unit) as unit,
        CASE
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >0 THEN 1
        ELSE 0