|
|
@@ -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());
|
|
|
+ }
|
|
|
+
|
|
|
}
|