3 天以前 5b8e83c5170e570b9247946e799556b53445c458
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -1,6 +1,7 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
@@ -8,15 +9,17 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
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.aftersalesservice.pojo.AfterSalesService;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.EnumUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -25,23 +28,23 @@
import com.ruoyi.other.mapper.TempFileMapper;
import com.ruoyi.other.pojo.TempFile;
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductionProductMainService;
import com.ruoyi.production.service.impl.ProductionProductMainServiceImpl;
import com.ruoyi.project.system.domain.SysDept;
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.quality.pojo.QualityInspect;
import com.ruoyi.sales.dto.*;
import com.ruoyi.sales.mapper.*;
import com.ruoyi.sales.pojo.*;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import com.ruoyi.sales.service.ISalesLedgerService;
import com.ruoyi.sales.service.ReceiptPaymentService;
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;
@@ -79,69 +82,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;
@@ -151,11 +126,50 @@
    private ProductStructureMapper productStructureMapper;
    @Autowired
    private ProductionProductMainService productionProductMainService;
    @Autowired
    private PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
    ;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private ReceiptPaymentService receiptPaymentService;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
        return salesLedgerMapper.selectSalesLedgerList(salesLedgerDto);
    }
    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
@@ -329,9 +343,6 @@
        return salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto);
    }
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult importData(MultipartFile file) {
@@ -400,6 +411,7 @@
                salesLedgerMapper.insert(salesLedger);
                List<SalesLedgerProduct> insertedProducts = new ArrayList<>();
                for (SalesLedgerProductImportDto salesLedgerProductImportDto : salesLedgerProductImportDtos) {
                    SalesLedgerProduct salesLedgerProduct = new SalesLedgerProduct();
                    BeanUtils.copyProperties(salesLedgerProductImportDto, salesLedgerProduct);
@@ -431,9 +443,12 @@
                    salesLedgerProduct.setApproveStatus(0);
                    salesLedgerProduct.setPendingInvoiceTotal(salesLedgerProductImportDto.getTaxInclusiveTotalPrice());
                    salesLedgerProductMapper.insert(salesLedgerProduct);
                    insertedProducts.add(salesLedgerProduct);
                    // 添加生产数据
                    salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
                }
                // 导入时同样自动全额开票、自动全额回款
                autoInvoiceAndReceiptPayment(salesLedger, insertedProducts);
            }
            return AjaxResult.success("导入成功");
@@ -455,7 +470,7 @@
    @Override
    public IPage<SalesLedgerDto> listSalesLedger(SalesLedgerDto salesLedgerDto, Page page) {
        IPage<SalesLedgerDto> salesLedgerDtoIPage = salesLedgerMapper.listSalesLedger(page, salesLedgerDto);
        IPage<SalesLedgerDto> salesLedgerDtoIPage = salesLedgerMapper.listSalesLedgerAndShipped(page, salesLedgerDto);
        for (SalesLedgerDto salesLedger : salesLedgerDtoIPage.getRecords()) {
            LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
            productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId());
@@ -473,49 +488,22 @@
                        .eq(ShippingInfo::getSalesLedgerProductId, product.getId())
                        .orderByDesc(ShippingInfo::getCreateTime)
                        .last("limit 1"));
                product.setShippingCarNumber(shippingInfo.getShippingCarNumber());
                product.setShippingDate(shippingInfo.getShippingDate());
                if (shippingInfo != null) {
                    product.setShippingStatus(shippingInfo.getStatus());
                }
            }
            // 过滤只保留发货记录
            products = products.stream().filter(product -> "已发货".equals(product.getShippingStatus())).collect(Collectors.toList());
            if (!products.isEmpty()) {
                salesLedger.setHasChildren(true);
                salesLedger.setProductData(products);
            }
        }
        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;
        }
    }
    /**
@@ -587,6 +575,16 @@
        if (CollectionUtils.isNotEmpty(invoiceLedgerIds)) {
            LambdaQueryWrapper<ReceiptPayment> wrapperTree = new LambdaQueryWrapper<>();
            wrapperTree.in(ReceiptPayment::getInvoiceLedgerId, invoiceLedgerIds);
            List<ReceiptPayment> receiptPayments = receiptPaymentMapper.selectList(wrapperTree);
            // 同步删除回款对应的财务收入记录,避免产生孤儿数据
            if (CollectionUtils.isNotEmpty(receiptPayments)) {
                List<Long> receiptPaymentIds = receiptPayments.stream()
                        .map(item -> item.getId().longValue())
                        .collect(Collectors.toList());
                accountIncomeService.remove(new LambdaQueryWrapper<AccountIncome>()
                        .in(AccountIncome::getBusinessId, receiptPaymentIds)
                        .eq(AccountIncome::getBusinessType, 1));
            }
            receiptPaymentMapper.delete(wrapperTree);
        }
        // 删除发货台账记录
@@ -625,7 +623,8 @@
            salesLedger.setCustomerName(customer.getCustomerName());
            salesLedger.setTenantId(customer.getTenantId());
            // 3. 新增或更新主表
            if (salesLedger.getId() == null) {
            boolean isNew = salesLedger.getId() == null;
            if (isNew) {
                String contractNo = generateSalesContractNo();
                salesLedger.setSalesContractNo(contractNo);
                salesLedgerMapper.insert(salesLedger);
@@ -636,7 +635,7 @@
            // 4. 处理子表数据
            List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
            if (productList != null && !productList.isEmpty()) {
                handleSalesLedgerProducts(salesLedger.getId(), productList, salesLedgerDto.getType());
                handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
                updateMainContractAmount(
                        salesLedger.getId(),
                        productList,
@@ -650,13 +649,16 @@
            if (salesLedgerDto.getTempFileIds() != null && !salesLedgerDto.getTempFileIds().isEmpty()) {
                migrateTempFilesToFormal(salesLedger.getId(), salesLedgerDto.getTempFileIds());
            }
            // 6. 新增台账时自动全额开票、自动全额回款
            if (isNew && productList != null && !productList.isEmpty()) {
                autoInvoiceAndReceiptPayment(salesLedger, productList);
            }
            return 1;
        } catch (IOException e) {
            throw new BaseException("文件迁移失败: " + e.getMessage());
        }
    }
    // 文件迁移方法
    /**
     * 将临时文件迁移到正式目录
@@ -733,8 +735,10 @@
        }
    }
    // 文件迁移方法
    private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, Integer type) {
    @Override
    public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
        // 按ID分组,区分新增和更新的记录
        Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream()
                .peek(p -> p.setSalesLedgerId(salesLedgerId))
@@ -746,14 +750,14 @@
        // 执行更新操作
        if (!updateList.isEmpty()) {
            for (SalesLedgerProduct product : updateList) {
                product.setType(type);
                product.setType(type.getCode());
                salesLedgerProductMapper.updateById(product);
            }
        }
        // 执行插入操作
        if (!insertList.isEmpty()) {
            for (SalesLedgerProduct salesLedgerProduct : insertList) {
                salesLedgerProduct.setType(type);
                salesLedgerProduct.setType(type.getCode());
                salesLedgerProduct.setNoInvoiceNum(salesLedgerProduct.getQuantity());
                salesLedgerProduct.setNoInvoiceAmount(salesLedgerProduct.getTaxInclusiveTotalPrice());
                salesLedgerProduct.setPendingInvoiceTotal(salesLedgerProduct.getTaxInclusiveTotalPrice());
@@ -762,6 +766,108 @@
                salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
            }
        }
    }
    /**
     * 新增销售台账时自动全额开票、自动全额回款
     * <p>
     * 1. 按合同全额自动生成开票登记(invoice_registration + invoice_registration_product + invoice_ledger);
     * 2. 按开票全额自动生成回款登记(receipt_payment)及财务收入记录(account_income)。
     *
     * @param salesLedger 销售台账主表(已入库)
     * @param products    台账产品列表(已入库,含主键ID)
     */
    private void autoInvoiceAndReceiptPayment(SalesLedger salesLedger, List<SalesLedgerProduct> products) {
        // 仅处理销售类产品
        List<SalesLedgerProduct> saleProducts = products.stream()
                .filter(Objects::nonNull)
                .filter(p -> p.getId() != null)
                .filter(p -> SaleEnum.SALE.getCode().equals(p.getType()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(saleProducts)) {
            return;
        }
        String register = SecurityUtils.getLoginUser().getUser().getNickName();
        // 1. 自动开票:生成一张开票登记主表,覆盖该台账全部产品
        InvoiceRegistration invoiceRegistration = new InvoiceRegistration();
        invoiceRegistration.setSalesLedgerId(salesLedger.getId().intValue());
        invoiceRegistration.setSalesContractNo(salesLedger.getSalesContractNo());
        invoiceRegistration.setCustomerId(salesLedger.getCustomerId() == null ? null : salesLedger.getCustomerId().intValue());
        invoiceRegistration.setSalesman(salesLedger.getSalesman());
        invoiceRegistration.setProjectName(salesLedger.getProjectName());
        invoiceRegistrationMapper.insert(invoiceRegistration);
        List<ReceiptPayment> receiptPaymentList = new ArrayList<>();
        for (SalesLedgerProduct product : saleProducts) {
            BigDecimal invoiceNum = product.getQuantity();
            BigDecimal invoiceAmount = product.getTaxInclusiveTotalPrice();
            // 数量或金额缺失时跳过,不阻断台账新增
            if (invoiceNum == null || invoiceAmount == null
                    || invoiceNum.compareTo(BigDecimal.ZERO) <= 0
                    || invoiceAmount.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            // 1.1 产品未开票数量/金额清零(全额开票)
            salesLedgerProductMapper.update(
                    null,
                    new LambdaUpdateWrapper<SalesLedgerProduct>()
                            .eq(SalesLedgerProduct::getId, product.getId())
                            .set(SalesLedgerProduct::getNoInvoiceNum, BigDecimal.ZERO)
                            .set(SalesLedgerProduct::getNoInvoiceAmount, BigDecimal.ZERO)
            );
            // 1.2 开票登记产品
            InvoiceRegistrationProduct invoiceRegistrationProduct = new InvoiceRegistrationProduct();
            BeanUtils.copyProperties(product, invoiceRegistrationProduct);
            invoiceRegistrationProduct.setId(null);
            invoiceRegistrationProduct.setSalesLedgerId(salesLedger.getId().intValue());
            invoiceRegistrationProduct.setInvoiceRegistrationId(invoiceRegistration.getId());
            invoiceRegistrationProduct.setSalesLedgerProductId(product.getId().intValue());
            invoiceRegistrationProduct.setInvoiceNum(invoiceNum);
            invoiceRegistrationProduct.setInvoiceAmount(invoiceAmount);
            invoiceRegistrationProduct.setNoInvoiceNum(BigDecimal.ZERO);
            invoiceRegistrationProduct.setNoInvoiceAmount(BigDecimal.ZERO);
            invoiceRegistrationProductMapper.insert(invoiceRegistrationProduct);
            // 1.3 开票台账(发票号自动生成)
            InvoiceLedger invoiceLedger = new InvoiceLedger();
            invoiceLedger.setInvoiceDate(LocalDate.now());
            invoiceLedger.setInvoiceRegistrationProductId(invoiceRegistrationProduct.getId());
            invoiceLedger.setInvoiceTotal(invoiceAmount);
            invoiceLedger.setInvoiceNo(generateAutoInvoiceNo(product.getId()));
            invoiceLedger.setCreateUser(SecurityUtils.getUserId().intValue());
            invoiceLedger.setInvoicePerson(register);
            invoiceLedgerMapper.insert(invoiceLedger);
            // 2. 组装自动回款记录(按开票全额回款)
            ReceiptPayment receiptPayment = new ReceiptPayment();
            receiptPayment.setReceiptPaymentType("0");
            receiptPayment.setReceiptPaymentAmount(invoiceAmount);
            receiptPayment.setRegistrant(register);
            receiptPayment.setInvoiceLedgerId(invoiceLedger.getId());
            receiptPayment.setSalesLedgerId(salesLedger.getId());
            receiptPayment.setSalesLedgerProductId(product.getId());
            receiptPayment.setReceiptPaymentDate(LocalDate.now());
            receiptPaymentList.add(receiptPayment);
        }
        // 3. 自动回款(内部同步更新产品回款金额并生成财务收入记录)
        if (!receiptPaymentList.isEmpty()) {
            receiptPaymentService.receiptPaymentSaveOrUpdate(receiptPaymentList);
        }
    }
    /**
     * 生成自动开票发票号
     *
     * @param salesLedgerProductId 销售台账产品ID
     * @return 自动发票号
     */
    private String generateAutoInvoiceNo(Long salesLedgerProductId) {
        return "AUTO" + System.currentTimeMillis() + salesLedgerProductId;
    }
    private SalesLedger convertToEntity(SalesLedgerDto dto) {
@@ -873,4 +979,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;
        }
    }
}