buhuazhen
2026-03-21 a45c4fb6232faf2f23b7b2ba45416f11a5390d77
feat(purchase): 完善采购退货模块及关联退货数量统计

- 新增采购退货相关实体类及DTO,支持退货数量和退货金额字段
- 扩展采购退货服务,实现退货订单详情获取及财务收入台账录入
- 采购退货Mapper新增查询退货数量聚合方法
- 采购台账接口及实现类新增根据ID列表批量查询产品退货数量并计算可用数
- 销售台账产品Controller中增加退货数量及可用库存的返回和计算
- 采购台账查询中支持供应商ID及审批状态过滤
- 优化销售台账产品相关SQL,关联退货数量及退货金额字段
- 统一采购退货Controller增加根据ID查询退货详情接口
- 补充退货相关字段到采购退货实体,完善时间格式及用户信息字段
- 新增简单聚合DTO用于退货数量统计
- 添加枚举项支持采购退货出库类型
- 调整部分代码格式及规范,删除冗余代码,完善注释和空格布局
已添加2个文件
已修改18个文件
471 ■■■■ 文件已修改
src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/ProcurementBusinessSummaryDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderDto.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/SimpleReturnOrderGroupDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrderProductsMapper.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 195 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrderProductsMapper.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
@@ -8,6 +8,7 @@
    CUSTOMIZATION_STOCK_OUT("1", "合格自定义出库"),
    PRODUCTION_REPORT_STOCK_OUT("3", "生产报工-出库"),
    SALE_STOCK_OUT("8", "销售-出库"),
    PURCHASE_RETURN_STOCK_OUT("9", "采购退货"),
    SALE_SHIP_STOCK_OUT("13", "销售-发货出库");
    private final String code;
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java
@@ -46,4 +46,12 @@
        }
        return AjaxResult.success(purchaseReturnOrdersService.add(purchaseReturnOrderDto));
    }
    @GetMapping("/selectById/{id}")
    public AjaxResult selectById(@PathVariable Long id) {
        return AjaxResult.success(purchaseReturnOrdersService.getPurchaseReturnOrderDtoById(id));
    }
}
src/main/java/com/ruoyi/purchase/dto/ProcurementBusinessSummaryDto.java
@@ -82,4 +82,16 @@
    @Excel(name = "录入日期", width = 30, dateFormat = "yyyy-MM-dd")
    private Date entryDate;
    /**
     * é€€è´§æ•°é‡
     */
    @Excel(name = "退货数量")
    private BigDecimal returnQuantity;
    /**
     * é€€è´§é‡‘额
     */
    @Excel(name = "退货金额")
    private BigDecimal returnAmount;
}
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderDto.java
@@ -11,6 +11,7 @@
    // æ˜¯å¦ä½¿ç”¨ç³»ç»Ÿå•号
    private Boolean isDefaultNo;
    private String supplierName;
    private List<PurchaseReturnOrderProductsDto> purchaseReturnOrderProductsDtos;
}
src/main/java/com/ruoyi/purchase/dto/SimpleReturnOrderGroupDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.purchase.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * @author buhuazhen
 * @date 2026/3/21
 * @email 3038525872@qq.com
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SimpleReturnOrderGroupDto implements Serializable {
    private Long salesLedgerProductId;
    private BigDecimal sumReturnQuantity;
}
src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrderProductsMapper.java
@@ -1,8 +1,13 @@
package com.ruoyi.purchase.mapper;
import com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto;
import com.ruoyi.purchase.pojo.PurchaseReturnOrderProducts;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
 * <p>
@@ -14,5 +19,5 @@
 */
@Mapper
public interface PurchaseReturnOrderProductsMapper extends BaseMapper<PurchaseReturnOrderProducts> {
    List<SimpleReturnOrderGroupDto> getReturnOrderGroupListByProductIds(@NotNull @Param("productIds") List<Long> productIds);
}
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java
@@ -13,8 +13,10 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.models.auth.In;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
/**
 * <p>
@@ -57,8 +59,14 @@
    @ApiModelProperty("制单人id")
    private Long preparedUserId;
    @ApiModelProperty("制单人名称")
    private String preparedUserName;
    @ApiModelProperty("退料人id")
    private Long returnUserId;
    @ApiModelProperty("退料人名称")
    private String returnUserName;
    @ApiModelProperty("采购订单id")
    private Long purchaseLedgerId;
@@ -77,9 +85,33 @@
    @ApiModelProperty("录入时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @ApiModelProperty("收入类型")
    @TableField(value = "income_type")
    private Integer incomeType;
    /**
     *
     */
    @TableField(value = "create_user",fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(value = "update_user",fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @TableField(value = "create_user_name", fill = FieldFill.INSERT)
    private String createUserName;
    @TableField(value = "update_user_name", fill = FieldFill.INSERT_UPDATE)
    private String updateUserName;
}
src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java
@@ -5,7 +5,10 @@
import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo;
import com.ruoyi.purchase.vo.PurchaseReturnOrderVo;
import javax.validation.constraints.NotNull;
/**
 * <p>
@@ -19,4 +22,7 @@
    IPage<PurchaseReturnOrderVo> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto);
    Boolean add(PurchaseReturnOrderDto purchaseReturnOrderDto);
    PurchaseReturnDetailsVo getPurchaseReturnOrderDtoById(@NotNull Long id);
}
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -159,6 +159,12 @@
        if (StringUtils.isNotBlank(purchaseLedger.getPurchaseContractNumber())) {
            queryWrapper.like(PurchaseLedger::getPurchaseContractNumber, purchaseLedger.getPurchaseContractNumber());
        }
        if(purchaseLedger.getSupplierId()!=null){
            queryWrapper.eq(PurchaseLedger::getSupplierId, purchaseLedger.getSupplierId());
        }
        if (purchaseLedger.getApprovalStatus() != null) {
            queryWrapper.eq(PurchaseLedger::getApprovalStatus, purchaseLedger.getApprovalStatus());
        }
        return purchaseLedgerMapper.selectList(queryWrapper);
    }
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java
@@ -1,22 +1,41 @@
package com.ruoyi.purchase.service.impl;
import cn.hutool.core.bean.BeanUtil;
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.pojo.AccountIncome;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
import com.ruoyi.purchase.dto.PurchaseReturnOrderProductsDto;
import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.purchase.pojo.PurchaseReturnOrderProducts;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo;
import com.ruoyi.purchase.vo.PurchaseReturnOrderVo;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * <p>
 *  æœåŠ¡å®žçŽ°ç±»
 * æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
@@ -29,6 +48,12 @@
    @Autowired
    private PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
    @Autowired
    private ISalesLedgerService salesLedgerService;
    @Resource
    private AccountIncomeService accountIncomeService;
    @Override
    public IPage<PurchaseReturnOrderVo> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto) {
        return purchaseReturnOrdersMapper.listPage(page, purchaseReturnOrderDto);
@@ -38,14 +63,58 @@
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(PurchaseReturnOrderDto purchaseReturnOrderDto) {
        this.save(purchaseReturnOrderDto);
        if (!purchaseReturnOrderDto.getPurchaseReturnOrderProductsDtos().isEmpty()) {
            for (PurchaseReturnOrderProductsDto purchaseReturnOrderProductsDto :purchaseReturnOrderDto.getPurchaseReturnOrderProductsDtos()) {
            for (PurchaseReturnOrderProductsDto purchaseReturnOrderProductsDto : purchaseReturnOrderDto.getPurchaseReturnOrderProductsDtos()) {
                purchaseReturnOrderProductsDto.setSalesLedgerProductId(purchaseReturnOrderProductsDto.getSalesLedgerProductId());
                purchaseReturnOrderProductsDto.setPurchaseReturnOrderId(purchaseReturnOrderDto.getId());
                purchaseReturnOrderProductsDto.setReturnQuantity(purchaseReturnOrderProductsDto.getReturnQuantity());
                // è¿™é‡Œä¸ºæ–°å¢žå› æ­¤id为null
                purchaseReturnOrderProductsDto.setId(null);
                purchaseReturnOrderProductsMapper.insert(purchaseReturnOrderProductsDto);
            }
        }else {
            throw new RuntimeException("请选择退货商品");
        }
        // å¯¹è´¢åŠ¡ç®¡ç†å°è´¦æ·»åŠ ä¸€ç¬”è¿›è´¦æ”¶å…¥.
        AccountIncome accountIncome = new AccountIncome();
        LoginUser loginUser = SecurityUtils.getLoginUser();
        accountIncome.setInputUser(loginUser.getUsername());
        accountIncome.setIncomeMoney(purchaseReturnOrderDto.getTotalAmount());
        accountIncome.setIncomeDate(DateUtils.toDate(purchaseReturnOrderDto.getPreparedAt()));
        accountIncome.setInputTime(DateUtils.toDate(purchaseReturnOrderDto.getPreparedAt()));
        accountIncome.setBusinessId(purchaseReturnOrderDto.getId());
        accountIncome.setBusinessType(1);
        accountIncome.setIncomeType("4");
        accountIncome.setCustomerName(purchaseReturnOrderDto.getSupplierName());
        accountIncome.setIncomeDescribed(purchaseReturnOrderDto.getRemark());
        accountIncome.setIncomeMethod(String.valueOf(purchaseReturnOrderDto.getIncomeType()));
        accountIncomeService.save(accountIncome);
        return true;
    }
    @Override
    public PurchaseReturnDetailsVo getPurchaseReturnOrderDtoById(Long id) {
        PurchaseReturnOrders purchaseReturnOrders = purchaseReturnOrdersMapper.selectById(id);
        PurchaseReturnDetailsVo purchaseReturnOrderDto = BeanUtil.copyProperties(purchaseReturnOrders, PurchaseReturnDetailsVo.class);
        // æŸ¥è¯¢å‡ºä»–具体对应的退货
        LambdaQueryWrapper<PurchaseReturnOrderProducts> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PurchaseReturnOrderProducts::getPurchaseReturnOrderId, purchaseReturnOrders.getId());
        List<PurchaseReturnOrderProducts> purchaseReturnOrderProducts = purchaseReturnOrderProductsMapper.selectList(queryWrapper);
        List<PurchaseReturnDetailsVo.PurchaseReturnOrderProductsDetailVo> purchaseReturnOrderProductsDetailVos = BeanUtil.copyToList(purchaseReturnOrderProducts, PurchaseReturnDetailsVo.PurchaseReturnOrderProductsDetailVo.class);
        // æŸ¥è¯¢å‡ºå¯¹åº”的商品信息
        List<Long> productIds = purchaseReturnOrderProductsDetailVos.stream().map(PurchaseReturnDetailsVo.PurchaseReturnOrderProductsDetailVo::getSalesLedgerProductId).distinct().filter(Objects::nonNull).collect(Collectors.toList());
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerService.getSalesLedgerProductListByIds(productIds, SaleEnum.PURCHASE);
        Map<Long, SalesLedgerProduct> productmap = salesLedgerProducts.stream().collect(Collectors.toMap(SalesLedgerProduct::getId, product -> product));
        purchaseReturnOrderProductsDetailVos.forEach(purchaseReturnOrderProductsDetailVo -> {
            purchaseReturnOrderProductsDetailVo.setSalesLedgerProduct(productmap.get(purchaseReturnOrderProductsDetailVo.getSalesLedgerProductId()));
        });
        purchaseReturnOrderDto.setPurchaseReturnOrderProductsDetailVoList(purchaseReturnOrderProductsDetailVos);
        return purchaseReturnOrderDto;
    }
}
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package com.ruoyi.purchase.vo;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
 * @author buhuazhen
 * @date 2026/3/21
 * @email 3038525872@qq.com
 */
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PurchaseReturnDetailsVo extends PurchaseReturnOrders implements Serializable {
    private List<PurchaseReturnOrderProductsDetailVo> purchaseReturnOrderProductsDetailVoList;
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PurchaseReturnOrderProductsDetailVo implements Serializable {
        private Long id;
        private BigDecimal returnQuantity;
        private Long salesLedgerProductId;
        private Long purchaseReturnOrderId;
        private SalesLedgerProduct salesLedgerProduct;
    }
}
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java
@@ -1,7 +1,13 @@
package com.ruoyi.purchase.vo;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PurchaseReturnOrderVo extends PurchaseReturnOrders {
    //供应商名称
    private String supplierName;
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
@@ -2,6 +2,8 @@
import javax.servlet.http.HttpServletResponse;
import cn.hutool.core.collection.CollUtil;
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.utils.poi.ExcelUtil;
@@ -10,6 +12,9 @@
import com.ruoyi.procurementrecord.dto.ProcurementPageDtoCopy;
import com.ruoyi.procurementrecord.service.ProcurementRecordService;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto;
import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper;
import com.ruoyi.purchase.pojo.PurchaseReturnOrderProducts;
import com.ruoyi.sales.dto.SalesLedgerProductDto;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
@@ -29,6 +34,8 @@
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * äº§å“ä¿¡æ¯Controller
@@ -46,6 +53,9 @@
    private ProcurementRecordService procurementRecordService;
    @Autowired
    private StockUtils stockUtils;
    @Autowired
    private PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
    /**
@@ -74,6 +84,14 @@
    @GetMapping("/list")
    public AjaxResult list(SalesLedgerProduct salesLedgerProduct) {
        List<SalesLedgerProduct> list = salesLedgerProductService.selectSalesLedgerProductList(salesLedgerProduct);
        if (CollUtil.isEmpty(list)) {
            return AjaxResult.success(list);
        }
        //
        List<Long> productIds = list.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList());
        List<SimpleReturnOrderGroupDto> groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds);
        Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getSalesLedgerProductId, item -> item.getSumReturnQuantity()));
        list.forEach(item -> {
            if (item.getFutureTickets().compareTo(BigDecimal.ZERO) == 0) {
                item.setFutureTickets(BigDecimal.ZERO);
@@ -95,6 +113,10 @@
                    item.setApproveStatus(1);
                }
            }
            // ç»Ÿè®¡é€€è´§æ•°é‡
            BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            item.setReturnQuality(returnQuality);
            item.setAvailableQuality(item.getQuantity().subtract(returnQuality));
        });
        return AjaxResult.success(list);
    }
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -235,4 +235,12 @@
    @TableField(exist = false)
    private Integer hasSufficientStock;
    // é€€è´§æ•°é‡
    @TableField(exist = false)
    private BigDecimal returnQuality;
    // å¯ç”¨æ•°é‡  quantity - returnQuality
    @TableField(exist = false)
    private BigDecimal availableQuality;
}
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -13,6 +13,7 @@
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.List;
@@ -32,6 +33,7 @@
    int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto);
    List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(@NotNull Long relateId,@NotNull SaleEnum type);
    List<SalesLedgerProduct> getSalesLedgerProductListByIds(@Nullable List<Long> relateIds, @NotNull SaleEnum type);
    void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type);
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -31,6 +31,8 @@
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto;
import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.sales.dto.*;
import com.ruoyi.sales.mapper.*;
@@ -39,6 +41,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -76,69 +79,41 @@
@RequiredArgsConstructor
@Slf4j
public class SalesLedgerServiceImpl extends ServiceImpl<SalesLedgerMapper, SalesLedger> implements ISalesLedgerService {
    private final AccountIncomeService accountIncomeService;
    private final SalesLedgerMapper salesLedgerMapper;
    private final CustomerMapper customerMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final SalesLedgerProductServiceImpl salesLedgerProductServiceImpl;
    private final CommonFileMapper commonFileMapper;
    private final TempFileMapper tempFileMapper;
    private final ReceiptPaymentMapper receiptPaymentMapper;
    private final ShippingInfoServiceImpl shippingInfoServiceImpl;
    private final CommonFileServiceImpl commonFileService;
    private final ShippingInfoMapper shippingInfoMapper;
    private final InvoiceLedgerMapper invoiceLedgerMapper;
    private final SalesLedgerSchedulingMapper salesLedgerSchedulingMapper;
    private final SalesLedgerWorkMapper salesLedgerWorkMapper;
    private final SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
    private final InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
    private final InvoiceRegistrationMapper invoiceRegistrationMapper;
    private final ProductOrderMapper productOrderMapper;
    private final ProcessRouteMapper processRouteMapper;
    private final ProductProcessRouteMapper productProcessRouteMapper;
    private final ProcessRouteItemMapper processRouteItemMapper;
    private final ProductProcessRouteItemMapper productProcessRouteItemMapper;
    private final ProductWorkOrderMapper productWorkOrderMapper;
    private final ProductionProductMainMapper productionProductMainMapper;
    private final ProductionProductOutputMapper productionProductOutputMapper;
    private final ProductionProductInputMapper productionProductInputMapper;
    private final QualityInspectMapper qualityInspectMapper;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    @Value("${file.upload-dir}")
    private String uploadDir;
    private static final String LOCK_PREFIX = "contract_no_lock:";
    private static final long LOCK_WAIT_TIMEOUT = 10; // é”ç­‰å¾…超时时间(秒)
    private static final long LOCK_EXPIRE_TIME = 30;  // é”è‡ªåŠ¨è¿‡æœŸæ—¶é—´ï¼ˆç§’ï¼‰
    private final AccountIncomeService accountIncomeService;
    private final SalesLedgerMapper salesLedgerMapper;
    private final CustomerMapper customerMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final SalesLedgerProductServiceImpl salesLedgerProductServiceImpl;
    private final CommonFileMapper commonFileMapper;
    private final TempFileMapper tempFileMapper;
    private final ReceiptPaymentMapper receiptPaymentMapper;
    private final ShippingInfoServiceImpl shippingInfoServiceImpl;
    private final CommonFileServiceImpl commonFileService;
    private final ShippingInfoMapper shippingInfoMapper;
    private final InvoiceLedgerMapper invoiceLedgerMapper;
    private final SalesLedgerSchedulingMapper salesLedgerSchedulingMapper;
    private final SalesLedgerWorkMapper salesLedgerWorkMapper;
    private final SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
    private final InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
    private final InvoiceRegistrationMapper invoiceRegistrationMapper;
    private final ProductOrderMapper productOrderMapper;
    private final ProcessRouteMapper processRouteMapper;
    private final ProductProcessRouteMapper productProcessRouteMapper;
    private final ProcessRouteItemMapper processRouteItemMapper;
    private final ProductProcessRouteItemMapper productProcessRouteItemMapper;
    private final ProductWorkOrderMapper productWorkOrderMapper;
    private final ProductionProductMainMapper productionProductMainMapper;
    private final ProductionProductOutputMapper productionProductOutputMapper;
    private final ProductionProductInputMapper productionProductInputMapper;
    private final QualityInspectMapper qualityInspectMapper;
    private final RedisTemplate<String, String> redisTemplate;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    @Value("${file.upload-dir}")
    private String uploadDir;
    @Autowired
    private ProductModelMapper productModelMapper;
@@ -148,19 +123,48 @@
    private ProductStructureMapper productStructureMapper;
    @Autowired
    private ProductionProductMainService productionProductMainService;
    @Autowired
    private PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
    ;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
        return salesLedgerMapper.selectSalesLedgerList(salesLedgerDto);
    }
    public List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(Long relateId, SaleEnum type){
    public List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(Long relateId, SaleEnum type) {
        LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
        productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, relateId);
        productWrapper.eq(SalesLedgerProduct::getType, type.getCode());
        return salesLedgerProductMapper.selectList(productWrapper);
    }
    @Override
    public List<SalesLedgerProduct> getSalesLedgerProductListByIds(@Nullable List<Long> relateIds, SaleEnum type) {
        if (CollectionUtils.isEmpty(relateIds)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
        productWrapper.in(SalesLedgerProduct::getId, relateIds);
        productWrapper.eq(SalesLedgerProduct::getType, type.getCode());
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(productWrapper);
        if (type.equals(SaleEnum.PURCHASE)) {
            // æŸ¥è¯¢é€€è´§ä¿¡æ¯
            List<Long> productIds = salesLedgerProducts.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList());
            List<SimpleReturnOrderGroupDto> groupListByProductIds = new ArrayList<>();
            if(CollectionUtils.isNotEmpty(productIds)){
                groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds);
            }
            Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getSalesLedgerProductId, SimpleReturnOrderGroupDto::getSumReturnQuantity));
            salesLedgerProducts.forEach(item -> {
                BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getId(), BigDecimal.ZERO);
                item.setReturnQuality(returnQuality);
                item.setAvailableQuality(item.getQuantity().subtract(returnQuality));
            });
        }
        return salesLedgerProducts;
    }
    @Override
@@ -334,9 +338,6 @@
        return salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto);
    }
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult importData(MultipartFile file) {
@@ -494,37 +495,6 @@
        return salesLedgerDtoIPage;
    }
    // å†…部类用于存储聚合结果
    private static class GroupedCustomer {
        private final Long customerId;
        private final String customerName;
        private BigDecimal totalAmount = BigDecimal.ZERO;
        public GroupedCustomer(Long customerId, String customerName) {
            this.customerId = customerId;
            this.customerName = customerName;
        }
        public void addAmount(BigDecimal amount) {
            if (amount != null) {
                this.totalAmount = this.totalAmount.add(amount);
            }
        }
        public Long getCustomerId() {
            return customerId;
        }
        public String getCustomerName() {
            return customerName;
        }
        public BigDecimal getTotalAmount() {
            return totalAmount;
        }
    }
    /**
     * ä¸‹åˆ’线命名转驼峰命名
     */
@@ -643,7 +613,7 @@
            // 4. å¤„理子表数据
            List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
            if (productList != null && !productList.isEmpty()) {
                handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class,salesLedgerDto.getType()));
                handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
                updateMainContractAmount(
                        salesLedger.getId(),
                        productList,
@@ -662,8 +632,6 @@
            throw new BaseException("文件迁移失败: " + e.getMessage());
        }
    }
    // æ–‡ä»¶è¿ç§»æ–¹æ³•
    /**
     * å°†ä¸´æ—¶æ–‡ä»¶è¿ç§»åˆ°æ­£å¼ç›®å½•
@@ -740,6 +708,7 @@
        }
    }
    // æ–‡ä»¶è¿ç§»æ–¹æ³•
    @Override
    public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
@@ -881,4 +850,34 @@
            throw new RuntimeException("动态更新主表金额失败", e);
        }
    }
    // å†…部类用于存储聚合结果
    private static class GroupedCustomer {
        private final Long customerId;
        private final String customerName;
        private BigDecimal totalAmount = BigDecimal.ZERO;
        public GroupedCustomer(Long customerId, String customerName) {
            this.customerId = customerId;
            this.customerName = customerName;
        }
        public void addAmount(BigDecimal amount) {
            if (amount != null) {
                this.totalAmount = this.totalAmount.add(amount);
            }
        }
        public Long getCustomerId() {
            return customerId;
        }
        public String getCustomerName() {
            return customerName;
        }
        public BigDecimal getTotalAmount() {
            return totalAmount;
        }
    }
}
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
@@ -62,6 +62,12 @@
            <if test="c.entryDateEnd != null and c.entryDateEnd != ''">
                AND pl.entry_date &lt;= #{c.entryDateEnd}
            </if>
            <if test="c.supplierId != null">
                AND pl.supplier_id = #{c.supplierId}
            </if>
            <if test="c.approvalStatus != null">
                AND pl.approval_status = #{c.approvalStatus}
            </if>
        </where>
        ORDER BY pl.entry_date DESC
    </select>
src/main/resources/mapper/purchase/PurchaseReturnOrderProductsMapper.xml
@@ -11,5 +11,14 @@
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
    </resultMap>
    <select id="getReturnOrderGroupListByProductIds" resultType="com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto"
            parameterType="java.util.List">
        select t1.sales_ledger_product_id,sum(t1.return_quantity) as sum_return_quantity from purchase_return_order_products as t1
        inner join purchase_return_orders as t2 on t1.purchase_return_order_id = t2.id
        WHERE t1.sales_ledger_product_id IN
        <foreach item="id" collection="productIds" separator="," open="(" close=")">
            #{id}
        </foreach>
        group by t1.sales_ledger_product_id
    </select>
</mapper>
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
@@ -23,14 +23,10 @@
        SELECT
        pro.*,
        sm.supplier_name as supplierName,
        pl.purchase_contract_number as purchaseContractNumber,
        su.user_name as returnUserName,
        su1.user_name as createUserName
        pl.purchase_contract_number as purchaseContractNumber
        FROM purchase_return_orders pro
        LEFT JOIN supplier_manage sm ON pro.supplier_id = sm.id
        LEFT JOIN purchase_ledger pl ON pl.id = pro.purchase_ledger_id
        LEFT JOIN sys_user su ON su.user_id = pro.return_user_id
        LEFT JOIN sys_user su1 ON su1.user_id = pro.prepared_user_id
        where 1=1
        <if test="params.no != null and params.no != '' ">
            AND pro.no LIKE CONCAT('%',#{params.no},'%')
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -114,10 +114,14 @@
        <!-- å¹³å‡å•ä»· = æ€»é‡‡è´­é‡‘额/总采购数量,保留2位小数,避免除0 -->
        ROUND(IF(SUM(slp.quantity) = 0, 0, SUM(slp.tax_inclusive_total_price) / SUM(slp.quantity)), 2) AS averagePrice,
        <!-- è¯¥äº§å“å¤§ç±»ä¸‹æœ€åŽä¸€ä¸ªå½•入日期(取台账主表的entry_date) -->
        MAX(sl.entry_date) AS entryDate
        MAX(sl.entry_date) AS entryDate,
        COALESCE(NULLIF(SUM(t1.return_quantity), 0), 0) AS return_quantity,
        COALESCE(SUM(t2.total_amount), 0) AS return_amount
        FROM sales_ledger_product slp
        <!-- å…³è”台账主表:获取录入日期entry_date -->
        LEFT JOIN purchase_ledger sl ON slp.sales_ledger_id = sl.id
        left join purchase_return_order_products as t1 on t1.sales_ledger_product_id = slp.id
        left join purchase_return_orders as t2 on t2.id = t1.purchase_return_order_id
        WHERE slp.type = 2 <!-- å›ºå®šç­›é€‰ï¼šé‡‡è´­å°è´¦ï¼ˆtype=2) -->
        <!-- é‡‡è´­æ—¥æœŸç­›é€‰ï¼šå¯é€‰æ¡ä»¶ -->
        <if test="req.entryDateStart != null and req.entryDateEnd != null">