liding
5 天以前 dce840653b78fc05a732c5c89773c74e15dbed09
src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java
@@ -6,18 +6,23 @@
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SalesLedgerType;
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;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.other.mapper.TempFileMapper;
import com.ruoyi.other.pojo.TempFile;
import com.ruoyi.purchase.dto.PaymentRegistrationDto;
import com.ruoyi.purchase.dto.PurchaseLedgerDto;
import com.ruoyi.purchase.dto.TicketRegistrationDto;
import com.ruoyi.purchase.mapper.PaymentRegistrationMapper;
import com.ruoyi.purchase.mapper.ProductRecordMapper;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.mapper.TicketRegistrationMapper;
import com.ruoyi.purchase.pojo.PaymentRegistration;
import com.ruoyi.purchase.pojo.ProductRecord;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.pojo.TicketRegistration;
@@ -26,6 +31,7 @@
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
@@ -36,6 +42,7 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -73,6 +80,9 @@
    private final ProductRecordMapper productRecordMapper;
    @Autowired
    private ISalesLedgerProductService salesLedgerProductService;
    @Autowired
    private PaymentRegistrationMapper paymentRegistrationMapper;
    @Value("${file.upload-dir}")
@@ -82,6 +92,8 @@
    @Override
    public List<TicketRegistration> selectTicketRegistrationList(TicketRegistration ticketRegistration) {
        LambdaQueryWrapper<TicketRegistration> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TicketRegistration::getTenantId,SecurityUtils.getLoginUser().getTenantId())
                .eq(TicketRegistration::getDeleteFlag,0);
        if (StringUtils.isNotBlank(ticketRegistration.getPurchaseContractNumber())) {
            queryWrapper.like(TicketRegistration::getPurchaseContractNumber, ticketRegistration.getPurchaseContractNumber())
                    .like(TicketRegistration::getSupplierName, ticketRegistration.getSupplierName())
@@ -113,20 +125,64 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateRegistration(TicketRegistrationDto ticketRegistrationDto) throws IOException {
        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
        Long userId = SecurityUtils.getUserId();
        if (ticketRegistrationDto.getPurchaseLedgerId() == null) throw new BaseException("请选择采购单");
        if (StringUtils.isBlank(ticketRegistrationDto.getInvoiceNumber())) throw new BaseException("发票号码不能为空");
        if (ticketRegistrationDto.getIssueDate() == null) throw new BaseException("开票日期不能为空");
        if (ticketRegistrationDto.getInvoiceAmount() == null || ticketRegistrationDto.getInvoiceAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BaseException("发票金额必须大于0");
        }
        // 1. 查询采购台账记录
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(ticketRegistrationDto.getPurchaseLedgerId());
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                .eq(PurchaseLedger::getId,ticketRegistrationDto.getPurchaseLedgerId())
                .eq(PurchaseLedger::getTenantId,tenantId));
        if (purchaseLedger == null) {
            // 处理采购台账不存在的情况,例如抛出异常或返回错误
            throw new IllegalArgumentException("采购台账记录不存在,ID: " + ticketRegistrationDto.getPurchaseLedgerId());
        }
        LambdaQueryWrapper<TicketRegistration> duplicate = new LambdaQueryWrapper<TicketRegistration>()
                .eq(TicketRegistration::getTenantId, tenantId)
                .eq(TicketRegistration::getInvoiceNumber, ticketRegistrationDto.getInvoiceNumber())
                .eq(TicketRegistration::getDeleteFlag, 0);
        if (ticketRegistrationDto.getId() != null) duplicate.ne(TicketRegistration::getId, ticketRegistrationDto.getId());
        if (ticketRegistrationMapper.selectCount(duplicate) > 0) throw new BaseException("该发票号码已登记");
        BigDecimal registered = ticketRegistrationMapper.selectList(new LambdaQueryWrapper<TicketRegistration>()
                .eq(TicketRegistration::getTenantId,tenantId)
                .eq(TicketRegistration::getPurchaseLedgerId,purchaseLedger.getId())
                .eq(TicketRegistration::getDeleteFlag,0)
                .ne(ticketRegistrationDto.getId()!=null,TicketRegistration::getId,ticketRegistrationDto.getId()))
                .stream().map(TicketRegistration::getInvoiceAmount).filter(java.util.Objects::nonNull)
                .reduce(BigDecimal.ZERO,BigDecimal::add);
        if (purchaseLedger.getContractAmount()!=null && registered.add(ticketRegistrationDto.getInvoiceAmount()).compareTo(purchaseLedger.getContractAmount())>0) {
            throw new BaseException("来票金额超过采购单剩余可登记金额:" + purchaseLedger.getContractAmount().subtract(registered));
        }
        // 3. 创建或更新票据登记实体
        TicketRegistration ticketRegistration = new TicketRegistration();
        BeanUtils.copyProperties(ticketRegistrationDto, ticketRegistration);
        ticketRegistration.setPurchaseContractNumber(purchaseLedger.getPurchaseContractNumber());
        ticketRegistration.setTenantId(purchaseLedger.getTenantId());
        ticketRegistration.setTenantId(tenantId);
        ticketRegistration.setSupplierId(purchaseLedger.getSupplierId());
        ticketRegistration.setSupplierName(purchaseLedger.getSupplierName());
        ticketRegistration.setIssueDate(ticketRegistrationDto.getIssueDate());
        ticketRegistration.setContractAmount(purchaseLedger.getContractAmount());
        ticketRegistration.setSalesLedgerId(purchaseLedger.getSalesLedgerId());
        ticketRegistration.setEnterDate(ticketRegistrationDto.getEnterDate());
        ticketRegistration.setCurrency(StringUtils.isBlank(ticketRegistration.getCurrency()) ? "CNY" : ticketRegistration.getCurrency());
        ticketRegistration.setInvoiceStatus(ticketRegistration.getInvoiceStatus()==null?1:ticketRegistration.getInvoiceStatus());
        ticketRegistration.setVerificationStatus(ticketRegistration.getVerificationStatus()==null?0:ticketRegistration.getVerificationStatus());
        ticketRegistration.setDeleteFlag(0);
        ticketRegistration.setUpdateUser(userId);
        ticketRegistration.setUpdateTime(DateUtils.getNowDate());
        if (ticketRegistration.getTaxAmount()==null && ticketRegistration.getTaxRate()!=null) {
            BigDecimal rate=ticketRegistration.getTaxRate().compareTo(BigDecimal.ONE)>0?ticketRegistration.getTaxRate().divide(new BigDecimal("100"),6,RoundingMode.HALF_UP):ticketRegistration.getTaxRate();
            ticketRegistration.setTaxAmount(ticketRegistration.getInvoiceAmount().multiply(rate).divide(BigDecimal.ONE.add(rate),2,RoundingMode.HALF_UP));
        }
        // 4. 处理子表数据
        List<SalesLedgerProduct> productData = ticketRegistrationDto.getProductData();
@@ -135,20 +191,38 @@
        }
        // 5. 执行插入或更新操作
        int rowsAffected = ticketRegistrationMapper.insert(ticketRegistration);
        int rowsAffected;
        if (ticketRegistration.getId()==null) {
            ticketRegistration.setCreateUser(userId);
            rowsAffected=ticketRegistrationMapper.insert(ticketRegistration);
        } else {
            rowsAffected=ticketRegistrationMapper.update(ticketRegistration,new LambdaQueryWrapper<TicketRegistration>()
                    .eq(TicketRegistration::getId,ticketRegistration.getId()).eq(TicketRegistration::getTenantId,tenantId));
            productRecordMapper.delete(new LambdaQueryWrapper<ProductRecord>().eq(ProductRecord::getTicketRegistrationId,ticketRegistration.getId()).eq(ProductRecord::getTenantId,tenantId));
        }
        // 6. 增加采购台账产品开票记录
        List<SalesLedgerProduct> salesLedgerProducts = ticketRegistrationDto.getProductData();
        if (CollectionUtils.isNotEmpty(salesLedgerProducts)) {
            int insert = 0 ;
            for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
                ProductRecord productRecord = new ProductRecord();
                productRecord.setTicketRegistrationId(ticketRegistration.getId());
                productRecord.setPurchaseLedgerId(ticketRegistrationDto.getPurchaseLedgerId());
                productRecord.setCreatedAt(DateUtils.getNowDate());
                BeanUtils.copyProperties(salesLedgerProduct, productRecord);
                productRecord.setId(null);
                productRecord.setType("2");
                productRecordMapper.insert(productRecord);
                //排除掉开票为0的数据
                if (salesLedgerProduct.getTicketsNum() != null && salesLedgerProduct.getTicketsNum().compareTo(BigDecimal.ZERO) > 0) {
                    ProductRecord productRecord = new ProductRecord();
                    productRecord.setTicketRegistrationId(ticketRegistration.getId());
                    productRecord.setPurchaseLedgerId(ticketRegistrationDto.getPurchaseLedgerId());
                    productRecord.setCreatedAt(DateUtils.getNowDate());
                    BeanUtils.copyProperties(salesLedgerProduct, productRecord);
                    productRecord.setSaleLedgerProjectId(salesLedgerProduct.getId());
                    productRecord.setId(null);
                    productRecord.setType("2");
                    productRecord.setTenantId(tenantId);
                     insert = productRecordMapper.insert(productRecord);
                }
                if (insert <= 0) {
                    throw new RuntimeException("产品开票数都为0,请检查");
                }
            }
        }
        // 迁移临时文件到正式目录
@@ -166,7 +240,7 @@
     * @param tempFileIds 临时文件ID列表
     * @throws IOException 文件操作异常
     */
    private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
    public void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
        if (CollectionUtils.isEmpty(tempFileIds)) {
            return;
        }
@@ -192,21 +266,25 @@
            // 构建正式文件名(包含业务ID和时间戳,避免冲突)
            String originalFilename = tempFile.getOriginalName();
            String fileExtension = FilenameUtils.getExtension(originalFilename);
            String baseName = FilenameUtils.getBaseName(originalFilename);
            String formalFilename = businessId + "_" +
                    System.currentTimeMillis() + "_" +
                    UUID.randomUUID().toString().substring(0, 8) +
                    UUID.randomUUID().toString().substring(0, 8) +baseName+
                    (com.ruoyi.common.utils.StringUtils.hasText(fileExtension) ? "." + fileExtension : "");
            Path formalFilePath = formalDirPath.resolve(formalFilename);
            try {
                // 执行文件迁移(使用原子操作确保安全性)
                Files.move(
                        Paths.get(tempFile.getTempPath()),
                        formalFilePath,
                        StandardCopyOption.REPLACE_EXISTING,
                        StandardCopyOption.ATOMIC_MOVE
                );
//                Files.move(
//                        Paths.get(tempFile.getTempPath()),
//                        formalFilePath,
//                        StandardCopyOption.REPLACE_EXISTING,
//                        StandardCopyOption.ATOMIC_MOVE
//                );
                // 原子移动失败,使用复制+删除
                Files.copy(Paths.get(tempFile.getTempPath()), formalFilePath, StandardCopyOption.REPLACE_EXISTING);
                Files.deleteIfExists(Paths.get(tempFile.getTempPath()));
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
                // 更新文件记录(关联到业务ID)
@@ -215,7 +293,7 @@
                fileRecord.setName(originalFilename);
                fileRecord.setUrl(formalFilePath.toString());
                fileRecord.setCreateTime(LocalDateTime.now());
                fileRecord.setType(tempFile.getType());
                fileRecord.setType(FileNameType.INVOICE.getValue());
                commonFileMapper.insert(fileRecord);
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
@@ -229,19 +307,36 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delRegistration(Long[] ids) {
        return ticketRegistrationMapper.deleteBatchIds(Arrays.asList(ids));
        Long tenantId=SecurityUtils.getLoginUser().getTenantId();
        LambdaQueryWrapper<ProductRecord> productRecordLambdaQueryWrapper = new LambdaQueryWrapper<>();
        productRecordLambdaQueryWrapper.in(ProductRecord::getTicketRegistrationId, Arrays.asList(ids)).eq(ProductRecord::getTenantId,tenantId);
        List<ProductRecord> productRecords = productRecordMapper.selectList(productRecordLambdaQueryWrapper);
        for (ProductRecord productRecord : productRecords) {
            SalesLedgerProduct product=salesLedgerProductMapper.selectById(productRecord.getSaleLedgerProjectId());
            if(product!=null){
                product.setTicketsNum(nvl(product.getTicketsNum()).subtract(nvl(productRecord.getTicketsNum())).max(BigDecimal.ZERO));
                product.setTicketsAmount(nvl(product.getTicketsAmount()).subtract(nvl(productRecord.getTicketsAmount())).max(BigDecimal.ZERO));
                product.setFutureTickets(nvl(product.getFutureTickets()).add(nvl(productRecord.getTicketsNum())));
                product.setFutureTicketsAmount(nvl(product.getFutureTicketsAmount()).add(nvl(productRecord.getTicketsAmount())));
                salesLedgerProductMapper.updateById(product);
            }
        }
        productRecordMapper.delete(productRecordLambdaQueryWrapper);
        paymentRegistrationMapper.delete(new LambdaQueryWrapper<PaymentRegistration>().in(PaymentRegistration::getTicketRegistrationId,Arrays.asList(ids)));
        return ticketRegistrationMapper.delete(new LambdaQueryWrapper<TicketRegistration>().in(TicketRegistration::getId,Arrays.asList(ids)).eq(TicketRegistration::getTenantId,tenantId));
    }
    @Override
    public TicketRegistrationDto getRegistrationById(TicketRegistrationDto ticketRegistrationDto) {
        TicketRegistration ticketRegistration = ticketRegistrationMapper.selectById(ticketRegistrationDto.getId());
        TicketRegistration ticketRegistration = ticketRegistrationMapper.selectOne(new LambdaQueryWrapper<TicketRegistration>()
                .eq(TicketRegistration::getId,ticketRegistrationDto.getId()).eq(TicketRegistration::getTenantId,SecurityUtils.getLoginUser().getTenantId()));
        if (ticketRegistration == null) throw new BaseException("来票登记不存在");
        LambdaQueryWrapper<PurchaseLedger> purchaseQueryWrapper = new LambdaQueryWrapper<>();
        purchaseQueryWrapper.eq(PurchaseLedger::getId, ticketRegistration.getPurchaseLedgerId());
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(purchaseQueryWrapper);
        if (ticketRegistration == null) {
            throw new BaseException("采购台账不存在");
        }
        if (purchaseLedger == null) throw new BaseException("采购台账不存在");
        // 创建并填充DTO
        TicketRegistrationDto resultDto = new TicketRegistrationDto();
        BeanUtils.copyProperties(ticketRegistration, resultDto);
@@ -259,7 +354,9 @@
    public List getTicketNo(TicketRegistrationDto ticketRegistrationDto) {
        LambdaQueryWrapper<TicketRegistration> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(TicketRegistration::getId, TicketRegistration::getInvoiceNumber, TicketRegistration::getInvoiceAmount)
                .eq(TicketRegistration::getPurchaseLedgerId, ticketRegistrationDto.getId());
                .eq(TicketRegistration::getPurchaseLedgerId, ticketRegistrationDto.getId())
                .eq(TicketRegistration::getTenantId,SecurityUtils.getLoginUser().getTenantId())
                .eq(TicketRegistration::getDeleteFlag,0);
        List<Map<String, Object>> result = ticketRegistrationMapper.selectMaps(queryWrapper);
        // 将下划线命名转换为驼峰命名
        return result.stream().map(map -> map.entrySet().stream()
@@ -272,13 +369,19 @@
    @Override
    public IPage<TicketRegistration> selectTicketRegistrationListPage(Page page, TicketRegistration ticketRegistration) {
        LambdaQueryWrapper<TicketRegistration> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(ticketRegistration.getPurchaseContractNumber())) {
            queryWrapper.like(TicketRegistration::getPurchaseContractNumber, ticketRegistration.getPurchaseContractNumber())
                    .like(TicketRegistration::getSupplierName, ticketRegistration.getSupplierName());
        if (StringUtils.isNotBlank(ticketRegistration.getSupplierNameOrContractNo())) {
            queryWrapper.and(wrapper -> wrapper
                    .like(TicketRegistration::getPurchaseContractNumber, ticketRegistration.getSupplierNameOrContractNo())
                    .or()
                    .like(TicketRegistration::getSupplierName, ticketRegistration.getSupplierNameOrContractNo())
                    .or()
                    .like(TicketRegistration::getSalesContractNo, ticketRegistration.getSupplierNameOrContractNo()));
        }
        if (ObjectUtils.isEmpty(ticketRegistration.getIssueDateStart())&&ObjectUtils.isEmpty(ticketRegistration.getIssueDateEnd())) {
            queryWrapper.between(TicketRegistration::getIssueDate,LocalDate.parse(ticketRegistration.getIssueDateStart(), DateTimeFormatter.ofPattern("yyyy-MM-dd")), LocalDate.parse(ticketRegistration.getIssueDateEnd(), DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        if (!ObjectUtils.isEmpty(ticketRegistration.getIssueDateStart()) && !ObjectUtils.isEmpty(ticketRegistration.getIssueDateEnd())) {
            queryWrapper.between(TicketRegistration::getIssueDate, LocalDate.parse(ticketRegistration.getIssueDateStart(), DateTimeFormatter.ofPattern("yyyy-MM-dd")), LocalDate.parse(ticketRegistration.getIssueDateEnd(), DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        }
        queryWrapper.eq(TicketRegistration::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .eq(TicketRegistration::getDeleteFlag,0);
        IPage<TicketRegistration> ticketRegistrationIPage = ticketRegistrationMapper.selectPage(page, queryWrapper);
        // 计算已付款金额
        if (CollectionUtils.isNotEmpty(ticketRegistrationIPage.getRecords())) {
@@ -302,9 +405,39 @@
            if (ticketRegistration.getStatus()) {
                ticketRegistrationIPage.getRecords().removeIf(receiptPaymentDto1 -> new BigDecimal("0.00").equals(receiptPaymentDto1.getUnPaymentAmountTotal()));
                ticketRegistrationIPage.setTotal(ticketRegistrationIPage.getRecords().size());
            }
        }
        ticketRegistrationIPage.getRecords().forEach(item -> {
            // 已付款金额 == 待付款金额
            item.setStatusName(item.getPaymentAmountTotal().compareTo(item.getInvoiceAmount()) == 0 ? "已完成付款" : "未完成付款");
        });
        return ticketRegistrationIPage;
    }
    @Override
    public PurchaseLedgerDto getPuargeById(Long id) {
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                .eq(PurchaseLedger::getId,id).eq(PurchaseLedger::getTenantId,SecurityUtils.getLoginUser().getTenantId()));
        if(purchaseLedger==null) throw new BaseException("采购台账不存在");
        PurchaseLedgerDto purchaseLedgerDto = new PurchaseLedgerDto();
        BeanUtils.copyProperties(purchaseLedger, purchaseLedgerDto);
        SalesLedgerProduct salesLedgerProduct = new SalesLedgerProduct();
        salesLedgerProduct.setSalesLedgerId(id);
        salesLedgerProduct.setType(SalesLedgerType.SALES_LEDGER_TYPE_SALES_LEDGER.getValue());
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductService.selectSalesLedgerProductList(salesLedgerProduct);
        purchaseLedgerDto.setProductData(salesLedgerProducts);
        List<CommonFile> commonFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                .eq(CommonFile::getType, FileNameType.PURCHASELEDGER.getValue())
                .eq(CommonFile::getCommonId, id));
        purchaseLedgerDto.setSalesLedgerFiles(commonFiles);
        return purchaseLedgerDto;
    }
    @Override
    public List<PaymentRegistrationDto> getPaymentRegistrationDtoById(Long id) {
        List<PaymentRegistrationDto> paymentRegistrationDtos =purchaseLedgerMapper.getPaymentRegistrationDtoById(id);
        return paymentRegistrationDtos;
    }
    private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, Integer type) {
@@ -339,22 +472,25 @@
                }
                // 计算 futureTickets(直接使用 BigDecimal 计算,避免精度丢失)
                product.setFutureTickets(
                        product.getQuantity()
                                .subtract(product.getTicketsNum())
                );
//                product.setFutureTickets(
//                        product.getQuantity()
//                                .subtract(product.getTicketsNum())
//                );
                // 计算 futureTicketsAmount
                product.setFutureTicketsAmount(
                        product.getTaxInclusiveTotalPrice()
                                .subtract(product.getTicketsAmount())
                );
//                product.setFutureTicketsAmount(
//                        product.getTaxInclusiveTotalPrice()
//                                .subtract(product.getTicketsAmount())
//                );
                product.setType(type);
                salesLedgerProductMapper.updateById(product);
            });
        }
    }
    private BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
    /**
     * 下划线命名转驼峰命名
     */