gongchunyi
2026-07-05 6f266c7dbc1f843e00d4f48dfd3a61671f70e124
Merge branch 'dev_New_pro' into dev_pro_山西长治
已修改44个文件
834 ■■■■ 文件已修改
sql/product-inventory-management-new-pro.sql 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/purchase/PurchaseReturnVo.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/sales/SalesReturnVo.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/sales/AccountInvoiceApplicationController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/sales/AccountInvoiceApplicationMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/sales/AccountInvoiceApplicationServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/sales/AccountInvoiceApplicationService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnManagementDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderHasAllInfoDto.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 129 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderProductsDetailVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseStockInProductVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerMapper.xml 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/SupplierManageMapper.xml 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml 89 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingInfoMapper.xml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInRecordMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/product-inventory-management-new-pro.sql
@@ -3299,6 +3299,10 @@
  `no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '退货单编号',
  `return_type` tinyint NOT NULL DEFAULT 0 COMMENT '退货方式 0退货退款 1拒收',
  `supplier_id` bigint NOT NULL DEFAULT 0 COMMENT '供应商id',
  `shipping_type` tinyint NULL DEFAULT NULL COMMENT '发货类型 1货车 2快递',
  `truck_plate_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发货车牌号',
  `express_company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '快递公司',
  `express_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '快递单号',
  `project_id` bigint NOT NULL DEFAULT 0 COMMENT '项目id',
  `project_phase` tinyint NOT NULL DEFAULT 0 COMMENT '项目阶段',
  `prepared_at` datetime NULL DEFAULT NULL COMMENT '制单日期',
@@ -3753,6 +3757,10 @@
  `return_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '退货单号',
  `customer_id` bigint NULL DEFAULT NULL COMMENT '客户 id',
  `shipping_id` bigint NULL DEFAULT NULL COMMENT '关联发货单号',
  `shipping_type` tinyint NULL DEFAULT NULL COMMENT '发货类型 1货车 2快递',
  `truck_plate_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发货车牌号',
  `express_company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '快递公司',
  `express_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '快递单号',
  `project_stage` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目阶段',
  `maker` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '制单人',
  `make_time` timestamp NULL DEFAULT NULL COMMENT '制单时间',
src/main/java/com/ruoyi/account/bean/vo/purchase/PurchaseReturnVo.java
@@ -29,6 +29,22 @@
    @Excel(name = "关联入库单号")
    private String inboundBatches;
    @Schema(description = "发货类型")
    @Excel(name = "发货类型")
    private Integer shippingType;
    @Schema(description = "发货车牌号")
    @Excel(name = "发货车牌号")
    private String truckPlateNo;
    @Schema(description = "快递公司")
    @Excel(name = "快递公司")
    private String expressCompany;
    @Schema(description = "快递单号")
    @Excel(name = "快递单号")
    private String expressNo;
    @Schema(description = "退货日期")
    @Excel(name = "退货日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
src/main/java/com/ruoyi/account/bean/vo/sales/SalesReturnVo.java
@@ -29,6 +29,22 @@
    @Excel(name = "关联发货单号")
    private String shippingNo;
    @Schema(description = "发货类型")
    @Excel(name = "发货类型")
    private Integer shippingType;
    @Schema(description = "发货车牌号")
    @Excel(name = "发货车牌号")
    private String truckPlateNo;
    @Schema(description = "快递公司")
    @Excel(name = "快递公司")
    private String expressCompany;
    @Schema(description = "快递单号")
    @Excel(name = "快递单号")
    private String expressNo;
    @Schema(description = "退货日期")
    @Excel(name = "退货日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
src/main/java/com/ruoyi/account/controller/sales/AccountInvoiceApplicationController.java
@@ -16,6 +16,7 @@
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
 * <p>
@@ -41,6 +42,13 @@
        return R.ok(listPage);
    }
    @GetMapping("/getAccountInvoiceApplicationList")
    @Log(title = "根据客户id查询开票申请台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--开票申请台账")
    public R<List<AccountInvoiceApplicationVo>> getAccountInvoiceApplicationList(@RequestParam Integer customerId){
        return R.ok(accountInvoiceApplicationService.getAccountInvoiceApplicationList(customerId));
    }
    @GetMapping("/getOutboundBatchesByCustomer")
    @Log(title = "根据客户查询出库单号(开票申请)", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--根据客户查询出库单号(开票申请)")
src/main/java/com/ruoyi/account/mapper/sales/AccountInvoiceApplicationMapper.java
@@ -29,4 +29,6 @@
    //判断该出库记录是否有开票申请
    boolean existsByStockOutRecordId(@Param("stockOutRecordIds") List<Long> stockOutRecordIds);
    List<AccountInvoiceApplicationVo> selectAccountInvoiceApplicationList(@Param("customerId") Integer customerId);
}
src/main/java/com/ruoyi/account/service/impl/sales/AccountInvoiceApplicationServiceImpl.java
@@ -91,6 +91,11 @@
        return removeBatchByIds(ids);
    }
    @Override
    public List<AccountInvoiceApplicationVo> getAccountInvoiceApplicationList(Integer customerId) {
        return accountInvoiceApplicationMapper.selectAccountInvoiceApplicationList(customerId);
    }
    private String genInvoiceApplicationNo() {
        return "KP" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
src/main/java/com/ruoyi/account/service/sales/AccountInvoiceApplicationService.java
@@ -30,4 +30,6 @@
    void exportAccountInvoiceApplication(HttpServletResponse response, AccountInvoiceApplicationDto accountInvoiceApplicationDto);
    boolean deleteAccountInvoiceApplication(List<Long> ids);
    List<AccountInvoiceApplicationVo> getAccountInvoiceApplicationList(Integer customerId);
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java
@@ -13,6 +13,8 @@
import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
import com.ruoyi.approve.pojo.ApprovalInstance;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
@@ -48,6 +50,7 @@
    private final ApprovalTemplateMapper approvalTemplateMapper;
    private final ApprovalTemplateNodeService approvalTemplateNodeService;
    private final ApprovalTemplateNodeApproverMapper approvalTemplateNodeApproverMapper;
    private final ApprovalInstanceMapper approvalInstanceMapper;
    private final FileUtil fileUtil;
    @Override
@@ -82,6 +85,10 @@
    @Override
    public Boolean updateApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto) {
        // 检查模板是否被审批实例引用
        if (isTemplateReferenced(approvalTemplateDto.getId())) {
            throw new RuntimeException("该审批模板已被审批流程引用,无法编辑");
        }
        approvalTemplateMapper.updateById(approvalTemplateDto);
        approvalTemplateNodeService.remove(
                new LambdaQueryWrapper<ApprovalTemplateNode>()
@@ -111,6 +118,12 @@
    public Boolean delete(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return false;
        }
        // 检查模板是否被审批实例引用
        for (Long id : ids) {
            if (isTemplateReferenced(id)) {
                throw new RuntimeException("审批模板(ID: " + id + ")已被审批流程引用,无法删除");
            }
        }
        ApprovalTemplate updateEntity = new ApprovalTemplate();
        updateEntity.setDeleted(1);
@@ -275,4 +288,21 @@
        BeanUtils.copyProperties(approver, approverVo);
        return approverVo;
    }
    /**
     * 检查审批模板是否被审批实例引用
     * @param templateId 模板ID
     * @return true 表示被引用,false 表示未被引用
     */
    private boolean isTemplateReferenced(Long templateId) {
        if (templateId == null) {
            return false;
        }
        Long count = approvalInstanceMapper.selectCount(
                new LambdaQueryWrapper<ApprovalInstance>()
                        .eq(ApprovalInstance::getTemplateId, templateId)
                        .eq(ApprovalInstance::getDeleted, 0)
        );
        return count != null && count > 0;
    }
}
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnManagementDto.java
@@ -27,6 +27,18 @@
    @Schema(description = "关联出库单号")
    private String shippingNo;
    @Schema(description = "发货类型")
    private Integer shippingType;
    @Schema(description = "发货车牌号")
    private String truckPlateNo;
    @Schema(description = "快递公司")
    private String expressCompany;
    @Schema(description = "快递单号")
    private String expressNo;
    @Schema(description = "项目名称")
    private String projectName;
@@ -36,4 +48,7 @@
    @Schema(description = "销售产品对象数组")
    private List<ReturnSaleProductDto> returnSaleProducts;
    @Schema(description = "销售退货入库审批状态")
    private Integer stockInApprovalStatus;
}
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java
@@ -35,4 +35,7 @@
    @Schema(description = "发货出库数量")
    private BigDecimal stockOutNum;
    @Schema(description = "待处理退货数量")
    private BigDecimal pendingReturnNum;
}
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java
@@ -40,4 +40,7 @@
    @Schema(description = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
    @Schema(description = "待处理退货数量")
    private BigDecimal pendingReturnNum;
}
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java
@@ -34,6 +34,18 @@
    @Schema(description = "关联出库单号Id")
    private Long shippingId;
    @Schema(description = "发货类型 1货车 2快递")
    private Integer shippingType;
    @Schema(description = "发货车牌号")
    private String truckPlateNo;
    @Schema(description = "快递公司")
    private String expressCompany;
    @Schema(description = "快递单号")
    private String expressNo;
    @Schema(description = "项目id")
    private Long projectId;
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
@@ -20,8 +20,6 @@
import com.ruoyi.procurementrecord.service.ReturnManagementService;
import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
import com.ruoyi.procurementrecord.utils.StockUtils;
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.RequiredArgsConstructor;
@@ -33,6 +31,7 @@
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author :yys
@@ -46,7 +45,6 @@
    private final ReturnManagementMapper returnManagementMapper;
    private final ReturnSaleProductService returnSaleProductService;
    private final ShippingInfoService shippingInfoService;
    private final SalesLedgerMapper salesLedgerMapper;
    private final SalesRefundAmountOrderService salesRefundAmountOrderService;
    private final StockUtils stockUtils;
@@ -122,21 +120,15 @@
        byId.setSettler(SecurityUtils.getLoginUser().getNickName());
        byId.setRefundAmount(totalRefund);
        updateById(byId);
        //更新退货产品列表的状态
        List<ReturnSaleProduct> updateList = list.stream().map(m -> {
            ReturnSaleProduct returnSaleProduct = new ReturnSaleProduct();
            returnSaleProduct.setId(m.getId());
            returnSaleProduct.setStatus(1);
            return returnSaleProduct;
        }).collect(Collectors.toList());
        returnSaleProductService.updateBatchById(updateList);
        // 3. 更新销售台账的实际合同金额
        if (byId.getShippingId() != null) {
            ShippingInfo shippingInfo = shippingInfoService.getById(byId.getShippingId());
            if (shippingInfo != null && shippingInfo.getSalesLedgerId() != null) {
                SalesLedger salesLedger = salesLedgerMapper.selectById(shippingInfo.getSalesLedgerId());
                if (salesLedger != null && salesLedger.getContractAmount() != null) {
                    BigDecimal baseAmount = salesLedger.getNetContractAmount() != null
                            ? salesLedger.getNetContractAmount()
                            : salesLedger.getContractAmount();
                    salesLedger.setNetContractAmount(baseAmount.subtract(totalRefund));
                    salesLedgerMapper.updateById(salesLedger);
                }
            }
        }
        return true;
    }
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -20,6 +20,7 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
@Component
@RequiredArgsConstructor
@@ -183,15 +184,25 @@
    //删除出库记录
    public void deleteStockOutRecord(Long recordId, String recordType) {
        StockOutRecord one = stockOutRecordService.getOne(new QueryWrapper<StockOutRecord>()
        List<StockOutRecord> records = stockOutRecordService.list(new QueryWrapper<StockOutRecord>()
                .lambda().eq(StockOutRecord::getRecordId, recordId)
                .eq(StockOutRecord::getRecordType, recordType), false);
        if (ObjectUtils.isNotEmpty(one)) {
            if (ReviewStatusEnum.APPROVED.getCode().equals(one.getApprovalStatus())) {
                stockOutRecordService.batchDelete(Collections.singletonList(one.getId()));
            } else {
                stockOutRecordService.removeById(one.getId());
                .eq(StockOutRecord::getRecordType, recordType));
        if (ObjectUtils.isEmpty(records)) {
            return;
            }
        List<Long> approvedIds = records.stream()
                .filter(one -> ReviewStatusEnum.APPROVED.getCode().equals(one.getApprovalStatus()))
                .map(StockOutRecord::getId)
                .toList();
        List<Long> pendingIds = records.stream()
                .filter(one -> !ReviewStatusEnum.APPROVED.getCode().equals(one.getApprovalStatus()))
                .map(StockOutRecord::getId)
                .toList();
        if (!approvedIds.isEmpty()) {
            stockOutRecordService.batchDelete(approvedIds);
        }
        if (!pendingIds.isEmpty()) {
            stockOutRecordService.removeByIds(pendingIds);
        }
    }
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java
@@ -161,11 +161,21 @@
        // 检查数据是否重复,避免重复保存
        boolean duplicate = productionOrderRoutingOperationParamMapper.selectCount(
                Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
                        .eq(ProductionOrderRoutingOperationParam::getProductionOrderId, item.getProductionOrderId())
                        .isNull(ProductionOrderRoutingOperationParam::getProductionProductMainId)
                        .eq(ProductionOrderRoutingOperationParam::getProductionOrderRoutingOperationId, item.getProductionOrderRoutingOperationId())
                        .eq(item.getTechnologyRoutingOperationParamId() != null,
                                ProductionOrderRoutingOperationParam::getTechnologyRoutingOperationParamId, item.getTechnologyRoutingOperationParamId())
                        .eq(item.getTechnologyRoutingOperationParamId() == null && item.getParamCode() != null,
                        .eq(item.getTechnologyRoutingOperationParamId() == null && item.getTechnologyOperationParamId() != null,
                                ProductionOrderRoutingOperationParam::getTechnologyOperationParamId, item.getTechnologyOperationParamId())
                        .eq(item.getTechnologyRoutingOperationParamId() == null
                                        && item.getTechnologyOperationParamId() == null
                                        && item.getParamId() != null,
                                ProductionOrderRoutingOperationParam::getParamId, item.getParamId())
                        .eq(item.getTechnologyRoutingOperationParamId() == null
                                        && item.getTechnologyOperationParamId() == null
                                        && item.getParamId() == null
                                        && item.getParamCode() != null,
                                ProductionOrderRoutingOperationParam::getParamCode, item.getParamCode())
                        .ne(item.getId() != null, ProductionOrderRoutingOperationParam::getId, item.getId())
        ) > 0;
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
@@ -118,6 +118,16 @@
    }
    /**
     * 查询采购退货可选采购台账
     */
    @GetMapping("/returnableList")
    public TableDataInfo returnableList(PurchaseLedger purchaseLedger) {
        startPage();
        List<PurchaseLedger> list = purchaseLedgerService.selectReturnablePurchaseLedgerList(purchaseLedger);
        return getDataTable(list);
    }
    /**
     * 导出采购台账列表
     */
    @Log(title = "采购台账", businessType = BusinessType.EXPORT)
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java
@@ -13,6 +13,8 @@
import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import com.ruoyi.stock.pojo.StockOutRecord;
import java.time.LocalDateTime;
import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
@@ -40,6 +42,7 @@
    private PurchaseReturnOrdersService purchaseReturnOrdersService;
    private PurchaseReturnOrdersMapper purchaseReturnOrdersMapper;
    private AccountStatementDetailsService accountStatementDetailsService;
    private StockOutRecordMapper stockOutRecordMapper;
    @GetMapping("/listPage")
@@ -72,6 +75,13 @@
        if (CollectionUtils.isNotEmpty(accountStatementDetails)){
            throw new ServiceException("该采购退货单已经生成对账单,无法删除");
        }
        List<StockOutRecord> approvedStockOutRecords = stockOutRecordMapper.selectList(Wrappers.<StockOutRecord>lambdaQuery()
                .eq(StockOutRecord::getRecordType, "9")
                .eq(StockOutRecord::getApprovalStatus, 1)
                .inSql(StockOutRecord::getRecordId, "select id from purchase_return_order_products where purchase_return_order_id = " + id));
        if (CollectionUtils.isNotEmpty(approvedStockOutRecords)) {
            throw new ServiceException("该采购退货单对应出库已审核通过,无法删除");
        }
        purchaseReturnOrdersService.deleteById(id);
        return AjaxResult.success();
    }
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
@@ -33,6 +33,11 @@
    private Long id;
    /**
     * 审批实例ID
     */
    private Long approvalInstanceId;
    /**
     * 采购合同号
     */
    @Excel(name = "采购合同号")
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderHasAllInfoDto.java
@@ -15,6 +15,13 @@
    // 采购合同号
    private String purchaseContractNumber;
    private String shippingTypeText;
    /**
     * 对应出库是否已审核通过
     */
    private Boolean stockOutApproved;
    //退料人姓名
    private String returnUserName;
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
@@ -23,6 +23,8 @@
    IPage<PurchaseLedgerDto> selectPurchaseLedgerListPage(IPage ipage, @Param("c") PurchaseLedgerDto purchaseLedger);
    List<PurchaseLedger> selectReturnablePurchaseLedgerList(@Param("c") PurchaseLedger purchaseLedger);
    List<IncomeExpenseAnalysisDto> selectPurchaseStats(@Param("startDate") String startDate, @Param("endDate") String endDate, @Param("dateFormat") String dateFormat);
    BigDecimal selectTotalPurchaseAmount(@Param("startDate") String startDate, @Param("endDate") String endDate);
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java
@@ -44,6 +44,18 @@
    @Schema(description = "供应商id")
    private Long supplierId;
    @Schema(description = "发货类型 1货车 2快递")
    private Integer shippingType;
    @Schema(description = "发货车牌号")
    private String truckPlateNo;
    @Schema(description = "快递公司")
    private String expressCompany;
    @Schema(description = "快递单号")
    private String expressNo;
    @Schema(description = "项目id")
    private Long projectId;
src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
@@ -23,6 +23,8 @@
    List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger);
    List<PurchaseLedger> selectReturnablePurchaseLedgerList(PurchaseLedger purchaseLedger);
    int addOrEditPurchase(PurchaseLedgerDto purchaseLedgerDto) throws Exception;
    R batchInsertPurchaseSteps(List<Long> ids);
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -6,6 +6,7 @@
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
import com.ruoyi.approve.pojo.ApprovalInstance;
@@ -22,10 +23,11 @@
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.enums.ApprovalStatusEnum;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.TypeEnums;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
@@ -136,6 +138,11 @@
    }
    @Override
    public List<PurchaseLedger> selectReturnablePurchaseLedgerList(PurchaseLedger purchaseLedger) {
        return purchaseLedgerMapper.selectReturnablePurchaseLedgerList(purchaseLedger);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrEditPurchase(PurchaseLedgerDto purchaseLedgerDto) throws Exception {
        PurchaseLedger purchaseLedger = new PurchaseLedger();
@@ -168,23 +175,10 @@
        // 实际合同金额始终等于合同金额
        purchaseLedger.setNetContractAmount(purchaseLedger.getContractAmount());
        // 3. 新增或更新主表
        if (purchaseLedger.getId() == null) {
            if (!StringUtils.hasText(purchaseLedger.getPurchaseContractNumber())) {
        if (purchaseLedger.getId() == null && !StringUtils.hasText(purchaseLedger.getPurchaseContractNumber())) {
                purchaseLedger.setPurchaseContractNumber(generatePurchaseContractNo(purchaseLedger.getEntryDate()));
            }
            purchaseLedgerMapper.insert(purchaseLedger);
        } else {
            // 删除采购审核,重新提交
            ApproveProcess one = approveProcessService.getOne(new LambdaQueryWrapper<ApproveProcess>()
                    .eq(ApproveProcess::getApproveType, 5)
                    .eq(ApproveProcess::getApproveReason, purchaseLedger.getPurchaseContractNumber())
                    .eq(ApproveProcess::getApproveDelete, 0)
                    .last("limit 1"));
            if (one != null) {
                approveProcessService.delByIds(Collections.singletonList(one.getId()));
            }
            purchaseLedgerMapper.updateById(purchaseLedger);
        }
        this.saveOrUpdate(purchaseLedger);
        // 4. 处理子表数据
        List<SalesLedgerProduct> productList = purchaseLedgerDto.getProductData();
@@ -192,7 +186,17 @@
            handleSalesLedgerProducts(purchaseLedger.getId(), productList, purchaseLedgerDto.getType());
        }
        // 6.采购审核新增;审批管理未配置采购审批人时,审批服务会自动置为审批通过。
        addApproveByPurchase(loginUser, purchaseLedger);
        //查询当前采购合同号是否已添加审批,如果有且不是驳回状态,则不重复添加
        ApprovalInstance oldApprovalInstance = approvalInstanceService.getOne(Wrappers.<ApprovalInstance>lambdaQuery()
                .eq(ApprovalInstance::getDeleted, 0)
                .eq(ApprovalInstance::getBusinessType, TypeEnums.PURCHASE_APPROVAL.getCode())
                .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
                .ne(ApprovalInstance::getStatus,"REJECTED")
                .last("limit 1")
        );
        if(Objects.isNull(oldApprovalInstance)){
            addApproveByPurchase(loginUser, purchaseLedger, productList);
        }
        // 5. 迁移临时文件到正式目录
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.PURCHASE_LEDGER, purchaseLedger.getId(), purchaseLedgerDto.getStorageBlobDTOS());
        return 1;
@@ -1013,7 +1017,12 @@
                    salesLedgerProductMapper.insert(salesLedgerProduct);
                }
                // 采购审核
                addApproveByPurchase(loginUser,salesLedger);
                List<SalesLedgerProduct> snapshotProducts = salesLedgerProductMapper.selectList(
                        new LambdaQueryWrapper<SalesLedgerProduct>()
                                .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId())
                                .eq(SalesLedgerProduct::getType, 2)
                );
                addApproveByPurchase(loginUser, salesLedger, snapshotProducts);
            }
            return AjaxResult.success("导入成功");
@@ -1025,21 +1034,91 @@
    @Override
    public PurchaseLedgerDto getPurchaseByCode(PurchaseLedgerDto purchaseLedgerDto) {
        // 1. 查询主表
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
        PurchaseLedger purchaseLedger = null;
        ApprovalInstance approvalSnapshotInstance = null;
        // 1. 优先按当前审批实例查询快照
        if (purchaseLedgerDto.getApprovalInstanceId() != null) {
            approvalSnapshotInstance = approvalInstanceService.getById(purchaseLedgerDto.getApprovalInstanceId());
            if (approvalSnapshotInstance != null && approvalSnapshotInstance.getBusinessId() != null) {
                purchaseLedger = purchaseLedgerMapper.selectById(approvalSnapshotInstance.getBusinessId());
            }
        }
        // 2. 没传审批实例ID时,退回到按采购合同号查询
        if (purchaseLedger == null) {
            purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                .eq(PurchaseLedger::getPurchaseContractNumber, purchaseLedgerDto.getPurchaseContractNumber())
                .last("LIMIT 1"));
        if (purchaseLedger == null) {
            throw new BaseException("采购台账不存在");
        }
        }
        // 2. 查询子表
        // 3. 查询当前采购单对应的审批快照
        if (approvalSnapshotInstance == null) {
            approvalSnapshotInstance = approvalInstanceService.getOne(new LambdaQueryWrapper<ApprovalInstance>()
                    .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
                    .eq(ApprovalInstance::getBusinessType, 5L)
                    .eq(ApprovalInstance::getDeleted, 0)
                    .orderByDesc(ApprovalInstance::getId)
                    .last("LIMIT 1"));
        }
        if (approvalSnapshotInstance != null && StringUtils.hasText(approvalSnapshotInstance.getFormConfig())) {
            try {
                PurchaseLedgerDto snapshot = JSON.parseObject(approvalSnapshotInstance.getFormConfig(), PurchaseLedgerDto.class);
                if (snapshot != null) {
                    snapshot.setId(purchaseLedger.getId());
                    if (snapshot.getPurchaseContractNumber() == null) {
                        snapshot.setPurchaseContractNumber(purchaseLedger.getPurchaseContractNumber());
                    }
                    if (snapshot.getSupplierId() == null) {
                        snapshot.setSupplierId(purchaseLedger.getSupplierId());
                    }
                    if (snapshot.getSupplierName() == null) {
                        snapshot.setSupplierName(purchaseLedger.getSupplierName());
                    }
                    if (snapshot.getSalesContractNo() == null) {
                        snapshot.setSalesContractNo(purchaseLedger.getSalesContractNo());
                    }
                    if (snapshot.getSalesContractNoId() == null) {
                        snapshot.setSalesContractNoId(purchaseLedger.getSalesLedgerId());
                    }
                    if (snapshot.getProjectName() == null) {
                        snapshot.setProjectName(purchaseLedger.getProjectName());
                    }
                    if (snapshot.getEntryDate() == null) {
                        snapshot.setEntryDate(purchaseLedger.getEntryDate());
                    }
                    if (snapshot.getExecutionDate() == null) {
                        snapshot.setExecutionDate(purchaseLedger.getExecutionDate());
                    }
                    if (snapshot.getRemarks() == null) {
                        snapshot.setRemarks(purchaseLedger.getRemarks());
                    }
                    if (snapshot.getRecorderId() == null) {
                        snapshot.setRecorderId(purchaseLedger.getRecorderId());
                    }
                    if (snapshot.getRecorderName() == null) {
                        snapshot.setRecorderName(purchaseLedger.getRecorderName());
                    }
                    snapshot.setProductData(snapshot.getProductData() == null ? Collections.emptyList() : snapshot.getProductData());
                    snapshot.setHasChildren(CollectionUtils.isNotEmpty(snapshot.getProductData()));
                    return snapshot;
                }
            } catch (Exception e) {
                log.warn("解析采购审批快照失败: {}", e.getMessage());
            }
        }
        // 4. 查询子表
        LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
        productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
                .eq(SalesLedgerProduct::getType, 2);
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
        // 4. 转换 DTO
        // 5. 转换 DTO
        PurchaseLedgerDto resultDto = new PurchaseLedgerDto();
        BeanUtils.copyProperties(purchaseLedger, resultDto);
        if (!products.isEmpty()) {
@@ -1049,7 +1128,7 @@
        return resultDto;
    }
    public void addApproveByPurchase(LoginUser loginUser,PurchaseLedger purchaseLedger) throws Exception {
    public void addApproveByPurchase(LoginUser loginUser, PurchaseLedger purchaseLedger, List<SalesLedgerProduct> productList) {
        if (loginUser == null) {
            return;
        }
@@ -1074,6 +1153,10 @@
        approvalInstance.setTitle(purchaseLedger.getPurchaseContractNumber()+"审批");
        approvalInstance.setApplicantName(loginUser.getNickName());
        approvalInstance.setApplyTime(LocalDateTime.now());
        PurchaseLedgerDto snapshot = new PurchaseLedgerDto();
        BeanUtils.copyProperties(purchaseLedger, snapshot);
        snapshot.setProductData(productList == null ? Collections.emptyList() : new ArrayList<>(productList));
        approvalInstance.setFormConfig(JSON.toJSONString(snapshot));
        approvalInstanceService.add(approvalInstance);
    }
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderProductsDetailVo.java
@@ -47,6 +47,9 @@
    @Schema(description = "已退货数量")
    private BigDecimal totalReturnNum;
    @Schema(description = "销售发货数量")
    private BigDecimal saleOutQuantity;
    @Schema(description = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
src/main/java/com/ruoyi/purchase/vo/PurchaseStockInProductVo.java
@@ -42,6 +42,9 @@
    @Schema(description = "退货总数")
    private BigDecimal totalReturnNum;
    @Schema(description = "销售发货数量")
    private BigDecimal saleOutQuantity;
    @Schema(description = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java
@@ -30,4 +30,7 @@
    @Schema(description = "应付金额")
    private BigDecimal payableAmount;
    @Schema(description = "退货金额")
    private BigDecimal returnAmount;
}
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java
@@ -24,7 +24,10 @@
    private BigDecimal paymentAmount;
    @Schema(description = "应付金额")
    //该供应商采购应付累计金额=财务(入库-退货)
    //该供应商采购应付累计金额=初始金额-付款-退款
    private BigDecimal payableAmount;
    @Schema(description = "退货金额")
    private BigDecimal returnAmount;
}
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
@@ -47,11 +47,18 @@
                java.math.BigDecimal returnQuantity = item.getReturnQuantity() == null
                        ? java.math.BigDecimal.ZERO
                        : item.getReturnQuantity();
                java.math.BigDecimal quantity = item.getQuantity() == null
                item.setReturnQuantity(returnQuantity);
                java.math.BigDecimal availableQuality = item.getQuantity() == null
                        ? java.math.BigDecimal.ZERO
                        : item.getQuantity();
                item.setReturnQuantity(returnQuantity);
                item.setAvailableQuality(quantity.subtract(returnQuantity));
                if (item.getType() != null && item.getType() == 2) {
                    availableQuality = item.getApprovedStockInNum() == null
                            ? java.math.BigDecimal.ZERO
                            : item.getApprovedStockInNum();
                    availableQuality = availableQuality
                            .subtract(item.getShippedQuantity() == null ? java.math.BigDecimal.ZERO : item.getShippedQuantity());
                }
                item.setAvailableQuality(availableQuality.subtract(returnQuantity));
            });
        }
        return AjaxResult.success(list);
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -187,6 +187,14 @@
    private BigDecimal returnQuantity;
    @TableField(exist = false)
    @Schema(description = "销售发货数量")
    private BigDecimal shippedQuantity;
    @TableField(exist = false)
    @Schema(description = "已审核入库数量")
    private BigDecimal approvedStockInNum;
    @TableField(exist = false)
    @Schema(description = "审批中数量")
    private BigDecimal pendingApprovalQuantity;
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -25,6 +25,7 @@
import com.ruoyi.technology.mapper.TechnologyBomStructureMapper;
import com.ruoyi.technology.mapper.TechnologyRoutingMapper;
import com.ruoyi.technology.pojo.TechnologyRouting;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -33,9 +34,7 @@
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
@@ -98,9 +97,10 @@
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectSalesLedgerProductList(salesLedgerProduct);
        if(!CollectionUtils.isEmpty(salesLedgerProducts)){
            salesLedgerProducts.forEach(item -> {
                // 发货信息(取最新一条)
                // 发货信息(取最新一条,只查询审核通过或已发货的记录)
                ShippingInfo latestShippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
                        .eq(ShippingInfo::getSalesLedgerProductId, item.getId())
                        .in(ShippingInfo::getStatus, "审核通过", "已发货")
                        .orderByDesc(ShippingInfo::getCreateTime)
                        .last("limit 1"));
                if(latestShippingInfo != null){
@@ -108,9 +108,10 @@
                    item.setExpressCompany(latestShippingInfo.getExpressCompany());
                    item.setExpressNumber(latestShippingInfo.getExpressNumber());
                }
                // 车牌号:取所有发货记录的车牌号,逗号拼接
                // 车牌号:取所有审核通过或已发货记录的车牌号,逗号拼接
                List<ShippingInfo> allShippingInfos = shippingInfoMapper.selectList(new LambdaQueryWrapper<ShippingInfo>()
                        .eq(ShippingInfo::getSalesLedgerProductId, item.getId())
                        .in(ShippingInfo::getStatus, "审核通过", "已发货")
                        .orderByAsc(ShippingInfo::getCreateTime));
                if(!CollectionUtils.isEmpty(allShippingInfos)){
                    String carNumbers = allShippingInfos.stream()
@@ -227,7 +228,7 @@
     */
    public void addProductionData(SalesLedgerProduct salesLedgerProduct) {
        //先判断该产品是否需要生产
        if (!salesLedgerProduct.getIsProduction()) {
        if (!BooleanUtils.isTrue(salesLedgerProduct.getIsProduction())) {
            return;
        }
        SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerProduct.getSalesLedgerId());
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
@@ -30,4 +30,7 @@
    @Schema(description = "应收金额")
    private BigDecimal receiptableAmount;
    @Schema(description = "退货金额")
    private BigDecimal returnAmount;
}
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java
@@ -24,7 +24,10 @@
    private BigDecimal receiptPaymentAmount;
    @Schema(description = "应收金额")
    //该客户销售应收累计金额=财务(出库-退货)
    //该客户销售应收累计金额=初始金额-回款-退款
    private BigDecimal receiptableAmount;
    @Schema(description = "退货金额")
    private BigDecimal returnAmount;
}
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -21,6 +21,14 @@
import com.ruoyi.stock.execl.StockInRecordExportData;
import com.ruoyi.production.mapper.ProductionOrderPickMapper;
import com.ruoyi.production.pojo.ProductionOrderPick;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.procurementrecord.mapper.ReturnSaleProductMapper;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.mapper.StockUninventoryMapper;
@@ -45,6 +53,10 @@
    private StockInventoryMapper stockInventoryMapper;
    private StockUninventoryMapper stockUninventoryMapper;
    private ProductionOrderPickMapper productionOrderPickMapper;
    private ReturnSaleProductMapper returnSaleProductMapper;
    private ReturnManagementMapper returnManagementMapper;
    private ShippingInfoMapper shippingInfoMapper;
    private SalesLedgerMapper salesLedgerMapper;
    @Override
    public IPage<StockInRecordDto> listPage(Page page, StockInRecordDto stockInRecordDto) {
@@ -285,6 +297,8 @@
                        stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto);
                    }
                }
                // 销售退货入库:扣减销售台账实际合同金额(退货生效)
                handleSalesReturnRefund(stockInRecord);
            }
        }
        return ids.size();
@@ -335,4 +349,47 @@
        return ids.size();
    }
    /**
     * 销售退货入库审批通过时,扣减对应销售台账的实际合同金额。
     * 关联链:stock_in_record.record_id = return_sale_product.id
     *        → return_management.shipping_id = shipping_info.id
     *        → shipping_info.sales_ledger_id = sales_ledger.id
     * 只处理 record_type 为 14(合格)或 15(不合格)的销售退货入库。
     */
    private void handleSalesReturnRefund(StockInRecord stockInRecord) {
        String recordType = stockInRecord.getRecordType();
        if (!StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode().equals(recordType)
                && !StockInQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode().equals(recordType)) {
            return;
        }
        if (stockInRecord.getRecordId() == null) {
            return;
        }
        ReturnSaleProduct rsp = returnSaleProductMapper.selectById(stockInRecord.getRecordId());
        if (rsp == null || rsp.getReturnManagementId() == null) {
            return;
        }
        ReturnManagement rm = returnManagementMapper.selectById(rsp.getReturnManagementId());
        if (rm == null || rm.getShippingId() == null) {
            return;
        }
        ShippingInfo shippingInfo = shippingInfoMapper.selectById(rm.getShippingId());
        if (shippingInfo == null || shippingInfo.getSalesLedgerId() == null) {
            return;
        }
        SalesLedger salesLedger = salesLedgerMapper.selectById(shippingInfo.getSalesLedgerId());
        if (salesLedger == null || salesLedger.getContractAmount() == null) {
            return;
        }
        BigDecimal refund = rsp.getAmount() != null ? rsp.getAmount() : BigDecimal.ZERO;
        if (refund.compareTo(BigDecimal.ZERO) == 0) {
            return;
        }
        BigDecimal baseAmount = salesLedger.getNetContractAmount() != null
                ? salesLedger.getNetContractAmount()
                : salesLedger.getContractAmount();
        salesLedger.setNetContractAmount(baseAmount.subtract(refund));
        salesLedgerMapper.updateById(salesLedger);
    }
}
src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml
@@ -72,5 +72,26 @@
            </foreach>
        )
    </select>
    <select id="selectAccountInvoiceApplicationList"
            resultType="com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo">
        select
            *
        from (
            select aia.*,
            asi.id AS sales_invoice_id,
            asi.status AS sales_invoice_status,
            c.customer_name,
            GROUP_CONCAT(sour.outbound_batches SEPARATOR ',') AS outboundBatches
            from account_invoice_application aia
            left join customer c on aia.customer_id = c.id
            left join stock_out_record sour on FIND_IN_SET(sour.id, aia.stock_out_record_ids) > 0
            left join account_sales_invoice asi on aia.id = asi.account_invoice_application_id
            GROUP BY aia.id
        )A
        WHERE A.customer_id = #{customerId}
        AND (COALESCE(A.sales_invoice_id,'') = '' OR A.sales_invoice_status = 1)
        AND A.status = 1
        order by A.id desc
    </select>
</mapper>
src/main/resources/mapper/basic/CustomerMapper.xml
@@ -126,24 +126,12 @@
    <select id="customewTransactions" resultType="com.ruoyi.sales.vo.CustomerTransactionsVo">
        select T1.customer_id,
               c.customer_name,
               T1.contractAmounts - IFNULL(T4.returnAmount, 0) AS contractAmounts,
               T1.contractAmounts AS contractAmounts,
               IFNULL(T2.receiptPaymentAmount, 0) AS receiptPaymentAmount,
               IFNULL(T3.outboundAmount, 0) - IFNULL(T4.returnAmount, 0) AS receiptableAmount
               T1.contractAmounts - (IFNULL(T2.receiptPaymentAmount, 0) - IFNULL(T4.returnAmount, 0)) AS receiptableAmount,
               IFNULL(T4.returnAmount, 0) AS returnAmount
        from (select customer_id, sum(contract_amount) as contractAmounts from sales_ledger group by customer_id) T1
        left join (select customer_id, sum(collection_amount) as receiptPaymentAmount from account_sales_collection group by customer_id) T2 on T1.customer_id = T2.customer_id
        left join (
            SELECT
                sl.customer_id,
                sum(sor.stock_out_num * slp.tax_inclusive_unit_price) as outboundAmount
            FROM stock_out_record sor
            LEFT join shipping_info s on sor.record_id = s.id
            LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
            LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id
            WHERE sor.record_type='13'
                and sor.approval_status=1
                and slp.type = 1
            group by sl.customer_id
        ) T3 on T3.customer_id=T1.customer_id
        left join (
            select
                sl.customer_id,
@@ -167,9 +155,10 @@
        select sl.id salesLedgerId,
               sl.sales_contract_no,
               sl.execution_date,
               sl.contract_amount - IFNULL(T3.returnAmount, 0) AS contract_amount,
               sl.contract_amount AS contract_amount,
               IFNULL(T1.receiptPaymentAmount, 0) AS receiptPaymentAmount,
               IFNULL(T2.outboundAmount, 0) - IFNULL(T3.returnAmount, 0) AS receiptableAmount
               sl.contract_amount - (IFNULL(T1.receiptPaymentAmount, 0) - IFNULL(T3.returnAmount, 0)) AS receiptableAmount,
               IFNULL(T3.returnAmount, 0) AS returnAmount
        from sales_ledger sl
        left join (
            select
@@ -183,19 +172,6 @@
              and sor.approval_status=1
            group by  sl.id
        )T1 on T1.id = sl.id
        left join (
            SELECT
                sl.id,
                sum(sor.stock_out_num * slp.tax_inclusive_unit_price) as outboundAmount
            FROM stock_out_record sor
                     left join shipping_info s on sor.record_id = s.id
                     LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
                     LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id
            WHERE sor.record_type='13'
              and sor.approval_status=1
              and slp.type = 1
            group by  sl.id
        )T2 on T2.id = sl.id
        left join (
            select sl.id,
                   sum(rm.refund_amount) as returnAmount
src/main/resources/mapper/basic/SupplierManageMapper.xml
@@ -74,26 +74,10 @@
               sm.supplier_name,
               T1.contractAmounts,
               IFNULL(T2.paymentAmount, 0) AS paymentAmount,
               IFNULL(T3.InboundAmount, 0) - IFNULL(T4.returnAmount, 0) AS payableAmount
               T1.contractAmounts - (IFNULL(T2.paymentAmount, 0) - IFNULL(T4.returnAmount, 0)) AS payableAmount,
               IFNULL(T4.returnAmount, 0) AS returnAmount
        from (select supplier_id, sum(COALESCE(net_contract_amount, contract_amount)) as contractAmounts from purchase_ledger group by supplier_id) T1
        left join (select supplier_id, sum(payment_amount) as paymentAmount from account_purchase_payment group by supplier_id) T2 on T1.supplier_id = T2.supplier_id
        left join (
            SELECT
                pl.supplier_id,
                sum(sir.stock_in_num * slp.tax_inclusive_unit_price) AS InboundAmount
            FROM stock_in_record sir
                     -- 10 类型才关联质检表
                     LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
                -- 动态关联采购(自动适配 7 和 10)
                     LEFT JOIN purchase_ledger pl
                               ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
                -- 产品关联不动
                     LEFT JOIN sales_ledger_product slp ON pl.id = slp.sales_ledger_id
            -- 条件
            WHERE sir.approval_status = 1 AND slp.type = 2
              AND sir.record_type IN ('7','10')
            group by pl.supplier_id
        ) T3 on T3.supplier_id=T1.supplier_id
        left join (
            select
                supplier_id,
@@ -116,7 +100,8 @@
              pl.execution_date,
              COALESCE(pl.net_contract_amount, pl.contract_amount) AS contract_amount,
              IFNULL(T1.paymentAmount, 0) AS paymentAmount,
              IFNULL(T2.InboundAmount, 0) - IFNULL(T3.returnAmount, 0) AS payableAmount
              COALESCE(pl.net_contract_amount, pl.contract_amount) - (IFNULL(T1.paymentAmount, 0) - IFNULL(T3.returnAmount, 0)) AS payableAmount,
              IFNULL(T3.returnAmount, 0) AS returnAmount
       from purchase_ledger pl
       left join (
           select
@@ -134,23 +119,6 @@
             AND sir.record_type IN ('7','10')
           group by pl.id
       )T1 on T1.id = pl.id
       left join (
           SELECT
               pl.id,
               sum(sir.stock_in_num * slp.tax_inclusive_unit_price) AS InboundAmount
           FROM stock_in_record sir
                    -- 10 类型才关联质检表
                    LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
               -- 动态关联采购(自动适配 7 和 10)
                    LEFT JOIN purchase_ledger pl
                              ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
               -- 产品关联不动
                    LEFT JOIN sales_ledger_product slp ON pl.id = slp.sales_ledger_id
           -- 条件
           WHERE sir.approval_status = 1 AND slp.type = 2
             AND sir.record_type IN ('7','10')
           group by pl.id
       )T2 on T2.id = pl.id
       left join (
           select pl.id,
                  sum(pro.total_amount) as returnAmount
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
@@ -6,13 +6,30 @@
        select rm.*,
               c.customer_name,
               si.shipping_no,
               rm.shipping_type,
               rm.truck_plate_no,
               rm.express_company,
               rm.express_no,
               sl.project_name,
               sl.sales_contract_no,
               sl.salesman
               sl.salesman,
                CASE
                    WHEN tempA.total_approval = 0 OR tempA.total_approval IS NULL THEN 0
                    WHEN tempA.approval_sum = tempA.total_approval THEN 1
                    ELSE 2
                END AS stock_in_approval_status
        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
                LEFT JOIN (
                SELECT
                rsp.return_management_id,
                COUNT(sir.id) AS total_approval,
                SUM(CASE WHEN sir.approval_status = 1 THEN 1 ELSE 0 END) AS approval_sum
                FROM return_sale_product rsp LEFT JOIN stock_in_record sir ON rsp.id = sir.record_id
                GROUP BY rsp.return_management_id
                ) tempA ON tempA.return_management_id = rm.id
        <where>
            <if test="req.returnNo != null and req.returnNo != ''">
                and rm.return_no like concat('%',#{req.returnNo},'%')
@@ -45,6 +62,10 @@
     select rm.*,
               c.customer_name,
               si.shipping_no,
               rm.shipping_type,
               rm.truck_plate_no,
               rm.express_company,
               rm.express_no,
               sl.project_name,
               sl.sales_contract_no,
               sl.salesman
@@ -59,6 +80,10 @@
                rm.return_no,
                c.customer_name,
                si.shipping_no,
                rm.shipping_type,
                rm.truck_plate_no,
                rm.express_company,
                rm.express_no,
                rm.make_time,
                rm.refund_amount,
                rm.return_reason,
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
@@ -19,8 +19,9 @@
            sor.outbound_batches,
            sor.stock_out_num,
            sor.batch_no,
               GREATEST(sor.stock_out_num - COALESCE(rs1.total_return_num1, 0), 0) AS un_quantity,
               COALESCE(rs.total_return_num, 0)                             AS total_return_num
            GREATEST( sor.stock_out_num - COALESCE ( rs.total_return_num, 0 ), 0 ) AS un_quantity,
            COALESCE ( rs.pending_return_num, 0 ) AS pending_return_num ,
            COALESCE ( rs.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
@@ -30,15 +31,11 @@
                left join product_model pm on slp.product_model_id = pm.id
                  LEFT JOIN product p on pm.product_id = p.id
                 LEFT JOIN (SELECT stock_out_record_id,
                                   SUM(num) AS total_return_num
                                   SUM(num) AS total_return_num,
                                   SUM( CASE WHEN return_management_id =#{returnManagementId} AND STATUS = 0 THEN num ELSE 0 END ) AS pending_return_num ,
                                   SUM( CASE WHEN STATUS = 1 THEN num ELSE 0 END ) AS return_num
                            FROM return_sale_product
                            WHERE 1 = 1 and return_management_id = #{returnManagementId}
                            GROUP BY stock_out_record_id) rs ON rs.stock_out_record_id = sor.id
                 LEFT JOIN (SELECT stock_out_record_id,
                                   SUM(num) AS total_return_num1
                            FROM return_sale_product
                            WHERE 1 = 1
                            GROUP BY stock_out_record_id) rs1 ON rs1.stock_out_record_id = sor.id
        where rm.id =#{returnManagementId}
    </select>
    <select id="listReturnSaleProduct" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto">
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
@@ -139,6 +139,46 @@
        ORDER BY result.entry_date DESC
    </select>
    <select id="selectReturnablePurchaseLedgerList" resultType="com.ruoyi.purchase.pojo.PurchaseLedger">
        SELECT DISTINCT pl.*
        FROM purchase_ledger pl
        WHERE 1 = 1
        <if test="c.purchaseContractNumber != null and c.purchaseContractNumber != ''">
            AND pl.purchase_contract_number LIKE CONCAT('%', #{c.purchaseContractNumber}, '%')
        </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>
        AND EXISTS (
            SELECT 1
            FROM stock_in_record sir
            LEFT JOIN quality_inspect qi
                ON sir.record_type = '10'
               AND sir.record_id = qi.id
            LEFT JOIN sales_ledger_product slp
                ON slp.sales_ledger_id = pl.id
               AND slp.product_model_id = sir.product_model_id
               AND slp.type = 2
            LEFT JOIN (
                SELECT stock_in_record_id, SUM(return_quantity) AS total_return_num
                FROM purchase_return_order_products
                GROUP BY stock_in_record_id
            ) rs ON rs.stock_in_record_id = sir.id
            WHERE sir.approval_status = 1
              AND sir.record_type IN ('7','10')
              AND (
                  (sir.record_type = '7' AND sir.record_id = pl.id)
                  OR (sir.record_type = '10' AND qi.purchase_ledger_id = pl.id)
              )
              AND slp.id IS NOT NULL
              AND GREATEST(sir.stock_in_num - COALESCE(rs.total_return_num, 0), 0) > 0
        )
        ORDER BY pl.entry_date DESC
    </select>
    <select id="selectTotalPurchaseAmount" resultType="java.math.BigDecimal">
        SELECT IFNULL(SUM(contract_amount), 0)
        FROM purchase_ledger
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
@@ -8,6 +8,10 @@
        <result column="no" property="no" />
        <result column="return_type" property="returnType" />
        <result column="supplier_id" property="supplierId" />
        <result column="shipping_type" property="shippingType" />
        <result column="truck_plate_no" property="truckPlateNo" />
        <result column="express_company" property="expressCompany" />
        <result column="express_no" property="expressNo" />
        <result column="project_id" property="projectId" />
        <result column="project_phase" property="projectPhase" />
        <result column="prepared_at" property="preparedAt" />
@@ -22,7 +26,18 @@
        SELECT
            pro.*,
            sm.supplier_name as supplier_name,
            pl.purchase_contract_number as purchase_contract_number
            pl.purchase_contract_number as purchase_contract_number,
            CASE
                WHEN EXISTS (
                    SELECT 1
                    FROM purchase_return_order_products prop
                    INNER JOIN stock_out_record sor ON sor.record_id = prop.id
                        AND sor.record_type = '9'
                        AND sor.approval_status = 1
                    WHERE prop.purchase_return_order_id = pro.id
                ) THEN 1
                ELSE 0
            END AS stock_out_approved
        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
@@ -59,6 +74,10 @@
                pro.no returnNo,
                t.inboundBatches,
                sm.supplier_name,
                pro.shipping_type,
                pro.truck_plate_no,
                pro.express_company,
                pro.express_no,
                pro.prepared_at,
                pro.total_amount,
                CASE pro.return_type WHEN 0 THEN '退货退款' WHEN 1 THEN '拒收' END AS returnType,
@@ -100,13 +119,22 @@
            sir.stock_in_num,
            sir.batch_no,
            slp.tax_inclusive_unit_price,
            GREATEST(sir.stock_in_num - COALESCE(rs.total_return_num, 0), 0) AS un_quantity,
            COALESCE(rs.total_return_num, 0) AS total_return_num
            GREATEST(
                sir.stock_in_num
                - COALESCE(rs.total_return_num, 0)
                - COALESCE(so.total_sale_out_num, 0),
                0
            ) AS un_quantity,
            COALESCE(rs.total_return_num, 0) AS total_return_num,
            COALESCE(so.total_sale_out_num, 0) AS saleOutQuantity
            FROM stock_in_record sir
            LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
            LEFT JOIN purchase_ledger pl
            ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
            LEFT JOIN sales_ledger_product slp ON pl.id = slp.sales_ledger_id
            LEFT JOIN sales_ledger_product slp
                ON pl.id = slp.sales_ledger_id
               AND slp.product_model_id = sir.product_model_id
               AND slp.type = 2
            LEFT JOIN (
                SELECT
                    stock_in_record_id,
@@ -116,7 +144,24 @@
                WHERE 1=1
                GROUP BY stock_in_record_id
            ) rs ON rs.stock_in_record_id = sir.id
        WHERE sir.approval_status = 1 AND slp.type = 2
            LEFT JOIN (
                SELECT
                    inv.batch_no,
                    inv.product_model_id,
                    SUM(spd.quantity) AS total_sale_out_num
                FROM shipping_product_detail spd
                INNER JOIN shipping_info si
                    ON si.id = spd.shipping_info_id
                INNER JOIN stock_out_record sor
                    ON sor.record_id = si.id
                   AND sor.record_type = '13'
                   AND sor.approval_status IN (0, 1, 3)
                INNER JOIN stock_inventory inv
                    ON inv.id = spd.stock_inventory_id
                GROUP BY inv.batch_no, inv.product_model_id
            ) so ON so.product_model_id = sir.product_model_id
               AND (so.batch_no = sir.batch_no OR (so.batch_no IS NULL AND sir.batch_no IS NULL))
        WHERE sir.approval_status = 1
        AND sir.record_type IN ('7','10')
         and pl.id = #{purchaseLedgerId}
    </select>
@@ -135,8 +180,14 @@
           slp.tax_inclusive_unit_price,
           prop.return_quantity,
           prop.purchase_return_order_id,
           GREATEST(sir.stock_in_num - COALESCE(rs1.total_return_num1, 0), 0) AS un_quantity,
           COALESCE(rs.total_return_num, 0)                             AS total_return_num
           GREATEST(
               sir.stock_in_num
               - COALESCE(rs.total_return_num, 0)
               - COALESCE(so.total_sale_out_num, 0),
               0
           ) AS un_quantity,
           COALESCE(rs.total_return_num, 0)                             AS total_return_num,
           COALESCE(so.total_sale_out_num, 0)                           AS saleOutQuantity
    from purchase_return_order_products prop
    left join purchase_return_orders pro on prop.purchase_return_order_id = pro.id
    LEFT JOIN stock_in_record sir ON prop.stock_in_record_id = sir.id and sir.record_type in ('7','10')
@@ -144,13 +195,25 @@
    LEFT JOIN (SELECT stock_in_record_id,
                      SUM(return_quantity) AS total_return_num
               FROM purchase_return_order_products
               WHERE 1 = 1 and purchase_return_order_id = #{id}
               WHERE 1 = 1
               GROUP BY stock_in_record_id) rs ON rs.stock_in_record_id = sir.id
    LEFT JOIN (SELECT stock_in_record_id,
                      SUM(return_quantity) AS total_return_num1
               FROM purchase_return_order_products
               WHERE 1 = 1 and purchase_return_order_id = #{id}
               GROUP BY stock_in_record_id) rs1 ON rs1.stock_in_record_id = sir.id
    LEFT JOIN (
        SELECT
            inv.batch_no,
            inv.product_model_id,
            SUM(spd.quantity) AS total_sale_out_num
        FROM shipping_product_detail spd
        INNER JOIN shipping_info si
            ON si.id = spd.shipping_info_id
        INNER JOIN stock_out_record sor
            ON sor.record_id = si.id
           AND sor.record_type = '13'
           AND sor.approval_status IN (0, 1, 3)
        INNER JOIN stock_inventory inv
            ON inv.id = spd.stock_inventory_id
        GROUP BY inv.batch_no, inv.product_model_id
    ) so ON so.product_model_id = sir.product_model_id
       AND (so.batch_no = sir.batch_no OR (so.batch_no IS NULL AND sir.batch_no IS NULL))
    where pro.id = #{id}
    </select>
</mapper>
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -34,7 +34,9 @@
        ELSE 0
        END as has_sufficient_stock,
        (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0) - IFNULL(t5.pending_approval_quantity, 0)) as no_quantity,
        IFNULL(t3.shipped_quantity, 0) as shipped_quantity,
        IFNULL(t6.return_quantity, 0) as return_quantity,
        IFNULL(t4.approved_stock_in_num, 0) as approved_stock_in_num,
        IFNULL(t5.pending_approval_quantity, 0) as pending_approval_quantity,
        CASE
         WHEN IFNULL(t3.shipped_quantity, 0) = 0 AND IFNULL(t5.pending_approval_quantity, 0) = 0 THEN '待发货'
@@ -56,16 +58,59 @@
        GROUP BY product_model_id
        ) t2 ON T1.product_model_id = t2.product_model_id
        LEFT JOIN (
        SELECT si.sales_ledger_product_id, IFNULL(SUM(spd.quantity), 0) as shipped_quantity
        SELECT
            slp.id AS sales_ledger_product_id,
            IFNULL(SUM(so.total_sale_out_num), 0) AS shipped_quantity
        FROM sales_ledger_product slp
        LEFT JOIN (
            SELECT
                pl.id AS purchase_ledger_id,
                sir.product_model_id,
                SUM(spd.quantity) AS total_sale_out_num
            FROM shipping_product_detail spd
            INNER JOIN shipping_info si
                ON si.id = spd.shipping_info_id
            INNER JOIN stock_out_record sor
                ON sor.record_id = si.id
               AND TRIM(sor.record_type) = '13'
               AND sor.approval_status IN (0, 1, 3)
            INNER JOIN stock_inventory inv
                ON inv.id = spd.stock_inventory_id
            INNER JOIN stock_in_record sir
                ON sir.product_model_id = inv.product_model_id
               AND (
                    (sir.batch_no IS NOT NULL AND sir.batch_no = inv.batch_no)
                    OR (sir.batch_no IS NULL AND inv.batch_no IS NULL)
               )
               AND sir.approval_status = 1
               AND sir.record_type IN ('7', '10')
            LEFT JOIN quality_inspect qi
                ON sir.record_type = '10'
               AND sir.record_id = qi.id
            LEFT JOIN purchase_ledger pl
                ON pl.id = IF(sir.record_type = '7', sir.record_id, qi.purchase_ledger_id)
            WHERE si.status IN ('审核通过', '已发货')
              AND pl.id IS NOT NULL
            GROUP BY pl.id, sir.product_model_id
        ) so ON so.purchase_ledger_id = slp.sales_ledger_id
            AND so.product_model_id = slp.product_model_id
        WHERE slp.type = 2
        GROUP BY slp.id
        UNION ALL
        -- 销售台账(type=1): shipping_info 直接挂 sales_ledger_product_id,只统计出库审核通过(approval_status=1)的发货量,与 t5 的 pending 逻辑互补
        SELECT
            si.sales_ledger_product_id AS sales_ledger_product_id,
            IFNULL(SUM(spd.quantity), 0) AS shipped_quantity
        FROM shipping_info si
        INNER JOIN shipping_product_detail spd ON si.id = spd.shipping_info_id
        WHERE si.status IN ('审核通过', '已发货')
          AND EXISTS (
            SELECT 1 FROM stock_out_record sor
            WHERE sor.record_id = si.id
        INNER JOIN stock_out_record sor
            ON sor.record_id = si.id
              AND TRIM(sor.record_type) = '13'
              AND sor.approval_status = 1
          )
        WHERE si.status IN ('审核通过', '已发货')
          AND si.sales_ledger_product_id IS NOT NULL
        GROUP BY si.sales_ledger_product_id
        ) t3 ON t3.sales_ledger_product_id = T1.id
        LEFT JOIN (
@@ -115,10 +160,27 @@
        GROUP BY si.sales_ledger_product_id
        ) t5 ON t5.sales_ledger_product_id = T1.id
        LEFT JOIN (
        SELECT prop.sales_ledger_product_id, IFNULL(SUM(prop.return_quantity), 0) AS return_quantity
            SELECT
                prop.sales_ledger_product_id,
                IFNULL(SUM(prop.return_quantity), 0) AS return_quantity
        FROM purchase_return_order_products prop
        INNER JOIN purchase_return_orders pro ON prop.purchase_return_order_id = pro.id
            LEFT JOIN purchase_return_orders pro
                ON pro.id = prop.purchase_return_order_id
        GROUP BY prop.sales_ledger_product_id
            UNION ALL
            SELECT
                si.sales_ledger_product_id,
                IFNULL(SUM(rsp.num), 0) AS return_quantity
            FROM return_sale_product rsp
            INNER JOIN return_management rm ON rm.id = rsp.return_management_id
            INNER JOIN shipping_info si ON si.id = rm.shipping_id
            INNER JOIN stock_in_record sir ON sir.record_id = rsp.id
                AND TRIM(sir.record_type) IN ('14','15')
                AND sir.approval_status = 1
            WHERE si.sales_ledger_product_id IS NOT NULL
            GROUP BY si.sales_ledger_product_id
        ) t6 ON t6.sales_ledger_product_id = T1.id
        left join product_model pm ON T1.product_model_id = pm.id
        left join product p ON pm.product_id = p.id
src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -78,7 +78,8 @@
            sor.batch_no,
            slp.tax_inclusive_unit_price,
            GREATEST(sor.stock_out_num - COALESCE(rs.total_return_num, 0), 0) AS un_quantity,
            COALESCE(rs.total_return_num, 0) AS total_return_num
            COALESCE(rs.return_num, 0) AS total_return_num,
            COALESCE(rs.pending_return_num, 0) AS pending_return_num
        FROM shipping_info si
        LEFT JOIN shipping_product_detail spd ON spd.shipping_info_id = si.id
        LEFT JOIN stock_out_record sor ON sor.record_id = si.id and sor.record_type = '13'
@@ -86,7 +87,9 @@
        LEFT JOIN (
        SELECT
        stock_out_record_id,
        SUM(num) AS total_return_num
        SUM(num) AS total_return_num,
        SUM( CASE WHEN rsp.STATUS = 0 THEN rsp.num ELSE 0 END ) AS pending_return_num ,
        SUM( CASE WHEN rsp.STATUS = 1 THEN rsp.num ELSE 0 END ) AS 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
src/main/resources/mapper/stock/StockInRecordMapper.xml
@@ -21,9 +21,9 @@
        pm.unit,
        u.nick_name as createBy,
        CASE
            WHEN #{params.topParentProductId} = 278 AND TRIM(sir.record_type) = '7' THEN
            WHEN TRIM(sir.record_type) = '7' THEN
                COALESCE(pl_by_product.purchase_contract_number, pl_direct.purchase_contract_number)
            WHEN #{params.topParentProductId} = 278 AND TRIM(sir.record_type) = '10' THEN
            WHEN TRIM(sir.record_type) = '10' THEN
                pl_by_quality.purchase_contract_number
            WHEN #{params.topParentProductId} = 276 THEN
                CASE
src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -352,7 +352,11 @@
        su.nick_name as create_by
        from
        stock_in_record sir
        left join stock_inventory si on sir.product_model_id = si.product_model_id
        left join (
            select product_model_id, sum(qualitity) as qualitity
            from stock_inventory
            group by product_model_id
        ) si on sir.product_model_id = si.product_model_id
        left join product_model pm on sir.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        left join sys_user su on sir.create_user = su.user_id