package com.malk.qiwang.Controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import com.malk.qiwang.Service.QiWangService; import com.malk.qiwang.Service.TXYInvoice; import com.malk.qiwang.entity.CompanyTitle; import com.malk.qiwang.entity.InvoiceLibrary; import com.malk.qiwang.model.McInvoiceDto; import com.malk.server.aliwork.YDConf; import com.malk.server.aliwork.YDParam; import com.malk.server.common.FilePath; import com.malk.server.common.McException; import com.malk.server.common.McR; import com.malk.server.dingtalk.DDR_New; import com.malk.service.aliwork.YDClient; import com.malk.service.dingtalk.DDClient; import com.malk.service.dingtalk.DDClient_Contacts; import com.malk.service.dingtalk.DDClient_Workflow; import com.malk.utils.*; import com.spire.pdf.PdfCompressionLevel; import com.spire.pdf.PdfDocument; import com.spire.pdf.PdfPageBase; import com.spire.pdf.exporting.PdfImageInfo; import com.spire.pdf.graphics.PdfBitmap; import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.exception.TencentCloudSDKException; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; import org.apache.commons.lang3.StringUtils; import org.apache.poi.util.IOUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.io.*; import java.math.BigDecimal; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @RestController @Slf4j @RequestMapping("/qw") public class QiWangController { @Autowired private QiWangService qiWangService; @Autowired private DDClient ddClient; @Autowired private YDClient ydClient; @Autowired private TXYInvoice txyInvoice; @Value("${dingtalk.operator}") private String operator; @Value("${dingtalk.downloadPath}") private String downloadPath; @Autowired private DDClient_Workflow ddClientWorkflow ; @Autowired private DDClient_Contacts ddClient_contacts; private static final String bdurl = "D://qiwang//files//"; private static final String url = "http://47.103.203.2:9092/qiwang/"; // private static final String bdurl = "C:\\Users\\EDY\\Desktop\\项目\\琦王\\files\\"; //private static final String url = "http://4784d4b1.r39.cpolar.top/qiwang/"; @PostMapping("/test") public McR test(@RequestBody Map map) { log.info("map:{}", map); String processInstanceId = UtilMap.getString(map, "processInstanceId"); // 获取审批实例信息 Map processInstance = ddClientWorkflow.getProcessInstanceId( ddClient.getAccessToken(), processInstanceId); // System.out.println("processInstance====" + processInstance); String businessId = (String) processInstance.get("businessId"); System.out.println(businessId); // // if (Objects.nonNull(processInstance)) { // List formComponentValues = (List) processInstance.get("formComponentValues"); // System.out.println("formComponentValues====" + formComponentValues); // 存储所有处理结果 // List allResults = new ArrayList<>(); // // // 遍历所有表单组件 // for (Map formComponentValue : formComponentValues) { // String id = String.valueOf(formComponentValue.get("id")); // // // 查找 TableField_1A1CDMEN8DDS0 组件 // if ("TableField_1A1CDMEN8DDS0".equals(id)) { // String tableFieldValue = String.valueOf(formComponentValue.get("value")); // System.out.println("原始value值: " + tableFieldValue); // } // // try { // if ("DDAttachment_OF8QX7XQWWW0".equals(id)) { // String attachmentListStr = UtilMap.getString(formComponentValue, "value"); // List attachmentList = (List) JSONObject.parse(attachmentListStr); // // for (Map attachment : attachmentList) { // // String fileId = UtilMap.getString(attachment, "fileId"); // String fileType = UtilMap.getString(attachment, "fileType"); // String fileName = UtilMap.getString(attachment, "fileName"); // // //下载钉盘文件 // String filePath = downloadPath + fileId + "." + fileType; // downloadDdFile(processInstanceId, fileId, filePath); // String hz = "qw/files/" + fileId + "." + fileType; // Map fileMap = new HashMap(); // fileMap.put("url", url + hz); // // // 处理每个附件并收集结果 // McR result = processMixedInvoice(fileMap); // allResults.add(result.getData()); //// allResults.add(fileMap); // } // } // } catch (Exception e) { // log.error("解析 TableField 数据失败", e); // return McR.error("300", "解析数据失败"); // } // } // 处理完所有附件后,汇总结果 // return McR.success() ; // // } return McR.success(); } @PostMapping ("/test3") public McR test3(@RequestBody Map map) { log.info("map:{}", map); String processInstanceId = UtilMap.getString(map, "processInstanceId"); // 获取审批实例信息 Map processInstance = ddClientWorkflow.getProcessInstanceId(ddClient.getAccessToken(), processInstanceId); System.out.println("processInstance====" + processInstance); if (Objects.nonNull(processInstance)) { List formComponentValues = (List) processInstance.get("formComponentValues"); System.out.println("formComponentValues====" + formComponentValues); // 查找 TableField_1A1CDMEN8DDS0 组件 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_1A1CDMEN8DDS0".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); System.out.println("原始value值: " + tableFieldValue); try { // 解析子表数据 List tableRows = (List) JSONObject.parse(tableFieldValue); // 收集所有行的结果 List allResults = new ArrayList<>(); // 遍历子表的每一行 for (Map row : tableRows) { List rowValues = (List) row.get("rowValue"); // 存储当前行的"是否有发票"状态和附件 String hasInvoice = null; List attachmentList = null; // 先收集这一行的字段值 for (Map rowItem : rowValues) { String key = String.valueOf(rowItem.get("key")); if ("DDSelectField_1ORUK0KIM5D6O".equals(key)) { hasInvoice = String.valueOf(rowItem.get("value")); } if ("DDAttachment_Z02OGR5QL8U8".equals(key)) { attachmentList = (List) rowItem.get("value"); } } // 只有当"是否有发票"为"是"且有附件时,才处理 if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { // 只处理这一行的第一个附件 Map attachment = attachmentList.get(0); System.out.println("fileName: " + UtilMap.getString(attachment, "fileName")); System.out.println("fileId: " + UtilMap.getString(attachment, "fileId")); String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); // 下载钉盘文件 String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); // 处理发票识别 // McR result = processMixedInvoice(fileMap); allResults.add(fileMap); } else { // 如果这一行不符合条件,可以添加null或跳过 // allResults.add(null); // 如果需要保持行数对应 } } // 所有行处理完成后统一返回 return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败"); } } } } return McR.success(); } @PostMapping("/test4") public McR test4(@RequestBody Map map) { log.info("map:{}", map); String processInstanceId = UtilMap.getString(map, "processInstanceId"); // 获取审批实例信息 Map processInstance = ddClientWorkflow.getProcessInstanceId(ddClient.getAccessToken(), processInstanceId); // System.out.println("processInstance====" + processInstance); String userId = (String) processInstance.get("originatorUserId"); System.out.println(userId); if (Objects.nonNull(processInstance)) { List formComponentValues = (List) processInstance.get("formComponentValues"); System.out.println("formComponentValues====" + formComponentValues); // 存储主表的 zt 和 je 值 String zt = "是"; String mainJe = null; // 先遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("DDSelectField_4SITXLYUEO80".equals(id)) { zt = String.valueOf(formComponentValue.get("value")); } if ("MoneyField_3QZLY8BD3780".equals(id)) { mainJe = String.valueOf(formComponentValue.get("value")); } } // 判断主表状态 if ("否".equals(zt)) { // 当为"否"时,直接返回 je 字段值 Map result = new HashMap<>(); result.put("je", mainJe); result.put("status", "否"); Map body = new HashMap<>(); body.put("processInstanceId",processInstanceId); body.put("text", result); body.put("commentUserId",userId); // body.put("file",fileMap); // String re =UtilHttp.doPost("https://api.dingtalk.com/v1.0/workflow/processInstances/comments",ddClient.initTokenHeader(),null,body); return McR.success(result); } // 当为"是"时,继续处理子表附件 if ("是".equals(zt)) { // 查找 TableField_1A1CDMEN8DDS0 组件 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_1A1CDMEN8DDS0".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); System.out.println("原始value值: " + tableFieldValue); try { // 解析子表数据 List tableRows = (List) JSONObject.parse(tableFieldValue); // 收集所有发票识别结果 List allResults = new ArrayList<>(); // 遍历子表的每一行 for (Map row : tableRows) { List rowValues = (List) row.get("rowValue"); // 先检查这一行的"是否有发票"字段值 String hasInvoice = null; List attachmentList = null; for (Map rowItem : rowValues) { String key = String.valueOf(rowItem.get("key")); // 获取"是否有发票"字段的值 if ("DDSelectField_1ORUK0KIM5D6O".equals(key)) { hasInvoice = String.valueOf(rowItem.get("value")); } // 获取附件字段 if ("DDAttachment_Z02OGR5QL8U8".equals(key)) { attachmentList = (List) rowItem.get("value"); } } // 只有当"是否有发票"为"是"时,才处理附件 if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { // 遍历这一行的所有附件 for (Map attachment : attachmentList) { System.out.println("fileName: " + UtilMap.getString(attachment, "fileName")); System.out.println("fileId: " + UtilMap.getString(attachment, "fileId")); String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); // 下载钉盘文件 String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); // 处理发票识别并收集结果 McR result = processMixedInvoice(fileMap); allResults.add(result.getData()); } } } Map body = new HashMap<>(); body.put("processInstanceId",processInstanceId); body.put("text", JSON.toJSONString(allResults)); body.put("commentUserId",userId); String re =UtilHttp.doPost("https://api.dingtalk.com/v1.0/workflow/processInstances/comments",ddClient.initTokenHeader(),null,body); // 所有附件处理完成后统一返回 return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败"); } } } } } return McR.success(); } @PostMapping("/test2") public McR test2(@RequestBody Map map) { log.info("map:{}", map); // String procInstId = String.valueOf(map.get("procInstId")); String processInstanceId = UtilMap.getString(map, "processInstanceId"); // 获取审批实例信息 Map processInstance = ddClientWorkflow.getProcessInstanceId(ddClient.getAccessToken(), processInstanceId); System.out.println("processInstance====" + processInstance); // Map body = new HashMap<>(); // body.put("processInstanceId",processInstanceId); // body.put("text", "发票信息如下:\n\n1. 电子发票(普通发票) | 全电普通发票 | 发票号码:26312000001701010876 | 开票日期:2026-03-20 | 金额:103.28 | 不含税:94.75 | 税额:8.53 | 购买方:罗庆宇(个人) | 销售方:上海聪雷贸易有限公司 | 税号:91310107MA1G0XPA4K\n\n2. 电子发票(普通发票) | 全电普通发票 | 发票号码:26442000003207012931 | 开票日期:2026-03-25 | 金额:239.00 | 不含税:211.50 | 税额:27.50 | 购买方:罗庆宇 | 销售方:广州延阳电子商务有限公司 | 税号:91440101MA5AWRGH4L\n\n3. 电子发票(普通发票) | 全电普通发票 | 发票号码:26332000002236540291 | 开票日期:2026-03-20 | 金额:518.00 | 不含税:458.41 | 税额:59.59 | 购买方:罗庆宇(个人) | 销售方:杭州亿奥网络科技有限公司 | 税号:91330108MA28MK7K08\n\n4. 电子发票(普通发票) | 全电普通发票 | 发票号码:26442000003052085371 | 开票日期:2026-03-21 | 金额:183.00 | 不含税:161.95 | 税额:21.05 | 购买方:罗庆宇 | 销售方:广州梓夕贸易有限公司 | 税号:91440101MA59M7J471"); // body.put("commentUserId","16441447913471369"); //// body.put("file",fileMap); // // String re =UtilHttp.doPost("https://api.dingtalk.com/v1.0/workflow/processInstances/comments",ddClient.initTokenHeader(),null,body); // System.out.println("re"+ re); // if (Objects.nonNull(processInstance)) { // Map formData = new HashMap<>(); // String title = String.valueOf(processInstance.get("title")); // String type = ""; // List formComponentValues = (List) processInstance.get("formComponentValues"); // } return McR.success(); } @GetMapping("/files/{fileId}") public ResponseEntity getFileResource( @PathVariable String fileId, @RequestParam(defaultValue = "download") String option) throws IOException { // 根据fileId获取实际文件路径,这里简化处理,实际可能需要从数据库查询 Path filePath = Paths.get(bdurl).resolve(fileId).normalize(); // 检查文件是否存在 if (!Files.exists(filePath)) { return ResponseEntity.notFound().build(); } // 创建Resource对象 Resource resource = new UrlResource(filePath.toUri()); // 根据选项设置响应头 HttpHeaders headers = new HttpHeaders(); if ("preview".equalsIgnoreCase(option)) { // 预览模式 - 尝试确定内容类型 String contentType = Files.probeContentType(filePath); // 强制修正 PDF 的 Content-Type if (filePath.toString().toLowerCase().endsWith(".pdf")) { contentType = "application/pdf"; } else if (contentType == null) { contentType = "application/octet-stream"; } headers.setContentType(MediaType.parseMediaType(contentType)); headers.add("Content-Disposition", "inline; filename=\"" + resource.getFilename() + "\""); headers.add("X-Content-Type-Options", "nosniff"); // 防止浏览器忽略 Content-Type } else { // 下载模式 - 默认处理 headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); } return ResponseEntity.ok() .headers(headers) .contentLength(resource.contentLength()) .body(resource); } @PostMapping("/test1") public McR test1(@RequestBody Map data) throws TencentCloudSDKException { log.info("test: {}", data); McException.assertParamException_Null(data, "url"); // String image = ydClient.convertTemporaryUrl(data.get("url")); String image = data.get("url"); log.info("混票识别, 免登地址, {}", image); // 非PDF, 且内存大于3M, 压缩后上传 if (UtilMap.getFloat(data, "size") > 3.0f && !UtilMap.getBoolean(data, "isPdf")) { image = imageUrlConvertBase64(image); } if (UtilMap.getFloat(data, "size") > 6.0f && UtilMap.getBoolean(data, "isPdf")) { image = pdfUrlConvertBase64(image); } List invoices = (List) txyInvoice.doRecognizeGeneralInvoice(image).get("MixedInvoiceItems"); System.out.println("invoices===="+invoices); List nos=new ArrayList<>(); List result = invoices.stream().map(item -> { Map prop = UtilMap.getMap(UtilMap.getMap(item, "SingleInvoiceInfos"), UtilMap.getString(item, "SubType")); // List> vatInvoiceItemInfos = UtilMap.getList(prop, "VatInvoiceItemInfos"); // if (!vatInvoiceItemInfos.isEmpty()) { // String taxRate = UtilMap.getString(vatInvoiceItemInfos.get(0), "TaxRate"); // System.out.println("TaxRate: " + taxRate); // 输出: 9% // } String taxRate = null; // List> vatInvoiceItemInfos = UtilMap.getList(prop, "VatInvoiceItemInfos"); // 优先使用 VatInvoiceItemInfos,如果为空则使用 VatElectronicItems List> vatInvoiceItemInfos = UtilMap.getList(prop, "VatInvoiceItemInfos") != null && !UtilMap.getList(prop, "VatInvoiceItemInfos").isEmpty() ? UtilMap.getList(prop, "VatInvoiceItemInfos") : UtilMap.getList(prop, "VatElectronicItems"); // if (!vatInvoiceItemInfos.isEmpty()) { // taxRate = UtilMap.getString(vatInvoiceItemInfos.get(0), "TaxRate"); // } if (!vatInvoiceItemInfos.isEmpty()) { String originalTaxRate = UtilMap.getString(vatInvoiceItemInfos.get(0), "TaxRate"); // 判断是否为有效数字(包括带%号的格式,如"9%") if (StringUtils.isNotBlank(originalTaxRate)) { // 移除百分号并尝试解析为数字 String cleanedTaxRate = originalTaxRate.replace("%", "").trim(); try { // 尝试转换为数字 new BigDecimal(cleanedTaxRate); taxRate = originalTaxRate; // 保持原值(可能带%) } catch (NumberFormatException e) { // 如果不是数字(如"不征税"、"免税"等),赋值为"0" taxRate = "0"; log.debug("税率格式异常,原始值: {}, 已设置为0", originalTaxRate); } } else { taxRate = "0"; } } else { taxRate = "0"; } System.out.println("prop====="+prop); String no=UtilMap.getString(prop, "Number"); if(nos.contains(no)){ return null; } nos.add(no); String kind = UtilMap.getString(item, "TypeDescription"); String invoiceName = UtilMap.getString(item, "SubTypeDescription"); if (kind.equals("全电发票")) { if(invoiceName.contains("铁路电子客票")){ kind="火车票"; } else if(invoiceName.contains("机票行程单")){ kind="机票行程单"; } else if (invoiceName.contains("专用发票")) { kind="全电专用发票"; }else { kind="全电普通发票"; } } if (kind.equals("增值税发票")) { if(invoiceName.contains("区块链电子发票")){ kind="区块链电子发票"; } else if(invoiceName.contains("增值税专用发票")){ kind="增值税专用发票"; } else if(invoiceName.contains("增值税普通发票")){ kind="增值税普通发票"; } else if (invoiceName.contains("增值税电子专用发票")) { kind="增值税电子专用发票"; } else if (invoiceName.contains("增值税电子普通发票")) { kind="增值税电子普通发票"; }else { kind="增值税普通发票"; } } // ppExt: 通用字段定义 McInvoiceDto invoiceDto = McInvoiceDto.builder() .name(UtilMap.getString(item, "SubTypeDescription")) .kindName(kind) .taxRate(taxRate) .kind(UtilMap.getInt(item, "Type")) .code(UtilMap.getString(prop, "Code")) .serial(UtilMap.getString(prop, "Number")) .date(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "Date"))) .checkCode(UtilMap.getString(prop, "CheckCode")) // ppExt: 多明细行时, 优先取值合计 [全电票返回了subTotal字段, 但值为空] // 2025.5.12 去除小计金额,并去除发票编号重复数据 .amount(UtilNumber.setBigDecimal(UtilMap.getString_first(prop, "Total", "Fare"))) // "SubTotal", .tax(UtilNumber.setBigDecimal(UtilMap.getString_first(prop, "Tax"))) // "SubTax", .excludingTax(UtilNumber.setBigDecimal(UtilMap.getString(prop, "PretaxAmount"))) .buyerName(StringUtils.isBlank(guyuanNameRepalce(UtilMap.getString(prop, "Buyer")))?"上海能辉科技股份有限公司":guyuanNameRepalce(UtilMap.getString(prop, "Buyer"))) // ppExt: 中央非税未返回税号官方说明: 非税发票理论是没有税号的,图片中属于信用代码 .buyerTaxId(StringUtils.isBlank(UtilMap.getString(prop, "BuyerTaxID"))?"": UtilMap.getString(prop, "BuyerTaxID")) .sellerName(guyuanNameRepalce(UtilMap.getString_first(prop, "Seller", "Issuer"))) // 行程单: 填开单位 .sellerTaxId(UtilMap.getString_first(prop, "SellerTaxID", "AgentCode")) // 行程单: 销售单位代号 .passengerName(UtilMap.getString_first(prop, "Name", "UserName")) // 火车票, 行程单 // 交通出行 .seatType(UtilMap.getString(prop, "Seat")) .departureTime(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn")) .departurePort(UtilMap.getString_first(prop, "StationGetOn", "Entrance", "Place")) // 火车票: 出发车站, 过路过桥费: 入口, 出租车: 发票所在地 .arrivePort(UtilMap.getString_first(prop, "StationGetOff", "Exit")) // 行程单, 火车票: 到达车站, 过路过桥费: 出口 .trainNo(UtilMap.getString_first(prop, "TrainNumber", "LicensePlate")) // 火车票: 车次, 出租车: 车牌号 .insuranceCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "Insurance")))) // 行程单: 保险费 .fuelCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "FuelSurcharge")))) // 行程单: 燃油附加费 .constructionCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "AirDevelopmentFund")))) // 行程单: 民航发展基金 .build(); // ppExt: 机票行程单, 行程与座位信息在明细内 if ("机票行程单".equals(kind)) { invoiceDto.setSellerName(UtilMap.getString_first(prop, "Issuer")); invoiceDto.setSellerTaxId(UtilMap.getString_first(prop, "Seller")); Map flight = (Map) UtilMap.getList(prop, "FlightItems").get(0); invoiceDto.setDepartureTime(UtilString.replaceDateZH_cn(UtilMap.getString(item, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn")); invoiceDto.setDeparturePort(UtilMap.getString(flight, "StationGetOn")); invoiceDto.setArrivePort(UtilMap.getString(flight, "StationGetOff")); invoiceDto.setSeatType(UtilMap.getString(flight, "Seat")); } if ("出租车发票".equals(item.get("TypeDescription"))) { // 上下车时间 invoiceDto.setDepartureTime(UtilMap.getString(prop, "TimeGetOn") + " ~ " + UtilMap.getString(prop, "TimeGetOff")); } return invoiceDto; }).filter(item -> item!=null).collect(Collectors.toList()); return McR.success(McInvoiceDto.formatResponse(result)); } @PostMapping("invoice-iv2") McR invoice_iv2(@RequestBody Map data) throws TencentCloudSDKException { McException.assertParamException_Null(data, "url"); String image = ydClient.convertTemporaryUrl(data.get("url")); log.info("混票识别, 免登地址, {}", image); // 非PDF, 且内存大于3M, 压缩后上传 if (UtilMap.getFloat(data, "size") > 3.0f && !UtilMap.getBoolean(data, "isPdf")) { image = imageUrlConvertBase64(image); } if (UtilMap.getFloat(data, "size") > 6.0f && UtilMap.getBoolean(data, "isPdf")) { image = pdfUrlConvertBase64(image); } List invoices = (List) txyInvoice.doRecognizeGeneralInvoice(image).get("MixedInvoiceItems"); List nos=new ArrayList<>(); List result = invoices.stream().map(item -> { Map prop = UtilMap.getMap(UtilMap.getMap(item, "SingleInvoiceInfos"), UtilMap.getString(item, "SubType")); String no=UtilMap.getString(prop, "Number"); if(nos.contains(no)){ return null; } nos.add(no); String kind = UtilMap.getString(item, "TypeDescription"); String invoiceName = UtilMap.getString(item, "SubTypeDescription"); if (kind.equals("全电发票")) { if(invoiceName.contains("铁路电子客票")){ kind="火车票"; } else if(invoiceName.contains("机票行程单")){ kind="机票行程单"; } else if (invoiceName.contains("专用发票")) { kind="全电专用发票"; }else { kind="全电普通发票"; } } if (kind.equals("增值税发票")) { if(invoiceName.contains("区块链电子发票")){ kind="区块链电子发票"; } else if(invoiceName.contains("增值税专用发票")){ kind="增值税专用发票"; } else if(invoiceName.contains("增值税普通发票")){ kind="增值税普通发票"; } else if (invoiceName.contains("增值税电子专用发票")) { kind="增值税电子专用发票"; } else if (invoiceName.contains("增值税电子普通发票")) { kind="增值税电子普通发票"; }else { kind="增值税普通发票"; } } // ppExt: 通用字段定义 McInvoiceDto invoiceDto = McInvoiceDto.builder() .name(UtilMap.getString(item, "SubTypeDescription")) .kindName(kind) .kind(UtilMap.getInt(item, "Type")) .code(UtilMap.getString(prop, "Code")) .serial(UtilMap.getString(prop, "Number")) .date(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "Date"))) .checkCode(UtilMap.getString(prop, "CheckCode")) // ppExt: 多明细行时, 优先取值合计 [全电票返回了subTotal字段, 但值为空] // 2025.5.12 去除小计金额,并去除发票编号重复数据 .amount(UtilNumber.setBigDecimal(UtilMap.getString_first(prop, "Total", "Fare"))) // "SubTotal", .tax(UtilNumber.setBigDecimal(UtilMap.getString_first(prop, "Tax"))) // "SubTax", .excludingTax(UtilNumber.setBigDecimal(UtilMap.getString(prop, "PretaxAmount"))) .buyerName(StringUtils.isBlank(guyuanNameRepalce(UtilMap.getString(prop, "Buyer")))?"上海能辉科技股份有限公司":guyuanNameRepalce(UtilMap.getString(prop, "Buyer"))) // ppExt: 中央非税未返回税号官方说明: 非税发票理论是没有税号的,图片中属于信用代码 .buyerTaxId(StringUtils.isBlank(UtilMap.getString(prop, "BuyerTaxID"))?"": UtilMap.getString(prop, "BuyerTaxID")) .sellerName(guyuanNameRepalce(UtilMap.getString_first(prop, "Seller", "Issuer"))) // 行程单: 填开单位 .sellerTaxId(UtilMap.getString_first(prop, "SellerTaxID", "AgentCode")) // 行程单: 销售单位代号 .passengerName(UtilMap.getString_first(prop, "Name", "UserName")) // 火车票, 行程单 // 交通出行 .seatType(UtilMap.getString(prop, "Seat")) .departureTime(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn")) .departurePort(UtilMap.getString_first(prop, "StationGetOn", "Entrance", "Place")) // 火车票: 出发车站, 过路过桥费: 入口, 出租车: 发票所在地 .arrivePort(UtilMap.getString_first(prop, "StationGetOff", "Exit")) // 行程单, 火车票: 到达车站, 过路过桥费: 出口 .trainNo(UtilMap.getString_first(prop, "TrainNumber", "LicensePlate")) // 火车票: 车次, 出租车: 车牌号 .insuranceCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "Insurance")))) // 行程单: 保险费 .fuelCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "FuelSurcharge")))) // 行程单: 燃油附加费 .constructionCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "AirDevelopmentFund")))) // 行程单: 民航发展基金 .build(); // ppExt: 机票行程单, 行程与座位信息在明细内 if ("机票行程单".equals(kind)) { invoiceDto.setSellerName(UtilMap.getString_first(prop, "Issuer")); invoiceDto.setSellerTaxId(UtilMap.getString_first(prop, "Seller")); Map flight = (Map) UtilMap.getList(prop, "FlightItems").get(0); invoiceDto.setDepartureTime(UtilString.replaceDateZH_cn(UtilMap.getString(item, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn")); invoiceDto.setDeparturePort(UtilMap.getString(flight, "StationGetOn")); invoiceDto.setArrivePort(UtilMap.getString(flight, "StationGetOff")); invoiceDto.setSeatType(UtilMap.getString(flight, "Seat")); } if ("出租车发票".equals(item.get("TypeDescription"))) { // 上下车时间 invoiceDto.setDepartureTime(UtilMap.getString(prop, "TimeGetOn") + " ~ " + UtilMap.getString(prop, "TimeGetOff")); } return invoiceDto; }).filter(item -> item!=null).collect(Collectors.toList()); return McR.success(McInvoiceDto.formatResponse(result)); } //校验查重 @PostMapping("invoice-va") McR invoice_va(@RequestBody Map data) { McException.assertParamException_Null(data, "param"); List invoices = JSON.parseArray(JSON.toJSONString(data.get("param")), McInvoiceDto.class); log.info("发票查重, 验真, {}", invoices); invoices.forEach(UtilMc.consumerWithIndex((item, index) -> { McInvoiceDto dto = (McInvoiceDto) item; String invoiceNo = dto.getSerial(); // 唯一标识, 发票号码 String serial = "第【" + (index + 1) + "】张发票"; validateBuyer(dto.getBuyerName(), serial + "有疑问"); McException.assertAccessException(StringUtils.isBlank(invoiceNo), serial + ", 识别结果为空, 请检查!"); YDParam ydParam = YDParam.builder() .formUuid("FORM-W2A66Z910O9B3LP9C6IYUDPRVWY62DO0YHIILY") .searchFieldJson(JSON.toJSONString(UtilMap.map("radioField_liihyrtb, textField_liihyrt8", "否", invoiceNo))) .build(); List idList = (List) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form_id).getData(); if (idList.size() > 0) { McException.exceptionAccess(serial + "已存在, 请勿重复提交!"); } // prd 仅仅识别增值税普通发票 // if (dto.getName().contains("普通发票")) { // String serialTips = serial + "有疑问"; // try { // // ppExt: 识别与验真后抬头对比 [全电票, 新版本识别接口, 返回名称为: 电子发票(普通发票) 不包含全电标识, 发类型为: 全电发票. 注意取值] // Map rsp = txyInvoice.doVatInvoiceVerifyNew(dto.getKindName(), dto.getCode(), invoiceNo, dto.getDate(), String.valueOf(dto.getAmount()), dto.getCheckCode(), String.valueOf(dto.getExcludingTax()), serialTips); // Map invoice = (Map) rsp.get("Invoice"); // McException.assertAccessException(!dto.getBuyerName().equals(guyuanNameRepalce(invoice.get("BuyerName").toString())), serialTips + ", 购买方名称不匹配!"); // McException.assertAccessException(!dto.getBuyerTaxId().equals(invoice.get("BuyerTaxCode")), serialTips + ", 购买方税号不匹配!"); // McException.assertAccessException(!dto.getSellerName().equals(guyuanNameRepalce(invoice.get("SellerName").toString())), serialTips + ", 销售方名称不匹配!"); // McException.assertAccessException(!dto.getSellerTaxId().equals(invoice.get("SellerTaxCode")), serialTips + ", 销售方税号不匹配!"); // } catch (TencentCloudSDKException e) { // log.error(e.getMessage(), e); // // prd: 上传发票为假发票时,提示:该发票有疑问,请联系财务人员 // String message = e.getMessage(); // // ppExt: 已经是新版本接口, 过滤提示 [官方答复: 提示不会检测您是否使用的是新版,所有的用户都会提示, 忽略即可] // if (message.contains("温馨提示")) { // message = message.split("温馨提示")[0]; // } // if (message.contains("发票不存在")) { // message = "有疑问,请联系财务人员"; // } // McException.exceptionAccess(serial + message); // } // } })); return McR.success(); } /// url压缩转base64 @SneakyThrows private String imageUrlConvertBase64(String imageUrl) { ByteArrayOutputStream out = new ByteArrayOutputStream(); // scale(比例), outputQuality(质量) Thumbnails.fromURLs(Arrays.asList(new URL(imageUrl))).scale(0.5f).outputQuality(0.25f).toOutputStream(out); InputStream inputStream = new ByteArrayInputStream(out.toByteArray()); //转换为base64 byte[] bytes = IOUtils.toByteArray(inputStream); return Base64.getEncoder().encodeToString(bytes); } private static String guyuanNameRepalce(String name) { name=name.replaceAll(" ",""); if (name.contains("谷元")) { return UtilString.replaceBracketIsWhole(name); } else { return UtilString.replaceBracketIsSemiangle(name); } } @Autowired private FilePath filePath; /// PDF压缩转base64 @SneakyThrows private String pdfUrlConvertBase64(String pdfUrl) { String fileName = "tmp_" + new Date().getTime() + ".pdf"; // 下载文件 File file = UtilFile.mkdirIfNot(fileName, filePath.getPath().getTmp()); UtilHttp.doDownload(pdfUrl, file); // PDF压缩 PdfDocument doc = new PdfDocument(); // 创建PdfDocument类的对象 doc.loadFromFile(file.getAbsolutePath()); // 加载PDF文档 doc.getFileInfo().setIncrementalUpdate(false); // 禁用增量更新 doc.setCompressionLevel(PdfCompressionLevel.Normal); // 将压缩级别设置为最佳 // 遍历文档页面 for (int i = 0; i < doc.getPages().getCount(); i++) { PdfPageBase page = doc.getPages().get(i); // 获取指定页面 PdfImageInfo[] images = page.getImagesInfo(); // 获取每个页面的图像信息集合 // 遍历集合中的所有项目 if (images != null && images.length > 0) for (int j = 0; j < images.length; j++) { PdfImageInfo image = images[j]; // 获取指定图片 PdfBitmap bp = new PdfBitmap(image.getImage()); bp.setQuality(30); // 设置压缩质量 page.replaceImage(j, bp); // 将原始图像替换为压缩图像 } } // 将结果文档保存至另一个PDF文档中: 覆盖 doc.saveToFile(file.getAbsolutePath()); doc.close(); // PDF转base64, 无需透出本地文件地址 String base64 = UtilFile.fileToBase64(file.getAbsolutePath()); // 删除临时PDF文件 UtilFile.deleteFile(file.getAbsolutePath()); return base64; } private void validateBuyer(String BuyerName, String tips) { List corpNames = Arrays.asList( "谷元(上海)文化科技有限责任公司", "上海爱鱼文化传媒有限公司", "上海渔米可禧文化传媒有限公司", "上海巧豆文化传媒有限公司", "钰鸿文化创意(上海)有限公司", "上海攸元文化科技有限公司", "上海盈田演出经纪有限公司", "上海小蝠文化科技有限公司", "沐田居海(厦门)文化传媒有限公司", "北京元环文化科技有限责任公司", "厦门攸元文化科技有限公司", "厦门亨有文化科技有限公司", "厦门银雀思汀文化传媒有限公司", "上海渝泽信息科技有限公司", "厦门神谷飞流影视传媒有限公司", "厦门谷钛数字科技有限公司", "上海观情科技有限公司", "渔米可禧文化传媒(香港)有限公司", "杭州渔米可禧文化传媒有限公司"); McException.assertAccessException(!corpNames.contains(BuyerName), tips + ", 购买方名称不合法!"); } //下载钉钉OA审批单附件 private void downloadDdFile(String processInstanceId,String fileId,String downloadPath){ try { Map body = new HashMap(); body.put("processInstanceId",processInstanceId); body.put("fileId",fileId); DDR_New ddrNew = (DDR_New) UtilHttp.doPost("https://api.dingtalk.com/v1.0/workflow/processInstances/spaces/files/urls/download", ddClient.initTokenHeader(), null, body, DDR_New.class); //2、执行下载 Map result = (Map) ddrNew.getResult(); String downloadUri = UtilMap.getString(result, "downloadUri"); downloadFile(downloadUri,downloadPath); }catch (Exception e){ throw new RuntimeException(e); } } //文件下载到本地 private void downloadFile(String downloadUri,String downloadPath){ try { URL url = new URL(downloadUri); HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); int responseCode = httpConn.getResponseCode(); // 检查HTTP响应代码是否为200 if (responseCode == HttpURLConnection.HTTP_OK) { InputStream inputStream = httpConn.getInputStream(); FileOutputStream outputStream = new FileOutputStream(downloadPath); byte[] buffer = new byte[4096]; int bytesRead = -1; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); inputStream.close(); } else { System.out.println("无法下载文件。HTTP响应代码: " + responseCode); } httpConn.disconnect(); }catch (Exception e){ throw new RuntimeException(e); } } /** * 私有化方法:混票识别处理 * @param data 包含url、size、isPdf的Map参数 * @return 识别结果 */ private McR processMixedInvoice(Map data) throws TencentCloudSDKException { McException.assertParamException_Null(data, "url"); String image = String.valueOf(data.get("url")); log.info("混票识别, 免登地址, {}", image); // 非PDF, 且内存大于3M, 压缩后上传 if (UtilMap.getFloat(data, "size") > 3.0f && !UtilMap.getBoolean(data, "isPdf")) { image = imageUrlConvertBase64(image); } if (UtilMap.getFloat(data, "size") > 6.0f && UtilMap.getBoolean(data, "isPdf")) { image = pdfUrlConvertBase64(image); } List invoices = (List) txyInvoice.doRecognizeGeneralInvoice(image).get("MixedInvoiceItems"); System.out.println("invoices====="+invoices); List nos = new ArrayList<>(); List result = invoices.stream().map(item -> { Map prop = UtilMap.getMap(UtilMap.getMap(item, "SingleInvoiceInfos"), UtilMap.getString(item, "SubType")); String no = UtilMap.getString(prop, "Number"); if (nos.contains(no)) { return null; } nos.add(no); String kind = UtilMap.getString(item, "TypeDescription"); String invoiceName = UtilMap.getString(item, "SubTypeDescription"); if (kind.equals("全电发票")) { if (invoiceName.contains("铁路电子客票")) { kind = "火车票"; } else if (invoiceName.contains("机票行程单")) { kind = "机票行程单"; } else if (invoiceName.contains("专用发票")) { kind = "全电专用发票"; } else { kind = "全电普通发票"; } } if (kind.equals("增值税发票")) { if (invoiceName.contains("区块链电子发票")) { kind = "区块链电子发票"; } else if (invoiceName.contains("增值税专用发票")) { kind = "增值税专用发票"; } else if (invoiceName.contains("增值税普通发票")) { kind = "增值税普通发票"; } else if (invoiceName.contains("增值税电子专用发票")) { kind = "增值税电子专用发票"; } else if (invoiceName.contains("增值税电子普通发票")) { kind = "增值税电子普通发票"; } else { kind = "增值税普通发票"; } } McInvoiceDto invoiceDto = McInvoiceDto.builder() .name(UtilMap.getString(item, "SubTypeDescription")) .kindName(kind) .kind(UtilMap.getInt(item, "Type")) .code(UtilMap.getString(prop, "Code")) .serial(UtilMap.getString(prop, "Number")) .date(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "Date"))) .checkCode(UtilMap.getString(prop, "CheckCode")) .amount(UtilNumber.setBigDecimal(UtilMap.getString_first(prop, "Total", "Fare"))) .tax(UtilNumber.setBigDecimal(UtilMap.getString_first(prop, "Tax"))) .excludingTax(UtilNumber.setBigDecimal(UtilMap.getString(prop, "PretaxAmount"))) .buyerName(StringUtils.isBlank(guyuanNameRepalce(UtilMap.getString(prop, "Buyer"))) ? "上海能辉科技股份有限公司" : guyuanNameRepalce(UtilMap.getString(prop, "Buyer"))) .buyerTaxId(StringUtils.isBlank(UtilMap.getString(prop, "BuyerTaxID")) ? "" : UtilMap.getString(prop, "BuyerTaxID")) .sellerName(guyuanNameRepalce(UtilMap.getString_first(prop, "Seller", "Issuer"))) .sellerTaxId(UtilMap.getString_first(prop, "SellerTaxID", "AgentCode")) .passengerName(UtilMap.getString_first(prop, "Name", "UserName")) .seatType(UtilMap.getString(prop, "Seat")) .departureTime(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn")) .departurePort(UtilMap.getString_first(prop, "StationGetOn", "Entrance", "Place")) .arrivePort(UtilMap.getString_first(prop, "StationGetOff", "Exit")) .trainNo(UtilMap.getString_first(prop, "TrainNumber", "LicensePlate")) .insuranceCosts(UtilNumber.setBigDecimal(UtilMap.getString(prop, "Insurance"))) .fuelCosts(UtilNumber.setBigDecimal(UtilMap.getString(prop, "FuelSurcharge"))) .constructionCosts(UtilNumber.setBigDecimal(UtilMap.getString(prop, "AirDevelopmentFund"))) .build(); if ("机票行程单".equals(kind)) { invoiceDto.setSellerName(UtilMap.getString_first(prop, "Issuer")); invoiceDto.setSellerTaxId(UtilMap.getString_first(prop, "Seller")); Map flight = (Map) UtilMap.getList(prop, "FlightItems").get(0); invoiceDto.setDepartureTime(UtilString.replaceDateZH_cn(UtilMap.getString(item, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn")); invoiceDto.setDeparturePort(UtilMap.getString(flight, "StationGetOn")); invoiceDto.setArrivePort(UtilMap.getString(flight, "StationGetOff")); invoiceDto.setSeatType(UtilMap.getString(flight, "Seat")); } if ("出租车发票".equals(item.get("TypeDescription"))) { invoiceDto.setDepartureTime(UtilMap.getString(prop, "TimeGetOn") + " ~ " + UtilMap.getString(prop, "TimeGetOff")); } return invoiceDto; }).filter(Objects::nonNull).collect(Collectors.toList()); return McR.success(McInvoiceDto.formatResponse(result)); } }