Преглед на файлове

发票批量下载,展示已下载功能

徐滕 преди 3 седмици
родител
ревизия
3572a4ea53

+ 21 - 3
src/main/java/com/jeeplus/modules/workinvoice/service/WorkInvoiceService.java

@@ -4951,7 +4951,7 @@ public class WorkInvoiceService extends CrudService<WorkInvoiceDao, WorkInvoice>
 	 * @return 临时压缩包路径
 	 * @throws Exception 异常抛出给Controller处理
 	 */
-	public String exportInvoiceAll(List<String> idList, String fileName) throws Exception {
+	public String exportInvoiceAll(List<String> idList, String fileName, List<String> failedMessages, Set<String> failedInvoiceIds) throws Exception {
 		// 1. 查询发票和附件关联信息(完全保留你原有逻辑)
 		List<WorkInvoice> invoicedList = dao.getByIdList(idList);
 		List<String> invoiceIdList = Lists.newArrayList();
@@ -4991,7 +4991,14 @@ public class WorkInvoiceService extends CrudService<WorkInvoiceDao, WorkInvoice>
 
 			// 校验必要参数(保留你原有逻辑)
 			if(StringUtils.isBlank(number) || StringUtils.isBlank(widNumber) || StringUtils.isBlank(clientName) || StringUtils.isBlank(omsAttachmentUrl)){
-				logger.warn("发票ID:{} 缺少必要参数,跳过下载", workInvoice.getId());
+				String failMsg = "发票号" + widNumber + "(编号" + number + "):缺少必要参数(发票号/编号/客户名/附件URL),跳过";
+				logger.warn("发票ID:{} {}", workInvoice.getId(), failMsg);
+				if (failedMessages != null) {
+					failedMessages.add(failMsg);
+				}
+				if (failedInvoiceIds != null) {
+					failedInvoiceIds.add(workInvoice.getId());
+				}
 				continue;
 			}
 
@@ -5016,7 +5023,18 @@ public class WorkInvoiceService extends CrudService<WorkInvoiceDao, WorkInvoice>
 
 			// 3.3 调用你的OSS批量下载方法(下载该发票的所有文件到专属文件夹)
 			// 注意:response参数此处传null,因为是后台批量下载,无需前端响应
-			new OSSClientUtil().downFolderByStreamSaveLocal(ossFolderPrefix, rootTempDir, invoiceFolderName, null);
+			try {
+				new OSSClientUtil().downFolderByStreamSaveLocal(ossFolderPrefix, rootTempDir, invoiceFolderName, null);
+			} catch (Exception e) {
+				String failMsg = "发票号" + widNumber + "(编号" + number + "):OSS文件下载失败 - " + e.getMessage();
+				logger.warn("发票ID:{} {}", workInvoice.getId(), failMsg, e);
+				if (failedMessages != null) {
+					failedMessages.add(failMsg);
+				}
+				if (failedInvoiceIds != null) {
+					failedInvoiceIds.add(workInvoice.getId());
+				}
+			}
 		}
 
 		// 4. 打包整个根临时文件夹为ZIP文件(修改:压缩包名称带时间戳,核心修改2)

+ 54 - 6
src/main/java/com/jeeplus/modules/workinvoice/web/WorkInvoiceController.java

@@ -45,6 +45,7 @@ import com.jeeplus.modules.workcontractinfo.service.WorkContractInfoService;
 import com.jeeplus.modules.workfullmanage.utils.ZipUtils;
 import com.jeeplus.modules.workinvoice.entity.WorkInvoice;
 import com.jeeplus.modules.workinvoice.entity.WorkInvoiceProjectRelation;
+import com.jeeplus.modules.workinvoice.service.WorkInvoiceDownloadRecordService;
 import com.jeeplus.modules.workinvoice.service.WorkInvoiceService;
 import com.jeeplus.modules.workinvoicealter.entity.WorkInvoiceAlter;
 import com.jeeplus.modules.workinvoicealter.service.WorkInvoiceAlterService;
@@ -94,6 +95,8 @@ public class WorkInvoiceController extends BaseController {
 	@Autowired
 	private WorkInvoiceService workInvoiceService;
 	@Autowired
+	private WorkInvoiceDownloadRecordService downloadRecordService;
+	@Autowired
 	private AreaService areaService;
 
 	@Autowired
@@ -1768,14 +1771,17 @@ public class WorkInvoiceController extends BaseController {
 		String zipFilePath = null;
 		InputStream is = null;
 		OutputStream os = null;
+		boolean downloadSuccess = false;
+		List<String> failedMessages = new ArrayList<>();
+		Set<String> failedInvoiceIds = new HashSet<>();
 
 		try {
 			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
 			String timeStamp = sdf.format(new Date());
 			String fileName = "数电发票批量下载_" + timeStamp;
 
-			// 1. 调用Service生成ZIP文件
-			zipFilePath = workInvoiceService.exportInvoiceAll(idList, fileName);
+			// 1. 调用Service生成ZIP文件(传入failedMessages收集失败信息)
+			zipFilePath = workInvoiceService.exportInvoiceAll(idList, fileName, failedMessages, failedInvoiceIds);
 
 			// 2. 校验ZIP文件
 			File zipFile = new File(zipFilePath);
@@ -1800,7 +1806,10 @@ public class WorkInvoiceController extends BaseController {
 				os.write(buffer, 0, len);
 			}
 			os.flush();
-			logger.info("批量下载ZIP文件已返回给前端:{}", zipFileName);
+			downloadSuccess = true;
+			// 将失败信息存入session,供前端AJAX查询
+			request.getSession().setAttribute("batchDownloadFailures", failedMessages);
+			logger.info("批量下载ZIP文件已返回给前端:{},失败数:{}", zipFileName, failedMessages.size());
 
 		} catch (Exception e) {
 			// 失败:返回JSON格式错误信息
@@ -1829,6 +1838,43 @@ public class WorkInvoiceController extends BaseController {
 				}
 			}
 		}
+
+		// 批量下载成功后,只记录成功下载的发票(排除失败的)
+		if (downloadSuccess) {
+			try {
+				for (String invoiceId : idList) {
+					if (!failedInvoiceIds.contains(invoiceId)) {
+						downloadRecordService.recordDownloadSuccess(invoiceId, "batch_download");
+					}
+				}
+				logger.info("批量下载记录成功:invoiceIds={}, userId={}", idList, UserUtils.getUser().getId());
+			} catch (Exception e) {
+				logger.error("批量下载记录失败!", e);
+			}
+		}
+	}
+
+	/**
+	 * 查询批量下载失败详情(AJAX接口)
+	 * 返回JSON格式:{code:200, failures:["msg1","msg2"]} 或 {code:200, failures:[]}
+	 */
+	@ResponseBody
+	@RequestMapping(value = "batchDownloadFailures")
+	public String batchDownloadFailures(HttpServletRequest request) {
+		@SuppressWarnings("unchecked")
+		List<String> failures = (List<String>) request.getSession().getAttribute("batchDownloadFailures");
+		// 查询后立即清除,避免重复读取
+		request.getSession().removeAttribute("batchDownloadFailures");
+		if (failures == null || failures.isEmpty()) {
+			return "{\"code\":200,\"failures\":[]}";
+		}
+		StringBuilder sb = new StringBuilder("{\"code\":200,\"failures\":[");
+		for (int i = 0; i < failures.size(); i++) {
+			if (i > 0) sb.append(",");
+			sb.append("\"").append(failures.get(i).replace("\"", "\\\"")).append("\"");
+		}
+		sb.append("]}");
+		return sb.toString();
 	}
 
 	/**
@@ -1836,11 +1882,13 @@ public class WorkInvoiceController extends BaseController {
 	 */
 	private void writeErrorResponse(HttpServletResponse response, String message) {
 		response.setCharacterEncoding("UTF-8");
-		response.setContentType("application/json;charset=UTF-8"); // 改为JSON格式
+		response.setContentType("application/json;charset=UTF-8");
 		response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
 		try {
-			// 输出标准JSON,方便前端解析
-			String errorJson = "{\"code\":500,\"msg\":\"" + message.replace("\"", "\\\"") + "\"}";
+			Map<String, Object> result = new HashMap<>();
+			result.put("code", 500);
+			result.put("msg", message);
+			String errorJson = JsonMapper.getInstance().toJson(result);
 			response.getWriter().write(errorJson);
 		} catch (IOException ioException) {
 			logger.error("响应错误信息失败!", ioException);

+ 101 - 14
src/main/webapp/webpage/modules/workinvoice/workInvoiceTwoList.jsp

@@ -1240,8 +1240,8 @@
 				layer.msg("请选择需要下载的发票信息", {icon: 2});
 				_this.prop('disabled', false).text('批量下载发票'); // 恢复按钮
 				return;
-			} else if (listId.length > 10) {
-				layer.msg("最多只能选择10条发票进行批量下载", {icon: 2, time: 3000});
+			} else if (listId.length > 25) {
+				layer.msg("最多只能选择25条发票进行批量下载", {icon: 2, time: 3000});
 				_this.prop('disabled', false).text('批量下载发票'); // 恢复按钮
 				return;
 			}
@@ -1264,39 +1264,126 @@
 			$('<input>', {'type':'hidden','name':'listId','value':listId.join(",")}).appendTo(downForm);
 			var downIframe = $('<iframe>', {'name':randomId,'style':'display:none;'}).appendTo('body');
 			downForm.attr('target', randomId);
-			downForm.submit();
-
-			// 4. 核心:固定延迟+强制恢复按钮(不管下载是否完成,3.5秒后必恢复)
-			setTimeout(function() {
-				cleanResource();
-			}, 3500); // 比提示框多0.5秒,确保体验
 
-			// 5. 兜底:iframe加载完成也恢复(双重保障)
+			// 4. 先绑定load事件,再提交表单(防止竞态条件导致事件丢失)
+			var handled = false;
 			downIframe.on('load', function() {
+				if (handled) return;
+				handled = true;
 				try {
 					var iframeDoc = this.contentDocument || this.contentWindow.document;
 					var responseText = iframeDoc.body.textContent || iframeDoc.body.innerText;
+					if (!responseText || responseText.length === 0) {
+						// 空响应,视为成功
+						layer.close(loadingIndex);
+						cleanResource(true);
+						return;
+					}
 					var res = JSON.parse(responseText);
 					if (res.code === 500) {
+						// 服务端返回错误JSON
+						layer.close(loadingIndex);
+						layer.msg(res.msg || '批量下载失败', {icon: 2, time: 5000});
+						cleanResource(false);
+					} else {
 						layer.close(loadingIndex);
-						layer.msg(res.msg, {icon: 2, time: 3000});
+						cleanResource(true);
 					}
 				} catch (e) {
+					// JSON解析失败,说明是文件流正常返回(ZIP下载),视为成功
 					layer.close(loadingIndex);
-					layer.msg("下载已触发,请等待浏览器弹窗~", {icon: 1, time: 2000});
+					cleanResource(true);
 				}
-				cleanResource(); // 加载完成立即恢复
 			});
 
-			// 统一清理+恢复按钮函数
-			function cleanResource(){
+			// 5. 绑定完事件后再提交表单
+			downForm.submit();
+
+			// 6. 兆底超时:30秒后强制恢复按钮(防止iframe load事件未触发)
+			setTimeout(function() {
+				if (!handled) {
+					handled = true;
+					layer.close(loadingIndex);
+					cleanResource(false);
+				}
+			}, 30000);
+
+			// 统一清理+恢复按钮函数,isSuccess标识下载是否成功
+			function cleanResource(isSuccess){
 				downForm.remove();
 				downIframe.remove();
 				// ✅ 强制恢复按钮(核心修复)
 				_this.prop('disabled', false).text('批量下载数电票');
+				// 下载成功后,查询失败详情并更新表格状态
+				if (isSuccess && listId.length > 0) {
+					setTimeout(function() {
+						// AJAX查询失败详情
+						$.ajax({
+							url: '${ctx}/workinvoice/workInvoice/batchDownloadFailures',
+							type: 'GET',
+							dataType: 'json',
+							success: function(res) {
+								if (res && res.code === 200 && res.failures && res.failures.length > 0) {
+									// 有失败的发票,展示详情
+									var failMsg = '以下发票下载失败(共' + res.failures.length + '条):\n\n' + res.failures.join('\n');
+									layer.alert(failMsg, {icon: 0, title: '批量下载部分失败', area: ['500px', '400px']});
+								}
+							},
+							error: function() {
+								console.log('查询批量下载失败信息异常');
+							}
+						});
+						// 更新表格中对应行的下载状态
+						updateBatchDownloadStatus(listId);
+					}, 3000);
+				}
 			}
 		});
 
+		/**
+		 * 批量下载后更新表格中多行的下载状态
+		 */
+		function updateBatchDownloadStatus(idList) {
+			try {
+				var tableData = layui.table.cache['contentTable'];
+				if (!tableData) return;
+				for (var j = 0; j < idList.length; j++) {
+					var invId = idList[j];
+					// 加入已下载集合
+					if (downloadedInvoiceIds) {
+						var ids = downloadedInvoiceIds.split(',');
+						if (ids.indexOf(invId) === -1) {
+							downloadedInvoiceIds += ',' + invId;
+						}
+					} else {
+						downloadedInvoiceIds = invId;
+					}
+					// 找到对应行并更新UI
+					for (var i = 0; i < tableData.length; i++) {
+						if (tableData[i].id === invId) {
+							var row = $("tr[data-index='" + i + "']");
+							// 更新操作列按钮
+							row.find('.btn-xs').each(function() {
+								var text = $(this).text();
+								if (text.indexOf('下载所有格式发票') !== -1 || text.indexOf('已下载') !== -1) {
+									$(this).text('已下载-再次下载').removeClass('btn-primary').addClass('btn-default');
+								}
+							});
+							// 更新下载状态列
+							row.find('td').each(function() {
+								if ($(this).text().trim() === '未下载') {
+									$(this).html('<span style="color:green;font-weight:bold;">已下载</span>');
+								}
+							});
+							break;
+						}
+					}
+				}
+			} catch(e) {
+				console.log('批量更新下载状态异常:', e);
+			}
+		}
+
 
 	})