Переглянути джерело

评估、会计 发票批量收款功能

徐滕 2 днів тому
батько
коміт
d4b80f8385
28 змінених файлів з 2429 додано та 44 видалено
  1. 358 3
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/controller/FinanceInvoiceController.java
  2. 13 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/FinanceInvoiceDetailMapper.java
  3. 25 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/FinanceInvoiceMapper.java
  4. 19 2
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceDetailMapper.xml
  5. 98 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceMapper.xml
  6. 204 5
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceService.java
  7. 32 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/InvoiceImportDTO.java
  8. 29 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/WorkInvoiceReceipt.java
  9. 74 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/utils/ExcelField.java
  10. 323 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/utils/ExportMultipleTabsExcel.java
  11. BIN
      jeeplus-modules/jeeplus-assess/src/main/resources/dot/收款导入模版.xlsx
  12. 355 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/controller/CwFinanceInvoiceController.java
  13. 13 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/CwFinanceInvoiceDetailMapper.java
  14. 25 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/CwFinanceInvoiceMapper.java
  15. 35 20
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceDetailMapper.xml
  16. 95 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceMapper.xml
  17. 201 1
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceService.java
  18. 32 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwInvoiceImportDTO.java
  19. 28 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwWorkInvoiceReceipt.java
  20. 31 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwWorkInvoiceReceiptInfo.java
  21. 74 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/util/ExcelField.java
  22. 323 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/util/ExportMultipleTabsExcel.java
  23. 26 11
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectRecords/service/CwProjectRecordsService.java
  24. 1 1
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/controller/CwProjectReportArchiveController.java
  25. 3 1
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/mapper/xml/CwProjectReportArchiveMapper.xml
  26. 10 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/service/CwProjectReportArchiveService.java
  27. 2 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/service/dto/CwProjectReportArchiveDTO.java
  28. BIN
      jeeplus-modules/jeeplus-finance/src/main/resources/dot/收款导入模版.xlsx

+ 358 - 3
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/controller/FinanceInvoiceController.java

@@ -4,8 +4,11 @@ import cn.hutool.extra.spring.SpringUtil;
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.common.collect.Lists;
 import com.jeeplus.aop.demo.annotation.DemoMode;
 import com.jeeplus.assess.invoice.service.OMS.*;
+import com.jeeplus.assess.invoice.service.dto.*;
+import com.jeeplus.assess.invoice.utils.ExportMultipleTabsExcel;
 import com.jeeplus.assess.invoice.utils.OMS.FileUtil;
 import com.jeeplus.assess.projectRecords.Utils.EasyPoiUtil;
 import com.jeeplus.common.TokenProvider;
@@ -14,10 +17,8 @@ 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.dto.FinanceDTO;
-import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceDTO;
-import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceDetailDTO;
 import com.jeeplus.logging.annotation.ApiLog;
+import com.jeeplus.logging.constant.enums.LogTypeEnum;
 import com.jeeplus.sys.feign.IUserApi;
 import com.jeeplus.sys.feign.IWorkAttachmentApi;
 import com.jeeplus.sys.service.dto.UserDTO;
@@ -26,6 +27,9 @@ import io.swagger.annotations.ApiOperation;
 import lombok.extern.log4j.Log4j2;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.ibatis.annotations.Param;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -42,6 +46,7 @@ import java.io.*;
 import java.lang.reflect.Field;
 import java.lang.reflect.Type;
 import java.net.URLEncoder;
+import java.nio.file.Files;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -671,4 +676,354 @@ public class FinanceInvoiceController {
         folder.delete(); // 删除空文件夹或者文件
     }
 
+
+
+
+
+    /**
+     * 下载项目导入模板
+     *
+     * @param response
+     * @return
+     */
+    @GetMapping("/importFinance/template")
+    @ApiOperation(value = "下载模板")
+    public void importFinanceTemplate(HttpServletResponse response, HttpServletRequest request) {
+        try {
+            // 去掉 this.getClass(),用当前线程的类加载器,路径去掉开头的 /
+            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("dot/收款导入模版.xlsx");
+            //强制下载不打开
+            response.setContentType("application/force-download");
+            OutputStream out = response.getOutputStream();
+            //使用URLEncoder来防止文件名乱码或者读取错误
+            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("project_records_template.xlsx", "UTF-8"));
+            int b = 0;
+            byte[] buffer = new byte[1000000];
+            while (b != -1) {
+                b = inputStream.read(buffer);
+                if (b != -1) out.write(buffer, 0, b);
+            }
+            inputStream.close();
+            out.close();
+            out.flush();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /**
+     * 导入项目数据excel
+     *
+     * @return
+     */
+    @DemoMode
+    @PostMapping("/importProjectRecords")
+    @ApiLog(value = "导入项目数据excel", type = LogTypeEnum.IMPORT)
+    public ResponseEntity importProjectRecords(MultipartFile file, HttpServletRequest request) throws IOException {
+        // 初始化返回结果
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", false); // 默认失败
+        result.put("msg", "");
+        result.put("excelFileName", null); // Excel文件名
+
+        List<WorkInvoiceReceiptInfo> effectiveList = Lists.newArrayList();
+        List<WorkInvoiceReceiptInfo> errorList = Lists.newArrayList();
+        String excelFileName = null;
+
+
+        try {
+            StringBuilder failureMsg = new StringBuilder();
+            List<WorkInvoiceReceiptInfo> list = new ArrayList<>();
+            //获取sheet
+            list = EasyPoiUtil.importExcel(file, 1, 1, WorkInvoiceReceiptInfo.class);
+            //去除excel中的空行
+            list = getExList(list);
+
+            // 【原有逻辑完全不变】迭代器过滤错误数据
+            Iterator<WorkInvoiceReceiptInfo> iterator = list.iterator();
+            while (iterator.hasNext()){
+                WorkInvoiceReceiptInfo workInvoiceReceiptInfo = iterator.next();
+                if(StringUtils.isBlank(workInvoiceReceiptInfo.getInvoiceNumber()) || StringUtils.isBlank(workInvoiceReceiptInfo.getBuyerName()) || null == workInvoiceReceiptInfo.getMoney() ){
+                    if(StringUtils.isNotBlank(workInvoiceReceiptInfo.getInvoiceNumber())){
+                        workInvoiceReceiptInfo.setErrorMessage("数据中发票号");
+                    }else if(StringUtils.isNotBlank(workInvoiceReceiptInfo.getBuyerName())){
+                        workInvoiceReceiptInfo.setErrorMessage("数据中缺少购买企业名称");
+                    }else if(null == workInvoiceReceiptInfo.getMoney()){
+                        workInvoiceReceiptInfo.setErrorMessage("数据中缺少金额");
+                    }
+                    errorList.add(workInvoiceReceiptInfo);
+                    iterator.remove();
+                }else{
+                    effectiveList.add(workInvoiceReceiptInfo);
+                }
+            }
+
+            // 【原有逻辑完全不变】有效数据去重
+            for (WorkInvoiceReceiptInfo info : effectiveList) {
+                String invoiceName = (info.getInvoiceNumber() != null) ? info.getInvoiceNumber() : "";
+                String buyerName = (info.getBuyerName() != null) ? info.getBuyerName() : "";
+                String money = (info.getMoney() != null) ? info.getMoney().toString() : "";
+                String resultStr = invoiceName + "," + buyerName + "," + money;
+                info.setDistinctStr(resultStr);
+            }
+
+            Map<String,List<WorkInvoiceReceiptInfo>> map = financeInvoiceService.distinctProjectMaterialStorage(effectiveList);
+
+            // 【原有逻辑完全不变】各类错误数据标记
+            List<WorkInvoiceReceiptInfo> uniqueList = map.get("uniqueList");
+            List<WorkInvoiceReceiptInfo> duplicateList = map.get("duplicateList");
+            for (WorkInvoiceReceiptInfo info : duplicateList) {
+                info.setErrorMessage("重复数据,添加失败");
+            }
+            List<WorkInvoiceReceiptInfo> alreadyExistList = map.get("alreadyExistList");
+            for (WorkInvoiceReceiptInfo info : alreadyExistList) {
+                info.setErrorMessage("该发票已存在收款信息,添加失败");
+            }
+            List<WorkInvoiceReceiptInfo> inexistenceList = map.get("inexistenceList");
+            for (WorkInvoiceReceiptInfo info : inexistenceList) {
+                info.setErrorMessage("系统中不存在该发票信息,添加失败");
+            }
+            List<WorkInvoiceReceiptInfo> moneyMismatchingList = map.get("moneyMismatchingList");
+            for (WorkInvoiceReceiptInfo info : moneyMismatchingList) {
+                info.setErrorMessage("发票金额大于系统开票金额,添加失败");
+            }
+
+            // 【核心逻辑不变】生成Excel文件到指定文件夹
+            if(list.size() > uniqueList.size()){
+                excelFileName = generateImportResultExcel(uniqueList, errorList, duplicateList, alreadyExistList,
+                        inexistenceList, moneyMismatchingList, list);
+                result.put("msg", "导入数据存在问题,可下载文档查看详情。");
+                result.put("excelFileName", excelFileName); // 返回Excel文件名
+            }else{
+                result.put("msg", "已成功导入 "+list.size()+" 条发票收款记录"+failureMsg);
+            }
+
+            result.put("success", true); // 业务处理成功(即使有Excel,也是处理完成)
+
+        } catch (Exception e) {
+            result.put("msg", "导入发票收款记录失败!失败信息:"+e.getMessage());
+        }
+
+        return ResponseEntity.ok(result);
+    }
+
+
+    /**
+     * 去除excel中的空行
+     * @param list
+     * @return
+     */
+    public ArrayList<WorkInvoiceReceiptInfo> getExList(List<WorkInvoiceReceiptInfo> list){
+
+        ArrayList<WorkInvoiceReceiptInfo> cwProjectRecordsImportDTOS = new ArrayList<>();
+
+        list.stream().forEach(item->{
+            if(objectInvoiceImportCheckIsNull(item)){
+                cwProjectRecordsImportDTOS.add(item);
+            }
+        });
+
+        //return cwFinanceImportDTOS;
+        return cwProjectRecordsImportDTOS;
+    }
+
+
+    public static boolean objectInvoiceImportCheckIsNull(Object object) {
+        boolean flag = false; //定义返回结果,默认为true
+
+        if (Objects.isNull(object)) {
+            flag = false;
+        } else {
+            Class clazz = (Class) object.getClass(); // 得到类对象
+            Field fields[] = clazz.getDeclaredFields(); // 得到所有属性
+            for (Field field : fields) {
+                if("serialVersionUID".equals(field.getName()) || "BIZ_CODE".equalsIgnoreCase(field.getName())){
+                    continue;
+                }
+                field.setAccessible(true);
+                Object fieldValue = null;
+                try {
+                    fieldValue = field.get(object); //得到属性值
+                    Type fieldType = field.getGenericType();//得到属性类型
+                    String fieldName = field.getName(); // 得到属性名
+                    log.info("属性类型:" + fieldType + ",属性名:" + fieldName + ",属性值:" + fieldValue);
+                } catch (IllegalArgumentException e) {
+                    log.error(e.getMessage(), e);
+                } catch (IllegalAccessException e) {
+                    log.error(e.getMessage(), e);
+                }
+                if (fieldValue != null && fieldValue != "") {  //只要有一个属性值不为null 就返回false 表示对象不为null
+                    flag = true;
+                    break;
+                }
+            }
+        }
+
+        return flag;
+    }
+
+    /**
+     * 生成导入结果Excel,分2个Sheet:有效导入数据、错误数据(含原因)
+     * @param uniqueList 唯一有效数据
+     * @param errorList 错误数据
+     * @param duplicateList 重复数据
+     * @param alreadyExistList 该发票已存在收款信息
+     * @param inexistenceList 系统中不存在该发票信息
+     * @param moneyMismatchingList 发票金额大于系统开票金额
+     * @param metadataList 导入原数据
+     * @param response 响应对象,用于输出下载流
+     */
+    /**
+     * 改造后:生成Excel到指定文件夹,返回唯一文件名(不再直接输出到response)
+     */
+    private String generateImportResultExcel(List<WorkInvoiceReceiptInfo> uniqueList,
+                                             List<WorkInvoiceReceiptInfo> errorList,
+                                             List<WorkInvoiceReceiptInfo> duplicateList,
+                                             List<WorkInvoiceReceiptInfo> alreadyExistList,
+                                             List<WorkInvoiceReceiptInfo> inexistenceList,
+                                             List<WorkInvoiceReceiptInfo> moneyMismatchingList,
+                                             List<WorkInvoiceReceiptInfo> metadataList) throws Exception {
+
+        String path = System.getProperty("os.name").toLowerCase().contains("win")
+                ? "D:/attachment-file/invoiceBatch/"
+                : "/attachment-file/invoiceBatch/";
+
+        File saveDir = new File(path);
+        if (!saveDir.exists()) {
+            saveDir.mkdirs();
+        }
+
+        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
+        String fileName = "发票收款导入数据汇总_" + userDTO.getId() + ".xlsx";
+        String fullFilePath = path + fileName;
+
+        Workbook workbook = new XSSFWorkbook();
+
+        // ========== 统一导出所有Sheet,格式完全对齐图二 ==========
+        exportSheet(workbook, "有效数据", "批量收款导入有效数据", uniqueList);
+        exportSheet(workbook, "错误数据", "批量收款导入错误数据", errorList);
+        exportSheet(workbook, "重复数据", "批量收款导入重复数据", duplicateList);
+        exportSheet(workbook, "已存在收款数据", "批量收款导入已存在收款数据", alreadyExistList);
+        exportSheet(workbook, "不存在的发票信息", "批量收款导入不存在的发票信息", inexistenceList);
+        exportSheet(workbook, "发票金额大于系统开票金额", "批量收款导入发票金额大于系统开票金额数据", moneyMismatchingList);
+        exportSheet(workbook, "原数据", "批量收款导入原数据", metadataList);
+
+        try (FileOutputStream fos = new FileOutputStream(fullFilePath)) {
+            workbook.write(fos);
+        } finally {
+            workbook.close();
+        }
+
+        return fileName;
+    }
+
+    /**
+     * 统一导出Sheet方法:完美复刻图二样式(大标题+灰色表头+边框+居中)
+     */
+    private void exportSheet(Workbook workbook, String sheetName, String title, List<WorkInvoiceReceiptInfo> dataList) {
+        if (dataList == null || dataList.isEmpty()) {
+            return;
+        }
+
+        // 1. 创建Sheet
+        Sheet sheet = workbook.createSheet(sheetName);
+
+        // 2. 创建样式(完全对齐图二)
+        // 大标题样式:居中、加粗、大号字体
+        CellStyle titleStyle = workbook.createCellStyle();
+        Font titleFont = workbook.createFont();
+        titleFont.setFontHeightInPoints((short) 24);
+        titleFont.setBold(true);
+        titleStyle.setFont(titleFont);
+        titleStyle.setAlignment(HorizontalAlignment.CENTER);
+        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        // 表头样式:灰色背景、居中、加粗、边框
+        CellStyle headerStyle = workbook.createCellStyle();
+        Font headerFont = workbook.createFont();
+        headerFont.setBold(true);
+        headerFont.setFontHeightInPoints((short) 12);
+        headerStyle.setFont(headerFont);
+        headerStyle.setAlignment(HorizontalAlignment.CENTER);
+        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        // 全边框
+        headerStyle.setBorderBottom(BorderStyle.THIN);
+        headerStyle.setBorderTop(BorderStyle.THIN);
+        headerStyle.setBorderLeft(BorderStyle.THIN);
+        headerStyle.setBorderRight(BorderStyle.THIN);
+
+        // 数据样式:居中、边框
+        CellStyle dataStyle = workbook.createCellStyle();
+        dataStyle.setAlignment(HorizontalAlignment.CENTER);
+        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        dataStyle.setBorderBottom(BorderStyle.THIN);
+        dataStyle.setBorderTop(BorderStyle.THIN);
+        dataStyle.setBorderLeft(BorderStyle.THIN);
+        dataStyle.setBorderRight(BorderStyle.THIN);
+        Font dataFont = workbook.createFont();
+        dataFont.setFontHeightInPoints((short) 11);
+        dataStyle.setFont(dataFont);
+
+        // 3. 写入大标题(第1行,合并A-D列,完全对齐图二)
+        Row titleRow = sheet.createRow(0);
+        titleRow.setHeightInPoints(40); // 标题行高
+        Cell titleCell = titleRow.createCell(0);
+        titleCell.setCellValue(title);
+        titleCell.setCellStyle(titleStyle);
+        // 合并单元格(根据你的4个字段,合并A-D列,0-3)
+        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
+
+        // 4. 写入表头(第2行,灰色背景,完全对齐图二)
+        Row headerRow = sheet.createRow(1);
+        headerRow.setHeightInPoints(25);
+        String[] headers = {"*发票号码", "*购方企业名称", "*收款金额", "*提醒"};
+        for (int i = 0; i < headers.length; i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellValue(headers[i]);
+            cell.setCellStyle(headerStyle);
+            // 设置列宽(适配内容)
+            sheet.setColumnWidth(i, 25 * 256);
+        }
+
+        // 5. 写入数据(从第3行开始)
+        for (int i = 0; i < dataList.size(); i++) {
+            WorkInvoiceReceiptInfo info = dataList.get(i);
+            Row dataRow = sheet.createRow(i + 2);
+            dataRow.setHeightInPoints(20);
+
+            // 按顺序写入4个字段
+            Cell cell0 = dataRow.createCell(0);
+            cell0.setCellValue(info.getInvoiceNumber());
+            cell0.setCellStyle(dataStyle);
+
+            Cell cell1 = dataRow.createCell(1);
+            cell1.setCellValue(info.getBuyerName());
+            cell1.setCellStyle(dataStyle);
+
+            Cell cell2 = dataRow.createCell(2);
+            cell2.setCellValue(info.getMoney());
+            cell2.setCellStyle(dataStyle);
+
+            Cell cell3 = dataRow.createCell(3);
+            cell3.setCellValue(info.getErrorMessage() == null ? "" : info.getErrorMessage());
+            cell3.setCellStyle(dataStyle);
+        }
+    }
+
+    @GetMapping("/downloadImportResult")
+    public void downloadImportResult(String fileName, HttpServletResponse response) throws Exception {
+        String path = System.getProperty("os.name").toLowerCase().contains("win")
+                ? "D:/attachment-file/invoiceBatch/"
+                : "/attachment-file/invoiceBatch/";
+
+        File file = new File(path + fileName);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
+
+        Files.copy(file.toPath(), response.getOutputStream());
+    }
+
 }

+ 13 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/FinanceInvoiceDetailMapper.java

@@ -2,7 +2,11 @@ package com.jeeplus.assess.invoice.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.jeeplus.assess.invoice.domain.FinanceInvoiceDetail;
+import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceDetailDTO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * @Entity generator.domain.FinanceInvoiceDetail
@@ -14,6 +18,15 @@ public interface FinanceInvoiceDetailMapper extends BaseMapper<FinanceInvoiceDet
      * @param invoice
      */
     void updateNumberById(FinanceInvoiceDetail invoice);
+
+
+
+    /**
+     * 根据发票号查询开票具体信息
+     * @param numberList
+     * @return
+     */
+    List<FinanceInvoiceDetailDTO> getInvoiceDetailByNumber(@Param("list") List<String> numberList);
 }
 
 

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

@@ -8,6 +8,8 @@ import com.jeeplus.assess.invoice.domain.FinanceInvoice;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.jeeplus.assess.invoice.domain.FinanceInvoiceTaxClassificationCode;
 import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceDTO;
+import com.jeeplus.assess.invoice.service.dto.WorkInvoiceReceipt;
+import com.jeeplus.assess.invoice.service.dto.WorkInvoiceReceiptInfo;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
@@ -87,6 +89,29 @@ public interface FinanceInvoiceMapper extends BaseMapper<FinanceInvoice> {
      * @return
      */
     List<FinanceInvoiceDTO> getFinanceInvoiceByProgramId(@Param("programIdList") List<String> programIdList);
+
+
+    /**
+     * 根据发票编号查询已经收款的信息
+     * @param invoiceNumberList
+     * @return
+     */
+    List<WorkInvoiceReceipt> getByInvoiceNumber(@Param("list") List<String> invoiceNumberList);
+
+
+    /**
+     * 批量导入
+     * @param list
+     */
+    void batchInsert(@Param("list") List<WorkInvoiceReceipt> list);
+
+
+    /**
+     * 批量修改(拼接多条UPDATE)
+     * @param list 待修改的数据集(moneyEqualList/moneyPartialList)
+     * @return 影响的行数
+     */
+    int batchUpdateByForeach(@Param("list") List<WorkInvoiceReceiptInfo> list);
 }
 
 

+ 19 - 2
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceDetailMapper.xml

@@ -13,7 +13,6 @@
             <result property="delFlag" column="del_flag" jdbcType="INTEGER"/>
             <result property="remarks" column="remarks" jdbcType="VARCHAR"/>
             <result property="invoiceId" column="invoice_id" jdbcType="VARCHAR"/>
-            <result property="code" column="code" jdbcType="VARCHAR"/>
             <result property="number" column="number" jdbcType="VARCHAR"/>
             <result property="account" column="account" jdbcType="VARCHAR"/>
             <result property="rate" column="rate" jdbcType="VARCHAR"/>
@@ -31,7 +30,6 @@
         fid.del_flag,
         fid.remarks,
         fid.invoice_id,
-        fid.code,
         fid.number,
         fid.account,
         fid.rate,
@@ -45,4 +43,23 @@
             number = #{number}
         WHERE invoice_id = #{invoiceId} and del_flag = 0
     </update>
+
+
+
+
+    <select id="getInvoiceDetailByNumber" resultMap="BaseResultMap" >
+        SELECT
+        <include refid="Base_Column_List"></include>
+        FROM finance_invoice_detail fid
+        <where>
+            fid.del_flag = 0
+            <if test="list!=null and list.size!=0">
+                and fid.number in
+                <foreach collection="list" item="number" separator="," open="(" close=")">
+                    #{number}
+                </foreach>
+            </if>
+        </where>
+    </select>
+
 </mapper>

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

@@ -517,4 +517,102 @@
             </if>
         </where>
     </select>
+
+
+
+    <select id="getByInvoiceNumber" resultType="com.jeeplus.assess.invoice.service.dto.WorkInvoiceReceipt" >
+        SELECT
+        a.id AS "id",
+        a.create_by_id AS "createBy.id",
+        a.create_time AS "createTime",
+        a.update_by_id AS "updateBy.id",
+        a.update_time AS "updateTime",
+        a.remarks AS "remarks",
+        a.del_flag AS "delFlag",
+        a.invoice_id AS "invoiceId",
+        a.remittance_amount AS "money",
+        date_format(a.remittance_date, '%Y-%m-%d') AS "receiptDate",
+        wid.number
+
+        FROM finance_invoice_receivables a
+        left join finance_invoice_detail wid on wid.invoice_id = a.invoice_id
+        <where>
+            a.del_flag = 0
+
+            <if test="list!=null and list.size!=0">
+                and wid.number in
+                <foreach collection="list" item="invoiceNumber" separator="," open="(" close=")">
+                    #{invoiceNumber}
+                </foreach>
+            </if>
+        </where>
+    </select>
+
+
+    <!-- 批量插入:新建id=batchInsert,保留原单条insert不变 -->
+    <insert id="batchInsert">
+        INSERT INTO finance_invoice_receivables (
+        id,
+        create_by_id,
+        create_time,
+        update_by_id,
+        update_time,
+        del_flag,
+        remarks,
+        invoice_id,
+        remittance_unit,
+        remittance_amount,
+        remittance_date,
+        status
+        ) VALUES
+        <!-- 核心:foreach遍历list,拼接多组VALUES,分隔符为逗号 -->
+        <foreach collection="list" item="info" separator=",">
+            (
+            #{info.id},
+            #{info.createBy.id},
+            #{info.createTime},
+            #{info.updateBy.id},
+            #{info.updateTime},
+            #{info.delFlag},
+            #{info.remarks},
+            #{info.invoiceId},
+            #{info.companyName},
+            #{info.money, jdbcType=DECIMAL},
+            #{info.receiptDate},
+            #{info.status}
+            )
+        </foreach>
+    </insert>
+
+    <!-- 方案一:foreach拼接多条UPDATE -->
+    <update id="batchUpdateByForeach">
+        UPDATE finance_invoice
+        <!-- trim处理SET后多余的逗号,避免语法错误 -->
+        <trim prefix="SET" suffixOverrides=",">
+            <!-- 批量更新receipt_money:匹配id才更新,无值则保留原数据 -->
+            receivables_status = CASE id
+            <foreach collection="list" item="info" separator="">
+                <if test="info.receiptMoney != null">
+                    WHEN #{info.invoiceId} THEN #{info.receiptMoney}
+                </if>
+            </foreach>
+            ELSE receivables_status
+            END,
+            <!-- 批量更新receipt_money_date:null/空字符串都不更新,保留原数据 -->
+            receivables_date = CASE id
+            <foreach collection="list" item="info" separator="">
+                <if test="info.receiptMoneyDate != null">
+                    WHEN #{info.invoiceId} THEN #{info.receiptMoneyDate}
+                </if>
+            </foreach>
+            ELSE receivables_date
+            END
+        </trim>
+        <!-- 关键:只更新传入的id集合,避免全表更新! -->
+        WHERE id IN
+        <foreach collection="list" item="info" open="(" separator="," close=")">
+            #{info.invoiceId}
+        </foreach>
+    </update>
+
 </mapper>

+ 204 - 5
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceService.java

@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.google.common.collect.Lists;
 import com.jeeplus.assess.invoice.domain.OMS.InvoiceDown.OMSInvoiceDetailInfo;
+import com.jeeplus.assess.invoice.service.dto.*;
 import com.jeeplus.assess.invoice.thread.ApprovalThread;
 import com.jeeplus.assess.invoice.thread.RedApprovalThread;
 import com.jeeplus.assess.invoice.utils.DingTalkUtil;
@@ -30,10 +31,6 @@ import com.jeeplus.sys.service.dto.RoleDTO;
 import com.jeeplus.sys.service.dto.UserDTO;
 import com.jeeplus.assess.invoice.domain.*;
 import com.jeeplus.assess.invoice.mapper.*;
-import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceBaseDTO;
-import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceDTO;
-import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceDetailDTO;
-import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceReceivablesDTO;
 import com.jeeplus.assess.invoice.service.mapstruct.*;
 import com.jeeplus.assess.workContract.mapper.WorkContractInfoMapper;
 import org.apache.commons.collections4.CollectionUtils;
@@ -46,6 +43,9 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Pattern;
@@ -669,7 +669,7 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
         if(isDDNotice){
             //根据字典判断是否钉钉通知用户
             String realTimeDictValue = SpringUtil.getBean(IDictApi.class).getRealTimeDictValue("是否接收钉钉通知");
-            if(realTimeDictValue.equals("1")){
+            if(StringUtils.isNotBlank(realTimeDictValue) && realTimeDictValue.equals("1")){
                 FinanceInvoiceDTO dto = queryById(financeInvoice.getId());
                 addDingtalkNotice(dto);
             }
@@ -1417,4 +1417,203 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
     public List<FinanceInvoiceDTO> getFinanceInvoiceByProgramId(List<String> programIdList) {
         return financeInvoiceMapper.getFinanceInvoiceByProgramId(programIdList);
     }
+
+
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    @Transactional(readOnly = false)
+    public Map<String, List<WorkInvoiceReceiptInfo>> distinctProjectMaterialStorage(
+            List<WorkInvoiceReceiptInfo> workInvoiceReceiptInfo) {
+
+        HashMap<String, List<WorkInvoiceReceiptInfo>> map = new HashMap<>();
+
+        // 第一步:对本次导入数据进行去重(自身重复)
+        Set<String> seen = new HashSet<>();
+        List<WorkInvoiceReceiptInfo> uniqueList = new ArrayList<>();      // 本次去重后的有效值
+        List<WorkInvoiceReceiptInfo> duplicateList = new ArrayList<>();   // 本次自身重复值
+        List<WorkInvoiceReceiptInfo> alreadyExistList = new ArrayList<>();   // 已经有收款信息的数据
+        List<WorkInvoiceReceiptInfo> inexistenceList = new ArrayList<>();   // 数据表中不存在的的数据
+        List<WorkInvoiceReceiptInfo> moneyMismatchingList = new ArrayList<>();   // 数据表中金额大于开票价的的数据
+
+        for (WorkInvoiceReceiptInfo item : workInvoiceReceiptInfo) {
+            String distinctStr = item.getDistinctStr();
+
+            // 处理null值(视为重复)
+            if (distinctStr == null) {
+                duplicateList.add(item);
+                continue;
+            }
+
+            if (!seen.contains(distinctStr)) {
+                seen.add(distinctStr);
+                uniqueList.add(item);
+            } else {
+                duplicateList.add(item);
+            }
+        }
+
+        //对有效值进行发票查询操作,如果已经存在记录 则表示 给该数据存在问题(重复上传)
+        List<String> invoiceNumberList = Lists.newArrayList();
+        for (WorkInvoiceReceiptInfo invoiceReceiptInfo : uniqueList) {
+            invoiceNumberList.add(invoiceReceiptInfo.getInvoiceNumber());
+        }
+        //根据 invoiceNumberList 去进行查询已经存在的数据信息
+        List<WorkInvoiceReceipt> alreadyExistReceiptList = financeInvoiceMapper.getByInvoiceNumber(invoiceNumberList);
+
+        //遍历有效值和查出来的已经存在的收款信息进行对比,若存在同一发票号的信息,金额进行累加,若金额大于应收金额,则表示该条数据存在问题,所小于则表示部分收款,若等于 则表示完全收款,改成已收款
+        // 迭代器方式:内层循环安全删除
+        for (WorkInvoiceReceipt workInvoiceReceipt : alreadyExistReceiptList) {
+            // 内层用迭代器遍历uniqueList,支持安全删除
+            Iterator<WorkInvoiceReceiptInfo> iterator = uniqueList.iterator();
+            while (iterator.hasNext()) {
+                WorkInvoiceReceiptInfo invoiceReceiptInfo = iterator.next();
+                if (workInvoiceReceipt.getNumber().equals(invoiceReceiptInfo.getInvoiceNumber())) {
+                    alreadyExistList.add(invoiceReceiptInfo); // 加入待处理列表
+                    invoiceReceiptInfo.setInvoiceId(workInvoiceReceipt.getInvoiceId());
+                    iterator.remove(); // 迭代器安全删除,不会触发并发修改异常
+                }
+            }
+        }
+
+        //对剩下的有效数据进行处理
+        //判定金额和开票额是否一致,若一致,则表示全部收款,若小于开票额,表示部分收款,若大于,应当是填写错误,进行抛出,不进行导入
+        invoiceNumberList = Lists.newArrayList();
+        for (WorkInvoiceReceiptInfo invoiceReceiptInfo : uniqueList) {
+            invoiceNumberList.add(invoiceReceiptInfo.getInvoiceNumber());
+        }
+        //根据 invoiceNumberList 去进行查询已经存在的数据信息
+        List<FinanceInvoiceDetailDTO> invoiceDetailList = financeInvoiceDetailMapper.getInvoiceDetailByNumber(invoiceNumberList);
+        for (FinanceInvoiceDetailDTO workInvoiceDetail : invoiceDetailList) {
+            for (WorkInvoiceReceiptInfo info : uniqueList) {
+                if(workInvoiceDetail.getNumber().equals(info.getInvoiceNumber())){
+                    info.setInvoiceId(workInvoiceDetail.getInvoiceId());
+                }
+            }
+        }
+
+        //如果两者数据量不同,说明 要么发票号错误,要么该发票没有录入,需要抛出,其余的再进行比较金额
+        if(invoiceDetailList.size() != uniqueList.size()){
+            //将invoiceDetailList 和uniqueList 进行对比,如果uniqueList中存在 但是 invoiceDetailList 不存在,则就要将这个值从uniqueList删除并写入到 inexistenceList中
+
+            // 1. 提取invoiceDetailList中所有发票号存入Set,用于O(1)快速判断是否存在(核心优化)
+            Set<String> detailInvoiceNumberSet = new HashSet<>();
+            for (FinanceInvoiceDetailDTO detail : invoiceDetailList) {
+                detailInvoiceNumberSet.add(detail.getNumber());
+            }
+
+            // 2. 迭代器遍历uniqueList,安全删除+收集不存在的元素(避免ConcurrentModificationException)
+            Iterator<WorkInvoiceReceiptInfo> iterator = uniqueList.iterator();
+            while (iterator.hasNext()) {
+                WorkInvoiceReceiptInfo uniqueInfo = iterator.next();
+                String uniqueInvoiceNo = uniqueInfo.getInvoiceNumber();
+                // 判断:uniqueList的发票号 在invoiceDetailList中不存在
+                if (!detailInvoiceNumberSet.contains(uniqueInvoiceNo)) {
+                    inexistenceList.add(uniqueInfo); // 加入不存在的列表
+                    iterator.remove(); // 迭代器安全删除,不会触发并发修改异常
+                }
+            }
+        }
+        //到此处 说明数据已经匹配几乎完成,现在还需要对有效的参数的金额进行匹配
+        for (FinanceInvoiceDetailDTO workInvoiceDetail : invoiceDetailList) {
+            for (WorkInvoiceReceiptInfo unique : uniqueList) {
+                if(workInvoiceDetail.getNumber().equals(unique.getInvoiceNumber())){
+                    //判定金额是否相同,小于为-1  相同为0 大于为1
+                    String totalMoney = workInvoiceDetail.getAccount();
+                    String money = unique.getMoney();
+                    BigDecimal totalMoneyB = new BigDecimal(totalMoney);
+                    BigDecimal moneyB = new BigDecimal(money);
+                    // 核心比较:直接得到-1/0/1的结果
+                    int compareResult = moneyB.compareTo(totalMoneyB);
+                    // 后续可根据结果做业务处理
+                    if (compareResult == 1) {
+                        // totalMoney 小于 money 业务逻辑
+                        //说明新导入的额度要大于开票额度
+                        unique.setMoneyFlag(1);
+                    } else if (compareResult == 0) {
+                        // 金额相等 业务逻辑
+                        //说明金额相等
+                        unique.setMoneyFlag(0);
+                    } else {
+                        // totalMoney 大于 money 业务逻辑
+                        //说明部分收款
+                        unique.setMoneyFlag(-1);
+                    }
+                }
+
+            }
+        }
+
+        //对处理完的数据进行区分,大于1的值不能进行导入,需要剔除
+        Iterator<WorkInvoiceReceiptInfo> iterator = uniqueList.iterator();
+        while (iterator.hasNext()) {
+            WorkInvoiceReceiptInfo info = iterator.next();
+            // 判定moneyFlag为1(导入金额超开票金额),剔除并收集
+            if (info.getMoneyFlag() == 1) {
+                moneyMismatchingList.add(info); // 加入无效数据列表
+                iterator.remove(); // 从有效列表uniqueList中安全删除
+            }
+        }
+
+        //此处可以对剩余的数据进行处理了
+
+        // 2. 生成唯一文件名(避免覆盖)
+        UserDTO userDTO = SpringUtil.getBean ( IUserApi.class ).getByToken(TokenProvider.getCurrentToken());
+
+        List<WorkInvoiceReceipt> insertReceiptList = Lists.newArrayList();
+        for (WorkInvoiceReceiptInfo info : uniqueList) {
+            WorkInvoiceReceipt insertReceipt = new WorkInvoiceReceipt();
+
+            if (info.getMoneyFlag() == 0) {
+                info.setReceiptMoneyDate(new Date());
+                info.setReceiptMoney(1);
+                insertReceipt.setStatus("1");
+            } else if(info.getMoneyFlag() == -1){
+                info.setReceiptMoney(2);
+                insertReceipt.setStatus("2");
+            }
+
+            insertReceipt.setInvoiceId(info.getInvoiceId());
+            String dateStr = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
+                    .withZone(ZoneId.systemDefault())
+                    .format(Instant.ofEpochMilli(new Date().getTime()));
+            insertReceipt.setReceiptDate(dateStr);
+            insertReceipt.setCompanyName(info.getBuyerName());
+            insertReceipt.setMoney(new BigDecimal(info.getMoney().trim()));
+
+            // 生成id
+            String id = UUID.randomUUID().toString().replace("-", "");
+            insertReceipt.setId(id);
+            insertReceipt.setCreateBy(userDTO);
+            insertReceipt.setCreateTime(new Date());
+            insertReceipt.setUpdateBy(userDTO);
+            insertReceipt.setUpdateTime(new Date());
+            insertReceipt.setDelFlag(0);
+            insertReceiptList.add(insertReceipt);
+        }
+        if(!insertReceiptList.isEmpty()){
+            //将收款信息添加到数据库中
+            financeInvoiceMapper.batchInsert(insertReceiptList);
+        }
+        if(!uniqueList.isEmpty()){
+            for (WorkInvoiceReceiptInfo invoiceReceiptInfo : uniqueList) {
+                invoiceReceiptInfo.setUpdateBy(userDTO);
+                invoiceReceiptInfo.setUpdateTime(new Date());
+                invoiceReceiptInfo.setDelFlag(0);
+            }
+            //修改work_invoice表中 receipt_money_date 和 receipt_money 参数信息
+            financeInvoiceMapper.batchUpdateByForeach(uniqueList);
+        }
+
+
+
+        // 最终返回处理后的列表
+        map.put("uniqueList", uniqueList);      // 仅包含本次新增且不与历史重复的数据
+        map.put("duplicateList", duplicateList); // 包含自身重复和与历史重复的数据
+        map.put("alreadyExistList", alreadyExistList); // 已经有收款信息的数据
+        map.put("inexistenceList", inexistenceList); // 数据表中不存在的的数据 invoiceDetail 表中不存在,也就是说 没有开票信息
+        map.put("moneyMismatchingList", moneyMismatchingList); // 数据表中金额大于开票价的的数据
+        return map;
+    }
 }

+ 32 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/InvoiceImportDTO.java

@@ -0,0 +1,32 @@
+package com.jeeplus.assess.invoice.service.dto;
+
+import cn.afterturn.easypoi.excel.annotation.Excel;
+import com.jeeplus.core.service.dto.BaseDTO;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author: 徐滕
+ * @version: 2025-3-18 10:02
+ */
+@Data
+public class InvoiceImportDTO extends BaseDTO {
+
+    private static final long serialVersionUID = 1L;
+
+    @Excel(name = "*发票号码",width = 255)
+    @NotNull(message="发票号码不可为空")
+    private String contractName;
+
+    @Excel(name = "*购方企业名称",width = 255)
+    @NotNull(message="购方企业名称不可为空")
+    private String projectName;
+
+    /**
+     * 风险等级
+     */
+    @Excel(name = "*收款金额",width = 20)
+    @NotNull(message="收款金额不可为空")
+    private String riskLevel;
+}

+ 29 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/WorkInvoiceReceipt.java

@@ -0,0 +1,29 @@
+package com.jeeplus.assess.invoice.service.dto;
+
+import com.jeeplus.core.service.dto.BaseDTO;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author: 徐滕
+ * @version: 2025-3-18 10:02
+ */
+@Data
+public class WorkInvoiceReceipt extends BaseDTO {
+
+    private static final long serialVersionUID = 1L;
+
+    private String invoiceId;   //发票id
+    private String companyName;  //公司名称
+    private BigDecimal money;   //汇款金额
+    private String receiptDate;  //汇款时间
+    private String createName;
+    private String updateName;
+    private String invoiceNumber;
+    private String number;
+    private String remittanceUnit;
+    private String status;
+    private String remarks;
+}

+ 74 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/utils/ExcelField.java

@@ -0,0 +1,74 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.assess.invoice.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Excel注解定义
+ * @author jeeplus
+ * @version 2013-03-10
+ */
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelField {
+
+	/**
+	 * 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”)
+	 */
+	String value() default "";
+	
+	/**
+	 * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效)
+	 */
+	String title();
+	
+	/**
+	 * 字段类型(0:导出导入;1:仅导出;2:仅导入)
+	 */
+	int type() default 0;
+
+	/**
+	 * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右)
+	 */
+	int align() default 0;
+	
+	/**
+	 * 导出字段字段排序(升序)
+	 */
+	int sort() default 0;
+
+	/**
+	 * 如果是字典类型,请设置字典的type值
+	 */
+	String dictType() default "";
+	String mainDictType() default "";
+	
+	/**
+	 * 反射类型
+	 */
+	Class<?> fieldType() default Class.class;
+	
+	/**
+	 * 字段归属组(根据分组导出导入)
+	 */
+	int[] groups() default {};
+
+    /**
+     * 列号
+     */
+    int colNum() default 0;
+
+    /**
+     * 模板下拉--字典
+     * @return
+     */
+    String valiName() default "";
+
+	// 添加format属性,用于日期格式化
+	String format() default "";  // 新增这一行
+}

+ 323 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/utils/ExportMultipleTabsExcel.java

@@ -0,0 +1,323 @@
+package com.jeeplus.assess.invoice.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 支持多Sheet的Excel导出工具类
+ */
+/**
+ * 多页签Excel导出工具类
+ * 支持创建多个Sheet页,每个页签可独立设置名称和数据
+ */
+
+/**
+ * 支持多Sheet的Excel导出工具类
+ */
+public class ExportMultipleTabsExcel {
+	private static final Logger log = LoggerFactory.getLogger(ExportMultipleTabsExcel.class);
+	private Workbook workbook; // 共享的工作簿
+	private Map<String, SheetData> sheetDataMap = new HashMap<>(); // 存储各Sheet的数据
+
+	/**
+	 * 内部类:存储单个Sheet的数据
+	 */
+	private static class SheetData {
+		String sheetName;
+		String title;
+		Class<?> entityClass;
+		List<?> dataList;
+		List<Object[]> annotationList; // 存储ExcelField注解信息
+		int rownum = 0; // 当前行号
+
+		public SheetData(String sheetName, String title, Class<?> entityClass) {
+			this.sheetName = sheetName;
+			this.title = title;
+			this.entityClass = entityClass;
+			this.annotationList = initAnnotationList(entityClass);
+		}
+
+		// 初始化注解列表
+		private List<Object[]> initAnnotationList(Class<?> cls) {
+			List<Object[]> list = new ArrayList<>();
+			// 处理字段注解
+			Field[] fields = cls.getDeclaredFields();
+			for (Field field : fields) {
+				ExcelField ef = field.getAnnotation(ExcelField.class);
+				if (ef != null && (ef.type() == 0 || ef.type() == 1)) {
+					list.add(new Object[]{ef, field});
+				}
+			}
+			// 处理方法注解
+			Method[] methods = cls.getDeclaredMethods();
+			for (Method method : methods) {
+				ExcelField ef = method.getAnnotation(ExcelField.class);
+				if (ef != null && (ef.type() == 0 || ef.type() == 1)) {
+					list.add(new Object[]{ef, method});
+				}
+			}
+			// 按排序号排序
+			list.sort(Comparator.comparingInt(o -> ((ExcelField) o[0]).sort()));
+			return list;
+		}
+	}
+
+	/**
+	 * 构造函数:初始化工作簿
+	 */
+	public ExportMultipleTabsExcel() {
+		this.workbook = new SXSSFWorkbook(1000); // 内存中保留1000行,适合大数据量
+	}
+
+	/**
+	 * 添加一个Sheet页
+	 */
+	public void addSheet(String sheetName, String title, Class<?> entityClass, List<?> dataList) {
+		SheetData sheetData = new SheetData(sheetName, title, entityClass);
+		sheetData.dataList = dataList;
+		sheetDataMap.put(sheetName, sheetData);
+	}
+
+	/**
+	 * 创建单元格样式
+	 */
+	private Map<String, CellStyle> createStyles(Workbook wb) {
+		Map<String, CellStyle> styles = new HashMap<>();
+
+		// 标题样式
+		CellStyle style = wb.createCellStyle();
+		style.setAlignment(HorizontalAlignment.CENTER);
+		style.setVerticalAlignment(VerticalAlignment.CENTER);
+		style.setBorderBottom(BorderStyle.THIN);
+		style.setBorderLeft(BorderStyle.THIN);
+		style.setBorderRight(BorderStyle.THIN);
+		style.setBorderTop(BorderStyle.THIN);
+		Font titleFont = wb.createFont();
+		titleFont.setFontName("宋体");
+		titleFont.setFontHeightInPoints((short) 16);
+		titleFont.setBold(true);
+		style.setFont(titleFont);
+		styles.put("title", style);
+
+		// 表头样式(无边框 + 更深底色 + 白色字体)
+		style = wb.createCellStyle();
+		style.setAlignment(HorizontalAlignment.CENTER);
+		style.setVerticalAlignment(VerticalAlignment.CENTER);
+		style.setBorderBottom(BorderStyle.NONE);
+		style.setBorderLeft(BorderStyle.NONE);
+		style.setBorderRight(BorderStyle.NONE);
+		style.setBorderTop(BorderStyle.NONE);
+		style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+		style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+		Font headerFont = wb.createFont();
+		headerFont.setFontName("宋体");
+		headerFont.setFontHeightInPoints((short) 11);
+		headerFont.setBold(true);
+		headerFont.setColor(IndexedColors.WHITE.getIndex()); // 白色字体
+		style.setFont(headerFont);
+		styles.put("header", style);
+
+		// 数据单元格样式(区分字符串和数字对齐)
+		// 字符串居中样式
+		CellStyle stringStyle = wb.createCellStyle();
+		stringStyle.setAlignment(HorizontalAlignment.CENTER);
+		stringStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+		stringStyle.setBorderBottom(BorderStyle.THIN);
+		stringStyle.setBorderLeft(BorderStyle.THIN);
+		stringStyle.setBorderRight(BorderStyle.THIN);
+		stringStyle.setBorderTop(BorderStyle.THIN);
+		Font stringFont = wb.createFont();
+		stringFont.setFontName("宋体");
+		stringFont.setFontHeightInPoints((short) 11);
+		stringStyle.setFont(stringFont);
+		styles.put("data_string", stringStyle);
+
+		// 数字靠右样式
+		CellStyle numberStyle = wb.createCellStyle();
+		numberStyle.setAlignment(HorizontalAlignment.RIGHT);
+		numberStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+		numberStyle.setBorderBottom(BorderStyle.THIN);
+		numberStyle.setBorderLeft(BorderStyle.THIN);
+		numberStyle.setBorderRight(BorderStyle.THIN);
+		numberStyle.setBorderTop(BorderStyle.THIN);
+		Font numberFont = wb.createFont();
+		numberFont.setFontName("宋体");
+		numberFont.setFontHeightInPoints((short) 11);
+		numberStyle.setFont(numberFont);
+		styles.put("data_number", numberStyle);
+
+		return styles;
+	}
+
+	/**
+	 * 生成Excel文件
+	 */
+	public void write(OutputStream os) throws IOException {
+		for (SheetData sheetData : sheetDataMap.values()) {
+			createSheetContent(sheetData);
+		}
+		// 写入输出流
+		workbook.write(os);
+	}
+
+	/**
+	 * 创建单个Sheet的内容
+	 */
+	private void createSheetContent(SheetData sheetData) {
+		Sheet sheet = workbook.createSheet(sheetData.sheetName);
+		Map<String, CellStyle> styles = createStyles(workbook);
+		List<Object[]> annotationList = sheetData.annotationList;
+		int rownum = sheetData.rownum;
+
+		// 创建标题行
+		if (StringUtils.isNotBlank(sheetData.title)) {
+			Row titleRow = sheet.createRow(rownum++);
+			titleRow.setHeightInPoints(30);
+			Cell titleCell = titleRow.createCell(0);
+			titleCell.setCellStyle(styles.get("title"));
+			titleCell.setCellValue(sheetData.title);
+			sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, annotationList.size() - 1));
+		}
+
+		// 创建表头行
+		Row headerRow = sheet.createRow(rownum++);
+		headerRow.setHeightInPoints(18);
+		for (int i = 0; i < annotationList.size(); i++) {
+			Object[] os = annotationList.get(i);
+			ExcelField ef = (ExcelField) os[0];
+			String header = ef.title();
+
+			// 估算列宽:中文字符每个占2个字符宽度,英文字符每个占1个
+			int width = 0;
+			for (char c : header.toCharArray()) {
+				width += (c > 127) ? 2 : 1;
+			}
+			// 最小宽度为10,最大为50
+			width = Math.max(width, 10);
+			width = Math.min(width, 50);
+			sheet.setColumnWidth(i, width * 256 * 2); // 设置列宽
+
+			// 新增:创建表头单元格并设置标题值
+			Cell headerCell = headerRow.createCell(i);
+			headerCell.setCellStyle(styles.get("header"));
+			headerCell.setCellValue(header);
+		}
+
+		// 填充数据行(区分字符串和数字对齐)
+		if (sheetData.dataList != null && !sheetData.dataList.isEmpty()) {
+			for (Object obj : sheetData.dataList) {
+				Row dataRow = sheet.createRow(rownum++);
+				dataRow.setHeightInPoints(16);
+				for (int i = 0; i < annotationList.size(); i++) {
+					Object[] os = annotationList.get(i);
+					ExcelField ef = (ExcelField) os[0];
+					Cell cell = dataRow.createCell(i);
+
+					// 获取单元格值
+					Object value = getFieldValue(obj, ef, os);
+
+					// 根据值的类型设置对齐样式
+					if (value instanceof Number) {
+						cell.setCellStyle(styles.get("data_number")); // 数字靠右
+					} else {
+						cell.setCellStyle(styles.get("data_string")); // 字符串居中
+					}
+
+					// 设置单元格值
+					setCellValue(cell, value, ef);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 使用标准反射获取字段值
+	 */
+	private Object getFieldValue(Object obj, ExcelField ef, Object[] os) {
+		try {
+			String fieldName = ef.value();
+			if (StringUtils.isBlank(fieldName)) {
+				if (os[1] instanceof Field) {
+					fieldName = ((Field) os[1]).getName();
+				} else if (os[1] instanceof Method) {
+					Method method = (Method) os[1];
+					return method.invoke(obj);
+				}
+			}
+
+			// 调用getter方法
+			if (StringUtils.isNotBlank(fieldName)) {
+				String getterMethod = "get" + fieldName.substring(0, 1).toUpperCase()
+						+ fieldName.substring(1);
+				Method method = obj.getClass().getMethod(getterMethod);
+				Object value = method.invoke(obj);
+
+
+				return value;
+			}
+		} catch (Exception e) {
+			log.error("获取字段值失败", e);
+		}
+		return "";
+	}
+
+	/**
+	 * 设置单元格值(处理不同数据类型)
+	 */
+	private void setCellValue(Cell cell, Object value, ExcelField ef) {
+		if (value == null) {
+			cell.setCellValue("");
+			return;
+		}
+
+		// 处理日期类型
+		if (value instanceof Date) {
+			String format = StringUtils.isNotBlank(ef.format()) ? ef.format() : "yyyy-MM-dd";
+			cell.setCellValue(new SimpleDateFormat(format).format((Date) value));
+		}
+		// 处理数字类型
+		else if (value instanceof Number) {
+			cell.setCellValue(((Number) value).doubleValue());
+		}
+		// 处理布尔类型
+		else if (value instanceof Boolean) {
+			cell.setCellValue((Boolean) value);
+		}
+		// 其他类型按字符串处理
+		else {
+			cell.setCellValue(value.toString());
+		}
+	}
+
+	/**
+	 * 输出到客户端下载
+	 */
+	public void write(HttpServletResponse response, String fileName) throws IOException {
+		response.reset();
+		response.setContentType("application/octet-stream; charset=utf-8");
+		response.setHeader("Content-Disposition", "attachment; filename="
+				+ new String(fileName.getBytes("utf-8"), "ISO-8859-1"));
+		write(response.getOutputStream());
+	}
+
+	/**
+	 * 清理临时文件(避免内存泄漏)
+	 */
+	public void dispose() {
+		if (workbook instanceof SXSSFWorkbook) {
+			((SXSSFWorkbook) workbook).dispose();
+		}
+	}
+}

BIN
jeeplus-modules/jeeplus-assess/src/main/resources/dot/收款导入模版.xlsx


+ 355 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/controller/CwFinanceInvoiceController.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.TypeReference;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.common.collect.Lists;
 import com.jeeplus.aop.demo.annotation.DemoMode;
 import com.jeeplus.common.TokenProvider;
 import com.jeeplus.common.excel.ExcelOptions;
@@ -28,6 +29,9 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.ibatis.annotations.Param;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -48,6 +52,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Type;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -1016,4 +1021,354 @@ public class CwFinanceInvoiceController {
         }
     }
 
+
+
+
+
+    /**
+     * 下载项目导入模板
+     *
+     * @param response
+     * @return
+     */
+    @GetMapping("/importCwFinance/template")
+    @ApiOperation(value = "下载模板")
+    public void importCwFinanceTemplate(HttpServletResponse response, HttpServletRequest request) {
+        try {
+            // 去掉 this.getClass(),用当前线程的类加载器,路径去掉开头的 /
+            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("dot/收款导入模版.xlsx");
+            //强制下载不打开
+            response.setContentType("application/force-download");
+            OutputStream out = response.getOutputStream();
+            //使用URLEncoder来防止文件名乱码或者读取错误
+            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("project_records_template.xlsx", "UTF-8"));
+            int b = 0;
+            byte[] buffer = new byte[1000000];
+            while (b != -1) {
+                b = inputStream.read(buffer);
+                if (b != -1) out.write(buffer, 0, b);
+            }
+            inputStream.close();
+            out.close();
+            out.flush();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /**
+     * 导入项目数据excel
+     *
+     * @return
+     */
+    @DemoMode
+    @PostMapping("/importProjectRecords")
+    @ApiLog(value = "导入项目数据excel", type = LogTypeEnum.IMPORT)
+    public ResponseEntity importProjectRecords(MultipartFile file, HttpServletRequest request) throws IOException {
+        // 初始化返回结果
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", false); // 默认失败
+        result.put("msg", "");
+        result.put("excelFileName", null); // Excel文件名
+
+        List<CwWorkInvoiceReceiptInfo> effectiveList = Lists.newArrayList();
+        List<CwWorkInvoiceReceiptInfo> errorList = Lists.newArrayList();
+        String excelFileName = null;
+
+
+        try {
+            StringBuilder failureMsg = new StringBuilder();
+            List<CwWorkInvoiceReceiptInfo> list = new ArrayList<>();
+            //获取sheet
+            list = EasyPoiUtil.importExcel(file, 1, 1, CwWorkInvoiceReceiptInfo.class);
+            //去除excel中的空行
+            list = getImportExList(list);
+
+            // 【原有逻辑完全不变】迭代器过滤错误数据
+            Iterator<CwWorkInvoiceReceiptInfo> iterator = list.iterator();
+            while (iterator.hasNext()){
+                CwWorkInvoiceReceiptInfo workInvoiceReceiptInfo = iterator.next();
+                if(StringUtils.isBlank(workInvoiceReceiptInfo.getInvoiceNumber()) || StringUtils.isBlank(workInvoiceReceiptInfo.getBuyerName()) || null == workInvoiceReceiptInfo.getMoney() ){
+                    if(StringUtils.isNotBlank(workInvoiceReceiptInfo.getInvoiceNumber())){
+                        workInvoiceReceiptInfo.setErrorMessage("数据中发票号");
+                    }else if(StringUtils.isNotBlank(workInvoiceReceiptInfo.getBuyerName())){
+                        workInvoiceReceiptInfo.setErrorMessage("数据中缺少购买企业名称");
+                    }else if(null == workInvoiceReceiptInfo.getMoney()){
+                        workInvoiceReceiptInfo.setErrorMessage("数据中缺少金额");
+                    }
+                    errorList.add(workInvoiceReceiptInfo);
+                    iterator.remove();
+                }else{
+                    effectiveList.add(workInvoiceReceiptInfo);
+                }
+            }
+
+            // 【原有逻辑完全不变】有效数据去重
+            for (CwWorkInvoiceReceiptInfo info : effectiveList) {
+                String invoiceName = (info.getInvoiceNumber() != null) ? info.getInvoiceNumber() : "";
+                String buyerName = (info.getBuyerName() != null) ? info.getBuyerName() : "";
+                String money = (info.getMoney() != null) ? info.getMoney().toString() : "";
+                String resultStr = invoiceName + "," + buyerName + "," + money;
+                info.setDistinctStr(resultStr);
+            }
+
+            Map<String,List<CwWorkInvoiceReceiptInfo>> map = cwFinanceInvoiceService.distinctProjectMaterialStorage(effectiveList);
+
+            // 【原有逻辑完全不变】各类错误数据标记
+            List<CwWorkInvoiceReceiptInfo> uniqueList = map.get("uniqueList");
+            List<CwWorkInvoiceReceiptInfo> duplicateList = map.get("duplicateList");
+            for (CwWorkInvoiceReceiptInfo info : duplicateList) {
+                info.setErrorMessage("重复数据,添加失败");
+            }
+            List<CwWorkInvoiceReceiptInfo> alreadyExistList = map.get("alreadyExistList");
+            for (CwWorkInvoiceReceiptInfo info : alreadyExistList) {
+                info.setErrorMessage("该发票已存在收款信息,添加失败");
+            }
+            List<CwWorkInvoiceReceiptInfo> inexistenceList = map.get("inexistenceList");
+            for (CwWorkInvoiceReceiptInfo info : inexistenceList) {
+                info.setErrorMessage("系统中不存在该发票信息,添加失败");
+            }
+            List<CwWorkInvoiceReceiptInfo> moneyMismatchingList = map.get("moneyMismatchingList");
+            for (CwWorkInvoiceReceiptInfo info : moneyMismatchingList) {
+                info.setErrorMessage("发票金额大于系统开票金额,添加失败");
+            }
+
+            // 【核心逻辑不变】生成Excel文件到指定文件夹
+            if(list.size() > uniqueList.size()){
+                excelFileName = generateImportResultExcel(uniqueList, errorList, duplicateList, alreadyExistList,
+                        inexistenceList, moneyMismatchingList, list);
+                result.put("msg", "导入数据存在问题,可下载文档查看详情。");
+                result.put("excelFileName", excelFileName); // 返回Excel文件名
+            }else{
+                result.put("msg", "已成功导入 "+list.size()+" 条发票收款记录"+failureMsg);
+            }
+
+            result.put("success", true); // 业务处理成功(即使有Excel,也是处理完成)
+
+        } catch (Exception e) {
+            result.put("msg", "导入发票收款记录失败!失败信息:"+e.getMessage());
+        }
+
+        return ResponseEntity.ok(result);
+    }
+
+
+    /**
+     * 去除excel中的空行
+     * @param list
+     * @return
+     */
+    public ArrayList<CwWorkInvoiceReceiptInfo> getImportExList(List<CwWorkInvoiceReceiptInfo> list){
+
+        ArrayList<CwWorkInvoiceReceiptInfo> cwProjectRecordsImportDTOS = new ArrayList<>();
+
+        list.stream().forEach(item->{
+            if(objectInvoiceImportCheckIsNull(item)){
+                cwProjectRecordsImportDTOS.add(item);
+            }
+        });
+
+        //return cwFinanceImportDTOS;
+        return cwProjectRecordsImportDTOS;
+    }
+
+
+    public static boolean objectInvoiceImportCheckIsNull(Object object) {
+        boolean flag = false; //定义返回结果,默认为true
+
+        if (Objects.isNull(object)) {
+            flag = false;
+        } else {
+            Class clazz = (Class) object.getClass(); // 得到类对象
+            Field fields[] = clazz.getDeclaredFields(); // 得到所有属性
+            for (Field field : fields) {
+                if("serialVersionUID".equals(field.getName()) || "BIZ_CODE".equalsIgnoreCase(field.getName())){
+                    continue;
+                }
+                field.setAccessible(true);
+                Object fieldValue = null;
+                try {
+                    fieldValue = field.get(object); //得到属性值
+                    Type fieldType = field.getGenericType();//得到属性类型
+                    String fieldName = field.getName(); // 得到属性名
+                    log.info("属性类型:" + fieldType + ",属性名:" + fieldName + ",属性值:" + fieldValue);
+                } catch (IllegalArgumentException e) {
+                    log.error(e.getMessage(), e);
+                } catch (IllegalAccessException e) {
+                    log.error(e.getMessage(), e);
+                }
+                if (fieldValue != null && fieldValue != "") {  //只要有一个属性值不为null 就返回false 表示对象不为null
+                    flag = true;
+                    break;
+                }
+            }
+        }
+
+        return flag;
+    }
+
+    /**
+     * 生成导入结果Excel,分2个Sheet:有效导入数据、错误数据(含原因)
+     * @param uniqueList 唯一有效数据
+     * @param errorList 错误数据
+     * @param duplicateList 重复数据
+     * @param alreadyExistList 该发票已存在收款信息
+     * @param inexistenceList 系统中不存在该发票信息
+     * @param moneyMismatchingList 发票金额大于系统开票金额
+     * @param metadataList 导入原数据
+     * @param response 响应对象,用于输出下载流
+     */
+    /**
+     * 改造后:生成Excel到指定文件夹,返回唯一文件名(不再直接输出到response)
+     */
+    private String generateImportResultExcel(List<CwWorkInvoiceReceiptInfo> uniqueList,
+                                             List<CwWorkInvoiceReceiptInfo> errorList,
+                                             List<CwWorkInvoiceReceiptInfo> duplicateList,
+                                             List<CwWorkInvoiceReceiptInfo> alreadyExistList,
+                                             List<CwWorkInvoiceReceiptInfo> inexistenceList,
+                                             List<CwWorkInvoiceReceiptInfo> moneyMismatchingList,
+                                             List<CwWorkInvoiceReceiptInfo> metadataList) throws Exception {
+
+        String path = System.getProperty("os.name").toLowerCase().contains("win")
+                ? "D:/attachment-file/invoiceBatch/"
+                : "/attachment-file/invoiceBatch/";
+
+        File saveDir = new File(path);
+        if (!saveDir.exists()) {
+            saveDir.mkdirs();
+        }
+
+        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
+        String fileName = "发票收款导入数据汇总_" + userDTO.getId() + ".xlsx";
+        String fullFilePath = path + fileName;
+
+        Workbook workbook = new XSSFWorkbook();
+
+        // ========== 统一导出所有Sheet,格式完全对齐图二 ==========
+        exportSheet(workbook, "有效数据", "批量收款导入有效数据", uniqueList);
+        exportSheet(workbook, "错误数据", "批量收款导入错误数据", errorList);
+        exportSheet(workbook, "重复数据", "批量收款导入重复数据", duplicateList);
+        exportSheet(workbook, "已存在收款数据", "批量收款导入已存在收款数据", alreadyExistList);
+        exportSheet(workbook, "不存在的发票信息", "批量收款导入不存在的发票信息", inexistenceList);
+        exportSheet(workbook, "发票金额大于系统开票金额", "批量收款导入发票金额大于系统开票金额数据", moneyMismatchingList);
+        exportSheet(workbook, "原数据", "批量收款导入原数据", metadataList);
+
+        try (FileOutputStream fos = new FileOutputStream(fullFilePath)) {
+            workbook.write(fos);
+        } finally {
+            workbook.close();
+        }
+
+        return fileName;
+    }
+
+    /**
+     * 统一导出Sheet方法:完美复刻图二样式(大标题+灰色表头+边框+居中)
+     */
+    private void exportSheet(Workbook workbook, String sheetName, String title, List<CwWorkInvoiceReceiptInfo> dataList) {
+        if (dataList == null || dataList.isEmpty()) {
+            return;
+        }
+
+        // 1. 创建Sheet
+        Sheet sheet = workbook.createSheet(sheetName);
+
+        // 2. 创建样式(完全对齐图二)
+        // 大标题样式:居中、加粗、大号字体
+        CellStyle titleStyle = workbook.createCellStyle();
+        Font titleFont = workbook.createFont();
+        titleFont.setFontHeightInPoints((short) 24);
+        titleFont.setBold(true);
+        titleStyle.setFont(titleFont);
+        titleStyle.setAlignment(HorizontalAlignment.CENTER);
+        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        // 表头样式:灰色背景、居中、加粗、边框
+        CellStyle headerStyle = workbook.createCellStyle();
+        Font headerFont = workbook.createFont();
+        headerFont.setBold(true);
+        headerFont.setFontHeightInPoints((short) 12);
+        headerStyle.setFont(headerFont);
+        headerStyle.setAlignment(HorizontalAlignment.CENTER);
+        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        // 全边框
+        headerStyle.setBorderBottom(BorderStyle.THIN);
+        headerStyle.setBorderTop(BorderStyle.THIN);
+        headerStyle.setBorderLeft(BorderStyle.THIN);
+        headerStyle.setBorderRight(BorderStyle.THIN);
+
+        // 数据样式:居中、边框
+        CellStyle dataStyle = workbook.createCellStyle();
+        dataStyle.setAlignment(HorizontalAlignment.CENTER);
+        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        dataStyle.setBorderBottom(BorderStyle.THIN);
+        dataStyle.setBorderTop(BorderStyle.THIN);
+        dataStyle.setBorderLeft(BorderStyle.THIN);
+        dataStyle.setBorderRight(BorderStyle.THIN);
+        Font dataFont = workbook.createFont();
+        dataFont.setFontHeightInPoints((short) 11);
+        dataStyle.setFont(dataFont);
+
+        // 3. 写入大标题(第1行,合并A-D列,完全对齐图二)
+        Row titleRow = sheet.createRow(0);
+        titleRow.setHeightInPoints(40); // 标题行高
+        Cell titleCell = titleRow.createCell(0);
+        titleCell.setCellValue(title);
+        titleCell.setCellStyle(titleStyle);
+        // 合并单元格(根据你的4个字段,合并A-D列,0-3)
+        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
+
+        // 4. 写入表头(第2行,灰色背景,完全对齐图二)
+        Row headerRow = sheet.createRow(1);
+        headerRow.setHeightInPoints(25);
+        String[] headers = {"*发票号码", "*购方企业名称", "*收款金额", "*提醒"};
+        for (int i = 0; i < headers.length; i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellValue(headers[i]);
+            cell.setCellStyle(headerStyle);
+            // 设置列宽(适配内容)
+            sheet.setColumnWidth(i, 25 * 256);
+        }
+
+        // 5. 写入数据(从第3行开始)
+        for (int i = 0; i < dataList.size(); i++) {
+            CwWorkInvoiceReceiptInfo info = dataList.get(i);
+            Row dataRow = sheet.createRow(i + 2);
+            dataRow.setHeightInPoints(20);
+
+            // 按顺序写入4个字段
+            Cell cell0 = dataRow.createCell(0);
+            cell0.setCellValue(info.getInvoiceNumber());
+            cell0.setCellStyle(dataStyle);
+
+            Cell cell1 = dataRow.createCell(1);
+            cell1.setCellValue(info.getBuyerName());
+            cell1.setCellStyle(dataStyle);
+
+            Cell cell2 = dataRow.createCell(2);
+            cell2.setCellValue(info.getMoney());
+            cell2.setCellStyle(dataStyle);
+
+            Cell cell3 = dataRow.createCell(3);
+            cell3.setCellValue(info.getErrorMessage() == null ? "" : info.getErrorMessage());
+            cell3.setCellStyle(dataStyle);
+        }
+    }
+
+    @GetMapping("/downloadImportResult")
+    public void downloadImportResult(String fileName, HttpServletResponse response) throws Exception {
+        String path = System.getProperty("os.name").toLowerCase().contains("win")
+                ? "D:/attachment-file/invoiceBatch/"
+                : "/attachment-file/invoiceBatch/";
+
+        File file = new File(path + fileName);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
+
+        Files.copy(file.toPath(), response.getOutputStream());
+    }
+
 }

+ 13 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/CwFinanceInvoiceDetailMapper.java

@@ -2,7 +2,11 @@ package com.jeeplus.finance.invoice.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.jeeplus.finance.invoice.domain.CwFinanceInvoiceDetail;
+import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDetailDTO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 @Mapper
 public interface CwFinanceInvoiceDetailMapper extends BaseMapper<CwFinanceInvoiceDetail> {
@@ -11,6 +15,15 @@ public interface CwFinanceInvoiceDetailMapper extends BaseMapper<CwFinanceInvoic
      * @param invoice
      */
     void updateNumberById(CwFinanceInvoiceDetail invoice);
+
+
+
+    /**
+     * 根据发票号查询开票具体信息
+     * @param numberList
+     * @return
+     */
+    List<CwFinanceInvoiceDetailDTO> getInvoiceDetailByNumber(@Param("list") List<String> numberList);
 }
 
 

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

@@ -11,6 +11,8 @@ 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.CwWorkInvoiceReceipt;
+import com.jeeplus.finance.invoice.service.dto.CwWorkInvoiceReceiptInfo;
 import com.jeeplus.sys.service.dto.AreaDTO;
 import com.jeeplus.sys.service.dto.UserDTO;
 import org.apache.ibatis.annotations.Mapper;
@@ -135,6 +137,29 @@ public interface CwFinanceInvoiceMapper extends BaseMapper<CwFinanceInvoice> {
      * @return
      */
     Integer updateRedInvoiceJsonByWorkInvoiceId(CwFinanceInvoiceDTO workInvoice);
+
+
+    /**
+     * 根据发票编号查询已经收款的信息
+     * @param invoiceNumberList
+     * @return
+     */
+    List<CwWorkInvoiceReceipt> getByInvoiceNumber(@Param("list") List<String> invoiceNumberList);
+
+
+    /**
+     * 批量导入
+     * @param list
+     */
+    void batchInsert(@Param("list") List<CwWorkInvoiceReceipt> list);
+
+
+    /**
+     * 批量修改(拼接多条UPDATE)
+     * @param list 待修改的数据集(moneyEqualList/moneyPartialList)
+     * @return 影响的行数
+     */
+    int batchUpdateByForeach(@Param("list") List<CwWorkInvoiceReceiptInfo> list);
 }
 
 

+ 35 - 20
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceDetailMapper.xml

@@ -5,33 +5,31 @@
 <mapper namespace="com.jeeplus.finance.invoice.mapper.CwFinanceInvoiceDetailMapper">
 
     <resultMap id="BaseResultMap" type="com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDetailDTO">
-            <id property="id" column="id" jdbcType="VARCHAR"/>
-            <result property="createBy.id" column="create_by" jdbcType="VARCHAR"/>
-            <result property="createTime" column="create_date" jdbcType="TIMESTAMP"/>
-            <result property="updateBy.id" column="update_by" jdbcType="VARCHAR"/>
-            <result property="updateTime" column="update_date" jdbcType="TIMESTAMP"/>
-            <result property="delFlag" column="del_flag" jdbcType="INTEGER"/>
-            <result property="remarks" column="remarks" jdbcType="VARCHAR"/>
-            <result property="invoiceId" column="invoice_id" jdbcType="VARCHAR"/>
-            <result property="code" column="code" jdbcType="VARCHAR"/>
-            <result property="number" column="number" jdbcType="VARCHAR"/>
-            <result property="account" column="account" jdbcType="VARCHAR"/>
-            <result property="rate" column="rate" jdbcType="VARCHAR"/>
-            <result property="amount" column="amount" jdbcType="VARCHAR"/>
-            <result property="tax" column="tax" jdbcType="VARCHAR"/>
-            <result property="allAmount" column="all_amount" jdbcType="VARCHAR"/>
+        <id property="id" column="id" jdbcType="VARCHAR"/>
+        <result property="createBy.id" column="create_by_id" jdbcType="VARCHAR"/>
+        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
+        <result property="updateBy.id" column="update_by_id" jdbcType="VARCHAR"/>
+        <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
+        <result property="delFlag" column="del_flag" jdbcType="INTEGER"/>
+        <result property="remarks" column="remarks" jdbcType="VARCHAR"/>
+        <result property="invoiceId" column="invoice_id" jdbcType="VARCHAR"/>
+        <result property="number" column="number" jdbcType="VARCHAR"/>
+        <result property="account" column="account" jdbcType="VARCHAR"/>
+        <result property="rate" column="rate" jdbcType="VARCHAR"/>
+        <result property="amount" column="amount" jdbcType="VARCHAR"/>
+        <result property="tax" column="tax" jdbcType="VARCHAR"/>
+        <result property="allAmount" column="all_amount" jdbcType="VARCHAR"/>
     </resultMap>
 
     <sql id="Base_Column_List">
         fid.id,
-        fid.create_by,
-        fid.create_date,
-        fid.update_by,
-        fid.update_date,
+        fid.create_by_id,
+        fid.create_time,
+        fid.update_by_id,
+        fid.update_time,
         fid.del_flag,
         fid.remarks,
         fid.invoice_id,
-        fid.code,
         fid.number,
         fid.account,
         fid.rate,
@@ -45,4 +43,21 @@
             number = #{number}
         WHERE invoice_id = #{invoiceId} and del_flag = 0
     </update>
+
+
+    <select id="getInvoiceDetailByNumber" resultMap="BaseResultMap" >
+        SELECT
+        <include refid="Base_Column_List"></include>
+        FROM cw_finance_invoice_detail fid
+        <where>
+            fid.del_flag = 0
+            <if test="list!=null and list.size!=0">
+                and fid.number in
+                <foreach collection="list" item="number" separator="," open="(" close=")">
+                    #{number}
+                </foreach>
+            </if>
+        </where>
+    </select>
+
 </mapper>

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

@@ -881,4 +881,99 @@
         WHERE id = #{id}
     </update>
 
+    <select id="getByInvoiceNumber" resultType="com.jeeplus.finance.invoice.service.dto.CwWorkInvoiceReceipt" >
+        SELECT
+        a.id AS "id",
+        a.create_by_id AS "createBy.id",
+        a.create_time AS "createTime",
+        a.update_by_id AS "updateBy.id",
+        a.update_time AS "updateTime",
+        a.remarks AS "remarks",
+        a.del_flag AS "delFlag",
+        a.invoice_id AS "invoiceId",
+        a.remittance_amount AS "money",
+        date_format(a.remittance_date, '%Y-%m-%d') AS "receiptDate",
+        wid.number
+
+        FROM cw_finance_invoice_receivables a
+        left join cw_finance_invoice_detail wid on wid.invoice_id = a.invoice_id
+        <where>
+            a.del_flag = 0
+
+            <if test="list!=null and list.size!=0">
+                and wid.number in
+                <foreach collection="list" item="invoiceNumber" separator="," open="(" close=")">
+                    #{invoiceNumber}
+                </foreach>
+            </if>
+        </where>
+    </select>
+
+
+    <!-- 批量插入:新建id=batchInsert,保留原单条insert不变 -->
+    <insert id="batchInsert">
+        INSERT INTO cw_finance_invoice_receivables (
+        id,
+        create_by_id,
+        create_time,
+        update_by_id,
+        update_time,
+        del_flag,
+        remarks,
+        invoice_id,
+        remittance_unit,
+        remittance_amount,
+        remittance_date,
+        status
+        ) VALUES
+        <!-- 核心:foreach遍历list,拼接多组VALUES,分隔符为逗号 -->
+        <foreach collection="list" item="info" separator=",">
+            (
+            #{info.id},
+            #{info.createBy.id},
+            #{info.createTime},
+            #{info.updateBy.id},
+            #{info.updateTime},
+            #{info.delFlag},
+            #{info.remarks},
+            #{info.invoiceId},
+            #{info.companyName},
+            #{info.money, jdbcType=DECIMAL},
+            #{info.receiptDate},
+            #{info.status}
+            )
+        </foreach>
+    </insert>
+
+    <!-- 方案一:foreach拼接多条UPDATE -->
+    <update id="batchUpdateByForeach">
+        UPDATE cw_finance_invoice
+        <!-- trim处理SET后多余的逗号,避免语法错误 -->
+        <trim prefix="SET" suffixOverrides=",">
+            <!-- 批量更新receipt_money:匹配id才更新,无值则保留原数据 -->
+            receivables_status = CASE id
+            <foreach collection="list" item="info" separator="">
+                <if test="info.receiptMoney != null">
+                    WHEN #{info.invoiceId} THEN #{info.receiptMoney}
+                </if>
+            </foreach>
+            ELSE receivables_status
+            END,
+            <!-- 批量更新receipt_money_date:null/空字符串都不更新,保留原数据 -->
+            receivables_date = CASE id
+            <foreach collection="list" item="info" separator="">
+                <if test="info.receiptMoneyDate != null">
+                    WHEN #{info.invoiceId} THEN #{info.receiptMoneyDate}
+                </if>
+            </foreach>
+            ELSE receivables_date
+            END
+        </trim>
+        <!-- 关键:只更新传入的id集合,避免全表更新! -->
+        WHERE id IN
+        <foreach collection="list" item="info" open="(" separator="," close=")">
+            #{info.invoiceId}
+        </foreach>
+    </update>
+
 </mapper>

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

@@ -53,7 +53,9 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -957,7 +959,7 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
         if(isDDNotice){
             //根据字典判断是否钉钉通知用户
             String realTimeDictValue = SpringUtil.getBean(IDictApi.class).getRealTimeDictValue("是否接收钉钉通知");
-            if(realTimeDictValue.equals("1")){
+            if(StringUtils.isNotBlank(realTimeDictValue) && realTimeDictValue.equals("1")){
                 CwFinanceInvoiceDTO dto = queryById(cwFinanceInvoice.getId());
                 addDingtalkNotice(dto);
             }
@@ -2375,4 +2377,202 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
         cwFinanceInvoiceMapper.updateOmsUrlById(workInvoice);
     }
 
+
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    @Transactional(readOnly = false)
+    public Map<String, List<CwWorkInvoiceReceiptInfo>> distinctProjectMaterialStorage(
+            List<CwWorkInvoiceReceiptInfo> workInvoiceReceiptInfo) {
+
+        HashMap<String, List<CwWorkInvoiceReceiptInfo>> map = new HashMap<>();
+
+        // 第一步:对本次导入数据进行去重(自身重复)
+        Set<String> seen = new HashSet<>();
+        List<CwWorkInvoiceReceiptInfo> uniqueList = new ArrayList<>();      // 本次去重后的有效值
+        List<CwWorkInvoiceReceiptInfo> duplicateList = new ArrayList<>();   // 本次自身重复值
+        List<CwWorkInvoiceReceiptInfo> alreadyExistList = new ArrayList<>();   // 已经有收款信息的数据
+        List<CwWorkInvoiceReceiptInfo> inexistenceList = new ArrayList<>();   // 数据表中不存在的的数据
+        List<CwWorkInvoiceReceiptInfo> moneyMismatchingList = new ArrayList<>();   // 数据表中金额大于开票价的的数据
+
+        for (CwWorkInvoiceReceiptInfo item : workInvoiceReceiptInfo) {
+            String distinctStr = item.getDistinctStr();
+
+            // 处理null值(视为重复)
+            if (distinctStr == null) {
+                duplicateList.add(item);
+                continue;
+            }
+
+            if (!seen.contains(distinctStr)) {
+                seen.add(distinctStr);
+                uniqueList.add(item);
+            } else {
+                duplicateList.add(item);
+            }
+        }
+
+        //对有效值进行发票查询操作,如果已经存在记录 则表示 给该数据存在问题(重复上传)
+        List<String> invoiceNumberList = Lists.newArrayList();
+        for (CwWorkInvoiceReceiptInfo invoiceReceiptInfo : uniqueList) {
+            invoiceNumberList.add(invoiceReceiptInfo.getInvoiceNumber());
+        }
+        //根据 invoiceNumberList 去进行查询已经存在的数据信息
+        List<CwWorkInvoiceReceipt> alreadyExistReceiptList = cwFinanceInvoiceMapper.getByInvoiceNumber(invoiceNumberList);
+
+        //遍历有效值和查出来的已经存在的收款信息进行对比,若存在同一发票号的信息,金额进行累加,若金额大于应收金额,则表示该条数据存在问题,所小于则表示部分收款,若等于 则表示完全收款,改成已收款
+        // 迭代器方式:内层循环安全删除
+        for (CwWorkInvoiceReceipt workInvoiceReceipt : alreadyExistReceiptList) {
+            // 内层用迭代器遍历uniqueList,支持安全删除
+            Iterator<CwWorkInvoiceReceiptInfo> iterator = uniqueList.iterator();
+            while (iterator.hasNext()) {
+                CwWorkInvoiceReceiptInfo invoiceReceiptInfo = iterator.next();
+                if (workInvoiceReceipt.getNumber().equals(invoiceReceiptInfo.getInvoiceNumber())) {
+                    alreadyExistList.add(invoiceReceiptInfo); // 加入待处理列表
+                    invoiceReceiptInfo.setInvoiceId(workInvoiceReceipt.getInvoiceId());
+                    iterator.remove(); // 迭代器安全删除,不会触发并发修改异常
+                }
+            }
+        }
+
+        //对剩下的有效数据进行处理
+        //判定金额和开票额是否一致,若一致,则表示全部收款,若小于开票额,表示部分收款,若大于,应当是填写错误,进行抛出,不进行导入
+        invoiceNumberList = Lists.newArrayList();
+        for (CwWorkInvoiceReceiptInfo invoiceReceiptInfo : uniqueList) {
+            invoiceNumberList.add(invoiceReceiptInfo.getInvoiceNumber());
+        }
+        //根据 invoiceNumberList 去进行查询已经存在的数据信息
+        List<CwFinanceInvoiceDetailDTO> invoiceDetailList = cwFinanceInvoiceDetailMapper.getInvoiceDetailByNumber(invoiceNumberList);
+        for (CwFinanceInvoiceDetailDTO workInvoiceDetail : invoiceDetailList) {
+            for (CwWorkInvoiceReceiptInfo info : uniqueList) {
+                if(workInvoiceDetail.getNumber().equals(info.getInvoiceNumber())){
+                    info.setInvoiceId(workInvoiceDetail.getInvoiceId());
+                }
+            }
+        }
+
+        //如果两者数据量不同,说明 要么发票号错误,要么该发票没有录入,需要抛出,其余的再进行比较金额
+        if(invoiceDetailList.size() != uniqueList.size()){
+            //将invoiceDetailList 和uniqueList 进行对比,如果uniqueList中存在 但是 invoiceDetailList 不存在,则就要将这个值从uniqueList删除并写入到 inexistenceList中
+
+            // 1. 提取invoiceDetailList中所有发票号存入Set,用于O(1)快速判断是否存在(核心优化)
+            Set<String> detailInvoiceNumberSet = new HashSet<>();
+            for (CwFinanceInvoiceDetailDTO detail : invoiceDetailList) {
+                detailInvoiceNumberSet.add(detail.getNumber());
+            }
+
+            // 2. 迭代器遍历uniqueList,安全删除+收集不存在的元素(避免ConcurrentModificationException)
+            Iterator<CwWorkInvoiceReceiptInfo> iterator = uniqueList.iterator();
+            while (iterator.hasNext()) {
+                CwWorkInvoiceReceiptInfo uniqueInfo = iterator.next();
+                String uniqueInvoiceNo = uniqueInfo.getInvoiceNumber();
+                // 判断:uniqueList的发票号 在invoiceDetailList中不存在
+                if (!detailInvoiceNumberSet.contains(uniqueInvoiceNo)) {
+                    inexistenceList.add(uniqueInfo); // 加入不存在的列表
+                    iterator.remove(); // 迭代器安全删除,不会触发并发修改异常
+                }
+            }
+        }
+        //到此处 说明数据已经匹配几乎完成,现在还需要对有效的参数的金额进行匹配
+        for (CwFinanceInvoiceDetailDTO workInvoiceDetail : invoiceDetailList) {
+            for (CwWorkInvoiceReceiptInfo unique : uniqueList) {
+                if(workInvoiceDetail.getNumber().equals(unique.getInvoiceNumber())){
+                    //判定金额是否相同,小于为-1  相同为0 大于为1
+                    String totalMoney = workInvoiceDetail.getAccount();
+                    String money = unique.getMoney();
+                    BigDecimal totalMoneyB = new BigDecimal(totalMoney);
+                    BigDecimal moneyB = new BigDecimal(money);
+                    // 核心比较:直接得到-1/0/1的结果
+                    int compareResult = moneyB.compareTo(totalMoneyB);
+                    // 后续可根据结果做业务处理
+                    if (compareResult == 1) {
+                        // totalMoney 小于 money 业务逻辑
+                        //说明新导入的额度要大于开票额度
+                        unique.setMoneyFlag(1);
+                    } else if (compareResult == 0) {
+                        // 金额相等 业务逻辑
+                        //说明金额相等
+                        unique.setMoneyFlag(0);
+                    } else {
+                        // totalMoney 大于 money 业务逻辑
+                        //说明部分收款
+                        unique.setMoneyFlag(-1);
+                    }
+                }
+
+            }
+        }
+
+        //对处理完的数据进行区分,大于1的值不能进行导入,需要剔除
+        Iterator<CwWorkInvoiceReceiptInfo> iterator = uniqueList.iterator();
+        while (iterator.hasNext()) {
+            CwWorkInvoiceReceiptInfo info = iterator.next();
+            // 判定moneyFlag为1(导入金额超开票金额),剔除并收集
+            if (info.getMoneyFlag() == 1) {
+                moneyMismatchingList.add(info); // 加入无效数据列表
+                iterator.remove(); // 从有效列表uniqueList中安全删除
+            }
+        }
+
+        //此处可以对剩余的数据进行处理了
+
+        // 2. 生成唯一文件名(避免覆盖)
+        UserDTO userDTO = SpringUtil.getBean ( IUserApi.class ).getByToken(TokenProvider.getCurrentToken());
+
+        List<CwWorkInvoiceReceipt> insertReceiptList = Lists.newArrayList();
+        for (CwWorkInvoiceReceiptInfo info : uniqueList) {
+            CwWorkInvoiceReceipt insertReceipt = new CwWorkInvoiceReceipt();
+
+            if (info.getMoneyFlag() == 0) {
+                info.setReceiptMoneyDate(new Date());
+                info.setReceiptMoney(1);
+                insertReceipt.setStatus("1");
+            } else if(info.getMoneyFlag() == -1){
+                info.setReceiptMoney(2);
+                insertReceipt.setStatus("2");
+            }
+
+            insertReceipt.setInvoiceId(info.getInvoiceId());
+            String dateStr = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
+                    .withZone(ZoneId.systemDefault())
+                    .format(Instant.ofEpochMilli(new Date().getTime()));
+            insertReceipt.setReceiptDate(dateStr);
+            insertReceipt.setCompanyName(info.getBuyerName());
+            insertReceipt.setMoney(new BigDecimal(info.getMoney().trim()));
+
+            // 生成id
+            String id = UUID.randomUUID().toString().replace("-", "");
+            insertReceipt.setId(id);
+            insertReceipt.setCreateBy(userDTO);
+            insertReceipt.setCreateTime(new Date());
+            insertReceipt.setUpdateBy(userDTO);
+            insertReceipt.setUpdateTime(new Date());
+            insertReceipt.setDelFlag(0);
+            insertReceiptList.add(insertReceipt);
+        }
+        if(!insertReceiptList.isEmpty()){
+            //将收款信息添加到数据库中
+            cwFinanceInvoiceMapper.batchInsert(insertReceiptList);
+        }
+        if(!uniqueList.isEmpty()){
+            for (CwWorkInvoiceReceiptInfo invoiceReceiptInfo : uniqueList) {
+                invoiceReceiptInfo.setUpdateBy(userDTO);
+                invoiceReceiptInfo.setUpdateTime(new Date());
+                invoiceReceiptInfo.setDelFlag(0);
+            }
+            //修改work_invoice表中 receipt_money_date 和 receipt_money 参数信息
+            cwFinanceInvoiceMapper.batchUpdateByForeach(uniqueList);
+        }
+
+
+
+        // 最终返回处理后的列表
+        map.put("uniqueList", uniqueList);      // 仅包含本次新增且不与历史重复的数据
+        map.put("duplicateList", duplicateList); // 包含自身重复和与历史重复的数据
+        map.put("alreadyExistList", alreadyExistList); // 已经有收款信息的数据
+        map.put("inexistenceList", inexistenceList); // 数据表中不存在的的数据 invoiceDetail 表中不存在,也就是说 没有开票信息
+        map.put("moneyMismatchingList", moneyMismatchingList); // 数据表中金额大于开票价的的数据
+        return map;
+    }
 }

+ 32 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwInvoiceImportDTO.java

@@ -0,0 +1,32 @@
+package com.jeeplus.finance.invoice.service.dto;
+
+import cn.afterturn.easypoi.excel.annotation.Excel;
+import com.jeeplus.core.service.dto.BaseDTO;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author: 徐滕
+ * @version: 2025-3-18 10:02
+ */
+@Data
+public class CwInvoiceImportDTO extends BaseDTO {
+
+    private static final long serialVersionUID = 1L;
+
+    @Excel(name = "*发票号码",width = 255)
+    @NotNull(message="发票号码不可为空")
+    private String contractName;
+
+    @Excel(name = "*购方企业名称",width = 255)
+    @NotNull(message="购方企业名称不可为空")
+    private String projectName;
+
+    /**
+     * 风险等级
+     */
+    @Excel(name = "*收款金额",width = 20)
+    @NotNull(message="收款金额不可为空")
+    private String riskLevel;
+}

+ 28 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwWorkInvoiceReceipt.java

@@ -0,0 +1,28 @@
+package com.jeeplus.finance.invoice.service.dto;
+
+import com.jeeplus.core.service.dto.BaseDTO;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author: 徐滕
+ * @version: 2025-3-18 10:02
+ */
+@Data
+public class CwWorkInvoiceReceipt extends BaseDTO {
+
+    private static final long serialVersionUID = 1L;
+
+    private String invoiceId;   //发票id
+    private String companyName;  //公司名称
+    private BigDecimal money;   //汇款金额
+    private String receiptDate;  //汇款时间
+    private String createName;
+    private String updateName;
+    private String invoiceNumber;
+    private String number;
+    private String remittanceUnit;
+    private String status;
+    private String remarks;
+}

+ 31 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwWorkInvoiceReceiptInfo.java

@@ -0,0 +1,31 @@
+package com.jeeplus.finance.invoice.service.dto;
+
+import cn.afterturn.easypoi.excel.annotation.Excel;
+import com.jeeplus.core.service.dto.BaseDTO;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author: 徐滕
+ * @version: 2025-3-18 10:02
+ */
+@Data
+public class CwWorkInvoiceReceiptInfo extends BaseDTO {
+
+    private static final long serialVersionUID = 1L;
+
+    @Excel(name = "*发票号码",width = 255)
+    private String invoiceNumber;	//发票号码
+    @Excel(name = "*购方企业名称",width = 255)
+    private String buyerName;	//购方名称
+    @Excel(name = "*收款金额",width = 255)
+    private String money;	//收款金额
+    @Excel(name = "*提醒",width = 255)
+    private String errorMessage;	//错误信息
+    private String distinctStr;	//用于去重用的临时参数
+    private Integer moneyFlag;	//用于对比金额是否符合的判定条件(0:相同。-1:小于.1:大于)
+    private String invoiceId;	//开票信息id
+    private Date receiptMoneyDate;	//全部收款时间
+    private Integer receiptMoney;	//收款状态
+}

+ 74 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/util/ExcelField.java

@@ -0,0 +1,74 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.finance.invoice.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Excel注解定义
+ * @author jeeplus
+ * @version 2013-03-10
+ */
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelField {
+
+	/**
+	 * 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”)
+	 */
+	String value() default "";
+	
+	/**
+	 * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效)
+	 */
+	String title();
+	
+	/**
+	 * 字段类型(0:导出导入;1:仅导出;2:仅导入)
+	 */
+	int type() default 0;
+
+	/**
+	 * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右)
+	 */
+	int align() default 0;
+	
+	/**
+	 * 导出字段字段排序(升序)
+	 */
+	int sort() default 0;
+
+	/**
+	 * 如果是字典类型,请设置字典的type值
+	 */
+	String dictType() default "";
+	String mainDictType() default "";
+	
+	/**
+	 * 反射类型
+	 */
+	Class<?> fieldType() default Class.class;
+	
+	/**
+	 * 字段归属组(根据分组导出导入)
+	 */
+	int[] groups() default {};
+
+    /**
+     * 列号
+     */
+    int colNum() default 0;
+
+    /**
+     * 模板下拉--字典
+     * @return
+     */
+    String valiName() default "";
+
+	// 添加format属性,用于日期格式化
+	String format() default "";  // 新增这一行
+}

+ 323 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/util/ExportMultipleTabsExcel.java

@@ -0,0 +1,323 @@
+package com.jeeplus.finance.invoice.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 支持多Sheet的Excel导出工具类
+ */
+/**
+ * 多页签Excel导出工具类
+ * 支持创建多个Sheet页,每个页签可独立设置名称和数据
+ */
+
+/**
+ * 支持多Sheet的Excel导出工具类
+ */
+public class ExportMultipleTabsExcel {
+	private static final Logger log = LoggerFactory.getLogger(ExportMultipleTabsExcel.class);
+	private Workbook workbook; // 共享的工作簿
+	private Map<String, SheetData> sheetDataMap = new HashMap<>(); // 存储各Sheet的数据
+
+	/**
+	 * 内部类:存储单个Sheet的数据
+	 */
+	private static class SheetData {
+		String sheetName;
+		String title;
+		Class<?> entityClass;
+		List<?> dataList;
+		List<Object[]> annotationList; // 存储ExcelField注解信息
+		int rownum = 0; // 当前行号
+
+		public SheetData(String sheetName, String title, Class<?> entityClass) {
+			this.sheetName = sheetName;
+			this.title = title;
+			this.entityClass = entityClass;
+			this.annotationList = initAnnotationList(entityClass);
+		}
+
+		// 初始化注解列表
+		private List<Object[]> initAnnotationList(Class<?> cls) {
+			List<Object[]> list = new ArrayList<>();
+			// 处理字段注解
+			Field[] fields = cls.getDeclaredFields();
+			for (Field field : fields) {
+				ExcelField ef = field.getAnnotation(ExcelField.class);
+				if (ef != null && (ef.type() == 0 || ef.type() == 1)) {
+					list.add(new Object[]{ef, field});
+				}
+			}
+			// 处理方法注解
+			Method[] methods = cls.getDeclaredMethods();
+			for (Method method : methods) {
+				ExcelField ef = method.getAnnotation(ExcelField.class);
+				if (ef != null && (ef.type() == 0 || ef.type() == 1)) {
+					list.add(new Object[]{ef, method});
+				}
+			}
+			// 按排序号排序
+			list.sort(Comparator.comparingInt(o -> ((ExcelField) o[0]).sort()));
+			return list;
+		}
+	}
+
+	/**
+	 * 构造函数:初始化工作簿
+	 */
+	public ExportMultipleTabsExcel() {
+		this.workbook = new SXSSFWorkbook(1000); // 内存中保留1000行,适合大数据量
+	}
+
+	/**
+	 * 添加一个Sheet页
+	 */
+	public void addSheet(String sheetName, String title, Class<?> entityClass, List<?> dataList) {
+		SheetData sheetData = new SheetData(sheetName, title, entityClass);
+		sheetData.dataList = dataList;
+		sheetDataMap.put(sheetName, sheetData);
+	}
+
+	/**
+	 * 创建单元格样式
+	 */
+	private Map<String, CellStyle> createStyles(Workbook wb) {
+		Map<String, CellStyle> styles = new HashMap<>();
+
+		// 标题样式
+		CellStyle style = wb.createCellStyle();
+		style.setAlignment(HorizontalAlignment.CENTER);
+		style.setVerticalAlignment(VerticalAlignment.CENTER);
+		style.setBorderBottom(BorderStyle.THIN);
+		style.setBorderLeft(BorderStyle.THIN);
+		style.setBorderRight(BorderStyle.THIN);
+		style.setBorderTop(BorderStyle.THIN);
+		Font titleFont = wb.createFont();
+		titleFont.setFontName("宋体");
+		titleFont.setFontHeightInPoints((short) 16);
+		titleFont.setBold(true);
+		style.setFont(titleFont);
+		styles.put("title", style);
+
+		// 表头样式(无边框 + 更深底色 + 白色字体)
+		style = wb.createCellStyle();
+		style.setAlignment(HorizontalAlignment.CENTER);
+		style.setVerticalAlignment(VerticalAlignment.CENTER);
+		style.setBorderBottom(BorderStyle.NONE);
+		style.setBorderLeft(BorderStyle.NONE);
+		style.setBorderRight(BorderStyle.NONE);
+		style.setBorderTop(BorderStyle.NONE);
+		style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+		style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+		Font headerFont = wb.createFont();
+		headerFont.setFontName("宋体");
+		headerFont.setFontHeightInPoints((short) 11);
+		headerFont.setBold(true);
+		headerFont.setColor(IndexedColors.WHITE.getIndex()); // 白色字体
+		style.setFont(headerFont);
+		styles.put("header", style);
+
+		// 数据单元格样式(区分字符串和数字对齐)
+		// 字符串居中样式
+		CellStyle stringStyle = wb.createCellStyle();
+		stringStyle.setAlignment(HorizontalAlignment.CENTER);
+		stringStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+		stringStyle.setBorderBottom(BorderStyle.THIN);
+		stringStyle.setBorderLeft(BorderStyle.THIN);
+		stringStyle.setBorderRight(BorderStyle.THIN);
+		stringStyle.setBorderTop(BorderStyle.THIN);
+		Font stringFont = wb.createFont();
+		stringFont.setFontName("宋体");
+		stringFont.setFontHeightInPoints((short) 11);
+		stringStyle.setFont(stringFont);
+		styles.put("data_string", stringStyle);
+
+		// 数字靠右样式
+		CellStyle numberStyle = wb.createCellStyle();
+		numberStyle.setAlignment(HorizontalAlignment.RIGHT);
+		numberStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+		numberStyle.setBorderBottom(BorderStyle.THIN);
+		numberStyle.setBorderLeft(BorderStyle.THIN);
+		numberStyle.setBorderRight(BorderStyle.THIN);
+		numberStyle.setBorderTop(BorderStyle.THIN);
+		Font numberFont = wb.createFont();
+		numberFont.setFontName("宋体");
+		numberFont.setFontHeightInPoints((short) 11);
+		numberStyle.setFont(numberFont);
+		styles.put("data_number", numberStyle);
+
+		return styles;
+	}
+
+	/**
+	 * 生成Excel文件
+	 */
+	public void write(OutputStream os) throws IOException {
+		for (SheetData sheetData : sheetDataMap.values()) {
+			createSheetContent(sheetData);
+		}
+		// 写入输出流
+		workbook.write(os);
+	}
+
+	/**
+	 * 创建单个Sheet的内容
+	 */
+	private void createSheetContent(SheetData sheetData) {
+		Sheet sheet = workbook.createSheet(sheetData.sheetName);
+		Map<String, CellStyle> styles = createStyles(workbook);
+		List<Object[]> annotationList = sheetData.annotationList;
+		int rownum = sheetData.rownum;
+
+		// 创建标题行
+		if (StringUtils.isNotBlank(sheetData.title)) {
+			Row titleRow = sheet.createRow(rownum++);
+			titleRow.setHeightInPoints(30);
+			Cell titleCell = titleRow.createCell(0);
+			titleCell.setCellStyle(styles.get("title"));
+			titleCell.setCellValue(sheetData.title);
+			sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, annotationList.size() - 1));
+		}
+
+		// 创建表头行
+		Row headerRow = sheet.createRow(rownum++);
+		headerRow.setHeightInPoints(18);
+		for (int i = 0; i < annotationList.size(); i++) {
+			Object[] os = annotationList.get(i);
+			ExcelField ef = (ExcelField) os[0];
+			String header = ef.title();
+
+			// 估算列宽:中文字符每个占2个字符宽度,英文字符每个占1个
+			int width = 0;
+			for (char c : header.toCharArray()) {
+				width += (c > 127) ? 2 : 1;
+			}
+			// 最小宽度为10,最大为50
+			width = Math.max(width, 10);
+			width = Math.min(width, 50);
+			sheet.setColumnWidth(i, width * 256 * 2); // 设置列宽
+
+			// 新增:创建表头单元格并设置标题值
+			Cell headerCell = headerRow.createCell(i);
+			headerCell.setCellStyle(styles.get("header"));
+			headerCell.setCellValue(header);
+		}
+
+		// 填充数据行(区分字符串和数字对齐)
+		if (sheetData.dataList != null && !sheetData.dataList.isEmpty()) {
+			for (Object obj : sheetData.dataList) {
+				Row dataRow = sheet.createRow(rownum++);
+				dataRow.setHeightInPoints(16);
+				for (int i = 0; i < annotationList.size(); i++) {
+					Object[] os = annotationList.get(i);
+					ExcelField ef = (ExcelField) os[0];
+					Cell cell = dataRow.createCell(i);
+
+					// 获取单元格值
+					Object value = getFieldValue(obj, ef, os);
+
+					// 根据值的类型设置对齐样式
+					if (value instanceof Number) {
+						cell.setCellStyle(styles.get("data_number")); // 数字靠右
+					} else {
+						cell.setCellStyle(styles.get("data_string")); // 字符串居中
+					}
+
+					// 设置单元格值
+					setCellValue(cell, value, ef);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 使用标准反射获取字段值
+	 */
+	private Object getFieldValue(Object obj, ExcelField ef, Object[] os) {
+		try {
+			String fieldName = ef.value();
+			if (StringUtils.isBlank(fieldName)) {
+				if (os[1] instanceof Field) {
+					fieldName = ((Field) os[1]).getName();
+				} else if (os[1] instanceof Method) {
+					Method method = (Method) os[1];
+					return method.invoke(obj);
+				}
+			}
+
+			// 调用getter方法
+			if (StringUtils.isNotBlank(fieldName)) {
+				String getterMethod = "get" + fieldName.substring(0, 1).toUpperCase()
+						+ fieldName.substring(1);
+				Method method = obj.getClass().getMethod(getterMethod);
+				Object value = method.invoke(obj);
+
+
+				return value;
+			}
+		} catch (Exception e) {
+			log.error("获取字段值失败", e);
+		}
+		return "";
+	}
+
+	/**
+	 * 设置单元格值(处理不同数据类型)
+	 */
+	private void setCellValue(Cell cell, Object value, ExcelField ef) {
+		if (value == null) {
+			cell.setCellValue("");
+			return;
+		}
+
+		// 处理日期类型
+		if (value instanceof Date) {
+			String format = StringUtils.isNotBlank(ef.format()) ? ef.format() : "yyyy-MM-dd";
+			cell.setCellValue(new SimpleDateFormat(format).format((Date) value));
+		}
+		// 处理数字类型
+		else if (value instanceof Number) {
+			cell.setCellValue(((Number) value).doubleValue());
+		}
+		// 处理布尔类型
+		else if (value instanceof Boolean) {
+			cell.setCellValue((Boolean) value);
+		}
+		// 其他类型按字符串处理
+		else {
+			cell.setCellValue(value.toString());
+		}
+	}
+
+	/**
+	 * 输出到客户端下载
+	 */
+	public void write(HttpServletResponse response, String fileName) throws IOException {
+		response.reset();
+		response.setContentType("application/octet-stream; charset=utf-8");
+		response.setHeader("Content-Disposition", "attachment; filename="
+				+ new String(fileName.getBytes("utf-8"), "ISO-8859-1"));
+		write(response.getOutputStream());
+	}
+
+	/**
+	 * 清理临时文件(避免内存泄漏)
+	 */
+	public void dispose() {
+		if (workbook instanceof SXSSFWorkbook) {
+			((SXSSFWorkbook) workbook).dispose();
+		}
+	}
+}

+ 26 - 11
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectRecords/service/CwProjectRecordsService.java

@@ -244,17 +244,32 @@ public class CwProjectRecordsService extends ServiceImpl<CwProjectRecordsMapper,
         UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
         QueryWrapper<CwProjectRecords> queryWrapper = QueryWrapperGenerator.buildQueryCondition(CwProjectRecordsWrapper.INSTANCE.toEntity(cwProjectRecordsDTO), CwProjectRecords.class);
         queryWrapper.eq("a.del_flag", "0");
-        /*queryWrapper.nested(wrapper -> {
-            wrapper.eq("a.create_by_id", userDTO.getId())  // 条件1:创建人是当前用户
-                    .or()                                   // 逻辑OR
-                    .eq("b.user_id", userDTO.getId());      // 条件2:项目成员是当前用户(b是关联表别名)
-        });*/
-        queryWrapper.nested(wrapper -> {
-            wrapper.eq("a.project_master_id", userDTO.getId())  // 条件1:创建人是项目经理1
-                    .or()                                   // 逻辑OR
-                    .eq("a.real_header", userDTO.getId())       // 条件2:创建人是项目经理2
-                    .or()                                   // 逻辑OR
-                    .eq("a.project_executor", userDTO.getId());  // 条件3:登陆人是项目执行人
+        // 时间分割线
+        String splitTime = "2026-03-24 00:00:00";
+
+        queryWrapper.and(wrapper -> {
+            // ================== 2026-03-15 及以后 → 你的旧权限(项目经理1/2/执行人) ==================
+            wrapper.nested(w1 -> {
+                        w1.ge("a.create_time", splitTime)
+                                .and(w2 -> {
+                                    w2.eq("a.project_master_id", userDTO.getId())
+                                            .or()
+                                            .eq("a.real_header", userDTO.getId())
+                                            .or()
+                                            .eq("a.project_executor", userDTO.getId());
+                                });
+                    })
+
+                    // ================== 2026-03-15 之前 → 你的新权限(创建人/项目成员) ==================
+                    .or()
+                    .nested(w1 -> {
+                        w1.lt("a.create_time", splitTime)
+                                .and(w2 -> {
+                                    w2.eq("a.create_by_id", userDTO.getId())
+                                            .or()
+                                            .eq("b.user_id", userDTO.getId());
+                                });
+                    });
         });
         if (ObjectUtil.isNotEmpty(cwProjectRecordsDTO)) {
             if (ArrayUtil.isNotEmpty(cwProjectRecordsDTO.getCreateDates())) {

+ 1 - 1
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/controller/CwProjectReportArchiveController.java

@@ -76,7 +76,7 @@ public class CwProjectReportArchiveController {
         LocalDate compareDate = LocalDate.of(2026, 3, 24);
         result.getRecords().stream().forEach(i -> {
             // 获取每条记录的创建时间(Date 类型)
-            Date createDate = i.getCreateDate();
+            Date createDate = i.getCwprCreateTime();
 
             // Date 转 LocalDate
             LocalDate createLocalDate = createDate.toInstant()

+ 3 - 1
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/mapper/xml/CwProjectReportArchiveMapper.xml

@@ -286,6 +286,7 @@
         cw_pr.project_master_id as project_master,
         cw_pr.real_header as real_header,
         cw_pr.project_executor as projectExecutor,
+        cw_pr.create_time as cwprCreateTime,
         cw_prnl.report_no,
         date_format(cw_prnl.report_date,'%Y-%m-%d') as report_date,
         art.ID_ as task_id,
@@ -364,7 +365,8 @@
         left join sys_user su1 on cw_pr.project_master_id = su1.id and su1.del_flag = '0'
         left join sys_user su2 on cw_pr.real_header = su2.id and su2.del_flag = '0'
         left join cw_project_report_new_line cw_prnl on cw_prnl.report_id = cw_pa.report_id and cw_prnl.del_flag = '0'
-        left join sys_user su3 on su3.id = cw_pa.create_by_id and su3.del_flag = '0'
+        left join sys_user su3 on su3.id = cw_pa.create_by_id
+        left join sys_office so on so.id = su3.office_id
 
         left join cw_project_report pr on cw_pa.report_id=pr.id and pr.del_flag = '0'
         left join cw_project_report_review cprr on cprr.report_id=pr.id and cprr.del_flag = '0'

+ 10 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/service/CwProjectReportArchiveService.java

@@ -794,6 +794,16 @@ public class CwProjectReportArchiveService extends ServiceImpl<CwProjectReportAr
                     queryWrapper.like("cw_prnl.report_no",cwProjectReportArchiveDTO.getReportNo());
                 }
 
+
+                // 报告文号
+                if (StringUtils.isNotBlank(cwProjectReportArchiveDTO.getSubordinateCompany())) {
+                    if ("10003".equals(cwProjectReportArchiveDTO.getSubordinateCompany())) {
+                        queryWrapper.notLike("so.parent_ids","1703668499751649282");
+                    }else if ("10006".equals(cwProjectReportArchiveDTO.getSubordinateCompany())) {
+                        queryWrapper.like("so.parent_ids","1703668499751649282");
+                    }
+                }
+
             }
         }
         String isBmzr = "0";

+ 2 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/projectReportArchive/service/dto/CwProjectReportArchiveDTO.java

@@ -302,6 +302,8 @@ public class CwProjectReportArchiveDTO extends BaseDTO {
 
     private Date createDate;
 
+    private Date cwprCreateTime;
+
     /**
      * 报告id
      */

BIN
jeeplus-modules/jeeplus-finance/src/main/resources/dot/收款导入模版.xlsx