Selaa lähdekoodia

批量导入发票收款功能

徐滕 7 tuntia sitten
vanhempi
commit
5056cf3062

+ 9 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/WorkInvoiceReceipt.java

@@ -18,6 +18,7 @@ public class WorkInvoiceReceipt extends DataEntity<WorkInvoiceReceipt> {
     private String createName;
     private String updateName;
     private String invoiceNumber;
+    private String number;
 
     public String getCreateName() {
         return createName;
@@ -74,4 +75,12 @@ public class WorkInvoiceReceipt extends DataEntity<WorkInvoiceReceipt> {
     public void setInvoiceNumber(String invoiceNumber) {
         this.invoiceNumber = invoiceNumber;
     }
+
+    public String getNumber() {
+        return number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
 }

+ 9 - 5
src/main/java/com/jeeplus/modules/workinvoice/service/WorkInvoiceService.java

@@ -4696,7 +4696,7 @@ public class WorkInvoiceService extends CrudService<WorkInvoiceDao, WorkInvoice>
 			Iterator<WorkInvoiceReceiptInfo> iterator = uniqueList.iterator();
 			while (iterator.hasNext()) {
 				WorkInvoiceReceiptInfo invoiceReceiptInfo = iterator.next();
-				if (workInvoiceReceipt.getInvoiceNumber().equals(invoiceReceiptInfo.getInvoiceNumber())) {
+				if (workInvoiceReceipt.getNumber().equals(invoiceReceiptInfo.getInvoiceNumber())) {
 					alreadyExistList.add(invoiceReceiptInfo); // 加入待处理列表
 					invoiceReceiptInfo.setInvoiceId(workInvoiceReceipt.getInvoiceId());
 					iterator.remove(); // 迭代器安全删除,不会触发并发修改异常
@@ -4804,10 +4804,14 @@ public class WorkInvoiceService extends CrudService<WorkInvoiceDao, WorkInvoice>
 			insertReceipt.preInsert();
 			insertReceiptList.add(insertReceipt);
 		}
-		//将收款信息添加到数据库中
-		workInvoiceReceiptDao.batchInsert(insertReceiptList);
-		//修改work_invoice表中 receipt_money_date 和 receipt_money 参数信息
-		dao.batchUpdateByForeach(uniqueList);
+		if(!insertReceiptList.isEmpty()){
+			//将收款信息添加到数据库中
+			workInvoiceReceiptDao.batchInsert(insertReceiptList);
+		}
+		if(!uniqueList.isEmpty()){
+			//修改work_invoice表中 receipt_money_date 和 receipt_money 参数信息
+			dao.batchUpdateByForeach(uniqueList);
+		}
 
 
 

+ 189 - 60
src/main/java/com/jeeplus/modules/workinvoice/web/WorkInvoiceAllController.java

@@ -23,6 +23,7 @@ import com.jeeplus.modules.act.service.ActTaskService;
 import com.jeeplus.modules.act.utils.ActUtils;
 import com.jeeplus.modules.projectmaterialstorage.entity.ProjectMaterialStorage;
 import com.jeeplus.modules.projectmaterialstorage.entity.ProjectMaterialStorageImport;
+import com.jeeplus.modules.projectrecord.entity.AdminProjectReportedImport;
 import com.jeeplus.modules.projectrecord.entity.ProjectRecords;
 import com.jeeplus.modules.projectrecord.enums.ProjectStatusEnum;
 import com.jeeplus.modules.projectrecord.service.ProjectRecordsService;
@@ -68,8 +69,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.ConstraintViolationException;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
 import java.lang.reflect.InvocationTargetException;
 import java.math.BigDecimal;
 import java.net.URLDecoder;
@@ -1640,26 +1640,48 @@ public class WorkInvoiceAllController extends BaseController {
 
 
 
-
 	/**
-	 * 导入Excel数据
-
+	 * 下载导入项目数据模板
+	 * @param response
+	 * @param redirectAttributes
+	 * @return
 	 */
-	//@RequiresPermissions("workinvoice:workInvoice:importReceiptDataFile")
+	@RequiresPermissions("project:adminProjectReportedImport:import")
+	@RequestMapping(value = "import/invoiceBatchTemplate")
+	public String invoiceBatchTemplate(HttpServletResponse response, RedirectAttributes redirectAttributes) {
+		try {
+			String fileName = "管理员上报导入模板.xlsx";
+			List<AdminProjectReportedImport> list = Lists.newArrayList();
+			new ExportExcel("项目上报数据", AdminProjectReportedImport.class, 1).setDataList(list).write(response, fileName).dispose();
+			return null;
+		} catch (Exception e) {
+			addMessage(redirectAttributes, "导入模板下载失败!失败信息:"+e.getMessage());
+		}
+		return "redirect:"+ Global.getAdminPath()+"/project/adminProjectReportedImport/?repage";
+	}
+
+
+	// 移除原有返回String的逻辑,改为返回JSON
+//@RequiresPermissions("workinvoice:workInvoice:importReceiptDataFile")
 	@RequestMapping(value = "importReceiptDataFile", method=RequestMethod.POST)
-	public String importReceiptDataFile(MultipartFile file, RedirectAttributes redirectAttributes, HttpServletResponse response) {// 初始化返回结果Map
-		//对相对有效的数据进行处理
-		//有效的数据
+	@ResponseBody // 关键:返回JSON而非视图/重定向
+	public Map<String, Object> importReceiptDataFile(MultipartFile file, HttpServletResponse response) {
+		// 初始化返回结果
+		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();
 			ImportExcel ei = new ImportExcel(file, 1, 0);
 			List<WorkInvoiceReceiptInfo> list = ei.getDataList(WorkInvoiceReceiptInfo.class);
 
-			//此处需要修改为迭代器形式
+			// 【原有逻辑完全不变】迭代器过滤错误数据
 			Iterator<WorkInvoiceReceiptInfo> iterator = list.iterator();
 			while (iterator.hasNext()){
 				WorkInvoiceReceiptInfo workInvoiceReceiptInfo = iterator.next();
@@ -1678,59 +1700,54 @@ public class WorkInvoiceAllController extends BaseController {
 				}
 			}
 
-			//对有效的数据进行去重处理
+			// 【原有逻辑完全不变】有效数据去重
 			for (WorkInvoiceReceiptInfo info : effectiveList) {
-				// 处理可能的null值,转为空字符串
 				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 = invoiceService.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("该发票已存在收款信息,添加失败");
 			}
-			//数据表中不存在的的数据 invoiceDetail 表中不存在,也就是说 没有开票信息
 			List<WorkInvoiceReceiptInfo> inexistenceList = map.get("inexistenceList");
 			for (WorkInvoiceReceiptInfo info : inexistenceList) {
 				info.setErrorMessage("系统中不存在该发票信息,添加失败");
 			}
-			//数据表中金额大于开票价的的数据
 			List<WorkInvoiceReceiptInfo> moneyMismatchingList = map.get("moneyMismatchingList");
 			for (WorkInvoiceReceiptInfo info : moneyMismatchingList) {
 				info.setErrorMessage("发票金额大于系统开票金额,添加失败");
 			}
-			//如果导入的数据量大于有效的数据量,说明导入的数据存在重复、错误等问题,直接抛出
-			if(list.size() >uniqueList.size()){
-				// 6. 核心:生成结果Excel文件(分2个Sheet),并通过浏览器下载
-				generateImportResultExcel(uniqueList, errorList, duplicateList, alreadyExistList, inexistenceList, moneyMismatchingList,list, response);
-				addMessage(redirectAttributes, "导入数据存在问题,详情请查看返回的文档信息。");
+
+			// 【核心逻辑不变】生成Excel文件到指定文件夹
+			if(list.size() > uniqueList.size()){
+				excelFileName = generateImportResultExcel(uniqueList, errorList, duplicateList, alreadyExistList,
+						inexistenceList, moneyMismatchingList, list);
+				result.put("msg", "导入数据存在问题,可下载文档查看详情。");
+				result.put("excelFileName", excelFileName); // 返回Excel文件名
 			}else{
-				addMessage(redirectAttributes, "已成功导入 "+list.size()+" 条发票收款记录"+failureMsg);
+				result.put("msg", "已成功导入 "+list.size()+" 条发票收款记录"+failureMsg);
 			}
 
+			result.put("success", true); // 业务处理成功(即使有Excel,也是处理完成)
+
 		} catch (Exception e) {
-			addMessage(redirectAttributes, "导入发票收款记录失败!失败信息:"+e.getMessage());
+			result.put("msg", "导入发票收款记录失败!失败信息:"+e.getMessage());
 			logger.error("Exception e:"+e);
 		}
 
-		return "redirect:"+Global.getAdminPath()+"/workinvoiceAll/workInvoiceAll/?repage";
-
+		return result; // 返回JSON结果给AJAX
 	}
 
 	/**
@@ -1744,19 +1761,38 @@ public class WorkInvoiceAllController extends BaseController {
 	 * @param metadataList 导入原数据
 	 * @param response 响应对象,用于输出下载流
 	 */
-	private void generateImportResultExcel(List<WorkInvoiceReceiptInfo> uniqueList,
-										   List<WorkInvoiceReceiptInfo> errorList,
-										   List<WorkInvoiceReceiptInfo> duplicateList,
-										   List<WorkInvoiceReceiptInfo> alreadyExistList,
-										   List<WorkInvoiceReceiptInfo> inexistenceList,
-										   List<WorkInvoiceReceiptInfo> moneyMismatchingList,
-										   List<WorkInvoiceReceiptInfo> metadataList,
-										   HttpServletResponse response) throws Exception {
-
-		// 1. 创建多页签导出工具实例
+	/**
+	 * 改造后:生成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 = null;
+		if (System.getProperty("os.name").toLowerCase().contains("win")) {
+			path = "D:/attachment-file/invoiceBatch/";
+		} else {
+			path = "/attachment-file/invoiceBatch/";
+		}
+
+		// 1. 创建存储文件夹(不存在则自动创建)
+		File saveDir = new File(path);
+		if (!saveDir.exists()) {
+			saveDir.mkdirs();
+		}
+
+		// 2. 生成唯一文件名(避免覆盖)
+		String fileName = "发票收款导入数据汇总_" + UserUtils.getUser().getId() + ".xlsx";
+		String fullFilePath = path + fileName;
+
+		// 3. 创建多页签导出工具实例(原有逻辑不变)
 		ExportMultipleTabsExcel exporter = new ExportMultipleTabsExcel();
 
-		// 处理可能的空列表,避免导出工具报错
+		// 4. 处理空列表(原有逻辑不变)
 		uniqueList = uniqueList == null ? Collections.emptyList() : uniqueList;
 		errorList = errorList == null ? Collections.emptyList() : errorList;
 		duplicateList = duplicateList == null ? Collections.emptyList() : duplicateList;
@@ -1764,7 +1800,7 @@ public class WorkInvoiceAllController extends BaseController {
 		inexistenceList = inexistenceList == null ? Collections.emptyList() : inexistenceList;
 		moneyMismatchingList = moneyMismatchingList == null ? Collections.emptyList() : moneyMismatchingList;
 
-		// 3. 添加Sheet页(页签名称、标题、实体类、数据列表
+		// 5. 添加Sheet页(原有逻辑完全不变
 		if (!uniqueList.isEmpty()) {
 			exporter.addSheet("有效数据", "批量收款导入有效数据", WorkInvoiceReceiptInfo.class, uniqueList);
 		}
@@ -1777,38 +1813,131 @@ public class WorkInvoiceAllController extends BaseController {
 		if (!alreadyExistList.isEmpty()) {
 			exporter.addSheet("已存在收款数据", "批量收款导入已存在收款数据", WorkInvoiceReceiptInfo.class, alreadyExistList);
 		}
-
 		if (!inexistenceList.isEmpty()) {
 			exporter.addSheet("不存在的发票信息", "批量收款导入不存在的发票信息", WorkInvoiceReceiptInfo.class, inexistenceList);
 		}
-
 		if (!moneyMismatchingList.isEmpty()) {
 			exporter.addSheet("发票金额大于系统开票金额", "批量收款导入发票金额大于系统开票金额数据", WorkInvoiceReceiptInfo.class, moneyMismatchingList);
 		}
-
 		if (!metadataList.isEmpty()) {
 			exporter.addSheet("原数据", "批量收款导入原数据", WorkInvoiceReceiptInfo.class, metadataList);
 		}
 
-
-		// 4. 导出文件(关键:设置响应头,确保浏览器正确下载)
-		String fileName = "发票收款导入数据汇总_" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
-		// 设置响应类型为Excel
-		response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
-		// 设置文件名(解决中文乱码)
-		String encodedFileName = URLEncoder.encode(fileName, "UTF-8");
-		response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
-		// 禁用缓存
-		response.setHeader("Pragma", "no-cache");
-		response.setHeader("Cache-Control", "no-cache");
-		response.setDateHeader("Expires", 0);
-
-		// 修正:直接传递response对象给write方法(移除错误的类型转换)
-		exporter.write(response, fileName);
+		// 6. 核心修改:生成Excel到指定文件夹(通过FileOutputStream)
+		try (FileOutputStream fos = new FileOutputStream(fullFilePath)) {
+			exporter.write(fos); // 输出到文件流,而非response
+		}
 		exporter.dispose(); // 清理资源
 
+		// 7. 返回唯一文件名(传给前端)
+		return fileName;
 	}
 
+	/**
+	 * 新增:Excel下载接口(前端询问用户确认后调用)
+	 * @param fileName 前端传递的Excel文件名
+	 * @param response 响应对象
+	 */
+	@RequestMapping("/downloadExcel")
+	public void downloadExcel(String fileName, HttpServletResponse response) {
+		// 1. 新增日志:打印关键信息,方便排查
+		logger.info("开始下载Excel,文件名:{}", fileName);
+		if (StringUtils.isBlank(fileName)) {
+			logger.error("下载Excel失败:文件名为空");
+			response.setContentType("text/plain;charset=UTF-8"); // 明确响应类型
+			try {
+				response.getWriter().write("文件名不能为空");
+			} catch (IOException e) {
+				logger.error("返回文件名空提示失败", e);
+			}
+			return;
+		}
+
+		// 2. 确定文件存储路径(新增日志打印路径)
+		String path = null;
+		if (System.getProperty("os.name").toLowerCase().contains("win")) {
+			path = "D:/attachment-file/invoiceBatch/";
+		} else {
+			path = "/attachment-file/invoiceBatch/";
+		}
+		String fullFilePath = path + fileName;
+		logger.info("Excel文件完整路径:{}", fullFilePath);
+		File excelFile = new File(fullFilePath);
+
+		// 3. 检查文件是否存在(新增权限检查)
+		if (!excelFile.exists()) {
+			logger.error("下载Excel失败:文件不存在,路径:{}", fullFilePath);
+			response.setContentType("text/plain;charset=UTF-8");
+			try {
+				response.getWriter().write("文件不存在或已被清理");
+			} catch (IOException e) {
+				logger.error("返回文件不存在提示失败", e);
+			}
+			return;
+		}
+		if (!excelFile.canRead()) {
+			logger.error("下载Excel失败:文件无读取权限,路径:{}", fullFilePath);
+			response.setContentType("text/plain;charset=UTF-8");
+			try {
+				response.getWriter().write("服务器无文件读取权限");
+			} catch (IOException e) {
+				logger.error("返回权限提示失败", e);
+			}
+			return;
+		}
+
+		try {
+			// 4. 核心修复:补全响应头(适配Servlet 2.5)
+			response.reset(); // 清空原有响应头,避免冲突
+			response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+			response.setCharacterEncoding("UTF-8");
+			// 修复Content-Disposition格式:兼容所有浏览器
+			String encodedFileName = URLEncoder.encode(fileName, "UTF-8");
+			response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
+
+			// ========== Servlet 2.5 兼容关键:替换 setContentLengthLong ==========
+			// setContentLength(int):Servlet 2.5 原生方法,Excel文件大小远小于2G,完全够用
+			long fileSize = excelFile.length();
+			if (fileSize > Integer.MAX_VALUE) {
+				logger.warn("文件大小超过2G({}字节),Servlet 2.5无法设置Content-Length,可能影响部分浏览器下载", fileSize);
+				// 超过2G时不设置(Excel几乎不会出现此情况)
+			} else {
+				response.setContentLength((int) fileSize); // 兼容Servlet 2.5
+			}
+
+			// 禁用缓存(补全)
+			response.setHeader("Pragma", "no-cache");
+			response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
+			response.setDateHeader("Expires", 0);
+
+			// 5. 读取文件并输出(增大缓冲区+强制flush)
+			try (FileInputStream fis = new FileInputStream(excelFile);
+				 OutputStream os = response.getOutputStream()) {
+				byte[] buffer = new byte[4096]; // 增大缓冲区,提升传输效率
+				int len;
+				while ((len = fis.read(buffer)) != -1) {
+					os.write(buffer, 0, len);
+				}
+				os.flush(); // 强制刷出所有数据,避免流残留
+			}
+			logger.info("Excel下载成功,文件名:{},路径:{}", fileName, fullFilePath);
+
+			// 可选:下载完成后删除文件
+			// if (excelFile.delete()) {
+			//     logger.info("下载后删除临时文件成功:{}", fullFilePath);
+			// } else {
+			//     logger.warn("下载后删除临时文件失败:{}", fullFilePath);
+			// }
 
+		} catch (Exception e) {
+			logger.error("下载Excel失败,文件名:{},路径:{}", fileName, fullFilePath, e);
+			response.setContentType("text/plain;charset=UTF-8");
+			try {
+				response.getWriter().write("下载失败:" + e.getMessage());
+			} catch (IOException ex) {
+				logger.error("返回下载失败提示失败", ex);
+			}
+		}
+	}
 
 }

BIN
src/main/webapp/dot/收款导入模版.xlsx


+ 3 - 2
src/main/webapp/webpage/modules/workinvoice/workInvoiceAllList.jsp

@@ -650,8 +650,9 @@
 						<shiro:hasPermission name="workinvoiceAll:workinvoiceAll:export">
 							<table:exportExcel url="${ctx}/workinvoiceAll/workInvoiceAll/export"></table:exportExcel><!-- 导出按钮 -->
 						</shiro:hasPermission>
-
-							<table:importExcel url="${ctx}/workinvoiceAll/workInvoiceAll/importReceiptDataFile"></table:importExcel><!-- 导入按钮 -->
+						<shiro:hasPermission name="workinvoiceAll:workinvoiceAll:importInvoiceBatch">
+							<table:importInvoiceBatchExcel url="${ctx}/workinvoiceAll/workInvoiceAll/importReceiptDataFile"></table:importInvoiceBatchExcel><!-- 导入按钮 -->
+						</shiro:hasPermission>
 
 						<shiro:hasPermission name="workInvoiceTwo:workInvoiceTwo:invoiceReport">
 							<a href="javascript:void(0)" style='background-color: #FFB800' onclick="downloadDialogre('开票汇总信息导出', '${ctx}/workinvoiceTwo/workinvoiceTwo/skipDownloadInvoiceForm?view=workInvoiceAll','40%', '400px','','下载,关闭')" class="layui-btn layui-btn-sm layui-bg-blue" > 开票汇总信息导出</a>