Преглед изворни кода

发票添加批量导出功能

徐滕 пре 4 недеља
родитељ
комит
179f0ae869
19 измењених фајлова са 835 додато и 3 уклоњено
  1. 95 1
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/controller/FinanceInvoiceController.java
  2. 50 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/domain/FinanceInvoiceDownload.java
  3. 29 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/FinanceInvoiceDownloadMapper.java
  4. 7 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/FinanceInvoiceMapper.java
  5. 34 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceDownloadMapper.xml
  6. 12 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceMapper.xml
  7. 79 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceDownloadService.java
  8. 45 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceService.java
  9. 18 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/FinanceInvoiceBatchDownloadDTO.java
  10. 11 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/FinanceInvoiceDTO.java
  11. 128 1
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/controller/CwFinanceInvoiceController.java
  12. 50 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/domain/CwFinanceInvoiceDownload.java
  13. 29 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/CwFinanceInvoiceDownloadMapper.java
  14. 8 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/CwFinanceInvoiceMapper.java
  15. 34 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceDownloadMapper.xml
  16. 79 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceDownloadService.java
  17. 97 1
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceService.java
  18. 18 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceInvoiceBatchDownloadDTO.java
  19. 12 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceInvoiceDTO.java

+ 95 - 1
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/controller/FinanceInvoiceController.java

@@ -17,6 +17,7 @@ import com.jeeplus.common.excel.ExportMode;
 import com.jeeplus.common.redis.RedisUtils;
 import com.jeeplus.common.utils.ResponseUtil;
 import com.jeeplus.assess.invoice.service.FinanceInvoiceService;
+import com.jeeplus.assess.invoice.service.FinanceInvoiceDownloadService;
 import com.jeeplus.logging.annotation.ApiLog;
 import com.jeeplus.logging.constant.enums.LogTypeEnum;
 import com.jeeplus.sys.feign.IUserApi;
@@ -70,6 +71,9 @@ public class FinanceInvoiceController {
 
     @Resource
     private RedInvoiceRetryScheduledService redInvoiceRetryScheduledService;
+
+    @Resource
+    private FinanceInvoiceDownloadService financeInvoiceDownloadService;
     /**
      * 查询发票列表
      * @param financeInvoiceDTO
@@ -83,6 +87,15 @@ public class FinanceInvoiceController {
     public ResponseEntity<IPage<FinanceInvoiceDTO>> data(FinanceInvoiceDTO financeInvoiceDTO, Page<FinanceInvoiceDTO> page) throws Exception {
         IPage<FinanceInvoiceDTO> result = new Page<FinanceInvoiceDTO>();
         result = financeInvoiceService.findList (page,financeInvoiceDTO);
+        // 设置下载状态
+        List<String> downloadedIds = financeInvoiceDownloadService.getDownloadedInvoiceIds();
+        for (FinanceInvoiceDTO record : result.getRecords()) {
+            if (downloadedIds.contains(record.getId())) {
+                record.setDownloadStatus("1");
+            } else {
+                record.setDownloadStatus("0");
+            }
+        }
         return ResponseEntity.ok (result);
     }
 
@@ -593,7 +606,11 @@ public class FinanceInvoiceController {
      * 下载附件(文件没有时间戳前缀)
      */
     @GetMapping("/downLoadOMSInvoiceAttachzip")
-    public void downLoadOMSInvoiceAttachzip(@RequestParam("fileUrl")String fileUrl,@RequestParam("invoiceNo")String invoiceNo, HttpServletResponse response) {
+    public void downLoadOMSInvoiceAttachzip(@RequestParam("fileUrl")String fileUrl,@RequestParam("invoiceNo")String invoiceNo, @RequestParam(value = "invoiceId", required = false) String invoiceId, HttpServletResponse response) {
+        // 记录下载行为
+        if (StringUtils.isNotBlank(invoiceId)) {
+            financeInvoiceDownloadService.saveDownloadRecords(Arrays.asList(invoiceId), "single");
+        }
         long timestamp = System.currentTimeMillis();
         UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
         String filePath = null;
@@ -1040,4 +1057,81 @@ public class FinanceInvoiceController {
         Files.copy(file.toPath(), response.getOutputStream());
     }
 
+    /**
+     * 查询当前用户已下载的发票ID列表
+     */
+    @GetMapping("/downloadedInvoiceIds")
+    public ResponseEntity<List<String>> getDownloadedInvoiceIds() {
+        List<String> downloadedIds = financeInvoiceDownloadService.getDownloadedInvoiceIds();
+        return ResponseEntity.ok(downloadedIds);
+    }
+
+    /**
+     * 批量下载数电票
+     */
+    @PostMapping("/batchDownloadInvoice")
+    @ApiLog("批量下载数电票")
+    public void batchDownloadInvoice(@RequestBody FinanceInvoiceBatchDownloadDTO dto, HttpServletResponse response) {
+        List<String> invoiceIds = dto.getInvoiceIds();
+        if (invoiceIds == null || invoiceIds.isEmpty()) {
+            throw new RuntimeException("请选择要下载的发票");
+        }
+        if (invoiceIds.size() > 25) {
+            throw new RuntimeException("单次批量下载不能超过25条");
+        }
+
+        // 记录下载行为
+        financeInvoiceDownloadService.saveDownloadRecords(invoiceIds, "batch");
+
+        // 查询发票数据
+        List<FinanceInvoiceDTO> invoices = financeInvoiceService.queryByIds(invoiceIds);
+
+        String tempDir = System.getProperty("os.name").toLowerCase().contains("win")
+                ? "D:/attachment-file/batch_invoice_" + System.currentTimeMillis()
+                : "/attachment-file/batch_invoice_" + System.currentTimeMillis();
+        File folder = new File(tempDir);
+        folder.mkdirs();
+
+        try {
+            // 下载每个发票的附件
+            for (FinanceInvoiceDTO invoice : invoices) {
+                if (StringUtils.isNotBlank(invoice.getOmsAttachmentUrl())) {
+                    String fileUrl = invoice.getOmsAttachmentUrl().substring(1);
+                    fileUrl = fileUrl.substring(0, fileUrl.lastIndexOf('/') + 1);
+                    String invoiceDir = tempDir + "/" + invoice.getNo();
+                    new File(invoiceDir).mkdirs();
+                    SpringUtil.getBean(IWorkAttachmentApi.class).downloadFolderFromOss(fileUrl, invoiceDir + "/");
+                }
+            }
+
+            // 打包成ZIP
+            FileUtil.zipFolder(tempDir, tempDir + ".zip");
+            File zipFile = new File(tempDir + ".zip");
+
+            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("批量数电票_" + new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".zip", "UTF-8"));
+            response.setContentType("application/octet-stream;charset=utf-8");
+
+            OutputStream outputStream = response.getOutputStream();
+            InputStream inputStream = new FileInputStream(zipFile);
+            byte[] bytes = new byte[1024];
+            int i = inputStream.read(bytes);
+            while (i != -1) {
+                outputStream.write(bytes, 0, i);
+                i = inputStream.read(bytes);
+            }
+            inputStream.close();
+            outputStream.flush();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("批量下载失败:" + e.getMessage());
+        } finally {
+            // 清理临时文件
+            deleteFile(new File(tempDir));
+            File zipFile = new File(tempDir + ".zip");
+            if (zipFile.exists()) {
+                zipFile.delete();
+            }
+        }
+    }
+
 }

+ 50 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/domain/FinanceInvoiceDownload.java

@@ -0,0 +1,50 @@
+package com.jeeplus.assess.invoice.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.jeeplus.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 评估发票下载记录
+ * @TableName finance_invoice_download
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("finance_invoice_download")
+public class FinanceInvoiceDownload extends BaseEntity {
+
+    /**
+     * 发票ID(finance_invoice.id)
+     */
+    private String invoiceId;
+
+    /**
+     * 下载用户ID
+     */
+    private String userId;
+
+    /**
+     * 下载用户姓名
+     */
+    private String userName;
+
+    /**
+     * 下载方式:single-单条下载,batch-批量下载
+     */
+    private String downloadType;
+
+    /**
+     * 下载时间
+     */
+    private Date downloadTime;
+
+    /**
+     * 备注信息
+     */
+    private String remarks;
+
+    private static final long serialVersionUID = 1L;
+}

+ 29 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/FinanceInvoiceDownloadMapper.java

@@ -0,0 +1,29 @@
+package com.jeeplus.assess.invoice.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.jeeplus.assess.invoice.domain.FinanceInvoiceDownload;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 评估发票下载记录 Mapper
+ * @author jsxg
+ */
+@Mapper
+public interface FinanceInvoiceDownloadMapper extends BaseMapper<FinanceInvoiceDownload> {
+
+    /**
+     * 查询当前用户已下载的发票ID列表
+     * @param userId 用户ID
+     * @return 已下载的发票ID集合
+     */
+    List<String> getDownloadedInvoiceIds(@Param("userId") String userId);
+
+    /**
+     * 批量插入下载记录(使用 ON DUPLICATE KEY UPDATE 避免唯一索引冲突)
+     * @param list 下载记录列表
+     */
+    void batchInsertOrUpdate(@Param("list") List<FinanceInvoiceDownload> list);
+}

+ 7 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/FinanceInvoiceMapper.java

@@ -114,6 +114,13 @@ public interface FinanceInvoiceMapper extends BaseMapper<FinanceInvoice> {
      * @return 影响的行数
      */
     int batchUpdateByForeach(@Param("list") List<WorkInvoiceReceiptInfo> list);
+
+    /**
+     * 批量查询发票数据
+     * @param ids 发票ID列表
+     * @return 发票数据列表
+     */
+    List<FinanceInvoiceDTO> selectByIds(@Param("ids") List<String> ids);
 }
 
 

+ 34 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceDownloadMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeeplus.assess.invoice.mapper.FinanceInvoiceDownloadMapper">
+
+    <!-- 查询当前用户已下载的发票ID列表 -->
+    <select id="getDownloadedInvoiceIds" resultType="java.lang.String">
+        SELECT invoice_id
+        FROM finance_invoice_download
+        WHERE del_flag = 0
+          AND user_id = #{userId}
+    </select>
+
+    <!-- 批量插入或更新下载记录(唯一索引冲突时更新下载时间和类型) -->
+    <insert id="batchInsertOrUpdate">
+        INSERT INTO finance_invoice_download
+            (id, invoice_id, user_id, user_name, download_type, download_time,
+             create_by_id, create_time, update_by_id, update_time, del_flag, remarks, tenant_id)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.id}, #{item.invoiceId}, #{item.userId}, #{item.userName},
+             #{item.downloadType}, #{item.downloadTime},
+             #{item.createById}, #{item.createTime}, #{item.updateById}, #{item.updateTime},
+             #{item.delFlag}, #{item.remarks}, #{item.tenantId})
+        </foreach>
+        ON DUPLICATE KEY UPDATE
+            download_type = VALUES(download_type),
+            download_time = VALUES(download_time),
+            update_by_id = VALUES(update_by_id),
+            update_time = VALUES(update_time)
+    </insert>
+
+</mapper>

+ 12 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceMapper.xml

@@ -650,4 +650,16 @@
         </foreach>
     </update>
 
+    <!-- 批量查询发票数据 -->
+    <select id="selectByIds" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>
+        FROM finance_invoice fi
+        WHERE fi.del_flag = 0
+        AND fi.id IN
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+
 </mapper>

+ 79 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceDownloadService.java

@@ -0,0 +1,79 @@
+package com.jeeplus.assess.invoice.service;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.jeeplus.assess.invoice.domain.FinanceInvoiceDownload;
+import com.jeeplus.assess.invoice.mapper.FinanceInvoiceDownloadMapper;
+import com.jeeplus.common.TokenProvider;
+import com.jeeplus.sys.feign.IUserApi;
+import com.jeeplus.sys.service.dto.UserDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 评估发票下载记录 Service
+ * @author jsxg
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class FinanceInvoiceDownloadService extends ServiceImpl<FinanceInvoiceDownloadMapper, FinanceInvoiceDownload> {
+
+    private static final Logger log = LoggerFactory.getLogger(FinanceInvoiceDownloadService.class);
+
+    @Resource
+    private FinanceInvoiceDownloadMapper financeInvoiceDownloadMapper;
+
+    /**
+     * 查询当前用户已下载的发票ID列表
+     * @return 已下载的发票ID集合
+     */
+    public List<String> getDownloadedInvoiceIds() {
+        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
+        if (userDTO == null) {
+            return new ArrayList<>();
+        }
+        return financeInvoiceDownloadMapper.getDownloadedInvoiceIds(userDTO.getId());
+    }
+
+    /**
+     * 保存下载记录(单条或批量)
+     * @param invoiceIds 发票ID列表
+     * @param downloadType 下载方式:single-单条下载,batch-批量下载
+     */
+    public void saveDownloadRecords(List<String> invoiceIds, String downloadType) {
+        if (invoiceIds == null || invoiceIds.isEmpty()) {
+            return;
+        }
+        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
+        String userId = userDTO != null ? userDTO.getId() : "";
+        String userName = userDTO != null ? userDTO.getName() : "";
+        Date now = new Date();
+
+        List<FinanceInvoiceDownload> records = new ArrayList<>();
+        for (String invoiceId : invoiceIds) {
+            FinanceInvoiceDownload record = new FinanceInvoiceDownload();
+            record.setId(UUID.randomUUID().toString().replace("-", ""));
+            record.setInvoiceId(invoiceId);
+            record.setUserId(userId);
+            record.setUserName(userName);
+            record.setDownloadType(downloadType);
+            record.setDownloadTime(now);
+            record.setCreateById(userId);
+            record.setCreateTime(now);
+            record.setUpdateById(userId);
+            record.setUpdateTime(now);
+            record.setDelFlag(0);
+            records.add(record);
+        }
+        financeInvoiceDownloadMapper.batchInsertOrUpdate(records);
+        log.info("保存评估发票下载记录:用户={},数量={},方式={}", userName, invoiceIds.size(), downloadType);
+    }
+}

+ 45 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceService.java

@@ -372,6 +372,20 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
             }
         }
 
+        // 下载状态筛选
+        if (StringUtils.isNotBlank(financeInvoiceDTO.getDownloadStatusFilter())) {
+            List<String> downloadedIds = SpringUtil.getBean(FinanceInvoiceDownloadService.class).getDownloadedInvoiceIds();
+            if ("1".equals(financeInvoiceDTO.getDownloadStatusFilter())) {
+                // 筛选已下载
+                if (CollectionUtil.isEmpty(downloadedIds)) {
+                    return new Page<FinanceInvoiceDTO>();
+                }
+                pageList.getRecords().removeIf(item -> !downloadedIds.contains(item.getId()));
+            } else if ("0".equals(financeInvoiceDTO.getDownloadStatusFilter())) {
+                // 筛选未下载
+                pageList.getRecords().removeIf(item -> downloadedIds.contains(item.getId()));
+            }
+        }
 
         return pageList;
     }
@@ -1813,4 +1827,35 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
         map.put("moneyMismatchingList", moneyMismatchingList); // 数据表中金额大于开票价的的数据
         return map;
     }
+
+    /**
+     * 批量查询发票数据并组装 omsAttachmentUrl
+     * @param ids 发票ID列表
+     * @return 发票数据列表
+     */
+    public List<FinanceInvoiceDTO> queryByIds(List<String> ids) {
+        if (CollectionUtil.isEmpty(ids)) {
+            return new ArrayList<>();
+        }
+        List<FinanceInvoiceDTO> list = financeInvoiceMapper.selectByIds(ids);
+        // 组装 omsAttachmentUrl
+        List<String> idList = Lists.newArrayList();
+        for (FinanceInvoiceDTO invoice : list) {
+            if (StringUtils.isNotBlank(invoice.getStatus()) && "5".equals(invoice.getStatus())) {
+                idList.add(invoice.getId());
+            }
+        }
+        List<WorkAttachmentInfo> omsInvoiceFileList = SpringUtil.getBean(IWorkAttachmentApi.class).getByAttachmentIdListAndFlag(idList);
+        if (CollectionUtil.isNotEmpty(omsInvoiceFileList)) {
+            for (FinanceInvoiceDTO invoice : list) {
+                for (WorkAttachmentInfo workattachment : omsInvoiceFileList) {
+                    if (invoice.getId().equals(workattachment.getAttachmentId()) && workattachment.getUrl().contains("OMS_invoice")) {
+                        invoice.setOmsAttachmentUrl(workattachment.getUrl());
+                        break;
+                    }
+                }
+            }
+        }
+        return list;
+    }
 }

+ 18 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/FinanceInvoiceBatchDownloadDTO.java

@@ -0,0 +1,18 @@
+package com.jeeplus.assess.invoice.service.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 评估发票批量下载请求DTO
+ * @author jsxg
+ */
+@Data
+public class FinanceInvoiceBatchDownloadDTO {
+
+    /**
+     * 发票ID列表
+     */
+    private List<String> invoiceIds;
+}

+ 11 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/FinanceInvoiceDTO.java

@@ -358,5 +358,16 @@ public class FinanceInvoiceDTO extends BaseDTO {
     private String isSmsNotice; //是否短信通知
     private String isOmsBilling; //是否手动开票
     private String invoiceTaskId;//开票任务id
+
+    /**
+     * 当前用户是否已下载(1=已下载,0=未下载)
+     */
+    private String downloadStatus;
+
+    /**
+     * 下载状态筛选条件(0=未下载,1=已下载)
+     */
+    private transient String downloadStatusFilter;
+
     private static final long serialVersionUID = 1L;
 }

+ 128 - 1
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/controller/CwFinanceInvoiceController.java

@@ -13,6 +13,7 @@ import com.jeeplus.common.excel.annotation.ExportMode;
 import com.jeeplus.common.redis.RedisUtils;
 import com.jeeplus.common.utils.ResponseUtil;
 import com.jeeplus.finance.invoice.domain.CwFinanceInvoice;
+import com.jeeplus.finance.invoice.service.CwFinanceInvoiceDownloadService;
 import com.jeeplus.finance.invoice.service.CwFinanceInvoiceService;
 import com.jeeplus.finance.invoice.service.OMS.*;
 import com.jeeplus.finance.invoice.service.dto.*;
@@ -80,6 +81,9 @@ public class CwFinanceInvoiceController {
     @Resource
     private RedInvoiceRetryScheduledService redInvoiceRetryScheduledService;
 
+    @Resource
+    private CwFinanceInvoiceDownloadService cwFinanceInvoiceDownloadService;
+
     /**
      * 查询发票列表
      * @param cwFinanceInvoiceDTO
@@ -94,7 +98,12 @@ public class CwFinanceInvoiceController {
         IPage<CwFinanceInvoiceDTO> result = new Page<CwFinanceInvoiceDTO>();
         result = cwFinanceInvoiceService.findList (page, cwFinanceInvoiceDTO,"");
         System.out.println("当前时间1:"+System.currentTimeMillis());
+        // 查询当前用户已下载的发票ID集合
+        List<String> downloadedIds = cwFinanceInvoiceDownloadService.getDownloadedInvoiceIds();
+        Set<String> downloadedSet = new HashSet<>(downloadedIds);
         result.getRecords().stream().forEach(i -> {
+            // 设置下载状态
+            i.setDownloadStatus(downloadedSet.contains(i.getId()) ? "1" : "0");
             // 发票申请
             if (StringUtils.isNotBlank(i.getTaskId()) && StringUtils.isNotBlank(i.getStatus())) {
                 if ("2".equals(i.getStatus())) { // “审核中”的数据要获取数据审核人
@@ -928,7 +937,7 @@ public class CwFinanceInvoiceController {
      * 下载附件(文件没有时间戳前缀)
      */
     @GetMapping("/downLoadOMSInvoiceAttachzip")
-    public void downLoadOMSInvoiceAttachzip(@RequestParam("fileUrl")String fileUrl,@RequestParam("invoiceNo")String invoiceNo, HttpServletResponse response) {
+    public void downLoadOMSInvoiceAttachzip(@RequestParam("fileUrl")String fileUrl, @RequestParam("invoiceNo")String invoiceNo, @RequestParam(value = "invoiceId", required = false)String invoiceId, HttpServletResponse response) {
         long timestamp = System.currentTimeMillis();
         UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
         String filePath = null;
@@ -1011,6 +1020,15 @@ public class CwFinanceInvoiceController {
         } catch (Exception e) {
             e.printStackTrace();
         }
+
+        // 记录单条下载行为
+        if (StringUtils.isNotBlank(invoiceId)) {
+            try {
+                cwFinanceInvoiceDownloadService.saveDownloadRecords(Collections.singletonList(invoiceId), "single");
+            } catch (Exception e) {
+                log.error("记录发票下载行为异常,invoiceId={}", invoiceId, e);
+            }
+        }
     }
 
     public static void deleteFile(File folder) {
@@ -1403,4 +1421,113 @@ public class CwFinanceInvoiceController {
         Files.copy(file.toPath(), response.getOutputStream());
     }
 
+    /**
+     * 查询当前用户已下载的发票ID列表
+     */
+    @ApiOperation(value = "查询已下载发票ID列表")
+    @GetMapping("/downloadedInvoiceIds")
+    public ResponseEntity<List<String>> getDownloadedInvoiceIds() {
+        List<String> ids = cwFinanceInvoiceDownloadService.getDownloadedInvoiceIds();
+        return ResponseEntity.ok(ids);
+    }
+
+    /**
+     * 批量下载数电票
+     * 将多张发票的OMS附件打包为ZIP返回,并记录下载行为
+     */
+    @ApiOperation(value = "批量下载数电票")
+    @PostMapping("/batchDownloadInvoice")
+    public void batchDownloadInvoice(@RequestBody CwFinanceInvoiceBatchDownloadDTO dto, HttpServletResponse response) {
+        List<String> invoiceIds = dto.getInvoiceIds();
+        if (invoiceIds == null || invoiceIds.isEmpty()) {
+            try {
+                response.setContentType("application/json;charset=utf-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"请选择需要下载的发票\"}");
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        if (invoiceIds.size() > 25) {
+            try {
+                response.setContentType("application/json;charset=utf-8");
+                response.getWriter().write("{\"code\":500,\"msg\":\"最多只能选择25条发票进行批量下载\"}");
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+
+        // 记录下载行为
+        cwFinanceInvoiceDownloadService.saveDownloadRecords(invoiceIds, "batch");
+
+        // 查询发票数据获取omsAttachmentUrl
+        List<CwFinanceInvoiceDTO> invoices = cwFinanceInvoiceService.queryByIds(invoiceIds);
+        List<String> failureMessages = new ArrayList<>();
+        String baseFilePath;
+        if (System.getProperty("os.name").toLowerCase().contains("win")) {
+            baseFilePath = "D:/attachment-file/batch_invoice_" + System.currentTimeMillis();
+        } else {
+            baseFilePath = "/attachment-file/batch_invoice_" + System.currentTimeMillis();
+        }
+
+        File baseFolder = new File(baseFilePath);
+        if (!baseFolder.exists()) {
+            baseFolder.mkdirs();
+        }
+
+        try {
+            // 逐个下载OMS附件到本地临时目录
+            for (CwFinanceInvoiceDTO invoice : invoices) {
+                if (org.apache.commons.lang3.StringUtils.isBlank(invoice.getOmsAttachmentUrl())) {
+                    failureMessages.add("发票[" + invoice.getNo() + "]无OMS附件");
+                    continue;
+                }
+                try {
+                    String invoicePath = baseFilePath + "/" + invoice.getNo();
+                    File invoiceFolder = new File(invoicePath);
+                    if (!invoiceFolder.exists()) {
+                        invoiceFolder.mkdirs();
+                    }
+                    SpringUtil.getBean(IWorkAttachmentApi.class).downloadFolderFromOss(invoice.getOmsAttachmentUrl(), invoicePath + "/");
+                } catch (Exception e) {
+                    log.error("批量下载-发票[{}]附件下载失败", invoice.getNo(), e);
+                    failureMessages.add("发票[" + invoice.getNo() + "]下载失败: " + e.getMessage());
+                }
+            }
+
+            // 打包为ZIP
+            FileUtil.zipFolder(baseFilePath, baseFilePath + ".zip");
+            File zipFile = new File(baseFilePath + ".zip");
+
+            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFile.getName(), "UTF-8"));
+            response.setContentType("application/octet-stream;charset=utf-8");
+
+            try (InputStream inputStream = new FileInputStream(zipFile);
+                 BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
+                 OutputStream outputStream = response.getOutputStream()) {
+                byte[] bytes = new byte[1024];
+                int len;
+                while ((len = bufferedInputStream.read(bytes)) != -1) {
+                    outputStream.write(bytes, 0, len);
+                }
+                outputStream.flush();
+            }
+
+            // 将失败信息写入response header
+            if (!failureMessages.isEmpty()) {
+                response.setHeader("X-Download-Failures", URLEncoder.encode(String.join(";", failureMessages), "UTF-8"));
+            }
+        } catch (Exception e) {
+            log.error("批量下载发票异常", e);
+        } finally {
+            // 清理临时文件
+            deleteFile(baseFolder);
+            File zipFile = new File(baseFilePath + ".zip");
+            if (zipFile.exists()) {
+                zipFile.delete();
+            }
+        }
+    }
+
 }

+ 50 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/domain/CwFinanceInvoiceDownload.java

@@ -0,0 +1,50 @@
+package com.jeeplus.finance.invoice.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.jeeplus.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 发票下载记录
+ * @TableName cw_finance_invoice_download
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("cw_finance_invoice_download")
+public class CwFinanceInvoiceDownload extends BaseEntity {
+
+    /**
+     * 发票ID(cw_finance_invoice.id)
+     */
+    private String invoiceId;
+
+    /**
+     * 下载用户ID
+     */
+    private String userId;
+
+    /**
+     * 下载用户姓名
+     */
+    private String userName;
+
+    /**
+     * 下载方式:single-单条下载,batch-批量下载
+     */
+    private String downloadType;
+
+    /**
+     * 下载时间
+     */
+    private Date downloadTime;
+
+    /**
+     * 备注信息
+     */
+    private String remarks;
+
+    private static final long serialVersionUID = 1L;
+}

+ 29 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/CwFinanceInvoiceDownloadMapper.java

@@ -0,0 +1,29 @@
+package com.jeeplus.finance.invoice.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.jeeplus.finance.invoice.domain.CwFinanceInvoiceDownload;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 发票下载记录 Mapper
+ * @author jsxg
+ */
+@Mapper
+public interface CwFinanceInvoiceDownloadMapper extends BaseMapper<CwFinanceInvoiceDownload> {
+
+    /**
+     * 查询当前用户已下载的发票ID列表
+     * @param userId 用户ID
+     * @return 已下载的发票ID集合
+     */
+    List<String> getDownloadedInvoiceIds(@Param("userId") String userId);
+
+    /**
+     * 批量插入下载记录(使用 ON DUPLICATE KEY UPDATE 避免唯一索引冲突)
+     * @param list 下载记录列表
+     */
+    void batchInsertOrUpdate(@Param("list") List<CwFinanceInvoiceDownload> list);
+}

+ 8 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/CwFinanceInvoiceMapper.java

@@ -11,6 +11,7 @@ import com.jeeplus.finance.invoice.domain.CwFinanceInvoiceBase;
 import com.jeeplus.finance.invoice.domain.WorkInvoiceTaxClassificationCode;
 import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceBaseDTO;
 import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDTO;
+import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDetailDTO;
 import com.jeeplus.finance.invoice.service.dto.CwWorkInvoiceReceipt;
 import com.jeeplus.finance.invoice.service.dto.CwWorkInvoiceReceiptInfo;
 import com.jeeplus.sys.service.dto.AreaDTO;
@@ -42,6 +43,13 @@ public interface CwFinanceInvoiceMapper extends BaseMapper<CwFinanceInvoice> {
 
     public List<CwFinanceInvoiceDTO> getByIds(@Param(Constants.WRAPPER) QueryWrapper<CwFinanceInvoice> queryWrapper);
 
+    /**
+     * 根据发票ID查询发票明细列表
+     * @param invoiceId 发票ID
+     * @return 发票明细列表
+     */
+    public List<CwFinanceInvoiceDetailDTO> getFidList(@Param("id") String invoiceId);
+
     public List<String> getIdByReportId(@Param("id") String id);
 
     public List<String> getIdByContractId(@Param("id") String id);

+ 34 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceDownloadMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeeplus.finance.invoice.mapper.CwFinanceInvoiceDownloadMapper">
+
+    <!-- 查询当前用户已下载的发票ID列表 -->
+    <select id="getDownloadedInvoiceIds" resultType="java.lang.String">
+        SELECT invoice_id
+        FROM cw_finance_invoice_download
+        WHERE del_flag = 0
+          AND user_id = #{userId}
+    </select>
+
+    <!-- 批量插入或更新下载记录(唯一索引冲突时更新下载时间和类型) -->
+    <insert id="batchInsertOrUpdate">
+        INSERT INTO cw_finance_invoice_download
+            (id, invoice_id, user_id, user_name, download_type, download_time,
+             create_by_id, create_time, update_by_id, update_time, del_flag, remarks, tenant_id)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.id}, #{item.invoiceId}, #{item.userId}, #{item.userName},
+             #{item.downloadType}, #{item.downloadTime},
+             #{item.createById}, #{item.createTime}, #{item.updateById}, #{item.updateTime},
+             #{item.delFlag}, #{item.remarks}, #{item.tenantId})
+        </foreach>
+        ON DUPLICATE KEY UPDATE
+            download_type = VALUES(download_type),
+            download_time = VALUES(download_time),
+            update_by_id = VALUES(update_by_id),
+            update_time = VALUES(update_time)
+    </insert>
+
+</mapper>

+ 79 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceDownloadService.java

@@ -0,0 +1,79 @@
+package com.jeeplus.finance.invoice.service;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.jeeplus.common.TokenProvider;
+import com.jeeplus.finance.invoice.domain.CwFinanceInvoiceDownload;
+import com.jeeplus.finance.invoice.mapper.CwFinanceInvoiceDownloadMapper;
+import com.jeeplus.sys.feign.IUserApi;
+import com.jeeplus.sys.service.dto.UserDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 发票下载记录 Service
+ * @author jsxg
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class CwFinanceInvoiceDownloadService extends ServiceImpl<CwFinanceInvoiceDownloadMapper, CwFinanceInvoiceDownload> {
+
+    private static final Logger log = LoggerFactory.getLogger(CwFinanceInvoiceDownloadService.class);
+
+    @Resource
+    private CwFinanceInvoiceDownloadMapper cwFinanceInvoiceDownloadMapper;
+
+    /**
+     * 查询当前用户已下载的发票ID列表
+     * @return 已下载的发票ID集合
+     */
+    public List<String> getDownloadedInvoiceIds() {
+        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
+        if (userDTO == null) {
+            return new ArrayList<>();
+        }
+        return cwFinanceInvoiceDownloadMapper.getDownloadedInvoiceIds(userDTO.getId());
+    }
+
+    /**
+     * 保存下载记录(单条或批量)
+     * @param invoiceIds 发票ID列表
+     * @param downloadType 下载方式:single-单条下载,batch-批量下载
+     */
+    public void saveDownloadRecords(List<String> invoiceIds, String downloadType) {
+        if (invoiceIds == null || invoiceIds.isEmpty()) {
+            return;
+        }
+        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
+        String userId = userDTO != null ? userDTO.getId() : "";
+        String userName = userDTO != null ? userDTO.getName() : "";
+        Date now = new Date();
+
+        List<CwFinanceInvoiceDownload> records = new ArrayList<>();
+        for (String invoiceId : invoiceIds) {
+            CwFinanceInvoiceDownload record = new CwFinanceInvoiceDownload();
+            record.setId(UUID.randomUUID().toString().replace("-", ""));
+            record.setInvoiceId(invoiceId);
+            record.setUserId(userId);
+            record.setUserName(userName);
+            record.setDownloadType(downloadType);
+            record.setDownloadTime(now);
+            record.setCreateById(userId);
+            record.setCreateTime(now);
+            record.setUpdateById(userId);
+            record.setUpdateTime(now);
+            record.setDelFlag(0);
+            records.add(record);
+        }
+        cwFinanceInvoiceDownloadMapper.batchInsertOrUpdate(records);
+        log.info("保存发票下载记录:用户={},数量={},方式={}", userName, invoiceIds.size(), downloadType);
+    }
+}

+ 97 - 1
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceService.java

@@ -18,6 +18,7 @@ import com.jeeplus.core.query.QueryWrapperGenerator;
 import com.jeeplus.finance.invoice.domain.*;
 import com.jeeplus.finance.invoice.domain.OMS.InvoiceDown.OMSInvoiceDetailInfo;
 import com.jeeplus.finance.invoice.mapper.*;
+import com.jeeplus.finance.invoice.mapper.CwFinanceInvoiceDownloadMapper;
 import com.jeeplus.finance.invoice.service.dto.*;
 import com.jeeplus.finance.invoice.service.mapstruct.*;
 import com.jeeplus.finance.invoice.thread.ApprovalThread;
@@ -105,6 +106,9 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
     @Resource
     private DingTalkUtil dingTalkUtil;
 
+    @Resource
+    private CwFinanceInvoiceDownloadMapper cwFinanceInvoiceDownloadMapper;
+
 
     Pattern mobilePattern = Pattern.compile("^1[3-9]\\d{9}$");
 
@@ -394,6 +398,22 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
         IPage<CwFinanceInvoiceDTO> list = null;
         //获取当前登录人角色是否是苏州报告签字盖章代办
         UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
+
+        // 下载状态筛选
+        if (ObjectUtil.isNotEmpty(cwFinanceInvoiceDTO) && StringUtils.isNotBlank(cwFinanceInvoiceDTO.getDownloadStatusFilter())) {
+            List<String> downloadedIds = cwFinanceInvoiceDownloadMapper.getDownloadedInvoiceIds(userDTO.getId());
+            if ("1".equals(cwFinanceInvoiceDTO.getDownloadStatusFilter())) {
+                if (CollectionUtil.isEmpty(downloadedIds)) {
+                    return new Page<>();
+                }
+                queryWrapper.in("fi.id", downloadedIds);
+            } else if ("0".equals(cwFinanceInvoiceDTO.getDownloadStatusFilter())) {
+                if (CollectionUtil.isNotEmpty(downloadedIds)) {
+                    queryWrapper.notIn("fi.id", downloadedIds);
+                }
+            }
+        }
+
         if (CollectionUtil.isNotEmpty(userDTO.getRoleDTOList())) {
             for (RoleDTO roleDTO : userDTO.getRoleDTOList()) {
                 RoleDTO dtoById = SpringUtil.getBean(IRoleApi.class).getRoleDTOById(roleDTO.getId());
@@ -721,6 +741,43 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
         return cwFinanceInvoiceDTO;
     }
 
+    /**
+     * 根据ID列表批量查询发票数据(用于批量下载)
+     * @param ids 发票ID列表
+     * @return 发票DTO列表(包含omsAttachmentUrl)
+     */
+    public List<CwFinanceInvoiceDTO> queryByIds(List<String> ids) {
+        if (CollectionUtil.isEmpty(ids)) {
+            return new ArrayList<>();
+        }
+        List<CwFinanceInvoiceDTO> result = new ArrayList<>();
+        for (String id : ids) {
+            CwFinanceInvoiceDTO dto = cwFinanceInvoiceMapper.queryById(id);
+            if (dto != null) {
+                result.add(dto);
+            }
+        }
+        // 组装omsAttachmentUrl
+        List<String> statusFiveIds = result.stream()
+                .filter(dto -> "5".equals(dto.getStatus()))
+                .map(CwFinanceInvoiceDTO::getId)
+                .collect(Collectors.toList());
+        if (CollectionUtil.isNotEmpty(statusFiveIds)) {
+            List<WorkAttachmentInfo> omsInvoiceFileList = SpringUtil.getBean(IWorkAttachmentApi.class).getByAttachmentIdListAndFlag(statusFiveIds);
+            if (CollectionUtil.isNotEmpty(omsInvoiceFileList)) {
+                for (CwFinanceInvoiceDTO invoice : result) {
+                    for (WorkAttachmentInfo workattachment : omsInvoiceFileList) {
+                        if (invoice.getId().equals(workattachment.getAttachmentId()) && workattachment.getUrl().contains("OMS_invoice")) {
+                            invoice.setOmsAttachmentUrl(workattachment.getUrl());
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
 
     public CwFinanceInvoice saveForm(CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
 
@@ -1174,7 +1231,46 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
                 queryWrapper.eq("fi.status", "5"); // 发票申请流程已通过
                 queryWrapper.orderByDesc("fi.create_time");
                 queryWrapper.eq("fi.invalid_status", "0"); // 未作废
-                return cwFinanceInvoiceMapper.getByIds(queryWrapper);
+                List<CwFinanceInvoiceDTO> list = cwFinanceInvoiceMapper.getByIds(queryWrapper);
+                
+                // 组装发票明细信息(发票号、开票金额)和 OMS 附件 URL
+                for (CwFinanceInvoiceDTO invoice : list) {
+                    // 1. 查询发票明细,合并展示发票号和开票金额
+                    List<CwFinanceInvoiceDetailDTO> detailList = cwFinanceInvoiceMapper.getFidList(invoice.getId());
+                    if (CollectionUtil.isNotEmpty(detailList)) {
+                        // 合并所有发票号(用逗号分隔)
+                        List<String> numbers = new ArrayList<>();
+                        BigDecimal totalAmount = new BigDecimal(0);
+                        for (CwFinanceInvoiceDetailDTO detail : detailList) {
+                            if (StringUtils.isNotBlank(detail.getNumber())) {
+                                numbers.add(detail.getNumber());
+                            }
+                            if (StringUtils.isNotBlank(detail.getAccount())) {
+                                totalAmount = totalAmount.add(new BigDecimal(detail.getAccount()));
+                            }
+                        }
+                        // 设置合并后的发票号
+                        if (!numbers.isEmpty()) {
+                            invoice.setNumber(String.join(", ", numbers));
+                        }
+                        // 设置合并后的开票金额
+                        invoice.setAccountDetail(totalAmount.setScale(2, BigDecimal.ROUND_HALF_UP).toString());
+                    }
+                    
+                    // 2. 查询 OMS 附件 URL
+                    if (StringUtils.isNotBlank(invoice.getStatus()) && "5".equals(invoice.getStatus())) {
+                        List<WorkAttachmentInfo> omsInvoiceFileList = SpringUtil.getBean(IWorkAttachmentApi.class).getByAttachmentIdListAndFlag(Arrays.asList(invoice.getId()));
+                        if (CollectionUtil.isNotEmpty(omsInvoiceFileList)) {
+                            for (WorkAttachmentInfo workattachment : omsInvoiceFileList) {
+                                if (workattachment.getUrl().contains("OMS_invoice")) {
+                                    invoice.setOmsAttachmentUrl(workattachment.getUrl());
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+                return list;
             }
         }
         return new ArrayList<>();

+ 18 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceInvoiceBatchDownloadDTO.java

@@ -0,0 +1,18 @@
+package com.jeeplus.finance.invoice.service.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 批量下载发票请求参数
+ * @author jsxg
+ */
+@Data
+public class CwFinanceInvoiceBatchDownloadDTO {
+
+    /**
+     * 发票ID列表
+     */
+    private List<String> invoiceIds;
+}

+ 12 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceInvoiceDTO.java

@@ -426,5 +426,17 @@ public class CwFinanceInvoiceDTO extends BaseDTO {
 
     private String invoiceTaskId;//开票任务id
 
+    /**
+     * 当前用户是否已下载(1=已下载,0=未下载)
+     * 由列表查询时 LEFT JOIN 下载记录表动态计算
+     */
+    private String downloadStatus;
+
+    /**
+     * 下载状态筛选条件(0=未下载,1=已下载)
+     * 仅用于查询参数传递,不映射数据库字段
+     */
+    private transient String downloadStatusFilter;
+
     private static final long serialVersionUID = 1L;
 }