maven
2026-03-16 69e1d95373227f364b2fa6d3852be24bafd1a9dc
yys 产品基础数据修改,分批发货开发
已添加17个文件
已修改23个文件
1726 ■■■■■ 文件已修改
src/main/java/com/ruoyi/CodeGenerator.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/ProductController.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductAndModelDto.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelExcelCopyDto.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelExcelDto.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelExcelItemDto.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/ProductModel.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 122 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/OrderUtils.java 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/dto/ReturnManagementDto.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/dto/ReturnSaleProductDto.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnSaleProductMapper.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnSaleProduct.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ReturnManagementService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ReturnSaleProductService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnSaleProductServiceImpl.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShippingInfoDetailController.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/ShippingInfoDetailMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingInfoDetail.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ShippingInfoDetailService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoDetailServiceImpl.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/ProductModelMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingInfoDetailMapper.xml 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingInfoMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/CodeGenerator.java
@@ -19,11 +19,11 @@
// æ¼”示例子,执行 main æ–¹æ³•控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
    public static String database_url = "jdbc:mysql://localhost:3306/product-inventory-management-new";
    public static String database_url = "jdbc:mysql://localhost:3306/product-inventory-management-jtwy";
    public static String database_username = "root";
    public static String database_password= "123456";
    public static String author = "芯导软件(江苏)有限公司";
    public static String model = "purchase"; // æ¨¡å—
    public static String model = "sales"; // æ¨¡å—
    public static String setParent = "com.ruoyi."+ model; // åŒ…路径
    public static String tablePrefix = ""; // è®¾ç½®è¿‡æ»¤è¡¨å‰ç¼€
    public static void main(String[] args) {
src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -3,26 +3,29 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.dto.*;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.production.pojo.ProductProcess;
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.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@@ -35,6 +38,14 @@
    private IProductModelService productModelService;
    @Autowired
    private ISalesLedgerProductService salesLedgerProductService;
    /**
     * åˆ†é¡µæŸ¥è¯¢
     */
    @GetMapping("/listPage")
    public AjaxResult listPage(Page page, ProductAndModelDto productDto) {
        return productService.listPage(page, productDto);
    }
    /**
     * æŸ¥è¯¢äº§å“
     */
@@ -74,15 +85,16 @@
     */
    @Log(title = "产品", businessType = BusinessType.DELETE)
    @DeleteMapping("/delProduct")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult remove(@RequestBody Long[] ids) {
        if (ids == null || ids.length == 0) {
            return AjaxResult.error("请传入要删除的ID");
        }
        // æ£€æŸ¥æ˜¯å¦æœ‰é”€å”®å•†å“è®°å½•关联该产品
        LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SalesLedgerProduct::getProductId, ids);
        queryWrapper.in(SalesLedgerProduct::getProductModelId, ids);
        List<SalesLedgerProduct> salesLedgerProductList = salesLedgerProductService.list(queryWrapper);
        if (salesLedgerProductList.size() > 0) {
        if (CollectionUtils.isNotEmpty(salesLedgerProductList)) {
            return AjaxResult.error("该产品存在销售/采购记录,不能删除");
        }
        return toAjax(productService.delProductByIds(ids));
@@ -129,4 +141,14 @@
    public AjaxResult importProduct(MultipartFile file) {
        return AjaxResult.success(productModelService.importProduct(file));
    }
    /**
     * ä¸‹è½½æ¨¡æ¿
     */
    @ApiOperation("下载模板")
    @PostMapping("downloadTemplate")
    public void downloadTemplate(HttpServletResponse response) {
        ExcelUtil<ProductModelExcelCopyDto> util = new ExcelUtil<ProductModelExcelCopyDto>(ProductModelExcelCopyDto.class);
        util.importTemplateExcel(response, "产品导入模板");
    }
}
src/main/java/com/ruoyi/basic/dto/ProductAndModelDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package com.ruoyi.basic.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @author :yys
 * @date : 2026/3/16 11:47
 */
@Data
public class ProductAndModelDto {
    /**
     * è§„æ ¼id
     */
    private Long id;
    /**
     * äº§å“id
     */
    private Long productId;
    /**
     *  äº§å“åç§°
     */
    private String productName;
    /**
     * è§„格型号
     */
    private String model;
    /**
     * å•位
     */
    private String unit;
    /**
     * å›¾çº¸ç¼–号
     */
    private String drawingNumber;
    /**
     * äº§å“ç±»åž‹(1-物料,2-产品)
     */
    @ApiModelProperty(value = "1=自制,2=外购,3=委外")
    private Integer productType;
}
src/main/java/com/ruoyi/basic/dto/ProductModelDto.java
@@ -9,4 +9,6 @@
@Data
public class ProductModelDto extends ProductModel {
    private List<ProductStructureDto> productStructureList;
    private String productName;
}
src/main/java/com/ruoyi/basic/dto/ProductModelExcelCopyDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.basic.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
/**
 * @author :yys
 * @date : 2026/3/16 10:48
 */
@Data
public class ProductModelExcelCopyDto {
    @Excel(name = "产品名称")
    private String productName;
    /**
     * å›¾çº¸ç¼–号 = äº§å“ç¼–号
     */
    @Excel(name = "产品编号")
    private String model;
    /**
     * å•位
     */
    @Excel(name = "单位")
    private String unit;
    @Excel(name = "产品属性",readConverterExp = "1=自制,2=外购,3=委外",prompt = "选填,只支持自制,外购,委外,如不填,默认自制")
    private Integer productType;
    @Excel(name = "产品规格")
    private String drawingNumber;
    @Excel(name = "工艺路线")
    private String processRoute;
}
src/main/java/com/ruoyi/basic/dto/ProductModelExcelDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package com.ruoyi.basic.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
import java.util.List;
/**
 * @author :yys
 * @date : 2026/3/13 15:34
 */
@Data
public class ProductModelExcelDto {
    private String index;
    @Excel(name = "序号")
    private String number;
    @Excel(name = "产品名称")
    private String productName;
    /**
     * è§„格型号
     */
    @Excel(name = "规格型号")
    private String model;
    /**
     * å•位
     */
    @Excel(name = "单位")
    private String unit;
    @Excel(name = "产品类型(物料,产品)",readConverterExp = "1=物料,2=产品")
    private Integer productType;
    @Excel(name = "图纸编号")
    private String drawingNumber;
    private List<ProductModelExcelDto> children;
    private List<ProductModelExcelItemDto> items;
}
src/main/java/com/ruoyi/basic/dto/ProductModelExcelItemDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.ruoyi.basic.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
/**
 * @author :yys
 * @date : 2026/3/13 16:00
 */
@Data
public class ProductModelExcelItemDto {
    /**
     * è§„格型号
     */
    @Excel(name = "规格型号")
    private String model;
    /**
     * å•位
     */
    @Excel(name = "单位")
    private String unit;
    @Excel(name = "产品类型(物料,产品)",readConverterExp = "1=物料,2=产品")
    private Integer productType;
    @Excel(name = "图纸编号")
    private String drawingNumber;
}
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.ProductAndModelDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
import org.apache.ibatis.annotations.Param;
@@ -26,4 +27,6 @@
    ProductModel selectLatestRecord();
    List<Map<String, Object>> getProductAndModelList();
    IPage<ProductAndModelDto> listPage(Page page,@Param("req") ProductAndModelDto productDto);
}
src/main/java/com/ruoyi/basic/pojo/ProductModel.java
@@ -33,7 +33,7 @@
    /**
     * è§„格型号
     */
    @Excel(name = "规格型号")
    @Excel(name = "图纸编号")
    private String model;
    /**
@@ -61,10 +61,13 @@
    @Excel(name = "剩余库存")
    private BigDecimal stockQuantity;
    @ApiModelProperty(value = "图纸编号")
    @Excel(name = "图纸编号")
    @ApiModelProperty(value = "规格型号")
    @Excel(name = "规格型号")
    private String drawingNumber;
    @TableField(exist = false)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "产品类型(1-物料,2-产品)")
    private Integer productType;
}
src/main/java/com/ruoyi/basic/service/IProductService.java
@@ -3,10 +3,12 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.ProductAndModelDto;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.AjaxResult;
import java.util.List;
@@ -19,4 +21,6 @@
    List<ProductTreeDto> selectProductList(ProductDto productDto);
    IPage<ProductModel> listPageProductModel(Page<ProductModel> page, ProductModel productModel);
    AjaxResult listPage(Page page, ProductAndModelDto productDto);
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -5,20 +5,23 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.dto.*;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
@@ -44,12 +47,27 @@
    @Override
    public int addOrEditProductModel(ProductModelDto productModelDto) {
        if(StringUtils.isEmpty(productModelDto.getProductName())){
            throw new RuntimeException("产品名称不能为空");
        }
        Product product = productMapper.selectOne(new LambdaQueryWrapper<Product>()
                .eq(Product::getProductName, productModelDto.getProductName()));
        if (productModelDto.getId() == null) {
            if(product == null){
                product = new Product();
                product.setProductName(productModelDto.getProductName());
                productMapper.insert(product);
            }
            ProductModel productModel = new ProductModel();
            BeanUtils.copyProperties(productModelDto,productModel);
            productModel.setProductId(product.getId());
            return productModelMapper.insert(productModel);
        } else {
            if(product != null){
                product.setProductName(productModelDto.getProductName());
                productMapper.updateById(product);
            }
            return productModelMapper.updateById(productModelDto);
        }
    }
@@ -88,24 +106,102 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean importProduct(MultipartFile file) {
        try {
            ExcelUtil<ProductModel> productModelExcelUtil = new ExcelUtil<>(ProductModel.class);
            List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
            Map<String, List<ProductModel>> collect = productModelList.stream().collect(Collectors.groupingBy(ProductModel::getProductName));
            collect.forEach((k,v)->{
                Product product = productMapper.selectOne(new LambdaQueryWrapper<Product>().eq(Product::getProductName, k).last("LIMIT 1"));
                if (product != null) {
                    v.forEach(productModel -> {
                        productModel.setProductId(product.getId());
            ExcelUtil<ProductModelExcelCopyDto> productModelExcelUtil = new ExcelUtil<>(ProductModelExcelCopyDto.class);
            List<ProductModelExcelCopyDto> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
            List<Product> productList = productMapper.selectList(new LambdaQueryWrapper<Product>()
                    .isNull(Product::getParentId));
            if(CollectionUtils.isEmpty(productList)) {
                throw new RuntimeException("请先添加父级产品");
            }
            if(CollectionUtils.isNotEmpty(productModelList)){
                // 2. æŒ‰äº§å“åç§°åˆ†ç»„
                Map<String, List<ProductModelExcelCopyDto>> groupedByProductName = productModelList.stream()
                        .collect(Collectors.groupingBy(ProductModelExcelCopyDto::getProductName));
                for (Map.Entry<String, List<ProductModelExcelCopyDto>> entry : groupedByProductName.entrySet()) {
                    // æ ¹æ®åç§°æŸ¥è¯¢æ˜¯å¦å­˜åœ¨äº§å“
                    Product product = productMapper.selectOne(new LambdaQueryWrapper<Product>()
                            .eq(Product::getProductName, entry.getKey())
                            .last("limit 1"));
                    if(product == null){
                        product = new Product();
                        product.setProductName(entry.getKey());
                        product.setParentId(null); // çˆ¶èŠ‚ç‚¹ID
                        // 2. æ’入当前节点,MyBatis会自动回填product的id属性
                        productMapper.insert(product);
                    }
                    Product finalProduct = product;
                    entry.getValue().forEach(productModelExcelDto -> {
                        // æ ¹æ®å›¾çº¸ç¼–号查询
                        ProductModel productModel = productModelMapper.selectOne(new LambdaQueryWrapper<ProductModel>()
                                .eq(ProductModel::getModel, productModelExcelDto.getModel())
                                .last("limit 1"));
                        if(productModel == null){
                            productModel = new ProductModel();
                            BeanUtils.copyProperties(productModelExcelDto,productModel);
                            if(productModelExcelDto.getProductType() == null) {
                                productModel.setProductType(1);
                            }
                            productModel.setProductId(finalProduct.getId());
                            productModelMapper.insert(productModel);
                        }else{
                            BeanUtils.copyProperties(productModelExcelDto,productModel);
                            productModel.setProductId(finalProduct.getId());
                            productModelMapper.updateById(productModel);
                        }
                    });
                    this.saveOrUpdateBatch(v);
                }
            });
            }
//            List<ProductModelExcelDto> productModelExcelDtos = OrderUtils.buildTree(productModelList);
//            if(CollectionUtils.isNotEmpty(productModelExcelDtos)){
//                recursiveSaveProduct(productModelExcelDtos.get(0),null);
//            }
            return true;
        }catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    /**
     * é€’归导入树形产品数据
     * @param excelDto Excel解析后的树形节点
     * @param parentId çˆ¶èŠ‚ç‚¹ID(顶级节点传null/0)
     */
    @Transactional(rollbackFor = Exception.class) // äº‹åŠ¡ä¿è¯ï¼Œå¤±è´¥åˆ™å›žæ»š
    public void recursiveSaveProduct(ProductModelExcelDto excelDto, Long parentId) {
        // 1. æž„建当前节点的Product实体
        Product product = new Product();
        product.setProductName(excelDto.getProductName());
        product.setParentId(parentId); // çˆ¶èŠ‚ç‚¹ID
        // 2. æ’入当前节点,MyBatis会自动回填product的id属性
        productMapper.insert(product);
        Long currentId = product.getId(); // èŽ·å–å½“å‰èŠ‚ç‚¹ä¸»é”®
        // 3. é€’归处理子节点
        if (excelDto.getChildren() != null && !excelDto.getChildren().isEmpty()) {
            for (ProductModelExcelDto childDto : excelDto.getChildren()) {
                recursiveSaveProduct(childDto, currentId); // çˆ¶ID为当前节点ID
            }
        }
        // 4. ï¼ˆå¯é€‰ï¼‰å¤„理items数据(如果需要插入到关联表)
         if (excelDto.getItems() != null && !excelDto.getItems().isEmpty()) {
             for (ProductModelExcelItemDto item : excelDto.getItems()) {
                 // æ’å…¥item关联数据,关联currentId
                 // æž„建ProductModel实体
                 ProductModel productModel = new ProductModel();
                 productModel.setProductId(currentId); // å…³è”当前产品节点ID
                 productModel.setModel(item.getModel()); // ä»ŽItemDTO获取型号
                 productModel.setUnit(item.getUnit());   // ä»ŽItemDTO获取单位
                 productModel.setDrawingNumber(item.getDrawingNumber()); // å›¾çº¸ç¼–号
                 productModel.setProductType(item.getProductType());
                 // æ’å…¥product_model表
                 productModelMapper.insert(productModel);
             }
         }
    }
}
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductAndModelDto;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.mapper.ProductMapper;
@@ -15,12 +16,14 @@
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
@@ -57,6 +60,11 @@
    @Override
    public IPage<ProductModel> listPageProductModel(Page<ProductModel> page, ProductModel productModel) {
        return productModelMapper.listPageProductModel(page, productModel);
    }
    @Override
    public AjaxResult listPage(Page page, ProductAndModelDto productDto) {
        return AjaxResult.success(productModelMapper.listPage(page, productDto));
    }
@@ -116,14 +124,24 @@
    @Override
    public int delProductByIds(Long[] ids) {
        // 1. åˆ é™¤å­è¡¨ product_model ä¸­å…³è”的数据
        LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ProductModel::getProductId, ids);
        productModelMapper.delete(queryWrapper);
        for (Long id : ids) {
            LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ProductModel::getId, id);
            List<ProductModel> productModels = productModelMapper.selectList(queryWrapper);
            if(CollectionUtils.isNotEmpty(productModels)){
                List<ProductModel> productList = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
                        .eq(ProductModel::getProductId, productModels.get(0).getProductId()));
                if(CollectionUtils.isNotEmpty(productList) && productList.size() == 1){
                    // å½“只有一条数据时删除产品
                    productMapper.deleteBatchIds(productModels.stream()
                            .map(ProductModel::getProductId)
                            .collect(Collectors.toList()));
                }
                return productModelMapper.deleteBatchIds(Arrays.asList(ids));
        // 2. åˆ é™¤ä¸»è¡¨ product æ•°æ®
        int deleteCount = productMapper.deleteBatchIds(Arrays.asList(ids));
        return deleteCount;
            }
        }
        return 1;
    }
}
src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
@@ -11,7 +11,8 @@
    PRODUCTION_REPORT_STOCK_IN("2", "生产报工-入库"),
    PURCHASE_STOCK_IN("7", "采购-入库"),
    QUALITYINSPECT_STOCK_IN("6", "质检-合格入库"),
    DEFECTIVE_PASS("11", "不合格-让步放行");
    DEFECTIVE_PASS("11", "不合格-让步放行"),
    RETURN_HE_IN("14", "销售退货-合格入库");
    private final String code;
src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java
@@ -10,7 +10,8 @@
    DEFECTIVE_SCRAP("4", "不合格处理-报废"),
    PRODUCTION_SCRAP("5", "生产报工-报废"),
    CUSTOMIZATION_UNSTOCK_IN("9", "不合格自定义入库"),
    QUALITYINSPECT_UNSTOCK_IN("12", "质检-不合格入库");
    QUALITYINSPECT_UNSTOCK_IN("12", "质检-不合格入库"),
    RETURN_UNSTOCK_IN("15", "销售退货-不合格入库");
    private final String code;
src/main/java/com/ruoyi/common/utils/OrderUtils.java
@@ -2,6 +2,9 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.ruoyi.basic.dto.ProductModelExcelDto;
import com.ruoyi.basic.dto.ProductModelExcelItemDto;
import com.ruoyi.common.utils.uuid.UUID;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.stereotype.Component;
@@ -11,9 +14,8 @@
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author :yys
@@ -54,4 +56,304 @@
        // æ‹¼æŽ¥è®¢å•编号 preFix + æ—¶é—´ï¼ˆyyyyMMdd) + è®¢å•数量(001)
        return preFix + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1)) + "-" + new Date().getTime();
    }
    /**
     * å°†å¹³é¢åˆ—表转换为树形结构(含规格型号拆分功能)
     * @param flatList å¯¼å…¥çš„平面数据列表
     * @return æ ‘形结构的根节点列表
     */
    public static List<ProductModelExcelDto> buildTree(List<ProductModelExcelDto> flatList) {
        // 1. å‚数校验
        if (CollectionUtils.isEmpty(flatList)) {
            return new ArrayList<>();
        }
        // 2. é¢„处理:过滤无效数据 + è§„格型号拆分 + åˆå§‹åŒ–子节点列表
        List<ProductModelExcelDto> validList = preprocessData(flatList);
        if (CollectionUtils.isEmpty(validList)) {
            return new ArrayList<>();
        }
        // 3. æŒ‰å±‚级分组,便于逐层处理
        Map<Integer, List<ProductModelExcelDto>> levelGroupMap = groupByLevel(validList);
        // 4. æž„建节点映射(序号 -> èŠ‚ç‚¹ï¼‰ï¼Œæé«˜æŸ¥è¯¢æ•ˆçŽ‡
        Map<String, ProductModelExcelDto> nodeMap = buildNodeMap(validList);
        // 5. é€å±‚构建父子关系
        buildParentChildRelation(levelGroupMap, nodeMap);
        // 6. èŽ·å–æ ¹èŠ‚ç‚¹ï¼ˆå±‚çº§1)并排序
        List<ProductModelExcelDto> rootNodes = getRootNodes(levelGroupMap);
        sortTreeNodes(rootNodes);
        return rootNodes;
    }
    /**
     * æ•°æ®é¢„处理:
     * 1. è¿‡æ»¤æ— æ•ˆæ•°æ®
     * 2. è§„格型号拆分并填充到items集合
     * 3. åˆå§‹åŒ–子节点列表
     */
    private static List<ProductModelExcelDto> preprocessData(List<ProductModelExcelDto> flatList) {
        return flatList.stream()
                // è¿‡æ»¤æ ¸å¿ƒå­—段无效的节点
                .filter(node -> isValidNode(node))
                // å¤„理规格型号拆分和items填充
                .peek(node -> {
                    // æ‹†åˆ†è§„格型号并填充到items
                    splitModelAndFillItems(node);
                    // åˆå§‹åŒ–子节点列表(避免空指针)
                    if (node.getChildren() == null) {
                        node.setChildren(new ArrayList<>());
                    }
                })
                .collect(Collectors.toList());
    }
    /**
     * éªŒè¯èŠ‚ç‚¹æ˜¯å¦æœ‰æ•ˆï¼ˆæ ¸å¿ƒå­—æ®µæ ¡éªŒï¼‰
     */
    private static boolean isValidNode(ProductModelExcelDto node) {
        if (node == null) {
            return false;
        }
        // åºå·å¿…须有效(基础校验)
        if (!isValidSequence(node.getNumber())) {
            return false;
        }
        // äº§å“åç§°éžç©ºï¼ˆä¸šåŠ¡æ ¡éªŒï¼Œå¯æ ¹æ®å®žé™…éœ€æ±‚è°ƒæ•´ï¼‰
        return StringUtils.isNotBlank(node.getProductName());
    }
    /**
     * è§„格型号拆分并填充到items集合
     * @param node å¾…处理的节点
     */
    private static void splitModelAndFillItems(ProductModelExcelDto node) {
        // 1. èŽ·å–åŽŸå§‹è§„æ ¼åž‹å·
        String originalModel = node.getModel();
        List<ProductModelExcelItemDto> items = new ArrayList<>();
        if (StringUtils.isNotBlank(originalModel)) {
            // 2. æŒ‰"+"拆分规格型号(如果有多个),处理每个片段
            List<String> splitModels = Arrays.stream(originalModel.split("\\+"))
                    .map(String::trim)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.toList());
            // 3. ä¸ºæ¯ä¸ªè§„格创建Item(即使只有一个,也生成一个Item)
            items = splitModels.stream()
                    .map(model -> createExcelItem(node, model))
                    .collect(Collectors.toList());
        }
        // 4. å¡«å……到节点的items集合(覆盖原有数据)
        node.setItems(items);
    }
    /**
     * åˆ›å»ºProductModelExcelItemDto对象(继承原节点的公共属性)
     * @param node åŽŸèŠ‚ç‚¹
     * @param model æ‹†åˆ†åŽçš„规格型号
     * @return æž„建好的Item对象
     */
    private static ProductModelExcelItemDto createExcelItem(ProductModelExcelDto node, String model) {
        ProductModelExcelItemDto item = new ProductModelExcelItemDto();
        // 1. æ‹†åˆ†åŽçš„规格型号
        item.setModel(model);
        // 2. ç»§æ‰¿åŽŸèŠ‚ç‚¹çš„å•ä½ï¼ˆç©ºå€¼å¤„ç†ï¼‰
        item.setUnit(StringUtils.defaultIfBlank(node.getUnit(), ""));
        // 3. ç»§æ‰¿åŽŸèŠ‚ç‚¹çš„äº§å“ç±»åž‹ï¼ˆç©ºå€¼å¤„ç†ï¼Œé»˜è®¤0表示未知)
        item.setProductType(Optional.ofNullable(node.getProductType()).orElse(0));
        // 4. ç»§æ‰¿åŽŸèŠ‚ç‚¹çš„å›¾çº¸ç¼–å·ï¼ˆç©ºå€¼å¤„ç†ï¼‰
        item.setDrawingNumber(StringUtils.defaultIfBlank(node.getDrawingNumber(), ""));
        return item;
    }
    /**
     * éªŒè¯åºå·æ ¼å¼æ˜¯å¦æœ‰æ•ˆï¼ˆæ”¯æŒ1、1.1、1.1.1等格式)
     */
    private static boolean isValidSequence(String sequence) {
        if (StringUtils.isBlank(sequence)) {
            return false;
        }
        // æ­£åˆ™è¡¨è¾¾å¼ï¼šåŒ¹é…æ•°å­—开头,后续可跟.和数字
        return sequence.trim().matches("^\\d+(\\.\\d+)*$");
    }
    /**
     * æŒ‰èŠ‚ç‚¹å±‚çº§åˆ†ç»„
     */
    private static Map<Integer, List<ProductModelExcelDto>> groupByLevel(List<ProductModelExcelDto> validList) {
        return validList.stream()
                .collect(Collectors.groupingBy(
                        node -> getNodeLevel(node.getNumber()),  // è®¡ç®—层级
                        TreeMap::new,  // æŒ‰å±‚级升序排序
                        Collectors.toList()
                ));
    }
    /**
     * è®¡ç®—节点层级(序号中.的数量 + 1)
     * ä¾‹ï¼š1 -> 1级,1.1 -> 2级,1.1.1 -> 3级
     */
    private static int getNodeLevel(String sequence) {
        if (StringUtils.isBlank(sequence)) {
            return 0;
        }
        String trimmedSeq = sequence.trim();
        return trimmedSeq.split("\\.").length;
    }
    /**
     * æž„建节点映射表(序号 -> èŠ‚ç‚¹ï¼‰
     */
    private static Map<String, ProductModelExcelDto> buildNodeMap(List<ProductModelExcelDto> validList) {
        return validList.stream()
                .collect(Collectors.toMap(
                        node -> node.getNumber().trim(),  // é”®ï¼šåºå·ï¼ˆåŽ»ç©ºæ ¼ï¼‰
                        node -> node,                     // å€¼ï¼šèŠ‚ç‚¹å¯¹è±¡
                        (oldVal, newVal) -> oldVal        // å¤„理重复序号:保留第一个
                ));
    }
    /**
     * æž„建父子节点关系
     */
    private static void buildParentChildRelation(Map<Integer, List<ProductModelExcelDto>> levelGroupMap,
                                                 Map<String, ProductModelExcelDto> nodeMap) {
        // ä»Žå±‚级2开始处理(层级1是根节点,无父节点)
        for (Map.Entry<Integer, List<ProductModelExcelDto>> entry : levelGroupMap.entrySet()) {
            int currentLevel = entry.getKey();
            if (currentLevel <= 1) {
                continue;  // è·³è¿‡æ ¹èŠ‚ç‚¹å±‚çº§
            }
            List<ProductModelExcelDto> currentLevelNodes = entry.getValue();
            for (ProductModelExcelDto currentNode : currentLevelNodes) {
                // è®¡ç®—父节点序号
                String parentSequence = getParentSequence(currentNode.getNumber());
                if (StringUtils.isBlank(parentSequence)) {
                    continue;
                }
                // æŸ¥æ‰¾çˆ¶èŠ‚ç‚¹
                ProductModelExcelDto parentNode = nodeMap.get(parentSequence);
                if (parentNode != null && parentNode.getChildren() != null) {
                    // æ·»åŠ åˆ°çˆ¶èŠ‚ç‚¹çš„å­èŠ‚ç‚¹åˆ—è¡¨
                    parentNode.getChildren().add(currentNode);
                }
            }
        }
    }
    /**
     * æ ¹æ®å½“前节点序号获取父节点序号
     * ä¾‹ï¼š1.1.2 -> 1.1,1.2 -> 1,1 -> null
     */
    private static String getParentSequence(String currentSequence) {
        if (StringUtils.isBlank(currentSequence)) {
            return null;
        }
        String trimmedSeq = currentSequence.trim();
        String[] seqParts = trimmedSeq.split("\\.");
        if (seqParts.length <= 1) {
            return null;  // æ ¹èŠ‚ç‚¹æ— çˆ¶èŠ‚ç‚¹
        }
        // åŽ»æŽ‰æœ€åŽä¸€éƒ¨åˆ†ï¼Œæ‹¼æŽ¥çˆ¶èŠ‚ç‚¹åºå·
        String[] parentParts = Arrays.copyOfRange(seqParts, 0, seqParts.length - 1);
        return String.join(".", parentParts);
    }
    /**
     * èŽ·å–æ ¹èŠ‚ç‚¹åˆ—è¡¨ï¼ˆå±‚çº§1的节点)
     */
    private static List<ProductModelExcelDto> getRootNodes(Map<Integer, List<ProductModelExcelDto>> levelGroupMap) {
        return levelGroupMap.getOrDefault(1, new ArrayList<>());
    }
    /**
     * é€’归排序所有节点(按序号数字顺序)
     */
    private static void sortTreeNodes(List<ProductModelExcelDto> nodeList) {
        if (CollectionUtils.isEmpty(nodeList)) {
            return;
        }
        // æŽ’序当前层级节点
        nodeList.sort(Comparator.comparing(
                ProductModelExcelDto::getNumber,
                new SequenceComparator()  // è‡ªå®šä¹‰åºå·æ¯”较器
        ));
        // é€’归排序子节点
        for (ProductModelExcelDto node : nodeList) {
            sortTreeNodes(node.getChildren());
        }
    }
    /**
     * åºå·æ¯”较器:按数字顺序排序(支持1.10在1.2之后)
     */
    private static class SequenceComparator implements Comparator<String> {
        @Override
        public int compare(String seq1, String seq2) {
            if (StringUtils.isBlank(seq1) && StringUtils.isBlank(seq2)) {
                return 0;
            }
            if (StringUtils.isBlank(seq1)) {
                return -1;
            }
            if (StringUtils.isBlank(seq2)) {
                return 1;
            }
            // åˆ†å‰²åºå·ä¸ºæ•°å­—数组
            String[] parts1 = seq1.trim().split("\\.");
            String[] parts2 = seq2.trim().split("\\.");
            // é€æ®µæ¯”较数字
            int minLength = Math.min(parts1.length, parts2.length);
            for (int i = 0; i < minLength; i++) {
                int num1 = parseSequencePart(parts1[i]);
                int num2 = parseSequencePart(parts2[i]);
                if (num1 != num2) {
                    return num1 - num2;
                }
            }
            // é•¿åº¦ä¸åŒæ—¶ï¼ŒçŸ­çš„在前(如1.1在1.1.1之前)
            return parts1.length - parts2.length;
        }
        /**
         * è§£æžåºå·ç‰‡æ®µä¸ºæ•°å­—(处理异常情况)
         */
        private int parseSequencePart(String part) {
            try {
                return Integer.parseInt(part.trim());
            } catch (NumberFormatException e) {
                return 0;  // å¼‚常情况按0处理
            }
        }
    }
    /**
     * äº§å“ç±»åž‹æ˜ å°„(1=物料,2=产品,0=未知)
     */
    private static String mapProductType(Integer productType) {
        return Optional.ofNullable(productType)
                .map(type -> type == 1 ? "物料" : (type == 2 ? "产品" : "未知"))
                .orElse("未知");
    }
}
src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java
@@ -1,14 +1,16 @@
package com.ruoyi.procurementrecord.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.procurementrecord.service.ReturnManagementService;
import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
import com.ruoyi.sales.dto.SalesLedgerDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,48 +24,69 @@
 * @date : 2025/9/17 10:34
 */
@RestController
@Api(tags = "到货管理")
@Api(tags = "销售退货")
@RequestMapping("/returnManagement")
public class ReturnManagementController extends BaseController {
    @Autowired
    private ReturnManagementService returnManagementService;
    @Autowired
    private ReturnManagementMapper returnManagementMapper;
    private ReturnSaleProductService returnSaleProductService;
    @GetMapping("/listPage")
    @ApiOperation("到货管理-查询")
    public AjaxResult listPage(Page page, ReturnManagement returnManagement) {
        IPage<ReturnManagement> result = returnManagementService.listPage(page, returnManagement);
    @ApiOperation("销售退货-查询")
    public AjaxResult listPage(Page page, ReturnManagementDto returnManagement) {
        IPage<ReturnManagementDto> result = returnManagementService.listPage(page, returnManagement);
        return AjaxResult.success(result);
    }
    @PostMapping("/add")
    @ApiOperation("到货管理-添加")
    @ApiOperation("销售退货-添加")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult add(@RequestBody ReturnManagement returnManagement) {
        String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT");
        returnManagement.setReturnNo(rt);
        boolean result = returnManagementService.save(returnManagement);
        return result ? success() : error();
    public AjaxResult add(@RequestBody ReturnManagementDto returnManagementDto) {
        return returnManagementService.addReturnManagementDto(returnManagementDto) ? success() : error();
    }
    @PostMapping("/update")
    @ApiOperation("到货管理-修改")
    @ApiOperation("销售退货-修改")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult update(@RequestBody ReturnManagement returnManagement) {
        boolean result = returnManagementService.updateById(returnManagement);
        return result ? success() : error();
    public AjaxResult update(@RequestBody ReturnManagementDto returnManagementDto) {
        return returnManagementService.updateReturnManagementDto(returnManagementDto) ? success() : error();
    }
    @ApiOperation("销售退货-处理退货单")
    @GetMapping("/handle")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult handle(Long returnManagementId) {
        return returnManagementService.handle(returnManagementId) ? success() : error();
    }
    @DeleteMapping("/del")
    @ApiOperation("到货管理-删除")
    @ApiOperation("销售退货-删除")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult del(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return error("请选择至少一条数据");
        returnSaleProductService.remove(new QueryWrapper<ReturnSaleProduct>()
                .lambda()
                .in(ReturnSaleProduct::getReturnManagementId, ids));
        boolean result = returnManagementService.removeByIds(ids);
        return result ? success() : error();
    }
    @GetMapping("/getById")
    @ApiOperation("销售退货-根据id查询")
    public AjaxResult getById(Long returnManagementId) {
        ReturnManagementDto returnManagementDto = returnManagementService.getReturnManagementDtoById(returnManagementId);
        return success(returnManagementDto);
    }
    @GetMapping("/getByShippingId")
    @ApiOperation("销售退货-根据出库单查询销售订单以及产品信息")
    public AjaxResult getByShippingId(Long shippingId) {
        SalesLedgerDto salesLedgerDto = returnManagementService.getReturnManagementDtoByShippingIdId(shippingId);
        return success(salesLedgerDto);
    }
}
src/main/java/com/ruoyi/procurementrecord/dto/ReturnManagementDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.ruoyi.procurementrecord.dto;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/9/17 10:28
 */
@Data
public class ReturnManagementDto extends ReturnManagement {
    @ApiModelProperty(value = "客户名称")
    private String customerName;
    @ApiModelProperty(value = "销售单号")
    private String salesContractNo;
    @ApiModelProperty(value = "业务员")
    private String salesman;
    @ApiModelProperty("关联出库单号")
    private String shippingNo;
    @ApiModelProperty(value = "项目名称")
    private String projectName;
    @ApiModelProperty(value = "销售台账id")
    private Long salesLedgerId;
    @ApiModelProperty(value = "销售产品对象数组")
    private List<ReturnSaleProductDto> returnSaleProducts;
}
src/main/java/com/ruoyi/procurementrecord/dto/ReturnSaleProductDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.procurementrecord.dto;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ReturnSaleProductDto extends ReturnSaleProduct {
    private String productName;
    private String model;
    private String unit;
    //未退货数量
    private BigDecimal unQuantity;
    private BigDecimal totalReturnNum;
    // é€€è´§æ€»ä»·
    private BigDecimal price;
    // é€€è´§æ€»ä»·
    private BigDecimal taxInclusiveUnitPrice;
}
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import org.apache.ibatis.annotations.Param;
@@ -18,5 +19,7 @@
     * @param page
     * @return
     */
    IPage<ReturnManagement> listPage(Page page,@Param("req") ReturnManagement returnManagement);
    IPage<ReturnManagementDto> listPage(Page page, @Param("req") ReturnManagementDto returnManagement);
    ReturnManagementDto getReturnManagementDtoById(Long id);
}
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnSaleProductMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.procurementrecord.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * é€€è´§äº§å“è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:57:42
 */
@Mapper
public interface ReturnSaleProductMapper extends BaseMapper<ReturnSaleProduct> {
    List<ReturnSaleProductDto> listReturnSaleProductDto(@Param("returnManagementId") Long returnManagementId);
    List<ReturnSaleProductDto> listReturnSaleProduct(@Param("returnManagementId") Long returnManagementId);
}
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java
@@ -1,14 +1,16 @@
package com.ruoyi.procurementrecord.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
/**
 * @author :yys
@@ -29,44 +31,45 @@
    @ApiModelProperty(value = "退货单号")
    private String returnNo;
    @ApiModelProperty(value = "关联单号")
    private String relatedNo;
    @ApiModelProperty("客户id")
    private Long customerId;
    @ApiModelProperty(value = "退货类型")
    private String returnType;
    @ApiModelProperty("关联出库单号Id")
    private Long shippingId;
    @ApiModelProperty(value = "供应商名称")
    private String supplierName;
    @ApiModelProperty("项目id")
    private Long projectId;
    @ApiModelProperty(value = "退货原因")
    @ApiModelProperty("项目阶段")
    private String projectStage;
    @ApiModelProperty("制单人")
    private String maker;
    @ApiModelProperty("退货原因")
    private String returnReason;
    @ApiModelProperty(value = "退货状态")
    private String status;
    @ApiModelProperty("退款总额")
    private BigDecimal refundAmount;
    @ApiModelProperty(value = "备注")
    private String remark;
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty("制单时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime makeTime;
    @ApiModelProperty("结算人")
    private String settler;
    @ApiModelProperty("状态")
    private Integer status;
    @ApiModelProperty("创建时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty("更新时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnSaleProduct.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.procurementrecord.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * <p>
 * é€€è´§äº§å“è¡¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:57:42
 */
@Getter
@Setter
@TableName("return_sale_product")
@ApiModel(value = "ReturnSaleProduct对象", description = "退货产品表")
public class ReturnSaleProduct implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("产品规格id")
    private Long productModelId;
    @ApiModelProperty("主键id")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("退货单id")
    private Long returnManagementId;
    @ApiModelProperty("退货产品id")
    private Long returnSaleLedgerProductId;
    @ApiModelProperty("退货产品数量")
    private BigDecimal num;
    @ApiModelProperty("退货产品单价")
    private BigDecimal price;
    @ApiModelProperty("退货产品金额")
    private BigDecimal amount;
    @ApiModelProperty("退货状态 0 æœªé€€å›ž 1已退货")
    private Integer status;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("是否有质量问题(1-是 2-否)")
    private Integer isQuality;
}
src/main/java/com/ruoyi/procurementrecord/service/ReturnManagementService.java
@@ -3,7 +3,9 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.sales.dto.SalesLedgerDto;
/**
 * @author :yys
@@ -18,5 +20,15 @@
     * @param returnManagement
     * @return
     */
    IPage<ReturnManagement> listPage(Page page, ReturnManagement returnManagement);
    IPage<ReturnManagementDto> listPage(Page page, ReturnManagementDto returnManagement);
    boolean addReturnManagementDto(ReturnManagementDto returnManagementDto);
    boolean updateReturnManagementDto(ReturnManagementDto returnManagementDto);
    SalesLedgerDto getReturnManagementDtoByShippingIdId(Long shippingId);
    boolean handle(Long returnManagementId);
    ReturnManagementDto getReturnManagementDtoById(Long returnManagementId);
}
src/main/java/com/ruoyi/procurementrecord/service/ReturnSaleProductService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.procurementrecord.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import java.util.List;
/**
 * <p>
 * é€€è´§äº§å“è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:57:42
 */
public interface ReturnSaleProductService extends IService<ReturnSaleProduct> {
    List<ReturnSaleProductDto> listReturnSaleProductDto(Long returnManagementId);
    List<ReturnSaleProductDto> listReturnSaleProduct(Long returnManagementId);
}
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
@@ -3,12 +3,35 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.mapper.AccountExpenseMapper;
import com.ruoyi.account.pojo.AccountExpense;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.procurementrecord.service.ReturnManagementService;
import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.ShippingInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * @author :yys
@@ -21,9 +44,115 @@
    @Autowired
    private ReturnManagementMapper returnManagementMapper;
    @Autowired
    private ReturnSaleProductService returnSaleProductService;
    @Autowired
    private ShippingInfoService shippingInfoService;
    @Autowired
    private SalesLedgerMapper salesLedgerMapper;
//    @Autowired
//    private SalesRefundAmountOrderService salesRefundAmountOrderService;
    @Autowired
    private StockUtils stockUtils;
    @Autowired
    private AccountExpenseMapper accountExpenseMapper;
    @Override
    public IPage<ReturnManagement> listPage(Page page, ReturnManagement returnManagement) {
        IPage<ReturnManagement> returnManagementIPage =  returnManagementMapper.listPage(page, returnManagement);
    public IPage<ReturnManagementDto> listPage(Page page, ReturnManagementDto returnManagement) {
        IPage<ReturnManagementDto> returnManagementIPage = returnManagementMapper.listPage(page, returnManagement);
        return returnManagementIPage;
    }
    @Override
    public boolean addReturnManagementDto(ReturnManagementDto returnManagementDto) {
        String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT");
        returnManagementDto.setReturnNo(rt);
        save(returnManagementDto);
        for (ReturnSaleProduct returnSaleProduct : returnManagementDto.getReturnSaleProducts()) {
            returnSaleProduct.setReturnManagementId(returnManagementDto.getId());
            returnSaleProduct.setStatus(0);
            returnSaleProductService.save(returnSaleProduct);
        }
        return true;
    }
    @Override
    public boolean updateReturnManagementDto(ReturnManagementDto returnManagementDto) {
        List<ReturnSaleProduct> returnSaleProducts = new ArrayList<>();
        if (!CollectionUtils.isEmpty(returnManagementDto.getReturnSaleProducts())) {
            returnManagementDto.getReturnSaleProducts().stream().forEach(returnSaleProductDto -> {
                ReturnSaleProduct returnSaleProduct = new ReturnSaleProduct();
                BeanUtils.copyProperties(returnSaleProductDto, returnSaleProduct);
                returnSaleProducts.add(returnSaleProduct);
            });
        }
        returnSaleProductService.updateBatchById(returnSaleProducts);
        return updateById(returnManagementDto);
    }
    @Override
    public SalesLedgerDto getReturnManagementDtoByShippingIdId(Long shippingId) {
        ShippingInfo byId = shippingInfoService.getById(shippingId);
        SalesLedger salesLedger = salesLedgerMapper.selectById(byId.getSalesLedgerId());
        SalesLedgerDto salesLedgerDto = new SalesLedgerDto();
        BeanUtils.copyProperties(salesLedger, salesLedgerDto);
//        List<SalesLedgerProductDto> salesLedgerProductDtos = shippingInfoService.getReturnManagementDtoById(byId.getId());
//        salesLedgerDto.setProductDtoData(salesLedgerProductDtos);
         return salesLedgerDto;
    }
    @Override
    public boolean handle(Long returnManagementId) {
        ReturnManagement byId = this.getById(returnManagementId);
        List<ReturnSaleProductDto> list = returnSaleProductService.listReturnSaleProduct(returnManagementId);
        byId.setStatus(1);
        updateById(byId);
//        SalesRefundAmountOrderDto salesRefundAmountOrder = new SalesRefundAmountOrderDto();
//        salesRefundAmountOrder.setReturnManagementId(returnManagementId);
//        salesRefundAmountOrder.setStatus(0);
//        salesRefundAmountOrder.setCreateTime(byId.getCreateTime());
//        salesRefundAmountOrder.setCreateUserId(SecurityUtils.getUserId());
        BigDecimal bigDecimal = new BigDecimal(0);
        for (ReturnSaleProductDto returnSaleProduct : list) {
            bigDecimal = bigDecimal.add(returnSaleProduct.getPrice());
//            salesRefundAmountOrder.setRefundedAmount(new BigDecimal(0));
            // æ˜¯å¦æœ‰è´¨é‡é—®é¢˜
            if (returnSaleProduct.getIsQuality() == 1) {
                // æœ‰è´¨é‡é—®é¢˜ï¼Œå…¥ä¸åˆæ ¼åº“
                stockUtils.addUnStock(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInUnQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode(),returnSaleProduct.getId());
            }else{
                // æ— è´¨é‡é—®é¢˜ï¼Œå…¥åˆæ ¼åº“
                stockUtils.addStock(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode(),returnSaleProduct.getId());
            }
        }
//        salesRefundAmountOrder.setRefundAmount(bigDecimal);
//        salesRefundAmountOrder.setNotRefundedAmount(salesRefundAmountOrder.getRefundedAmount());
        // åˆ†æ‰¹é€€æ¬¾
//        salesRefundAmountOrderService.addSalesRefundAmountOrderDto(salesRefundAmountOrder);
        // å’Œè´¢åŠ¡è”åŠ¨ï¼Œæ–°å¢žæ”¯å‡º
        AccountExpense accountExpense = new AccountExpense();
        accountExpense.setBusinessType(3);
        accountExpense.setExpenseMoney(byId.getRefundAmount());
        accountExpense.setBusinessId(byId.getId());
        accountExpense.setExpenseDate(new Date());
        accountExpense.setExpenseMethod("3");
        accountExpense.setExpenseType("5");
        accountExpense.setExpenseDescribed("销售退货退款");
        accountExpense.setNote(byId.getReturnReason());
        accountExpense.setInputUser(SecurityUtils.getLoginUser().getNickName());
        accountExpense.setInputTime(new Date());
        accountExpenseMapper.insert(accountExpense);
        return true;
    }
    @Override
    public ReturnManagementDto getReturnManagementDtoById(Long returnManagementId) {
        ReturnManagementDto returnManagementDtoById = returnManagementMapper.getReturnManagementDtoById(returnManagementId);
        returnManagementDtoById.setReturnSaleProducts(returnSaleProductService.listReturnSaleProductDto(returnManagementId));
        return returnManagementDtoById;
    }
}
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnSaleProductServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.ruoyi.procurementrecord.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.mapper.ReturnSaleProductMapper;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * é€€è´§äº§å“è¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:57:42
 */
@Service
public class ReturnSaleProductServiceImpl extends ServiceImpl<ReturnSaleProductMapper, ReturnSaleProduct> implements ReturnSaleProductService {
    @Autowired
    private ReturnSaleProductMapper returnSaleProductMapper;
    @Override
    public List<ReturnSaleProductDto> listReturnSaleProductDto(Long returnManagementId) {
        return returnSaleProductMapper.listReturnSaleProductDto(returnManagementId);
    }
    @Override
    public List<ReturnSaleProductDto> listReturnSaleProduct(Long returnManagementId) {
        return returnSaleProductMapper.listReturnSaleProduct(returnManagementId);
    }
}
src/main/java/com/ruoyi/sales/controller/ShippingInfoDetailController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.ruoyi.sales.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.pojo.ShippingInfoDetail;
import com.ruoyi.sales.service.ShippingInfoDetailService;
import com.ruoyi.sales.service.ShippingInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * å‘货明细表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-16 01:27:40
 */
@Api(tags = "发货明细表")
@RestController
@RequestMapping("/shippingInfoDetail")
public class ShippingInfoDetailController {
    @Autowired
    private ShippingInfoDetailService shippingInfoDetailService;
    @ApiOperation("分页查询")
    @GetMapping("/listPage")
    public AjaxResult listPage(Page page, ShippingInfoDetail shippingInfoDetail){
        return AjaxResult.success(shippingInfoDetailService.listPage(page, shippingInfoDetail));
    }
    @ApiOperation("新增")
    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
    @Log(title = "发货明细表", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody ShippingInfoDetail shippingInfoDetail) throws Exception{
        return shippingInfoDetailService.add(shippingInfoDetail);
    }
    @ApiOperation("修改")
    @PostMapping("/update")
    @Transactional(rollbackFor = Exception.class)
    @Log(title = "发货明细表", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody ShippingInfoDetail shippingInfoDetail){
        return shippingInfoDetailService.updateShippingInfoDetail(shippingInfoDetail);
    }
    @ApiOperation("删除")
    @DeleteMapping("/delete")
    @Transactional(rollbackFor = Exception.class)
    @Log(title = "发货明细表", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids){
        return shippingInfoDetailService.delete(ids);
    }
}
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
@@ -7,6 +7,7 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -29,4 +30,14 @@
    @TableField(exist = false)
    private List<CommonFile> commonFileList;
    /**
     * å·²å‘货数量
     */
    private BigDecimal shippingSuccessTotal = BigDecimal.ZERO;
    /**
     * å¾…发货数量
     */
    private BigDecimal waitShippingTotal = BigDecimal.ZERO;
}
src/main/java/com/ruoyi/sales/mapper/ShippingInfoDetailMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.sales.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.sales.pojo.ShippingInfoDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 * å‘货明细表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-16 01:27:40
 */
@Mapper
public interface ShippingInfoDetailMapper extends BaseMapper<ShippingInfoDetail> {
    IPage<ShippingInfoDetail> listPage(Page page,@Param("req") ShippingInfoDetail shippingInfoDetail);
}
src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
@@ -7,6 +7,7 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
@@ -67,6 +68,9 @@
    @Excel(name = "发货车牌号")
    private String shippingCarNumber;
    @ApiModelProperty(value = "发货总数量")
    private BigDecimal shippingTotal;
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
src/main/java/com/ruoyi/sales/pojo/ShippingInfoDetail.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
package com.ruoyi.sales.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
 * <p>
 * å‘货明细表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-16 01:27:40
 */
@Getter
@Setter
@TableName("shipping_info_detail")
@ApiModel(value = "ShippingInfoDetail对象", description = "发货明细表")
public class ShippingInfoDetail implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableField(exist = false)
    private List<String> tempFileIds;
    @TableField(exist = false)
    private List<CommonFile> commonFileList;
    @ApiModelProperty("发货总量")
    @TableField(exist = false)
    private BigDecimal shippingTotal;
    @ApiModelProperty("id")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("销售台账id")
    private Long salesLedgerId;
    @ApiModelProperty("发货信息id")
    private Long shippingInfoId;
    @ApiModelProperty("发货日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",shape = JsonFormat.Shape.STRING)
    private LocalDateTime shippingDate;
    @ApiModelProperty("发货车牌号")
    private String shippingCarNumber;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    @ApiModelProperty("销售报价产品表id")
    private Integer salesLedgerProductId;
    @ApiModelProperty("发货编号")
    private String shippingNo;
    @ApiModelProperty("快递单号")
    private String expressNumber;
    @ApiModelProperty("快递公司")
    private String expressCompany;
    @ApiModelProperty("发货类型")
    private String type;
    @ApiModelProperty("状态")
    private String status;
    @ApiModelProperty("发货数量")
    private BigDecimal shippingNum;
}
src/main/java/com/ruoyi/sales/service/ShippingInfoDetailService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.pojo.ShippingInfoDetail;
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.IOException;
import java.util.List;
/**
 * <p>
 * å‘货明细表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-16 01:27:40
 */
public interface ShippingInfoDetailService extends IService<ShippingInfoDetail> {
    IPage<ShippingInfoDetail> listPage(Page page, ShippingInfoDetail shippingInfoDetail);
    AjaxResult updateShippingInfoDetail(ShippingInfoDetail shippingInfoDetail);
    AjaxResult add(ShippingInfoDetail shippingInfoDetail) throws IOException;
    AjaxResult delete(List<Long> ids);
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -592,9 +592,12 @@
            // 3. æ–°å¢žæˆ–更新主表
            if (salesLedger.getId() == null) {
                String contractNo = generateSalesContractNo();
                salesLedger.setSalesContractNo(contractNo);
                salesLedger.setSalesContractNo(salesLedgerDto.getSalesContractNo() == null ? contractNo : salesLedgerDto.getSalesContractNo());
                salesLedgerMapper.insert(salesLedger);
            } else {
                if(StringUtils.isNotEmpty(salesLedgerDto.getSalesContractNo())){
                    salesLedger.setSalesContractNo(salesLedgerDto.getSalesContractNo());
                }
                salesLedgerMapper.updateById(salesLedger);
            }
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoDetailServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.other.service.impl.TempFileServiceImpl;
import com.ruoyi.sales.dto.ShippingInfoDto;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.pojo.ShippingInfoDetail;
import com.ruoyi.sales.mapper.ShippingInfoDetailMapper;
import com.ruoyi.sales.service.ShippingInfoDetailService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.sales.service.ShippingInfoService;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
 * <p>
 * å‘货明细表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-16 01:27:40
 */
@Service
public class ShippingInfoDetailServiceImpl extends ServiceImpl<ShippingInfoDetailMapper, ShippingInfoDetail> implements ShippingInfoDetailService {
    @Autowired
    private ShippingInfoDetailMapper shippingInfoDetailMapper;
    @Autowired
    private ShippingInfoService shippingInfoService;
    @Autowired
    private TempFileServiceImpl tempFileService;
    @Autowired
    private CommonFileServiceImpl commonFileService;
    @Override
    public IPage<ShippingInfoDetail> listPage(Page page, ShippingInfoDetail shippingInfoDetail) {
        IPage<ShippingInfoDetail> shippingInfoDetailIPage = shippingInfoDetailMapper.listPage(page, shippingInfoDetail);
        shippingInfoDetailIPage.getRecords().forEach(item ->{
            item.setCommonFileList(commonFileService.getFileListByBusinessId(item.getId(), FileNameType.SHIP.getValue()));
        });
        return shippingInfoDetailIPage;
    }
    @Override
    public AjaxResult updateShippingInfoDetail(ShippingInfoDetail shippingInfoDetail) {
        return AjaxResult.success(shippingInfoDetailMapper.updateById(shippingInfoDetail));
    }
    @Override
    public AjaxResult add(ShippingInfoDetail shippingInfoDetail) throws IOException {
        BigDecimal shippingSuccessTotal = getShippingSuccessTotal(shippingInfoDetail.getShippingInfoId());
        BigDecimal add = shippingSuccessTotal.add(shippingInfoDetail.getShippingNum());
        shippingInfoDetailMapper.insert(shippingInfoDetail);
        ShippingInfoDto shippingInfo = new ShippingInfoDto();
        BeanUtils.copyProperties(shippingInfoDetail, shippingInfo);
        shippingInfo.setId(shippingInfoDetail.getShippingInfoId());
        // åˆ¤æ–­æ˜¯ä¸æ˜¯æœ€åŽä¸€æ‰¹è´§
        if (add.compareTo(shippingInfoDetail.getShippingTotal()) == 0) {
            shippingInfo.setStatus("已发货");
        }else{
            shippingInfo.setStatus("发货中");
        }
        shippingInfoService.deductStock(shippingInfo);
        // è¿ç§»æ–‡ä»¶
        if(CollectionUtils.isNotEmpty(shippingInfoDetail.getTempFileIds())){
            tempFileService.migrateTempFilesToFormal(shippingInfoDetail.getId(), shippingInfoDetail.getTempFileIds(), FileNameType.SHIP.getValue());
        }
        return AjaxResult.success("发货成功");
    }
    @Override
    public AjaxResult delete(List<Long> ids) {
        boolean b = this.removeByIds(ids);
        // åˆ é™¤é™„ä»¶
        commonFileService.deleteByBusinessIds(ids, FileNameType.SHIP.getValue());
        return b ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
    }
    /**
     * é€šè¿‡å‘货信息id获取已发货数量
     */
    public BigDecimal getShippingSuccessTotal(Long shippingInfoId) {
        List<ShippingInfoDetail> shippingInfoDetails = shippingInfoDetailMapper.selectList(new LambdaQueryWrapper<ShippingInfoDetail>()
                .eq(ShippingInfoDetail::getShippingInfoId, shippingInfoId));
        return Optional.ofNullable(shippingInfoDetails)
                .orElse(Collections.emptyList())
                .stream()
                .filter(Objects::nonNull)
                .map(ShippingInfoDetail::getShippingNum)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -12,9 +12,12 @@
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.sales.dto.ShippingInfoDto;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.ShippingInfoDetailMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.pojo.ShippingInfoDetail;
import com.ruoyi.sales.service.ShippingInfoDetailService;
import com.ruoyi.sales.service.ShippingInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
@@ -22,8 +25,12 @@
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
 * @author :yys
@@ -37,8 +44,6 @@
    private ShippingInfoMapper shippingInfoMapper;
    @Autowired
    private TempFileServiceImpl tempFileService;
    @Autowired
    private SalesLedgerProductMapper salesLedgerProductMapper;
    @Autowired
    private StockUtils stockUtils;
@@ -48,11 +53,29 @@
    @Autowired
    private ApproveProcessServiceImpl approveProcessService;
    @Autowired
    private ShippingInfoDetailMapper shippingInfoDetailMapper;
    @Autowired
    private ShippingInfoDetailService shippingInfoDetailService;
    @Override
    public IPage<ShippingInfoDto> listPage(Page page, ShippingInfo req) {
        IPage<ShippingInfoDto> listPage = shippingInfoMapper.listPage(page, req);
        listPage.getRecords().forEach(item ->{
            item.setCommonFileList(commonFileService.getFileListByBusinessId(item.getId(), FileNameType.SHIP.getValue()));
            List<ShippingInfoDetail> shippingInfoDetails = shippingInfoDetailMapper.selectList(new LambdaQueryWrapper<ShippingInfoDetail>()
                    .eq(ShippingInfoDetail::getShippingInfoId, item.getId()));
            // æ ¸å¿ƒä¼˜åŒ–:层层防护空指针 + å¤„理空集合场景
            item.setShippingSuccessTotal(Optional.ofNullable(shippingInfoDetails)
                    .orElse(Collections.emptyList())
                    .stream()
                    .filter(Objects::nonNull)
                    .map(ShippingInfoDetail::getShippingNum)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add)); // ç”¨Lambda替代方法引用
            item.setWaitShippingTotal(item.getShippingTotal().subtract(item.getShippingSuccessTotal()));
        });
        return listPage;
    }
@@ -64,20 +87,13 @@
            throw new RuntimeException("发货信息不存在");
        }
        //扣减库存
        if(!"已发货".equals(byId.getStatus())){
            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(byId.getSalesLedgerProductId());
            stockUtils.substractStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId());
        }
        SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(byId.getSalesLedgerProductId());
        stockUtils.substractStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId());
        byId.setExpressNumber(req.getExpressNumber());
        byId.setExpressCompany(req.getExpressCompany());
        byId.setStatus("已发货");
        byId.setStatus(req.getStatus());
        byId.setShippingCarNumber(req.getShippingCarNumber());
        boolean update = this.updateById(byId);
        // è¿ç§»æ–‡ä»¶
        if(CollectionUtils.isNotEmpty(req.getTempFileIds())){
            tempFileService.migrateTempFilesToFormal(req.getId(), req.getTempFileIds(), FileNameType.SHIP.getValue());
        }
        return update ;
        return this.updateById(byId);
    }
    @Override
@@ -85,8 +101,12 @@
        List<ShippingInfo> shippingInfos = shippingInfoMapper.selectList(new LambdaQueryWrapper<ShippingInfo>()
                .in(ShippingInfo::getId, ids));
        if(CollectionUtils.isEmpty(shippingInfos)) return false;
        // åˆ é™¤é™„ä»¶
        commonFileService.deleteByBusinessIds(ids, FileNameType.SHIP.getValue());
        // åˆ é™¤å­è¡¨
        List<ShippingInfoDetail> shippingInfoDetails = shippingInfoDetailMapper.selectList(new LambdaQueryWrapper<ShippingInfoDetail>()
                .in(ShippingInfoDetail::getShippingInfoId, ids));
        if(CollectionUtils.isNotEmpty(shippingInfoDetails)){
            shippingInfoDetailService.delete(shippingInfoDetails.stream().map(ShippingInfoDetail::getId).collect(Collectors.toList()));
        }
        // æ‰£å·²å‘货库存
        for (ShippingInfo shippingInfo : shippingInfos) {
            if("已发货".equals(shippingInfo.getStatus())) {
src/main/resources/mapper/basic/ProductModelMapper.xml
@@ -106,5 +106,34 @@
        left join product p on p.id = pm.product_id
        order by p.id,pm.id desc
    </select>
    <select id="listPage" resultType="com.ruoyi.basic.dto.ProductAndModelDto">
        select pm.id as id,
               p.id as productId,
               p.product_name as productName ,
               pm.model as  model,
               pm.unit as unit,
               pm.drawing_number as drawingNumber,
               pm.product_type as productType
        from product_model pm
                 left join product p on p.id = pm.product_id
        <where>
            <if test="req.productName != null and req.productName != ''">
                and p.product_name like concat('%',#{req.productName},'%')
            </if>
            <if test="req.model != null and req.model != ''">
                and pm.model like concat('%',#{req.model},'%')
            </if>
            <if test="req.unit != null and req.unit != ''">
                and pm.unit like concat('%',#{req.unit},'%')
            </if>
            <if test="req.drawingNumber != null and req.drawingNumber != ''">
                and pm.drawing_number like concat('%',#{req.drawingNumber},'%')
            </if>
            <if test="req.productType != null and req.productType != ''">
                and pm.product_type = #{req.productType}
            </if>
        </where>
        order by pm.id desc
    </select>
</mapper>
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
@@ -2,15 +2,55 @@
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.procurementrecord.mapper.ReturnManagementMapper">
    <select id="listPage" resultType="com.ruoyi.procurementrecord.pojo.ReturnManagement">
        select * from return_management
    <select id="listPage" resultType="com.ruoyi.procurementrecord.dto.ReturnManagementDto">
        select rm.*,
               c.customer_name,
               si.shipping_no,
               sl.project_name,
               sl.sales_contract_no,
               sl.salesman
        from return_management rm
                 left join shipping_info si on rm.shipping_id = si.id
                 left join customer c on rm.customer_id = c.id
                 left join sales_ledger sl on si.sales_ledger_id = sl.id
        <where>
            <if test="req.returnNo != null and req.returnNo != ''">
                and return_no like concat('%',#{req.returnNo},'%')
                and rm.return_no like concat('%',#{req.returnNo},'%')
            </if>
            <if test="req.returnType != null and req.returnType != ''">
                and return_type = #{req.returnType}
            <if test="req.customerName != null and req.customerName != ''">
                and c.customer_name like concat('%',#{req.customerName},'%')
            </if>
            <if test="req.projectName != null and req.projectName != ''">
                and sl.project_name like concat('%',#{req.projectName},'%')
            </if>
            <if test="req.salesman != null and req.salesman != ''">
                and sl.salesman like concat('%',#{req.salesman},'%')
            </if>
            <if test="req.shippingNo != null and req.shippingNo != ''">
                and si.shipping_no like concat('%',#{req.shippingNo},'%')
            </if>
            <if test="req.projectStage != null and req.projectStage != ''">
                and rm.project_stage like concat('%',#{req.projectStage},'%')
            </if>
            <if test="req.maker != null and req.maker != ''">
                and rm.maker like concat('%',#{req.maker},'%')
            </if>
            <if test="req.salesContractNo != null and req.salesContractNo != ''">
                and sl.sales_contract_no like concat('%',#{req.salesContractNo},'%')
            </if>
        </where>
    </select>
    <select id="getReturnManagementDtoById" resultType="com.ruoyi.procurementrecord.dto.ReturnManagementDto">
     select rm.*,
               c.customer_name,
               si.shipping_no,
               sl.project_name,
               sl.sales_contract_no,
               sl.salesman
        from return_management rm
                 left join shipping_info si on rm.shipping_id = si.id
                 left join customer c on rm.customer_id = c.id
                 left join sales_ledger sl on si.sales_ledger_id = sl.id
        where rm.id = #{id}
    </select>
</mapper>
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.procurementrecord.mapper.ReturnSaleProductMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.procurementrecord.pojo.ReturnSaleProduct">
        <id column="id" property="id" />
        <result column="return_management_id" property="returnManagementId" />
        <result column="return_sale_ledger_product_id" property="returnSaleLedgerProductId" />
        <result column="num" property="num" />
        <result column="status" property="status" />
    </resultMap>
    <select id="listReturnSaleProductDto" resultType="com.ruoyi.procurementrecord.dto.ReturnSaleProductDto">
        SELECT slp.product_category                                         as product_name,
               slp.specification_model                                      as model,
               slp.unit                                      as unit,
               rsp.*,
               GREATEST(slp.quantity - COALESCE(rs.total_return_num, 0), 0) AS un_quantity,
               COALESCE(rs.total_return_num, 0)                             AS total_return_num
        FROM return_sale_product rsp
                 LEFT JOIN return_management rm ON rm.id = rsp.return_management_id
                 LEFT JOIN shipping_info si ON si.id = rm.shipping_id
                 LEFT JOIN sales_ledger_product slp ON si.sales_ledger_product_id = slp.id and slp.type = 1
                 LEFT JOIN (SELECT return_sale_ledger_product_id,
                                   SUM(num) AS total_return_num
                            FROM return_sale_product
                            WHERE 1 = 1 and return_management_id != #{returnManagementId}
                            GROUP BY return_sale_ledger_product_id) rs ON rs.return_sale_ledger_product_id = slp.id
        where rm.id =#{returnManagementId}
    </select>
    <select id="listReturnSaleProduct" resultType="com.ruoyi.procurementrecord.dto.ReturnSaleProductDto">
        select rsp.*,slp.tax_inclusive_unit_price ,slp.tax_inclusive_total_price*rsp.num as price
        from return_sale_product rsp
                 left join sales_ledger_product slp on slp.id = rsp.return_sale_ledger_product_id
        where rsp.return_management_id = #{returnManagementId}
    </select>
</mapper>
src/main/resources/mapper/sales/ShippingInfoDetailMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sales.mapper.ShippingInfoDetailMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.sales.pojo.ShippingInfoDetail">
        <id column="id" property="id" />
        <result column="sales_ledger_id" property="salesLedgerId" />
        <result column="shipping_info_id" property="shippingInfoId" />
        <result column="shipping_date" property="shippingDate" />
        <result column="shipping_car_number" property="shippingCarNumber" />
        <result column="create_time" property="createTime" />
        <result column="create_user" property="createUser" />
        <result column="update_time" property="updateTime" />
        <result column="update_user" property="updateUser" />
        <result column="sales_ledger_product_id" property="salesLedgerProductId" />
        <result column="shipping_no" property="shippingNo" />
        <result column="express_number" property="expressNumber" />
        <result column="express_company" property="expressCompany" />
        <result column="type" property="type" />
        <result column="status" property="status" />
        <result column="shipping_num" property="shippingNum" />
    </resultMap>
    <select id="listPage" resultType="com.ruoyi.sales.pojo.ShippingInfoDetail">
        SELECT
        *
        FROM shipping_info_detail
        <where>
            <if test="req.shippingInfoId != null">
                AND shipping_info_id = #{req.shippingInfoId}
            </if>
        </where>
    </select>
</mapper>
src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -19,7 +19,8 @@
        s.update_user,
        s.tenant_id,
        sl.sales_contract_no,
        sl.customer_name
        sl.customer_name,
        s.shipping_total AS shipping_total
        FROM shipping_info s
        LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
        WHERE 1=1