package com.malk.qiwang.Service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 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.mapper.CompanyTitleMapper; import com.malk.qiwang.mapper.InvoiceLibraryMapper; import com.malk.qiwang.Service.IInvoiceLibraryService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.malk.qiwang.model.McInvoiceDto; 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.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.hpsf.Decimal; 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.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.RestTemplate; 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.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; /** *

* 发票库表 服务实现类 *

* * @author LQY * @since 2026-04-27 */ @Service @Slf4j public class InvoiceLibraryServiceImpl extends ServiceImpl implements IInvoiceLibraryService { @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 FilePath filePath; @Autowired private DDClient_Workflow ddClientWorkflow; @Autowired private InvoiceLibraryMapper baseMapper; @Autowired private CompanyTitleMapper companyTitleMapper; @Autowired private DDClient_Contacts ddClient_contacts; private static final String url = "http://47.103.203.2:9092/qiwang/"; // private static final String url = "http://24120b4f.r39.cpolar.top/qiwang/"; @Override public McR invoiceLibrary(Map map) { log.info("接收到的参数: {}", map); try { String processInstanceId = UtilMap.getString(map, "processInstanceId"); if (StringUtils.isBlank(processInstanceId)) { log.error("processInstanceId为空"); return McR.error("400", "审批实例ID不能为空"); } // 获取审批实例信息 String accessToken = ddClient.getAccessToken(); Map processInstance = ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId); if (processInstance == null) { log.error("获取审批实例失败: {}", processInstanceId); return McR.error("500", "获取审批实例信息失败"); } String userId = (String) processInstance.get("originatorUserId"); log.info("审批人ID: {}", userId); List formComponentValues = (List) processInstance.get("formComponentValues"); if (formComponentValues == null || formComponentValues.isEmpty()) { log.warn("表单数据为空"); return McR.error("400", "表单数据为空"); } // 存储主表的字段值 String zt = null; BigDecimal mainJe = null; String bxlb = null; String bm = null; String fkzhxx = null; String fkzh = null; String yhqc = null; String sfct = null; // 遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); Object value = formComponentValue.get("value"); if ("DDSelectField_4SITXLYUEO80".equals(id)) { zt = value != null ? String.valueOf(value) : ""; } if ("MoneyField_3QZLY8BD3780".equals(id)) { if (value instanceof BigDecimal) { mainJe = (BigDecimal) value; } else if (value != null) { try { mainJe = new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { log.error("金额格式转换失败: {}", value); mainJe = BigDecimal.ZERO; } } } if ("DDSelectField_1TP75OVCPAAO0".equals(id)) { bxlb = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_1RD2SROH579C0".equals(id)) { bm = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_11D6S45JKFHS0".equals(id)) { fkzhxx = value != null ? String.valueOf(value) : ""; } if ("TextField_1S1VV9PIIQWW0".equals(id)) { fkzh = value != null ? String.valueOf(value) : ""; } if ("TextField_ZUVAAH3FIGW0".equals(id)) { yhqc = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_DEKPLHARB6O0".equals(id)) { sfct = value != null ? String.valueOf(value) : "否"; } } // 判断主表状态 - 无发票 if ("否".equals(zt)) { // 保存基础报销记录 InvoiceLibrary baseInvoice = new InvoiceLibrary(); baseInvoice.setOaId(processInstanceId); baseInvoice.setOaStatus("0"); baseInvoice.setInvoiceStatus("0"); baseInvoice.setDep(bm != null ? bm : ""); baseInvoice.setAccountTitle(bxlb != null ? bxlb : ""); baseInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); baseInvoice.setCreatedAt(LocalDateTime.now()); baseInvoice.setUpdatedAt(LocalDateTime.now()); baseInvoice.setInvoiceCode(""); baseInvoice.setInvoiceNumber(""); baseInvoice.setInvoiceType(""); baseInvoice.setAmount(BigDecimal.ZERO); baseInvoice.setTaxAmount(BigDecimal.ZERO); baseInvoice.setTotalAmount(BigDecimal.ZERO); baseInvoice.setBuyerName(""); baseInvoice.setBuyerTaxId(""); baseInvoice.setSellerName(""); baseInvoice.setSellerTaxId(""); baseInvoice.setFormName("员工费用报销"); baseInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); baseInvoice.setPayAccount(fkzh != null ? fkzh : ""); baseInvoice.setBankName(yhqc != null ? yhqc : ""); baseInvoice.setIsLongTerm(sfct != null ? sfct : ""); baseInvoice.setHasInvoice("否"); baseInvoice.setDetailAmount(null); baseInvoice.setSharedTaxAmount(null); baseInvoice.setSharedAmount(null); baseInvoice.setSharedRate(null); baseMapper.insert(baseInvoice); addWorkflowComment(processInstanceId, userId, "报销总金额:" + mainJe); return McR.success(); } // 判断主表状态 - 有发票 if ("是".equals(zt)) { for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_1A1CDMEN8DDS0".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); log.info("子表数据: {}", tableFieldValue); try { List tableRows = (List) JSONObject.parse(tableFieldValue); if (tableRows == null || tableRows.isEmpty()) { log.warn("子表数据为空"); return McR.error("400", "发票明细为空"); } List allResults = new ArrayList<>(); List invoiceList = new ArrayList<>(); // 存储第一遍解析的发票数据,避免重复解析 List> parsedInvoiceDataList = new ArrayList<>(); // 存储共享发票数据 List> sharedInvoiceDataList = new ArrayList<>(); // 用于校验的集合 List invoiceNumberList = new ArrayList<>(); Map invoiceNumberToFileName = new HashMap<>(); List invalidBuyerTaxIds = new ArrayList<>(); // 存储税率值,供共享发票使用 BigDecimal sharedTaxRate = BigDecimal.ZERO; String sharedKind = ""; // ========== 第一遍遍历:收集并校验所有发票数据 ========== for (Map row : tableRows) { List rowValues = (List) row.get("rowValue"); String hasInvoice = null; String cdbm = null; String detailje = null; List attachmentList = null; for (Map rowItem : rowValues) { String key = String.valueOf(rowItem.get("key")); if ("DDSelectField_1ORUK0KIM5D6O".equals(key)) { Object value = rowItem.get("value"); hasInvoice = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_ATSWRJBJ7K00".equals(key)) { Object value = rowItem.get("value"); cdbm = value != null ? String.valueOf(value) : ""; } if ("NumberField_1622XLFLYEWW0".equals(key)) { Object value = rowItem.get("value"); detailje = value != null ? String.valueOf(value) : ""; } if ("DDAttachment_Z02OGR5QL8U8".equals(key)) { Object value = rowItem.get("value"); if (value instanceof List) { attachmentList = (List) value; } } } if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { for (Map attachment : attachmentList) { String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); try { String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); fileMap.put("size", new File(filePath).length() / 1024f / 1024f); fileMap.put("isPdf", "pdf".equalsIgnoreCase(fileType)); McR result = processMixedInvoice(fileMap); Object responseData = result.getData(); // 解析发票数据 if (responseData != null) { String jsonStr = JSONObject.toJSONString(responseData); JSONObject jsonObject = JSONObject.parseObject(jsonStr); JSONArray resultArray = jsonObject.getJSONArray("result"); if (resultArray != null && !resultArray.isEmpty()) { JSONObject invoice = resultArray.getJSONObject(0); String invoiceNumber = invoice.getString("serial") != null ? invoice.getString("serial") : ""; String buyerTaxId = invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""; String buyerName = invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""; // 获取税率,供共享发票使用 Object taxRateObj = invoice.get("taxRate"); Object kind = invoice.getString("kindName"); if (taxRateObj != null) { String taxRateStr = String.valueOf(taxRateObj); String taxRateNum = taxRateStr.replace("%", "").trim(); sharedKind = kind != null ? String.valueOf(kind) : ""; sharedTaxRate = new BigDecimal(taxRateNum); sharedTaxRate = sharedTaxRate.divide(new BigDecimal("100"), 10, BigDecimal.ROUND_HALF_UP); log.info("从有发票中获取税率: 原始={}, 转换后={}", taxRateStr, sharedTaxRate); } // 保存解析的数据供后续使用 Map parsedData = new HashMap<>(); parsedData.put("invoice", invoice); parsedData.put("fileId", fileId); parsedData.put("fileName", fileName); parsedData.put("fileType", fileType); parsedData.put("cdbm", cdbm); parsedData.put("hasInvoice", hasInvoice); parsedData.put("detailje", detailje); parsedData.put("responseData", responseData); parsedInvoiceDataList.add(parsedData); // ========== 校验1:抬头校验 ========== if (StringUtils.isNotBlank(buyerName)) { CompanyTitle existingTitle = companyTitleMapper.selectByTaxId(buyerName); if (existingTitle == null) { String errorInfo = String.format("发票: %s, 购买方名称: %s, 税号: %s", fileName, buyerName, buyerTaxId); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 税号={} 不存在于抬头库, 文件名={}", buyerTaxId, fileName); } } else { String errorInfo = String.format("发票: %s, 购买方税号为空", fileName); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 购买方税号为空, 文件名={}", fileName); } // ========== 校验2:发票号重复校验 ========== if (StringUtils.isNotBlank(invoiceNumber)) { // 检查数据库中是否已存在相同的发票号 InvoiceLibrary existingInvoice = baseMapper.selectByInvoiceNumber(invoiceNumber); if (existingInvoice != null) { String errorMsg = String.format("发票号重复: %s (当前文件: %s, 已存在于OA审批单: %s)", invoiceNumber, fileName, existingInvoice.getOaId()); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } // 同时检查同一审批单内是否有重复 if (invoiceNumberList.contains(invoiceNumber)) { String errorMsg = String.format("当前审批单内发票号重复: %s (当前文件: %s, 已存在文件: %s)", invoiceNumber, fileName, invoiceNumberToFileName.get(invoiceNumber)); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } invoiceNumberList.add(invoiceNumber); invoiceNumberToFileName.put(invoiceNumber, fileName); } } } } catch (Exception e) { log.error("发票解析异常: fileId={}, fileName={}", fileId, fileName, e); terminateWorkflow(accessToken, processInstanceId, userId, "发票解析失败: " + e.getMessage()); return McR.error("400", "发票解析失败: " + e.getMessage()); } } } else if ("共享".equals(hasInvoice)) { // 收集共享发票数据,待第二遍遍历时保存 if (detailje != null && StringUtils.isNotBlank(detailje)) { Map sharedData = new HashMap<>(); sharedData.put("detailje", detailje); sharedData.put("cdbm", cdbm); sharedData.put("hasInvoice", hasInvoice); sharedInvoiceDataList.add(sharedData); log.info("收集共享发票数据: 金额={}, 成本部门={}", detailje, cdbm); } } } // ========== 抬头校验失败处理 ========== if (!invalidBuyerTaxIds.isEmpty()) { String errorMsg = "发票抬头有误,以下发票的购买方税号不在公司抬头库中:\n" + String.join("\n", invalidBuyerTaxIds); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } BigDecimal totalAmount = BigDecimal.ZERO; // ========== 第二遍遍历:保存发票数据(校验通过后) ========== // 1. 保存普通发票数据 for (Map parsedData : parsedInvoiceDataList) { JSONObject invoice = (JSONObject) parsedData.get("invoice"); Object responseData = parsedData.get("responseData"); String cdbm = (String) parsedData.get("cdbm"); String hasInvoice = (String) parsedData.get("hasInvoice"); String detailje = (String) parsedData.get("detailje"); // 修改为 String 类型 allResults.add(responseData); // 汇总 amount BigDecimal amount = invoice.getBigDecimal("amount"); if (amount != null) { totalAmount = totalAmount.add(amount); } // 构建InvoiceLibrary对象 InvoiceLibrary invoiceLibrary = new InvoiceLibrary(); invoiceLibrary.setOaId(processInstanceId); invoiceLibrary.setInvoiceCode(invoice.getString("code") != null ? invoice.getString("code") : ""); invoiceLibrary.setInvoiceNumber(invoice.getString("serial") != null ? invoice.getString("serial") : ""); invoiceLibrary.setInvoiceType(invoice.getString("kindName") != null ? invoice.getString("kindName") : ""); String date = invoice.getString("date"); if (StringUtils.isNotBlank(date)) { try { invoiceLibrary.setInvoiceDate(LocalDate.parse(date.trim())); } catch (Exception e) { log.warn("日期解析失败: {}", date); } } invoiceLibrary.setAmount(invoice.getBigDecimal("excludingTax") != null ? invoice.getBigDecimal("excludingTax") : BigDecimal.ZERO); invoiceLibrary.setTaxAmount(invoice.getBigDecimal("tax") != null ? invoice.getBigDecimal("tax") : BigDecimal.ZERO); invoiceLibrary.setTotalAmount(invoice.getBigDecimal("amount") != null ? invoice.getBigDecimal("amount") : BigDecimal.ZERO); invoiceLibrary.setBuyerName(invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""); invoiceLibrary.setBuyerTaxId(invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""); invoiceLibrary.setSellerName(invoice.getString("sellerName") != null ? invoice.getString("sellerName") : ""); invoiceLibrary.setSellerTaxId(invoice.getString("sellerTaxId") != null ? invoice.getString("sellerTaxId") : ""); invoiceLibrary.setOaStatus("0"); invoiceLibrary.setFormName("员工费用报销"); invoiceLibrary.setPaySubject(fkzhxx != null ? fkzhxx : ""); invoiceLibrary.setPayAccount(fkzh != null ? fkzh : ""); invoiceLibrary.setBankName(yhqc != null ? yhqc : ""); invoiceLibrary.setIsLongTerm(sfct != null ? sfct : ""); invoiceLibrary.setInvoiceStatus("0"); invoiceLibrary.setHasInvoice(hasInvoice); // 处理 detailAmount,将 String 转换为 BigDecimal if (detailje != null && StringUtils.isNotBlank(detailje)) { try { invoiceLibrary.setDetailAmount(new BigDecimal(detailje)); } catch (NumberFormatException e) { log.error("detailJe格式转换失败: {}", detailje); invoiceLibrary.setDetailAmount(null); } } else { invoiceLibrary.setDetailAmount(null); } // 普通发票不涉及共享字段,设置为 null invoiceLibrary.setSharedTaxAmount(null); invoiceLibrary.setSharedAmount(null); invoiceLibrary.setSharedRate(null); invoiceLibrary.setAccountTitle(bxlb != null ? bxlb : ""); invoiceLibrary.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); invoiceLibrary.setCreatedAt(LocalDateTime.now()); invoiceLibrary.setUpdatedAt(LocalDateTime.now()); // 设置成本部门 if (cdbm != null && StringUtils.isNotBlank(cdbm)) { invoiceLibrary.setDep(cdbm); } else { invoiceLibrary.setDep(bm != null ? bm : ""); } invoiceList.add(invoiceLibrary); } for (Map sharedData : sharedInvoiceDataList) { String detailje = (String) sharedData.get("detailje"); String cdbm = (String) sharedData.get("cdbm"); String hasInvoice = (String) sharedData.get("hasInvoice"); try { BigDecimal detailAmount = new BigDecimal(detailje); // 使用从普通发票中解析到的税率 BigDecimal ocrTaxRate = sharedTaxRate; if (ocrTaxRate.compareTo(BigDecimal.ZERO) == 0) { log.warn("共享发票税率未获取到,使用默认税率0,金额={}", detailAmount); } // 计算金额: 含税金额 / (1 + 税率) = 税额 BigDecimal divisor = BigDecimal.ONE.add(ocrTaxRate); BigDecimal sharedTaxAmount = detailAmount.divide(divisor, 2, BigDecimal.ROUND_HALF_UP); BigDecimal sharedAmount = detailAmount.subtract(sharedTaxAmount); BigDecimal sharedRate = ocrTaxRate; // 这是小数形式的税率,如 0.09 log.info("共享发票计算: 含税金额={}, 税率={}%, 不含税金额={}, 税额={}", detailAmount, ocrTaxRate.multiply(new BigDecimal("100")), sharedAmount, sharedTaxAmount); // 保存共享发票数据 InvoiceLibrary sharedInvoice = new InvoiceLibrary(); sharedInvoice.setOaId(processInstanceId); sharedInvoice.setOaStatus("0"); sharedInvoice.setInvoiceStatus("0"); sharedInvoice.setDep(cdbm != null && StringUtils.isNotBlank(cdbm) ? cdbm : (bm != null ? bm : "")); sharedInvoice.setAccountTitle(bxlb != null ? bxlb : ""); sharedInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); sharedInvoice.setAmount(null); sharedInvoice.setTaxAmount(null); sharedInvoice.setTotalAmount(null); // 共享发票不设置总金额 sharedInvoice.setFormName("员工费用报销"); sharedInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); sharedInvoice.setPayAccount(fkzh != null ? fkzh : ""); sharedInvoice.setBankName(yhqc != null ? yhqc : ""); sharedInvoice.setIsLongTerm(sfct != null ? sfct : ""); sharedInvoice.setHasInvoice(hasInvoice); sharedInvoice.setDetailAmount(detailAmount); // 共享发票字段设置值 - 修正:存储数字而不是带百分号的字符串 sharedInvoice.setSharedTaxAmount(sharedAmount); sharedInvoice.setSharedAmount(sharedTaxAmount); sharedInvoice.setSharedRate(String.valueOf(sharedRate)); // 直接存储小数,如 0.09 sharedInvoice.setCreatedAt(LocalDateTime.now()); sharedInvoice.setUpdatedAt(LocalDateTime.now()); sharedInvoice.setInvoiceCode(""); sharedInvoice.setInvoiceNumber(""); sharedInvoice.setInvoiceType(sharedKind); sharedInvoice.setBuyerName(""); sharedInvoice.setBuyerTaxId(""); sharedInvoice.setSellerName(""); sharedInvoice.setSellerTaxId(""); invoiceList.add(sharedInvoice); log.info("成功保存共享发票记录,成本部门: {}", cdbm); } catch (NumberFormatException e) { log.error("共享发票金额格式转换失败: detailje={}", detailje, e); } catch (Exception e) { log.error("共享发票处理失败", e); } } // 保存发票记录 if (!invoiceList.isEmpty()) { for (InvoiceLibrary invoice : invoiceList) { baseMapper.insert(invoice); } log.info("成功保存 {} 条发票记录(普通发票: {}, 共享发票: {})", invoiceList.size(), parsedInvoiceDataList.size(), sharedInvoiceDataList.size()); } addWorkflowComment1(processInstanceId, userId, totalAmount); return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败: " + e.getMessage()); } } } } return McR.success(); } catch (Exception e) { log.error("处理审批实例失败", e); return McR.error("500", "系统处理失败: " + e.getMessage()); } } /** * 终止钉钉审批流程 * * @param accessToken 钉钉accessToken * @param processInstanceId 审批实例ID * @param operatingUserId 操作人用户ID(通常是发起人ID) * @param remark 终止原因备注 */ private void terminateWorkflow(String accessToken, String processInstanceId, String operatingUserId, String remark) { // 异步延迟15秒后执行终止 CompletableFuture.runAsync(() -> { try { log.info("延迟15秒后开始终止审批流程: processInstanceId={}, remark={}", processInstanceId, remark); Thread.sleep(15000); // 实际执行终止操作 doTerminateWorkflow(accessToken, processInstanceId, operatingUserId, remark); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("延迟终止流程被中断: processInstanceId={}", processInstanceId, e); } catch (Exception e) { log.error("异步执行终止流程异常: processInstanceId={}", processInstanceId, e); } }); } /** * 实际执行钉钉审批流程终止 */ private void doTerminateWorkflow(String accessToken, String processInstanceId, String operatingUserId, String remark) { try { log.info("开始调用钉钉API终止审批流程: processInstanceId={}", processInstanceId); // 构建请求URL String url = "https://api.dingtalk.com/v1.0/workflow/processInstances/terminate"; // 构建请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("x-acs-dingtalk-access-token", accessToken); // 构建请求体 Map requestBody = new HashMap<>(); requestBody.put("processInstanceId", processInstanceId); requestBody.put("isSystem", true); // 系统调用终止 requestBody.put("remark", remark); requestBody.put("operatingUserId", operatingUserId); log.info("钉钉终止请求参数: {}", requestBody); // 发送POST请求 HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); RestTemplate restTemplate = new RestTemplate(); ResponseEntity response = restTemplate.postForEntity(url, requestEntity, String.class); if (response.getStatusCode().is2xxSuccessful()) { log.info("审批流程终止成功: processInstanceId={}, response={}", processInstanceId, response.getBody()); } else { log.error("审批流程终止失败: processInstanceId={}, statusCode={}, response={}", processInstanceId, response.getStatusCode(), response.getBody()); } } catch (Exception e) { log.error("调用钉钉终止流程接口异常: processInstanceId={}, error={}", processInstanceId, e.getMessage(), e); } } @Override public void updateOaStatusByOaId(Map map) { try { String oaId = UtilMap.getString(map, "oaId"); // 创建更新条件 LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(InvoiceLibrary::getOaId, oaId) .set(InvoiceLibrary::getOaStatus, "1") .set(InvoiceLibrary::getUpdatedAt, LocalDateTime.now()); int updateCount = baseMapper.update(null, updateWrapper); if (updateCount > 0) { log.info("更新成功: 共更新{}条记录", updateCount); } else { log.warn("未找到符合条件的记录: oaId={}", oaId); } } catch (Exception e) { throw new RuntimeException("更新OA状态失败", e); } } @Override public McR invoiceLibrarys(Map map) { log.info("接收到的参数: {}", map); try { String processInstanceId = UtilMap.getString(map, "processInstanceId"); if (StringUtils.isBlank(processInstanceId)) { log.error("processInstanceId为空"); return McR.error("400", "审批实例ID不能为空"); } // 获取审批实例信息 String accessToken = ddClient.getAccessToken(); Map processInstance = ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId); if (processInstance == null) { log.error("获取审批实例失败: {}", processInstanceId); return McR.error("500", "获取审批实例信息失败"); } String userId = (String) processInstance.get("originatorUserId"); log.info("审批人ID: {}", userId); List formComponentValues = (List) processInstance.get("formComponentValues"); if (formComponentValues == null || formComponentValues.isEmpty()) { log.warn("表单数据为空"); return McR.error("400", "表单数据为空"); } // 存储主表的字段值 String zt = null; BigDecimal mainJe = null; String bxlb = null; String bm = null; // 遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); Object value = formComponentValue.get("value"); if ("DDSelectField_4SITXLYUEO80".equals(id)) { zt = value != null ? String.valueOf(value) : ""; } if ("MoneyField_3QZLY8BD3780".equals(id)) { if (value instanceof BigDecimal) { mainJe = (BigDecimal) value; } else if (value != null) { try { mainJe = new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { log.error("金额格式转换失败: {}", value); mainJe = BigDecimal.ZERO; } } } if ("DDSelectField_1TP75OVCPAAO0".equals(id)) { bxlb = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_OKBSJN0MD6O0".equals(id)) { bm = value != null ? String.valueOf(value) : ""; } } // 判断主表状态 - 无发票 if ("否".equals(zt)) { Map result = new HashMap<>(); result.put("je", mainJe); result.put("status", "否"); result.put("message", "无发票报销"); // 保存基础报销记录 InvoiceLibrary baseInvoice = new InvoiceLibrary(); baseInvoice.setOaId(processInstanceId); baseInvoice.setOaStatus("0"); baseInvoice.setInvoiceStatus("0"); baseInvoice.setDep(bm != null ? bm : ""); baseInvoice.setAccountTitle(bxlb != null ? bxlb : ""); baseInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); baseInvoice.setCreatedAt(LocalDateTime.now()); baseInvoice.setUpdatedAt(LocalDateTime.now()); baseInvoice.setInvoiceCode(""); baseInvoice.setInvoiceNumber(""); baseInvoice.setInvoiceType(""); baseInvoice.setAmount(BigDecimal.ZERO); baseInvoice.setTaxAmount(BigDecimal.ZERO); baseInvoice.setTotalAmount(BigDecimal.ZERO); baseInvoice.setBuyerName(""); baseInvoice.setBuyerTaxId(""); baseInvoice.setSellerName(""); baseInvoice.setSellerTaxId(""); baseMapper.insert(baseInvoice); addWorkflowComment(processInstanceId, userId, result); return McR.success(result); } // 判断主表状态 - 有发票 if ("是".equals(zt)) { for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_1A1CDMEN8DDS0".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); log.info("子表数据: {}", tableFieldValue); try { List tableRows = (List) JSONObject.parse(tableFieldValue); if (tableRows == null || tableRows.isEmpty()) { log.warn("子表数据为空"); return McR.error("400", "发票明细为空"); } List allResults = new ArrayList<>(); List invoiceList = 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)) { Object value = rowItem.get("value"); hasInvoice = value != null ? String.valueOf(value) : ""; } if ("DDAttachment_Z02OGR5QL8U8".equals(key)) { Object value = rowItem.get("value"); if (value instanceof List) { attachmentList = (List) value; } } } if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { for (Map attachment : attachmentList) { String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); log.info("处理发票: fileName={}, fileId={}", fileName, fileId); try { String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); fileMap.put("size", new File(filePath).length() / 1024f / 1024f); fileMap.put("isPdf", "pdf".equalsIgnoreCase(fileType)); McR result = processMixedInvoice(fileMap); Object responseData = result.getData(); allResults.add(responseData); // 解析发票数据 - responseData 是对象,不是数组 if (responseData != null) { try { String jsonStr = JSONObject.toJSONString(responseData); JSONObject jsonObject = JSONObject.parseObject(jsonStr); // 获取 result 数组 JSONArray resultArray = jsonObject.getJSONArray("result"); if (resultArray != null && !resultArray.isEmpty()) { JSONObject invoice = resultArray.getJSONObject(0); InvoiceLibrary invoiceLibrary = new InvoiceLibrary(); invoiceLibrary.setOaId(processInstanceId); invoiceLibrary.setInvoiceCode(invoice.getString("code") != null ? invoice.getString("code") : ""); invoiceLibrary.setInvoiceNumber(invoice.getString("serial") != null ? invoice.getString("serial") : ""); invoiceLibrary.setInvoiceType(invoice.getString("kindName") != null ? invoice.getString("kindName") : ""); String date = invoice.getString("date"); if (StringUtils.isNotBlank(date)) { try { invoiceLibrary.setInvoiceDate(LocalDate.parse(date.trim())); } catch (Exception e) { log.warn("日期解析失败: {}", date); } } invoiceLibrary.setAmount(invoice.getBigDecimal("excludingTax") != null ? invoice.getBigDecimal("excludingTax") : BigDecimal.ZERO); invoiceLibrary.setTaxAmount(invoice.getBigDecimal("tax") != null ? invoice.getBigDecimal("tax") : BigDecimal.ZERO); invoiceLibrary.setTotalAmount(invoice.getBigDecimal("amount") != null ? invoice.getBigDecimal("amount") : BigDecimal.ZERO); invoiceLibrary.setBuyerName(invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""); invoiceLibrary.setBuyerTaxId(invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""); invoiceLibrary.setSellerName(invoice.getString("sellerName") != null ? invoice.getString("sellerName") : ""); invoiceLibrary.setSellerTaxId(invoice.getString("sellerTaxId") != null ? invoice.getString("sellerTaxId") : ""); invoiceLibrary.setOaStatus("0"); invoiceLibrary.setInvoiceStatus("0"); invoiceLibrary.setDep(bm != null ? bm : ""); invoiceLibrary.setAccountTitle(bxlb != null ? bxlb : ""); invoiceLibrary.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); invoiceLibrary.setCreatedAt(LocalDateTime.now()); invoiceLibrary.setUpdatedAt(LocalDateTime.now()); invoiceList.add(invoiceLibrary); } } catch (Exception e) { log.error("解析发票数据失败", e); } } } catch (Exception e) { log.error("处理发票失败: fileId={}, fileName={}", fileId, fileName, e); } } } } if (!invoiceList.isEmpty()) { for (InvoiceLibrary invoice : invoiceList) { baseMapper.insert(invoice); } log.info("成功保存 {} 条发票记录", invoiceList.size()); } addWorkflowComment(processInstanceId, userId, allResults); return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败: " + e.getMessage()); } } } } return McR.success(); } catch (Exception e) { log.error("处理审批实例失败", e); return McR.error("500", "系统处理失败: " + e.getMessage()); } } @Override public McR deleteAllByOaId(String oaId) { try { // 创建删除条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(InvoiceLibrary::getOaId, oaId); // 执行删除 int deleteCount = baseMapper.delete(queryWrapper); if (deleteCount > 0) { return McR.success("删除成功,共删除 " + deleteCount + " 条记录"); } else { return McR.error("404", "未找到 oa_id 为 " + oaId + " 的记录"); } } catch (Exception e) { log.error("删除失败", e); return McR.error("500", "删除失败: " + e.getMessage()); } } @Override public McR invoiceLibrary1(Map map) { log.info("接收到的参数: {}", map); try { String processInstanceId = UtilMap.getString(map, "processInstanceId"); if (StringUtils.isBlank(processInstanceId)) { log.error("processInstanceId为空"); return McR.error("400", "审批实例ID不能为空"); } // 获取审批实例信息 String accessToken = ddClient.getAccessToken(); Map processInstance = ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId); if (processInstance == null) { log.error("获取审批实例失败: {}", processInstanceId); return McR.error("500", "获取审批实例信息失败"); } String userId = (String) processInstance.get("originatorUserId"); log.info("审批人ID: {}", userId); List formComponentValues = (List) processInstance.get("formComponentValues"); if (formComponentValues == null || formComponentValues.isEmpty()) { log.warn("表单数据为空"); return McR.error("400", "表单数据为空"); } // 存储主表的字段值 String zt = null; BigDecimal mainJe = null; String bxlb = null; String bm = null; String fkzhxx = null; String fkzh = null; String yhqc = null; String sfct = null; // 遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); Object value = formComponentValue.get("value"); if ("DDSelectField_4TXFWTKFF7K0".equals(id)) { zt = value != null ? String.valueOf(value) : ""; } if ("MoneyField_1FVEBDQBSQ680".equals(id)) { if (value instanceof BigDecimal) { mainJe = (BigDecimal) value; } else if (value != null) { try { mainJe = new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { log.error("金额格式转换失败: {}", value); mainJe = BigDecimal.ZERO; } } } if ("DDSelectField_YLLB7AD1ATS0".equals(id)) { bxlb = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_RF93A6VHA9C0".equals(id)) { bm = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_23RVAEKUM5UO0".equals(id)) { fkzhxx = value != null ? String.valueOf(value) : ""; } if ("TextField_1TE3AOHRMD4W0".equals(id)) { fkzh = value != null ? String.valueOf(value) : ""; } if ("TextField_EN2MDZIMUTK0".equals(id)) { yhqc = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_P2EFYKN5ALC0".equals(id)) { sfct = value != null ? String.valueOf(value) : "否"; } } // 判断主表状态 - 无发票 if ("否".equals(zt)) { // 保存基础报销记录 InvoiceLibrary baseInvoice = new InvoiceLibrary(); baseInvoice.setOaId(processInstanceId); baseInvoice.setOaStatus("0"); baseInvoice.setInvoiceStatus("0"); baseInvoice.setDep(bm != null ? bm : ""); baseInvoice.setAccountTitle(bxlb != null ? bxlb : ""); baseInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); baseInvoice.setCreatedAt(LocalDateTime.now()); baseInvoice.setUpdatedAt(LocalDateTime.now()); baseInvoice.setInvoiceCode(""); baseInvoice.setInvoiceNumber(""); baseInvoice.setInvoiceType(""); baseInvoice.setAmount(BigDecimal.ZERO); baseInvoice.setTaxAmount(BigDecimal.ZERO); baseInvoice.setTotalAmount(BigDecimal.ZERO); baseInvoice.setBuyerName(""); baseInvoice.setBuyerTaxId(""); baseInvoice.setSellerName(""); baseInvoice.setSellerTaxId(""); baseInvoice.setFormName("付款申请"); baseInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); baseInvoice.setPayAccount(fkzh != null ? fkzh : ""); baseInvoice.setBankName(yhqc != null ? yhqc : ""); baseInvoice.setIsLongTerm(sfct != null ? sfct : ""); baseInvoice.setHasInvoice("否"); baseInvoice.setDetailAmount(null); baseInvoice.setSharedTaxAmount(null); baseInvoice.setSharedAmount(null); baseInvoice.setSharedRate(null); baseMapper.insert(baseInvoice); addWorkflowComment(processInstanceId, userId, "报销总金额:" + mainJe); return McR.success(); } // 判断主表状态 - 有发票 if ("是".equals(zt)) { for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_1VKP90DW7WKG0".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); log.info("子表数据: {}", tableFieldValue); try { List tableRows = (List) JSONObject.parse(tableFieldValue); if (tableRows == null || tableRows.isEmpty()) { log.warn("子表数据为空"); return McR.error("400", "发票明细为空"); } List allResults = new ArrayList<>(); List invoiceList = new ArrayList<>(); // 存储第一遍解析的发票数据,避免重复解析 List> parsedInvoiceDataList = new ArrayList<>(); // 存储共享发票数据 List> sharedInvoiceDataList = new ArrayList<>(); // 用于校验的集合 List invoiceNumberList = new ArrayList<>(); Map invoiceNumberToFileName = new HashMap<>(); List invalidBuyerTaxIds = new ArrayList<>(); // 存储税率值,供共享发票使用 BigDecimal sharedTaxRate = BigDecimal.ZERO; String sharedKind = ""; // ========== 第一遍遍历:收集并校验所有发票数据 ========== for (Map row : tableRows) { List rowValues = (List) row.get("rowValue"); String hasInvoice = null; String cdbm = null; String detailje = null; List attachmentList = null; for (Map rowItem : rowValues) { String key = String.valueOf(rowItem.get("key")); if ("DDSelectField_20ZGDOIRHB400".equals(key)) { Object value = rowItem.get("value"); hasInvoice = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_1VQ3M98PC7SW0".equals(key)) { Object value = rowItem.get("value"); cdbm = value != null ? String.valueOf(value) : ""; } if ("NumberField_BLX4BRW8GEW0".equals(key)) { Object value = rowItem.get("value"); detailje = value != null ? String.valueOf(value) : ""; } if ("DDAttachment_1YQ3DD7BH4BK0".equals(key)) { Object value = rowItem.get("value"); if (value instanceof List) { attachmentList = (List) value; } } } if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { for (Map attachment : attachmentList) { String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); try { String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); fileMap.put("size", new File(filePath).length() / 1024f / 1024f); fileMap.put("isPdf", "pdf".equalsIgnoreCase(fileType)); McR result = processMixedInvoice(fileMap); Object responseData = result.getData(); // 解析发票数据 if (responseData != null) { String jsonStr = JSONObject.toJSONString(responseData); JSONObject jsonObject = JSONObject.parseObject(jsonStr); JSONArray resultArray = jsonObject.getJSONArray("result"); if (resultArray != null && !resultArray.isEmpty()) { JSONObject invoice = resultArray.getJSONObject(0); String invoiceNumber = invoice.getString("serial") != null ? invoice.getString("serial") : ""; String buyerTaxId = invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""; String buyerName = invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""; // 获取税率,供共享发票使用 Object taxRateObj = invoice.get("taxRate"); Object kind = invoice.getString("kindName"); System.out.println("qqq"+taxRateObj); System.out.println("qqq"+kind); if (taxRateObj != null) { String taxRateStr = String.valueOf(taxRateObj); String taxRateNum = taxRateStr.replace("%", "").trim(); sharedKind = kind != null ? String.valueOf(kind) : ""; sharedTaxRate = new BigDecimal(taxRateNum); sharedTaxRate = sharedTaxRate.divide(new BigDecimal("100"), 10, BigDecimal.ROUND_HALF_UP); log.info("从有发票中获取税率: 原始={}, 转换后={}", taxRateStr, sharedTaxRate); } // 保存解析的数据供后续使用 Map parsedData = new HashMap<>(); parsedData.put("invoice", invoice); parsedData.put("fileId", fileId); parsedData.put("fileName", fileName); parsedData.put("fileType", fileType); parsedData.put("cdbm", cdbm); parsedData.put("hasInvoice", hasInvoice); parsedData.put("detailje", detailje); parsedData.put("responseData", responseData); parsedInvoiceDataList.add(parsedData); // ========== 校验1:抬头校验 ========== if (StringUtils.isNotBlank(buyerName)) { CompanyTitle existingTitle = companyTitleMapper.selectByTaxId(buyerName); if (existingTitle == null) { String errorInfo = String.format("发票: %s, 购买方名称: %s, 税号: %s", fileName, buyerName, buyerTaxId); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 税号={} 不存在于抬头库, 文件名={}", buyerTaxId, fileName); } } else { String errorInfo = String.format("发票: %s, 购买方税号为空", fileName); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 购买方税号为空, 文件名={}", fileName); } // ========== 校验2:发票号重复校验 ========== if (StringUtils.isNotBlank(invoiceNumber)) { // 检查数据库中是否已存在相同的发票号 InvoiceLibrary existingInvoice = baseMapper.selectByInvoiceNumber(invoiceNumber); if (existingInvoice != null) { String errorMsg = String.format("发票号重复: %s (当前文件: %s, 已存在于OA审批单: %s)", invoiceNumber, fileName, existingInvoice.getOaId()); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } // 同时检查同一审批单内是否有重复 if (invoiceNumberList.contains(invoiceNumber)) { String errorMsg = String.format("当前审批单内发票号重复: %s (当前文件: %s, 已存在文件: %s)", invoiceNumber, fileName, invoiceNumberToFileName.get(invoiceNumber)); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } invoiceNumberList.add(invoiceNumber); invoiceNumberToFileName.put(invoiceNumber, fileName); } } } } catch (Exception e) { log.error("发票解析异常: fileId={}, fileName={}", fileId, fileName, e); terminateWorkflow(accessToken, processInstanceId, userId, "发票解析失败: " + e.getMessage()); return McR.error("400", "发票解析失败: " + e.getMessage()); } } } else if ("共享".equals(hasInvoice)) { // 收集共享发票数据,待第二遍遍历时保存 if (detailje != null && StringUtils.isNotBlank(detailje)) { Map sharedData = new HashMap<>(); sharedData.put("detailje", detailje); sharedData.put("cdbm", cdbm); sharedData.put("hasInvoice", hasInvoice); sharedInvoiceDataList.add(sharedData); log.info("收集共享发票数据: 金额={}, 成本部门={}", detailje, cdbm); } } } // ========== 抬头校验失败处理 ========== if (!invalidBuyerTaxIds.isEmpty()) { String errorMsg = "发票抬头有误,以下发票的购买方税号不在公司抬头库中:\n" + String.join("\n", invalidBuyerTaxIds); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } BigDecimal totalAmount = BigDecimal.ZERO; // ========== 第二遍遍历:保存发票数据(校验通过后) ========== // 1. 保存普通发票数据 for (Map parsedData : parsedInvoiceDataList) { JSONObject invoice = (JSONObject) parsedData.get("invoice"); Object responseData = parsedData.get("responseData"); String cdbm = (String) parsedData.get("cdbm"); String hasInvoice = (String) parsedData.get("hasInvoice"); String detailje = (String) parsedData.get("detailje"); // 修改为 String 类型 allResults.add(responseData); // 汇总 amount BigDecimal amount = invoice.getBigDecimal("amount"); if (amount != null) { totalAmount = totalAmount.add(amount); } // 构建InvoiceLibrary对象 InvoiceLibrary invoiceLibrary = new InvoiceLibrary(); invoiceLibrary.setOaId(processInstanceId); invoiceLibrary.setInvoiceCode(invoice.getString("code") != null ? invoice.getString("code") : ""); invoiceLibrary.setInvoiceNumber(invoice.getString("serial") != null ? invoice.getString("serial") : ""); invoiceLibrary.setInvoiceType(invoice.getString("kindName") != null ? invoice.getString("kindName") : ""); String date = invoice.getString("date"); if (StringUtils.isNotBlank(date)) { try { invoiceLibrary.setInvoiceDate(LocalDate.parse(date.trim())); } catch (Exception e) { log.warn("日期解析失败: {}", date); } } invoiceLibrary.setAmount(invoice.getBigDecimal("excludingTax") != null ? invoice.getBigDecimal("excludingTax") : BigDecimal.ZERO); invoiceLibrary.setTaxAmount(invoice.getBigDecimal("tax") != null ? invoice.getBigDecimal("tax") : BigDecimal.ZERO); invoiceLibrary.setTotalAmount(invoice.getBigDecimal("amount") != null ? invoice.getBigDecimal("amount") : BigDecimal.ZERO); invoiceLibrary.setBuyerName(invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""); invoiceLibrary.setBuyerTaxId(invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""); invoiceLibrary.setSellerName(invoice.getString("sellerName") != null ? invoice.getString("sellerName") : ""); invoiceLibrary.setSellerTaxId(invoice.getString("sellerTaxId") != null ? invoice.getString("sellerTaxId") : ""); invoiceLibrary.setOaStatus("0"); invoiceLibrary.setFormName("付款申请"); invoiceLibrary.setPaySubject(fkzhxx != null ? fkzhxx : ""); invoiceLibrary.setPayAccount(fkzh != null ? fkzh : ""); invoiceLibrary.setBankName(yhqc != null ? yhqc : ""); invoiceLibrary.setIsLongTerm(sfct != null ? sfct : ""); invoiceLibrary.setInvoiceStatus("0"); invoiceLibrary.setHasInvoice(hasInvoice); // 处理 detailAmount,将 String 转换为 BigDecimal if (detailje != null && StringUtils.isNotBlank(detailje)) { try { invoiceLibrary.setDetailAmount(new BigDecimal(detailje)); } catch (NumberFormatException e) { log.error("detailJe格式转换失败: {}", detailje); invoiceLibrary.setDetailAmount(null); } } else { invoiceLibrary.setDetailAmount(null); } // 普通发票不涉及共享字段,设置为 null invoiceLibrary.setSharedTaxAmount(null); invoiceLibrary.setSharedAmount(null); invoiceLibrary.setSharedRate(null); invoiceLibrary.setAccountTitle(bxlb != null ? bxlb : ""); invoiceLibrary.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); invoiceLibrary.setCreatedAt(LocalDateTime.now()); invoiceLibrary.setUpdatedAt(LocalDateTime.now()); // 设置成本部门 if (cdbm != null && StringUtils.isNotBlank(cdbm)) { invoiceLibrary.setDep(cdbm); } else { invoiceLibrary.setDep(bm != null ? bm : ""); } invoiceList.add(invoiceLibrary); } for (Map sharedData : sharedInvoiceDataList) { String detailje = (String) sharedData.get("detailje"); String cdbm = (String) sharedData.get("cdbm"); String hasInvoice = (String) sharedData.get("hasInvoice"); try { BigDecimal detailAmount = new BigDecimal(detailje); // 使用从普通发票中解析到的税率 BigDecimal ocrTaxRate = sharedTaxRate; if (ocrTaxRate.compareTo(BigDecimal.ZERO) == 0) { log.warn("共享发票税率未获取到,使用默认税率0,金额={}", detailAmount); } // 计算金额: 含税金额 / (1 + 税率) = 税额 BigDecimal divisor = BigDecimal.ONE.add(ocrTaxRate); BigDecimal sharedTaxAmount = detailAmount.divide(divisor, 2, BigDecimal.ROUND_HALF_UP); BigDecimal sharedAmount = detailAmount.subtract(sharedTaxAmount); BigDecimal sharedRate = ocrTaxRate; // 这是小数形式的税率,如 0.09 log.info("共享发票计算: 含税金额={}, 税率={}%, 不含税金额={}, 税额={}", detailAmount, ocrTaxRate.multiply(new BigDecimal("100")), sharedAmount, sharedTaxAmount); // 保存共享发票数据 InvoiceLibrary sharedInvoice = new InvoiceLibrary(); sharedInvoice.setOaId(processInstanceId); sharedInvoice.setOaStatus("0"); sharedInvoice.setInvoiceStatus("0"); sharedInvoice.setDep(cdbm != null && StringUtils.isNotBlank(cdbm) ? cdbm : (bm != null ? bm : "")); sharedInvoice.setAccountTitle(bxlb != null ? bxlb : ""); sharedInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); sharedInvoice.setAmount(null); sharedInvoice.setTaxAmount(null); sharedInvoice.setTotalAmount(null); // 共享发票不设置总金额 sharedInvoice.setFormName("付款申请"); sharedInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); sharedInvoice.setPayAccount(fkzh != null ? fkzh : ""); sharedInvoice.setBankName(yhqc != null ? yhqc : ""); sharedInvoice.setIsLongTerm(sfct != null ? sfct : ""); sharedInvoice.setHasInvoice(hasInvoice); sharedInvoice.setDetailAmount(detailAmount); // 共享发票字段设置值 - 修正:存储数字而不是带百分号的字符串 sharedInvoice.setSharedTaxAmount(sharedAmount); sharedInvoice.setSharedAmount(sharedTaxAmount); sharedInvoice.setSharedRate(String.valueOf(sharedRate)); // 直接存储小数,如 0.09 sharedInvoice.setCreatedAt(LocalDateTime.now()); sharedInvoice.setUpdatedAt(LocalDateTime.now()); sharedInvoice.setInvoiceCode(""); sharedInvoice.setInvoiceNumber(""); sharedInvoice.setInvoiceType(sharedKind); sharedInvoice.setBuyerName(""); sharedInvoice.setBuyerTaxId(""); sharedInvoice.setSellerName(""); sharedInvoice.setSellerTaxId(""); invoiceList.add(sharedInvoice); log.info("成功保存共享发票记录,成本部门: {}", cdbm); } catch (NumberFormatException e) { log.error("共享发票金额格式转换失败: detailje={}", detailje, e); } catch (Exception e) { log.error("共享发票处理失败", e); } } // 保存发票记录 if (!invoiceList.isEmpty()) { for (InvoiceLibrary invoice : invoiceList) { baseMapper.insert(invoice); } log.info("成功保存 {} 条发票记录(普通发票: {}, 共享发票: {})", invoiceList.size(), parsedInvoiceDataList.size(), sharedInvoiceDataList.size()); } addWorkflowComment1(processInstanceId, userId, totalAmount); return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败: " + e.getMessage()); } } } } return McR.success(); } catch (Exception e) { log.error("处理审批实例失败", e); return McR.error("500", "系统处理失败: " + e.getMessage()); } } @Override public McR invoiceLibrary2(Map map) { log.info("接收到的参数: {}", map); try { String processInstanceId = UtilMap.getString(map, "processInstanceId"); if (StringUtils.isBlank(processInstanceId)) { log.error("processInstanceId为空"); return McR.error("400", "审批实例ID不能为空"); } // 获取审批实例信息 String accessToken = ddClient.getAccessToken(); Map processInstance = ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId); if (processInstance == null) { log.error("获取审批实例失败: {}", processInstanceId); return McR.error("500", "获取审批实例信息失败"); } String userId = (String) processInstance.get("originatorUserId"); log.info("审批人ID: {}", userId); List formComponentValues = (List) processInstance.get("formComponentValues"); if (formComponentValues == null || formComponentValues.isEmpty()) { log.warn("表单数据为空"); return McR.error("400", "表单数据为空"); } // 存储主表的字段值 String zt = null; BigDecimal mainJe = null; String bxlb = null; String bm = null; String fkzhxx = null; String fkzh = null; String yhqc = null; String sfct = null; // 遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); Object value = formComponentValue.get("value"); if ("DDSelectField_15ZO6PMU1ZC00".equals(id)) { zt = value != null ? String.valueOf(value) : ""; } if ("MoneyField_1V4A9P72K6TC0".equals(id)) { if (value instanceof BigDecimal) { mainJe = (BigDecimal) value; } else if (value != null) { try { mainJe = new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { log.error("金额格式转换失败: {}", value); mainJe = BigDecimal.ZERO; } } } if ("DDSelectField_YLLB7AD1ATS0".equals(id)) { bxlb = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_ZI9KALEYOTC0".equals(id)) { bm = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_1WEX2WWCJ3PC0".equals(id)) { fkzhxx = value != null ? String.valueOf(value) : ""; } if ("TextField_2W39FPCZH1A0".equals(id)) { fkzh = value != null ? String.valueOf(value) : ""; } if ("TextField_QW7HJIR7T800".equals(id)) { yhqc = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_12JNC3P1K5V40".equals(id)) { sfct = value != null ? String.valueOf(value) : "否"; } } // 判断主表状态 - 无发票 if ("否".equals(zt)) { // 保存基础报销记录 InvoiceLibrary baseInvoice = new InvoiceLibrary(); baseInvoice.setOaId(processInstanceId); baseInvoice.setOaStatus("0"); baseInvoice.setInvoiceStatus("0"); baseInvoice.setDep(bm != null ? bm : ""); baseInvoice.setAccountTitle(bxlb != null ? bxlb : ""); baseInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); baseInvoice.setCreatedAt(LocalDateTime.now()); baseInvoice.setUpdatedAt(LocalDateTime.now()); baseInvoice.setInvoiceCode(""); baseInvoice.setInvoiceNumber(""); baseInvoice.setInvoiceType(""); baseInvoice.setAmount(BigDecimal.ZERO); baseInvoice.setTaxAmount(BigDecimal.ZERO); baseInvoice.setTotalAmount(BigDecimal.ZERO); baseInvoice.setBuyerName(""); baseInvoice.setBuyerTaxId(""); baseInvoice.setSellerName(""); baseInvoice.setSellerTaxId(""); baseInvoice.setFormName("合作商付款申请"); baseInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); baseInvoice.setPayAccount(fkzh != null ? fkzh : ""); baseInvoice.setBankName(yhqc != null ? yhqc : ""); baseInvoice.setIsLongTerm(sfct != null ? sfct : ""); baseInvoice.setHasInvoice("否"); baseInvoice.setDetailAmount(null); baseInvoice.setSharedTaxAmount(null); baseInvoice.setSharedAmount(null); baseInvoice.setSharedRate(null); baseMapper.insert(baseInvoice); addWorkflowComment(processInstanceId, userId, "报销总金额:" + mainJe); return McR.success(); } // 判断主表状态 - 有发票 if ("是".equals(zt)) { for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_182UNZEX02U80".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); log.info("子表数据: {}", tableFieldValue); try { List tableRows = (List) JSONObject.parse(tableFieldValue); if (tableRows == null || tableRows.isEmpty()) { log.warn("子表数据为空"); return McR.error("400", "发票明细为空"); } List allResults = new ArrayList<>(); List invoiceList = new ArrayList<>(); // 存储第一遍解析的发票数据,避免重复解析 List> parsedInvoiceDataList = new ArrayList<>(); // 存储共享发票数据 List> sharedInvoiceDataList = new ArrayList<>(); // 用于校验的集合 List invoiceNumberList = new ArrayList<>(); Map invoiceNumberToFileName = new HashMap<>(); List invalidBuyerTaxIds = new ArrayList<>(); // 存储税率值,供共享发票使用 BigDecimal sharedTaxRate = BigDecimal.ZERO; String sharedKind = ""; // ========== 第一遍遍历:收集并校验所有发票数据 ========== for (Map row : tableRows) { List rowValues = (List) row.get("rowValue"); String hasInvoice = null; String cdbm = null; String detailje = null; List attachmentList = null; for (Map rowItem : rowValues) { String key = String.valueOf(rowItem.get("key")); if ("DDSelectField_44I0SP6M1I00".equals(key)) { Object value = rowItem.get("value"); hasInvoice = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_RC2YZRJQAQ80".equals(key)) { Object value = rowItem.get("value"); cdbm = value != null ? String.valueOf(value) : ""; } if ("NumberField_1UC3HZ8OEIPS0".equals(key)) { Object value = rowItem.get("value"); detailje = value != null ? String.valueOf(value) : ""; } if ("DDAttachment_LEUWYIC68TC0".equals(key)) { Object value = rowItem.get("value"); if (value instanceof List) { attachmentList = (List) value; } } } if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { for (Map attachment : attachmentList) { String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); try { String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); fileMap.put("size", new File(filePath).length() / 1024f / 1024f); fileMap.put("isPdf", "pdf".equalsIgnoreCase(fileType)); McR result = processMixedInvoice(fileMap); Object responseData = result.getData(); // 解析发票数据 if (responseData != null) { String jsonStr = JSONObject.toJSONString(responseData); JSONObject jsonObject = JSONObject.parseObject(jsonStr); JSONArray resultArray = jsonObject.getJSONArray("result"); if (resultArray != null && !resultArray.isEmpty()) { JSONObject invoice = resultArray.getJSONObject(0); String invoiceNumber = invoice.getString("serial") != null ? invoice.getString("serial") : ""; String buyerTaxId = invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""; String buyerName = invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""; // 获取税率,供共享发票使用 Object taxRateObj = invoice.get("taxRate"); Object kind = invoice.getString("kindName"); if (taxRateObj != null) { String taxRateStr = String.valueOf(taxRateObj); String taxRateNum = taxRateStr.replace("%", "").trim(); sharedKind = kind != null ? String.valueOf(kind) : ""; sharedTaxRate = new BigDecimal(taxRateNum); sharedTaxRate = sharedTaxRate.divide(new BigDecimal("100"), 10, BigDecimal.ROUND_HALF_UP); log.info("从有发票中获取税率: 原始={}, 转换后={}", taxRateStr, sharedTaxRate); } // 保存解析的数据供后续使用 Map parsedData = new HashMap<>(); parsedData.put("invoice", invoice); parsedData.put("fileId", fileId); parsedData.put("fileName", fileName); parsedData.put("fileType", fileType); parsedData.put("cdbm", cdbm); parsedData.put("hasInvoice", hasInvoice); parsedData.put("detailje", detailje); parsedData.put("responseData", responseData); parsedInvoiceDataList.add(parsedData); // ========== 校验1:抬头校验 ========== if (StringUtils.isNotBlank(buyerName)) { CompanyTitle existingTitle = companyTitleMapper.selectByTaxId(buyerName); if (existingTitle == null) { String errorInfo = String.format("发票: %s, 购买方名称: %s, 税号: %s", fileName, buyerName, buyerTaxId); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 税号={} 不存在于抬头库, 文件名={}", buyerTaxId, fileName); } } else { String errorInfo = String.format("发票: %s, 购买方税号为空", fileName); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 购买方税号为空, 文件名={}", fileName); } // ========== 校验2:发票号重复校验 ========== if (StringUtils.isNotBlank(invoiceNumber)) { // 检查数据库中是否已存在相同的发票号 InvoiceLibrary existingInvoice = baseMapper.selectByInvoiceNumber(invoiceNumber); if (existingInvoice != null) { String errorMsg = String.format("发票号重复: %s (当前文件: %s, 已存在于OA审批单: %s)", invoiceNumber, fileName, existingInvoice.getOaId()); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } // 同时检查同一审批单内是否有重复 if (invoiceNumberList.contains(invoiceNumber)) { String errorMsg = String.format("当前审批单内发票号重复: %s (当前文件: %s, 已存在文件: %s)", invoiceNumber, fileName, invoiceNumberToFileName.get(invoiceNumber)); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } invoiceNumberList.add(invoiceNumber); invoiceNumberToFileName.put(invoiceNumber, fileName); } } } } catch (Exception e) { log.error("发票解析异常: fileId={}, fileName={}", fileId, fileName, e); terminateWorkflow(accessToken, processInstanceId, userId, "发票解析失败: " + e.getMessage()); return McR.error("400", "发票解析失败: " + e.getMessage()); } } } else if ("共享".equals(hasInvoice)) { // 收集共享发票数据,待第二遍遍历时保存 if (detailje != null && StringUtils.isNotBlank(detailje)) { Map sharedData = new HashMap<>(); sharedData.put("detailje", detailje); sharedData.put("cdbm", cdbm); sharedData.put("hasInvoice", hasInvoice); sharedInvoiceDataList.add(sharedData); log.info("收集共享发票数据: 金额={}, 成本部门={}", detailje, cdbm); } } } // ========== 抬头校验失败处理 ========== if (!invalidBuyerTaxIds.isEmpty()) { String errorMsg = "发票抬头有误,以下发票的购买方税号不在公司抬头库中:\n" + String.join("\n", invalidBuyerTaxIds); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } BigDecimal totalAmount = BigDecimal.ZERO; // ========== 第二遍遍历:保存发票数据(校验通过后) ========== // 1. 保存普通发票数据 for (Map parsedData : parsedInvoiceDataList) { JSONObject invoice = (JSONObject) parsedData.get("invoice"); Object responseData = parsedData.get("responseData"); String cdbm = (String) parsedData.get("cdbm"); String hasInvoice = (String) parsedData.get("hasInvoice"); String detailje = (String) parsedData.get("detailje"); // 修改为 String 类型 allResults.add(responseData); // 汇总 amount BigDecimal amount = invoice.getBigDecimal("amount"); if (amount != null) { totalAmount = totalAmount.add(amount); } // 构建InvoiceLibrary对象 InvoiceLibrary invoiceLibrary = new InvoiceLibrary(); invoiceLibrary.setOaId(processInstanceId); invoiceLibrary.setInvoiceCode(invoice.getString("code") != null ? invoice.getString("code") : ""); invoiceLibrary.setInvoiceNumber(invoice.getString("serial") != null ? invoice.getString("serial") : ""); invoiceLibrary.setInvoiceType(invoice.getString("kindName") != null ? invoice.getString("kindName") : ""); String date = invoice.getString("date"); if (StringUtils.isNotBlank(date)) { try { invoiceLibrary.setInvoiceDate(LocalDate.parse(date.trim())); } catch (Exception e) { log.warn("日期解析失败: {}", date); } } invoiceLibrary.setAmount(invoice.getBigDecimal("excludingTax") != null ? invoice.getBigDecimal("excludingTax") : BigDecimal.ZERO); invoiceLibrary.setTaxAmount(invoice.getBigDecimal("tax") != null ? invoice.getBigDecimal("tax") : BigDecimal.ZERO); invoiceLibrary.setTotalAmount(invoice.getBigDecimal("amount") != null ? invoice.getBigDecimal("amount") : BigDecimal.ZERO); invoiceLibrary.setBuyerName(invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""); invoiceLibrary.setBuyerTaxId(invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""); invoiceLibrary.setSellerName(invoice.getString("sellerName") != null ? invoice.getString("sellerName") : ""); invoiceLibrary.setSellerTaxId(invoice.getString("sellerTaxId") != null ? invoice.getString("sellerTaxId") : ""); invoiceLibrary.setOaStatus("0"); invoiceLibrary.setFormName("合作商付款申请"); invoiceLibrary.setPaySubject(fkzhxx != null ? fkzhxx : ""); invoiceLibrary.setPayAccount(fkzh != null ? fkzh : ""); invoiceLibrary.setBankName(yhqc != null ? yhqc : ""); invoiceLibrary.setIsLongTerm(sfct != null ? sfct : ""); invoiceLibrary.setInvoiceStatus("0"); invoiceLibrary.setHasInvoice(hasInvoice); // 处理 detailAmount,将 String 转换为 BigDecimal if (detailje != null && StringUtils.isNotBlank(detailje)) { try { invoiceLibrary.setDetailAmount(new BigDecimal(detailje)); } catch (NumberFormatException e) { log.error("detailJe格式转换失败: {}", detailje); invoiceLibrary.setDetailAmount(null); } } else { invoiceLibrary.setDetailAmount(null); } // 普通发票不涉及共享字段,设置为 null invoiceLibrary.setSharedTaxAmount(null); invoiceLibrary.setSharedAmount(null); invoiceLibrary.setSharedRate(null); invoiceLibrary.setAccountTitle(bxlb != null ? bxlb : ""); invoiceLibrary.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); invoiceLibrary.setCreatedAt(LocalDateTime.now()); invoiceLibrary.setUpdatedAt(LocalDateTime.now()); // 设置成本部门 if (cdbm != null && StringUtils.isNotBlank(cdbm)) { invoiceLibrary.setDep(cdbm); } else { invoiceLibrary.setDep(bm != null ? bm : ""); } invoiceList.add(invoiceLibrary); } for (Map sharedData : sharedInvoiceDataList) { String detailje = (String) sharedData.get("detailje"); String cdbm = (String) sharedData.get("cdbm"); String hasInvoice = (String) sharedData.get("hasInvoice"); try { BigDecimal detailAmount = new BigDecimal(detailje); // 使用从普通发票中解析到的税率 BigDecimal ocrTaxRate = sharedTaxRate; if (ocrTaxRate.compareTo(BigDecimal.ZERO) == 0) { log.warn("共享发票税率未获取到,使用默认税率0,金额={}", detailAmount); } // 计算金额: 含税金额 / (1 + 税率) = 税额 BigDecimal divisor = BigDecimal.ONE.add(ocrTaxRate); BigDecimal sharedTaxAmount = detailAmount.divide(divisor, 2, BigDecimal.ROUND_HALF_UP); BigDecimal sharedAmount = detailAmount.subtract(sharedTaxAmount); BigDecimal sharedRate = ocrTaxRate; // 这是小数形式的税率,如 0.09 log.info("共享发票计算: 含税金额={}, 税率={}%, 不含税金额={}, 税额={}", detailAmount, ocrTaxRate.multiply(new BigDecimal("100")), sharedAmount, sharedTaxAmount); // 保存共享发票数据 InvoiceLibrary sharedInvoice = new InvoiceLibrary(); sharedInvoice.setOaId(processInstanceId); sharedInvoice.setOaStatus("0"); sharedInvoice.setInvoiceStatus("0"); sharedInvoice.setDep(cdbm != null && StringUtils.isNotBlank(cdbm) ? cdbm : (bm != null ? bm : "")); sharedInvoice.setAccountTitle(bxlb != null ? bxlb : ""); sharedInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); sharedInvoice.setAmount(null); sharedInvoice.setTaxAmount(null); sharedInvoice.setTotalAmount(null); // 共享发票不设置总金额 sharedInvoice.setFormName("合作商付款申请"); sharedInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); sharedInvoice.setPayAccount(fkzh != null ? fkzh : ""); sharedInvoice.setBankName(yhqc != null ? yhqc : ""); sharedInvoice.setIsLongTerm(sfct != null ? sfct : ""); sharedInvoice.setHasInvoice(hasInvoice); sharedInvoice.setDetailAmount(detailAmount); // 共享发票字段设置值 - 修正:存储数字而不是带百分号的字符串 sharedInvoice.setSharedTaxAmount(sharedAmount); sharedInvoice.setSharedAmount(sharedTaxAmount); sharedInvoice.setSharedRate(String.valueOf(sharedRate)); // 直接存储小数,如 0.09 sharedInvoice.setCreatedAt(LocalDateTime.now()); sharedInvoice.setUpdatedAt(LocalDateTime.now()); sharedInvoice.setInvoiceCode(""); sharedInvoice.setInvoiceNumber(""); sharedInvoice.setInvoiceType(sharedKind); sharedInvoice.setBuyerName(""); sharedInvoice.setBuyerTaxId(""); sharedInvoice.setSellerName(""); sharedInvoice.setSellerTaxId(""); invoiceList.add(sharedInvoice); log.info("成功保存共享发票记录,成本部门: {}", cdbm); } catch (NumberFormatException e) { log.error("共享发票金额格式转换失败: detailje={}", detailje, e); } catch (Exception e) { log.error("共享发票处理失败", e); } } // 保存发票记录 if (!invoiceList.isEmpty()) { for (InvoiceLibrary invoice : invoiceList) { baseMapper.insert(invoice); } log.info("成功保存 {} 条发票记录(普通发票: {}, 共享发票: {})", invoiceList.size(), parsedInvoiceDataList.size(), sharedInvoiceDataList.size()); } addWorkflowComment1(processInstanceId, userId, totalAmount); return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败: " + e.getMessage()); } } } } return McR.success(); } catch (Exception e) { log.error("处理审批实例失败", e); return McR.error("500", "系统处理失败: " + e.getMessage()); } } @Override public McR invoiceLibrary3(Map map) { log.info("接收到的参数: {}", map); try { String processInstanceId = UtilMap.getString(map, "processInstanceId"); if (StringUtils.isBlank(processInstanceId)) { log.error("processInstanceId为空"); return McR.error("400", "审批实例ID不能为空"); } // 获取审批实例信息 String accessToken = ddClient.getAccessToken(); Map processInstance = ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId); if (processInstance == null) { log.error("获取审批实例失败: {}", processInstanceId); return McR.error("500", "获取审批实例信息失败"); } String userId = (String) processInstance.get("originatorUserId"); log.info("审批人ID: {}", userId); List formComponentValues = (List) processInstance.get("formComponentValues"); if (formComponentValues == null || formComponentValues.isEmpty()) { log.warn("表单数据为空"); return McR.error("400", "表单数据为空"); } // 存储主表的字段值 String zt = null; BigDecimal mainJe = null; String bxlb = null; String bm = null; String fkzhxx = null; String fkzh = null; String yhqc = null; String sfct = null; // 遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); Object value = formComponentValue.get("value"); if ("DDSelectField_19B5ZAI2SE000".equals(id)) { zt = value != null ? String.valueOf(value) : "";//ocr识别 } if ("MoneyField_5U5SLLA3CS40".equals(id)) { if (value instanceof BigDecimal) { mainJe = (BigDecimal) value; } else if (value != null) { try { mainJe = new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { log.error("金额格式转换失败: {}", value); mainJe = BigDecimal.ZERO; } } } if ("DDSelectField_BS1HCMO7YTK0".equals(id)) { bxlb = value != null ? String.valueOf(value) : ""; } // if ("DepartmentField_ZI9KALEYOTC0".equals(id)) { // bm = value != null ? String.valueOf(value) : ""; // } if ("DDSelectField_GLW8YHBHSLS0".equals(id)) { fkzhxx = value != null ? String.valueOf(value) : ""; } if ("TextField_1PR1HQ4H28SG0".equals(id)) { fkzh = value != null ? String.valueOf(value) : ""; } if ("TextField_1UPR1XZTI3VK".equals(id)) { yhqc = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_1TMBRS8T7DPC0".equals(id)) { sfct = value != null ? String.valueOf(value) : "否"; } } // 判断主表状态 - 无发票 if ("否".equals(zt)) { // 保存基础报销记录 InvoiceLibrary baseInvoice = new InvoiceLibrary(); baseInvoice.setOaId(processInstanceId); baseInvoice.setOaStatus("0"); baseInvoice.setInvoiceStatus("0"); baseInvoice.setDep(bm != null ? bm : ""); baseInvoice.setAccountTitle(bxlb != null ? bxlb : ""); baseInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); baseInvoice.setCreatedAt(LocalDateTime.now()); baseInvoice.setUpdatedAt(LocalDateTime.now()); baseInvoice.setInvoiceCode(""); baseInvoice.setInvoiceNumber(""); baseInvoice.setInvoiceType(""); baseInvoice.setAmount(BigDecimal.ZERO); baseInvoice.setTaxAmount(BigDecimal.ZERO); baseInvoice.setTotalAmount(BigDecimal.ZERO); baseInvoice.setBuyerName(""); baseInvoice.setBuyerTaxId(""); baseInvoice.setSellerName(""); baseInvoice.setSellerTaxId(""); baseInvoice.setFormName("合作商报销申请"); baseInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); baseInvoice.setPayAccount(fkzh != null ? fkzh : ""); baseInvoice.setBankName(yhqc != null ? yhqc : ""); baseInvoice.setIsLongTerm(sfct != null ? sfct : ""); baseInvoice.setHasInvoice("否"); baseInvoice.setDetailAmount(null); baseInvoice.setSharedTaxAmount(null); baseInvoice.setSharedAmount(null); baseInvoice.setSharedRate(null); baseMapper.insert(baseInvoice); addWorkflowComment(processInstanceId, userId, "报销总金额:" + mainJe); return McR.success(); } // 判断主表状态 - 有发票 if ("是".equals(zt)) { for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_9LJ65U3CQ980".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); log.info("子表数据: {}", tableFieldValue); try { List tableRows = (List) JSONObject.parse(tableFieldValue); if (tableRows == null || tableRows.isEmpty()) { log.warn("子表数据为空"); return McR.error("400", "发票明细为空"); } List allResults = new ArrayList<>(); List invoiceList = new ArrayList<>(); // 存储第一遍解析的发票数据,避免重复解析 List> parsedInvoiceDataList = new ArrayList<>(); // 存储共享发票数据 List> sharedInvoiceDataList = new ArrayList<>(); // 用于校验的集合 List invoiceNumberList = new ArrayList<>(); Map invoiceNumberToFileName = new HashMap<>(); List invalidBuyerTaxIds = new ArrayList<>(); // 存储税率值,供共享发票使用 BigDecimal sharedTaxRate = BigDecimal.ZERO; String sharedKind = ""; // ========== 第一遍遍历:收集并校验所有发票数据 ========== for (Map row : tableRows) { List rowValues = (List) row.get("rowValue"); String hasInvoice = null; String cdbm = null; String detailje = null; List attachmentList = null; for (Map rowItem : rowValues) { String key = String.valueOf(rowItem.get("key")); if ("DDSelectField_TTTOAI0FDXJ4".equals(key)) { Object value = rowItem.get("value"); hasInvoice = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_15QGD1PV69XC0".equals(key)) { Object value = rowItem.get("value"); cdbm = value != null ? String.valueOf(value) : ""; } if ("NumberField_FYHZK218TTDS".equals(key)) { Object value = rowItem.get("value"); detailje = value != null ? String.valueOf(value) : ""; } if ("DDAttachment_XVU9V89QOEM8".equals(key)) { Object value = rowItem.get("value"); if (value instanceof List) { attachmentList = (List) value; } } } if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { for (Map attachment : attachmentList) { String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); try { String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); fileMap.put("size", new File(filePath).length() / 1024f / 1024f); fileMap.put("isPdf", "pdf".equalsIgnoreCase(fileType)); McR result = processMixedInvoice(fileMap); Object responseData = result.getData(); // 解析发票数据 if (responseData != null) { String jsonStr = JSONObject.toJSONString(responseData); JSONObject jsonObject = JSONObject.parseObject(jsonStr); JSONArray resultArray = jsonObject.getJSONArray("result"); if (resultArray != null && !resultArray.isEmpty()) { JSONObject invoice = resultArray.getJSONObject(0); String invoiceNumber = invoice.getString("serial") != null ? invoice.getString("serial") : ""; String buyerTaxId = invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""; String buyerName = invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""; // 获取税率,供共享发票使用 Object taxRateObj = invoice.get("taxRate"); Object kind = invoice.getString("kindName"); if (taxRateObj != null) { String taxRateStr = String.valueOf(taxRateObj); String taxRateNum = taxRateStr.replace("%", "").trim(); sharedKind = kind != null ? String.valueOf(kind) : ""; sharedTaxRate = new BigDecimal(taxRateNum); sharedTaxRate = sharedTaxRate.divide(new BigDecimal("100"), 10, BigDecimal.ROUND_HALF_UP); log.info("从有发票中获取税率: 原始={}, 转换后={}", taxRateStr, sharedTaxRate); } // 保存解析的数据供后续使用 Map parsedData = new HashMap<>(); parsedData.put("invoice", invoice); parsedData.put("fileId", fileId); parsedData.put("fileName", fileName); parsedData.put("fileType", fileType); parsedData.put("cdbm", cdbm); parsedData.put("hasInvoice", hasInvoice); parsedData.put("detailje", detailje); parsedData.put("responseData", responseData); parsedInvoiceDataList.add(parsedData); // ========== 校验1:抬头校验 ========== if (StringUtils.isNotBlank(buyerName)) { CompanyTitle existingTitle = companyTitleMapper.selectByTaxId(buyerName); if (existingTitle == null) { String errorInfo = String.format("发票: %s, 购买方名称: %s, 税号: %s", fileName, buyerName, buyerTaxId); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 税号={} 不存在于抬头库, 文件名={}", buyerTaxId, fileName); } } else { String errorInfo = String.format("发票: %s, 购买方税号为空", fileName); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 购买方税号为空, 文件名={}", fileName); } // ========== 校验2:发票号重复校验 ========== if (StringUtils.isNotBlank(invoiceNumber)) { // 检查数据库中是否已存在相同的发票号 InvoiceLibrary existingInvoice = baseMapper.selectByInvoiceNumber(invoiceNumber); if (existingInvoice != null) { String errorMsg = String.format("发票号重复: %s (当前文件: %s, 已存在于OA审批单: %s)", invoiceNumber, fileName, existingInvoice.getOaId()); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } // 同时检查同一审批单内是否有重复 if (invoiceNumberList.contains(invoiceNumber)) { String errorMsg = String.format("当前审批单内发票号重复: %s (当前文件: %s, 已存在文件: %s)", invoiceNumber, fileName, invoiceNumberToFileName.get(invoiceNumber)); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } invoiceNumberList.add(invoiceNumber); invoiceNumberToFileName.put(invoiceNumber, fileName); } } } } catch (Exception e) { log.error("发票解析异常: fileId={}, fileName={}", fileId, fileName, e); terminateWorkflow(accessToken, processInstanceId, userId, "发票解析失败: " + e.getMessage()); return McR.error("400", "发票解析失败: " + e.getMessage()); } } } else if ("共享".equals(hasInvoice)) { // 收集共享发票数据,待第二遍遍历时保存 if (detailje != null && StringUtils.isNotBlank(detailje)) { Map sharedData = new HashMap<>(); sharedData.put("detailje", detailje); sharedData.put("cdbm", cdbm); sharedData.put("hasInvoice", hasInvoice); sharedInvoiceDataList.add(sharedData); log.info("收集共享发票数据: 金额={}, 成本部门={}", detailje, cdbm); } } } // ========== 抬头校验失败处理 ========== if (!invalidBuyerTaxIds.isEmpty()) { String errorMsg = "发票抬头有误,以下发票的购买方税号不在公司抬头库中:\n" + String.join("\n", invalidBuyerTaxIds); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } BigDecimal totalAmount = BigDecimal.ZERO; // ========== 第二遍遍历:保存发票数据(校验通过后) ========== // 1. 保存普通发票数据 for (Map parsedData : parsedInvoiceDataList) { JSONObject invoice = (JSONObject) parsedData.get("invoice"); Object responseData = parsedData.get("responseData"); String cdbm = (String) parsedData.get("cdbm"); String hasInvoice = (String) parsedData.get("hasInvoice"); String detailje = (String) parsedData.get("detailje"); // 修改为 String 类型 allResults.add(responseData); // 汇总 amount BigDecimal amount = invoice.getBigDecimal("amount"); if (amount != null) { totalAmount = totalAmount.add(amount); } // 构建InvoiceLibrary对象 InvoiceLibrary invoiceLibrary = new InvoiceLibrary(); invoiceLibrary.setOaId(processInstanceId); invoiceLibrary.setInvoiceCode(invoice.getString("code") != null ? invoice.getString("code") : ""); invoiceLibrary.setInvoiceNumber(invoice.getString("serial") != null ? invoice.getString("serial") : ""); invoiceLibrary.setInvoiceType(invoice.getString("kindName") != null ? invoice.getString("kindName") : ""); String date = invoice.getString("date"); if (StringUtils.isNotBlank(date)) { try { invoiceLibrary.setInvoiceDate(LocalDate.parse(date.trim())); } catch (Exception e) { log.warn("日期解析失败: {}", date); } } invoiceLibrary.setAmount(invoice.getBigDecimal("excludingTax") != null ? invoice.getBigDecimal("excludingTax") : BigDecimal.ZERO); invoiceLibrary.setTaxAmount(invoice.getBigDecimal("tax") != null ? invoice.getBigDecimal("tax") : BigDecimal.ZERO); invoiceLibrary.setTotalAmount(invoice.getBigDecimal("amount") != null ? invoice.getBigDecimal("amount") : BigDecimal.ZERO); invoiceLibrary.setBuyerName(invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""); invoiceLibrary.setBuyerTaxId(invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""); invoiceLibrary.setSellerName(invoice.getString("sellerName") != null ? invoice.getString("sellerName") : ""); invoiceLibrary.setSellerTaxId(invoice.getString("sellerTaxId") != null ? invoice.getString("sellerTaxId") : ""); invoiceLibrary.setOaStatus("0"); invoiceLibrary.setFormName("合作商报销申请"); invoiceLibrary.setPaySubject(fkzhxx != null ? fkzhxx : ""); invoiceLibrary.setPayAccount(fkzh != null ? fkzh : ""); invoiceLibrary.setBankName(yhqc != null ? yhqc : ""); invoiceLibrary.setIsLongTerm(sfct != null ? sfct : ""); invoiceLibrary.setInvoiceStatus("0"); invoiceLibrary.setHasInvoice(hasInvoice); // 处理 detailAmount,将 String 转换为 BigDecimal if (detailje != null && StringUtils.isNotBlank(detailje)) { try { invoiceLibrary.setDetailAmount(new BigDecimal(detailje)); } catch (NumberFormatException e) { log.error("detailJe格式转换失败: {}", detailje); invoiceLibrary.setDetailAmount(null); } } else { invoiceLibrary.setDetailAmount(null); } // 普通发票不涉及共享字段,设置为 null invoiceLibrary.setSharedTaxAmount(null); invoiceLibrary.setSharedAmount(null); invoiceLibrary.setSharedRate(null); invoiceLibrary.setAccountTitle(bxlb != null ? bxlb : ""); invoiceLibrary.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); invoiceLibrary.setCreatedAt(LocalDateTime.now()); invoiceLibrary.setUpdatedAt(LocalDateTime.now()); // 设置成本部门 if (cdbm != null && StringUtils.isNotBlank(cdbm)) { invoiceLibrary.setDep(cdbm); } else { invoiceLibrary.setDep(bm != null ? bm : ""); } invoiceList.add(invoiceLibrary); } for (Map sharedData : sharedInvoiceDataList) { String detailje = (String) sharedData.get("detailje"); String cdbm = (String) sharedData.get("cdbm"); String hasInvoice = (String) sharedData.get("hasInvoice"); try { BigDecimal detailAmount = new BigDecimal(detailje); // 使用从普通发票中解析到的税率 BigDecimal ocrTaxRate = sharedTaxRate; if (ocrTaxRate.compareTo(BigDecimal.ZERO) == 0) { log.warn("共享发票税率未获取到,使用默认税率0,金额={}", detailAmount); } // 计算金额: 含税金额 / (1 + 税率) = 税额 BigDecimal divisor = BigDecimal.ONE.add(ocrTaxRate); BigDecimal sharedTaxAmount = detailAmount.divide(divisor, 2, BigDecimal.ROUND_HALF_UP); BigDecimal sharedAmount = detailAmount.subtract(sharedTaxAmount); BigDecimal sharedRate = ocrTaxRate; // 这是小数形式的税率,如 0.09 log.info("共享发票计算: 含税金额={}, 税率={}%, 不含税金额={}, 税额={}", detailAmount, ocrTaxRate.multiply(new BigDecimal("100")), sharedAmount, sharedTaxAmount); // 保存共享发票数据 InvoiceLibrary sharedInvoice = new InvoiceLibrary(); sharedInvoice.setOaId(processInstanceId); sharedInvoice.setOaStatus("0"); sharedInvoice.setInvoiceStatus("0"); sharedInvoice.setDep(cdbm != null && StringUtils.isNotBlank(cdbm) ? cdbm : (bm != null ? bm : "")); sharedInvoice.setAccountTitle(bxlb != null ? bxlb : ""); sharedInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); sharedInvoice.setAmount(null); sharedInvoice.setTaxAmount(null); sharedInvoice.setTotalAmount(null); // 共享发票不设置总金额 sharedInvoice.setFormName("合作商报销申请"); sharedInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); sharedInvoice.setPayAccount(fkzh != null ? fkzh : ""); sharedInvoice.setBankName(yhqc != null ? yhqc : ""); sharedInvoice.setIsLongTerm(sfct != null ? sfct : ""); sharedInvoice.setHasInvoice(hasInvoice); sharedInvoice.setDetailAmount(detailAmount); // 共享发票字段设置值 - 修正:存储数字而不是带百分号的字符串 sharedInvoice.setSharedTaxAmount(sharedAmount); sharedInvoice.setSharedAmount(sharedTaxAmount); sharedInvoice.setSharedRate(String.valueOf(sharedRate)); // 直接存储小数,如 0.09 sharedInvoice.setCreatedAt(LocalDateTime.now()); sharedInvoice.setUpdatedAt(LocalDateTime.now()); sharedInvoice.setInvoiceCode(""); sharedInvoice.setInvoiceNumber(""); sharedInvoice.setInvoiceType(sharedKind); sharedInvoice.setBuyerName(""); sharedInvoice.setBuyerTaxId(""); sharedInvoice.setSellerName(""); sharedInvoice.setSellerTaxId(""); invoiceList.add(sharedInvoice); log.info("成功保存共享发票记录,成本部门: {}", cdbm); } catch (NumberFormatException e) { log.error("共享发票金额格式转换失败: detailje={}", detailje, e); } catch (Exception e) { log.error("共享发票处理失败", e); } } // 保存发票记录 if (!invoiceList.isEmpty()) { for (InvoiceLibrary invoice : invoiceList) { baseMapper.insert(invoice); } log.info("成功保存 {} 条发票记录(普通发票: {}, 共享发票: {})", invoiceList.size(), parsedInvoiceDataList.size(), sharedInvoiceDataList.size()); } addWorkflowComment1(processInstanceId, userId, totalAmount); return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败: " + e.getMessage()); } } } } return McR.success(); } catch (Exception e) { log.error("处理审批实例失败", e); return McR.error("500", "系统处理失败: " + e.getMessage()); } } @Override public McR invoiceLibrary5(Map map) { log.info("接收到的参数: {}", map); try { String processInstanceId = UtilMap.getString(map, "processInstanceId"); if (StringUtils.isBlank(processInstanceId)) { log.error("processInstanceId为空"); return McR.error("400", "审批实例ID不能为空"); } // 获取审批实例信息 String accessToken = ddClient.getAccessToken(); Map processInstance = ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId); if (processInstance == null) { log.error("获取审批实例失败: {}", processInstanceId); return McR.error("500", "获取审批实例信息失败"); } String userId = (String) processInstance.get("originatorUserId"); log.info("审批人ID: {}", userId); List formComponentValues = (List) processInstance.get("formComponentValues"); if (formComponentValues == null || formComponentValues.isEmpty()) { log.warn("表单数据为空"); return McR.error("400", "表单数据为空"); } // 存储主表的字段值 String zt = null; BigDecimal mainJe = null; String bxlb = null; String bm = null; String fkzhxx = null; String fkzh = null; String yhqc = null; String sfct = null; // 遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); Object value = formComponentValue.get("value"); if ("DDSelectField_7F1V8IJJ9QG".equals(id)) { zt = value != null ? String.valueOf(value) : "";//ocr识别 } if ("MoneyField_1435GRXGYPA80".equals(id)) { if (value instanceof BigDecimal) { mainJe = (BigDecimal) value; } else if (value != null) { try { mainJe = new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { log.error("金额格式转换失败: {}", value); mainJe = BigDecimal.ZERO; } } } if ("DDSelectField_1UX0JVU89Q000".equals(id)) { bxlb = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_1BJT6UY9Y5B40".equals(id)) { bm = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_1D77TV92WXEO0".equals(id)) { fkzhxx = value != null ? String.valueOf(value) : ""; } if ("TextField_3CQPLF9BVBO0".equals(id)) { fkzh = value != null ? String.valueOf(value) : ""; } if ("TextField_YSFYQQ305SG0".equals(id)) { yhqc = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_1AYKIPZ3435S0".equals(id)) { sfct = value != null ? String.valueOf(value) : "否"; } } // 判断主表状态 - 无发票 if ("否".equals(zt)) { // 保存基础报销记录 InvoiceLibrary baseInvoice = new InvoiceLibrary(); baseInvoice.setOaId(processInstanceId); baseInvoice.setOaStatus("0"); baseInvoice.setInvoiceStatus("0"); baseInvoice.setDep(bm != null ? bm : ""); baseInvoice.setAccountTitle(bxlb != null ? bxlb : ""); baseInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); baseInvoice.setCreatedAt(LocalDateTime.now()); baseInvoice.setUpdatedAt(LocalDateTime.now()); baseInvoice.setInvoiceCode(""); baseInvoice.setInvoiceNumber(""); baseInvoice.setInvoiceType(""); baseInvoice.setAmount(BigDecimal.ZERO); baseInvoice.setTaxAmount(BigDecimal.ZERO); baseInvoice.setTotalAmount(BigDecimal.ZERO); baseInvoice.setBuyerName(""); baseInvoice.setBuyerTaxId(""); baseInvoice.setSellerName(""); baseInvoice.setSellerTaxId(""); baseInvoice.setFormName("出差费用报销"); baseInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); baseInvoice.setPayAccount(fkzh != null ? fkzh : ""); baseInvoice.setBankName(yhqc != null ? yhqc : ""); baseInvoice.setIsLongTerm(sfct != null ? sfct : ""); baseInvoice.setHasInvoice("否"); baseInvoice.setDetailAmount(null); baseInvoice.setSharedTaxAmount(null); baseInvoice.setSharedAmount(null); baseInvoice.setSharedRate(null); baseMapper.insert(baseInvoice); addWorkflowComment(processInstanceId, userId, "报销总金额:" + mainJe); return McR.success(); } // 判断主表状态 - 有发票 if ("是".equals(zt)) { for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("TableField_169TONKJ3KW00".equals(id)) { String tableFieldValue = String.valueOf(formComponentValue.get("value")); log.info("子表数据: {}", tableFieldValue); try { List tableRows = (List) JSONObject.parse(tableFieldValue); if (tableRows == null || tableRows.isEmpty()) { log.warn("子表数据为空"); return McR.error("400", "发票明细为空"); } List allResults = new ArrayList<>(); List invoiceList = new ArrayList<>(); // 存储第一遍解析的发票数据,避免重复解析 List> parsedInvoiceDataList = new ArrayList<>(); // 存储共享发票数据 List> sharedInvoiceDataList = new ArrayList<>(); // 用于校验的集合 List invoiceNumberList = new ArrayList<>(); Map invoiceNumberToFileName = new HashMap<>(); List invalidBuyerTaxIds = new ArrayList<>(); // 存储税率值,供共享发票使用 BigDecimal sharedTaxRate = BigDecimal.ZERO; String sharedKind = ""; // ========== 第一遍遍历:收集并校验所有发票数据 ========== for (Map row : tableRows) { List rowValues = (List) row.get("rowValue"); String hasInvoice = null; String cdbm = null; String detailje = null; List attachmentList = null; for (Map rowItem : rowValues) { String key = String.valueOf(rowItem.get("key")); if ("DDSelectField_1G6XMYA3FMCG0".equals(key)) { Object value = rowItem.get("value"); hasInvoice = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_KJIWTTZLE800".equals(key)) { Object value = rowItem.get("value"); cdbm = value != null ? String.valueOf(value) : ""; } if ("NumberField_L0ALER5S6180".equals(key)) { Object value = rowItem.get("value"); detailje = value != null ? String.valueOf(value) : ""; } if ("DDAttachment_EPL7ILUTD0G0".equals(key)) { Object value = rowItem.get("value"); if (value instanceof List) { attachmentList = (List) value; } } } if ("是".equals(hasInvoice) && attachmentList != null && !attachmentList.isEmpty()) { for (Map attachment : attachmentList) { String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); try { String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); fileMap.put("size", new File(filePath).length() / 1024f / 1024f); fileMap.put("isPdf", "pdf".equalsIgnoreCase(fileType)); McR result = processMixedInvoice(fileMap); Object responseData = result.getData(); // 解析发票数据 if (responseData != null) { String jsonStr = JSONObject.toJSONString(responseData); JSONObject jsonObject = JSONObject.parseObject(jsonStr); JSONArray resultArray = jsonObject.getJSONArray("result"); if (resultArray != null && !resultArray.isEmpty()) { JSONObject invoice = resultArray.getJSONObject(0); String invoiceNumber = invoice.getString("serial") != null ? invoice.getString("serial") : ""; String buyerTaxId = invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""; String buyerName = invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""; // 获取税率,供共享发票使用 Object taxRateObj = invoice.get("taxRate"); Object kind = invoice.getString("kindName"); if (taxRateObj != null) { String taxRateStr = String.valueOf(taxRateObj); String taxRateNum = taxRateStr.replace("%", "").trim(); sharedKind = kind != null ? String.valueOf(kind) : ""; sharedTaxRate = new BigDecimal(taxRateNum); sharedTaxRate = sharedTaxRate.divide(new BigDecimal("100"), 10, BigDecimal.ROUND_HALF_UP); log.info("从有发票中获取税率: 原始={}, 转换后={}", taxRateStr, sharedTaxRate); } // 保存解析的数据供后续使用 Map parsedData = new HashMap<>(); parsedData.put("invoice", invoice); parsedData.put("fileId", fileId); parsedData.put("fileName", fileName); parsedData.put("fileType", fileType); parsedData.put("cdbm", cdbm); parsedData.put("hasInvoice", hasInvoice); parsedData.put("detailje", detailje); parsedData.put("responseData", responseData); parsedInvoiceDataList.add(parsedData); // ========== 校验1:抬头校验 ========== if (StringUtils.isNotBlank(buyerName)) { CompanyTitle existingTitle = companyTitleMapper.selectByTaxId(buyerName); if (existingTitle == null) { String errorInfo = String.format("发票: %s, 购买方名称: %s, 税号: %s", fileName, buyerName, buyerTaxId); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 税号={} 不存在于抬头库, 文件名={}", buyerTaxId, fileName); } } else { String errorInfo = String.format("发票: %s, 购买方税号为空", fileName); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 购买方税号为空, 文件名={}", fileName); } // ========== 校验2:发票号重复校验 ========== if (StringUtils.isNotBlank(invoiceNumber)) { // 检查数据库中是否已存在相同的发票号 InvoiceLibrary existingInvoice = baseMapper.selectByInvoiceNumber(invoiceNumber); if (existingInvoice != null) { String errorMsg = String.format("发票号重复: %s (当前文件: %s, 已存在于OA审批单: %s)", invoiceNumber, fileName, existingInvoice.getOaId()); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } // 同时检查同一审批单内是否有重复 if (invoiceNumberList.contains(invoiceNumber)) { String errorMsg = String.format("当前审批单内发票号重复: %s (当前文件: %s, 已存在文件: %s)", invoiceNumber, fileName, invoiceNumberToFileName.get(invoiceNumber)); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } invoiceNumberList.add(invoiceNumber); invoiceNumberToFileName.put(invoiceNumber, fileName); } } } } catch (Exception e) { log.error("发票解析异常: fileId={}, fileName={}", fileId, fileName, e); terminateWorkflow(accessToken, processInstanceId, userId, "发票解析失败: " + e.getMessage()); return McR.error("400", "发票解析失败: " + e.getMessage()); } } } else if ("共享".equals(hasInvoice)) { // 收集共享发票数据,待第二遍遍历时保存 if (detailje != null && StringUtils.isNotBlank(detailje)) { Map sharedData = new HashMap<>(); sharedData.put("detailje", detailje); sharedData.put("cdbm", cdbm); sharedData.put("hasInvoice", hasInvoice); sharedInvoiceDataList.add(sharedData); log.info("收集共享发票数据: 金额={}, 成本部门={}", detailje, cdbm); } } } // ========== 抬头校验失败处理 ========== if (!invalidBuyerTaxIds.isEmpty()) { String errorMsg = "发票抬头有误,以下发票的购买方税号不在公司抬头库中:\n" + String.join("\n", invalidBuyerTaxIds); log.error(errorMsg); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } BigDecimal totalAmount = BigDecimal.ZERO; // ========== 第二遍遍历:保存发票数据(校验通过后) ========== // 1. 保存普通发票数据 for (Map parsedData : parsedInvoiceDataList) { JSONObject invoice = (JSONObject) parsedData.get("invoice"); Object responseData = parsedData.get("responseData"); String cdbm = (String) parsedData.get("cdbm"); String hasInvoice = (String) parsedData.get("hasInvoice"); String detailje = (String) parsedData.get("detailje"); // 修改为 String 类型 allResults.add(responseData); // 汇总 amount BigDecimal amount = invoice.getBigDecimal("amount"); if (amount != null) { totalAmount = totalAmount.add(amount); } // 构建InvoiceLibrary对象 InvoiceLibrary invoiceLibrary = new InvoiceLibrary(); invoiceLibrary.setOaId(processInstanceId); invoiceLibrary.setInvoiceCode(invoice.getString("code") != null ? invoice.getString("code") : ""); invoiceLibrary.setInvoiceNumber(invoice.getString("serial") != null ? invoice.getString("serial") : ""); invoiceLibrary.setInvoiceType(invoice.getString("kindName") != null ? invoice.getString("kindName") : ""); String date = invoice.getString("date"); if (StringUtils.isNotBlank(date)) { try { invoiceLibrary.setInvoiceDate(LocalDate.parse(date.trim())); } catch (Exception e) { log.warn("日期解析失败: {}", date); } } invoiceLibrary.setAmount(invoice.getBigDecimal("excludingTax") != null ? invoice.getBigDecimal("excludingTax") : BigDecimal.ZERO); invoiceLibrary.setTaxAmount(invoice.getBigDecimal("tax") != null ? invoice.getBigDecimal("tax") : BigDecimal.ZERO); invoiceLibrary.setTotalAmount(invoice.getBigDecimal("amount") != null ? invoice.getBigDecimal("amount") : BigDecimal.ZERO); invoiceLibrary.setBuyerName(invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""); invoiceLibrary.setBuyerTaxId(invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""); invoiceLibrary.setSellerName(invoice.getString("sellerName") != null ? invoice.getString("sellerName") : ""); invoiceLibrary.setSellerTaxId(invoice.getString("sellerTaxId") != null ? invoice.getString("sellerTaxId") : ""); invoiceLibrary.setOaStatus("0"); invoiceLibrary.setFormName("出差费用报销"); invoiceLibrary.setPaySubject(fkzhxx != null ? fkzhxx : ""); invoiceLibrary.setPayAccount(fkzh != null ? fkzh : ""); invoiceLibrary.setBankName(yhqc != null ? yhqc : ""); invoiceLibrary.setIsLongTerm(sfct != null ? sfct : ""); invoiceLibrary.setInvoiceStatus("0"); invoiceLibrary.setHasInvoice(hasInvoice); // 处理 detailAmount,将 String 转换为 BigDecimal if (detailje != null && StringUtils.isNotBlank(detailje)) { try { invoiceLibrary.setDetailAmount(new BigDecimal(detailje)); } catch (NumberFormatException e) { log.error("detailJe格式转换失败: {}", detailje); invoiceLibrary.setDetailAmount(null); } } else { invoiceLibrary.setDetailAmount(null); } // 普通发票不涉及共享字段,设置为 null invoiceLibrary.setSharedTaxAmount(null); invoiceLibrary.setSharedAmount(null); invoiceLibrary.setSharedRate(null); invoiceLibrary.setAccountTitle(bxlb != null ? bxlb : ""); invoiceLibrary.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); invoiceLibrary.setCreatedAt(LocalDateTime.now()); invoiceLibrary.setUpdatedAt(LocalDateTime.now()); // 设置成本部门 if (cdbm != null && StringUtils.isNotBlank(cdbm)) { invoiceLibrary.setDep(cdbm); } else { invoiceLibrary.setDep(bm != null ? bm : ""); } invoiceList.add(invoiceLibrary); } for (Map sharedData : sharedInvoiceDataList) { String detailje = (String) sharedData.get("detailje"); String cdbm = (String) sharedData.get("cdbm"); String hasInvoice = (String) sharedData.get("hasInvoice"); try { BigDecimal detailAmount = new BigDecimal(detailje); // 使用从普通发票中解析到的税率 BigDecimal ocrTaxRate = sharedTaxRate; if (ocrTaxRate.compareTo(BigDecimal.ZERO) == 0) { log.warn("共享发票税率未获取到,使用默认税率0,金额={}", detailAmount); } // 计算金额: 含税金额 / (1 + 税率) = 税额 BigDecimal divisor = BigDecimal.ONE.add(ocrTaxRate); BigDecimal sharedTaxAmount = detailAmount.divide(divisor, 2, BigDecimal.ROUND_HALF_UP); BigDecimal sharedAmount = detailAmount.subtract(sharedTaxAmount); BigDecimal sharedRate = ocrTaxRate; // 这是小数形式的税率,如 0.09 log.info("共享发票计算: 含税金额={}, 税率={}%, 不含税金额={}, 税额={}", detailAmount, ocrTaxRate.multiply(new BigDecimal("100")), sharedAmount, sharedTaxAmount); // 保存共享发票数据 InvoiceLibrary sharedInvoice = new InvoiceLibrary(); sharedInvoice.setOaId(processInstanceId); sharedInvoice.setOaStatus("0"); sharedInvoice.setInvoiceStatus("0"); sharedInvoice.setDep(cdbm != null && StringUtils.isNotBlank(cdbm) ? cdbm : (bm != null ? bm : "")); sharedInvoice.setAccountTitle(bxlb != null ? bxlb : ""); sharedInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); sharedInvoice.setAmount(null); sharedInvoice.setTaxAmount(null); sharedInvoice.setTotalAmount(null); // 共享发票不设置总金额 sharedInvoice.setFormName("出差费用报销"); sharedInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); sharedInvoice.setPayAccount(fkzh != null ? fkzh : ""); sharedInvoice.setBankName(yhqc != null ? yhqc : ""); sharedInvoice.setIsLongTerm(sfct != null ? sfct : ""); sharedInvoice.setHasInvoice(hasInvoice); sharedInvoice.setDetailAmount(detailAmount); // 共享发票字段设置值 - 修正:存储数字而不是带百分号的字符串 sharedInvoice.setSharedTaxAmount(sharedAmount); sharedInvoice.setSharedAmount(sharedTaxAmount); sharedInvoice.setSharedRate(String.valueOf(sharedRate)); // 直接存储小数,如 0.09 sharedInvoice.setCreatedAt(LocalDateTime.now()); sharedInvoice.setUpdatedAt(LocalDateTime.now()); sharedInvoice.setInvoiceCode(""); sharedInvoice.setInvoiceNumber(""); sharedInvoice.setInvoiceType(sharedKind); sharedInvoice.setBuyerName(""); sharedInvoice.setBuyerTaxId(""); sharedInvoice.setSellerName(""); sharedInvoice.setSellerTaxId(""); invoiceList.add(sharedInvoice); log.info("成功保存共享发票记录,成本部门: {}", cdbm); } catch (NumberFormatException e) { log.error("共享发票金额格式转换失败: detailje={}", detailje, e); } catch (Exception e) { log.error("共享发票处理失败", e); } } // 保存发票记录 if (!invoiceList.isEmpty()) { for (InvoiceLibrary invoice : invoiceList) { baseMapper.insert(invoice); } log.info("成功保存 {} 条发票记录(普通发票: {}, 共享发票: {})", invoiceList.size(), parsedInvoiceDataList.size(), sharedInvoiceDataList.size()); } addWorkflowComment1(processInstanceId, userId, totalAmount); return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败: " + e.getMessage()); } } } } return McR.success(); } catch (Exception e) { log.error("处理审批实例失败", e); return McR.error("500", "系统处理失败: " + e.getMessage()); } } @Override public McR invoiceLibrary6(Map map) { log.info("接收到的参数: {}", map); try { String processInstanceId = UtilMap.getString(map, "processInstanceId"); if (StringUtils.isBlank(processInstanceId)) { log.error("processInstanceId为空"); return McR.error("400", "审批实例ID不能为空"); } // 获取审批实例信息 String accessToken = ddClient.getAccessToken(); Map processInstance = ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId); if (processInstance == null) { log.error("获取审批实例失败: {}", processInstanceId); return McR.error("500", "获取审批实例信息失败"); } String userId = (String) processInstance.get("originatorUserId"); log.info("审批人ID: {}", userId); List formComponentValues = (List) processInstance.get("formComponentValues"); if (formComponentValues == null || formComponentValues.isEmpty()) { log.warn("表单数据为空"); return McR.error("400", "表单数据为空"); } // 存储主表的字段值 String zt = null; BigDecimal mainJe = null; String bxlb = null; String bm = null; String fkzhxx = null; String fkzh = null; String yhqc = null; String sfct = null; // 遍历收集主表字段值 for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); Object value = formComponentValue.get("value"); if ("DDSelectField_VWIFJ1JZK0W0".equals(id)) { zt = value != null ? String.valueOf(value) : ""; } if ("MoneyField_3QS10ALLABU0".equals(id)) { if (value instanceof BigDecimal) { mainJe = (BigDecimal) value; } else if (value != null) { try { mainJe = new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { log.error("金额格式转换失败: {}", value, e); mainJe = BigDecimal.ZERO; } } } // 如果需要这两个字段,请取消注释并填写正确的字段ID if ("DDSelectField_YLLB7AD1ATS0".equals(id)) { bxlb = value != null ? String.valueOf(value) : ""; } if ("DepartmentField_RF93A6VHA9C0".equals(id)) { bm = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_IQXAGL7Z8XC0".equals(id)) { fkzhxx = value != null ? String.valueOf(value) : ""; } if ("TextField_3W9CLQOZZ540".equals(id)) { fkzh = value != null ? String.valueOf(value) : ""; } if ("TextField_9PJ4JW8KTCS0".equals(id)) { yhqc = value != null ? String.valueOf(value) : ""; } if ("DDSelectField_21L6KT7DT70G0".equals(id)) { sfct = value != null ? String.valueOf(value) : "否"; } } // 判断主表状态 - 无发票 if ("否".equals(zt)) { // Map result = new HashMap<>(); // result.put("je", mainJe); // result.put("status", "否"); // result.put("message", "无发票报销"); // 保存基础报销记录 InvoiceLibrary baseInvoice = new InvoiceLibrary(); baseInvoice.setOaId(processInstanceId); baseInvoice.setOaStatus("0"); baseInvoice.setInvoiceStatus("0"); baseInvoice.setDep(bm != null ? bm : ""); baseInvoice.setAccountTitle(bxlb != null ? bxlb : ""); baseInvoice.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); baseInvoice.setCreatedAt(LocalDateTime.now()); baseInvoice.setUpdatedAt(LocalDateTime.now()); baseInvoice.setInvoiceCode(""); baseInvoice.setInvoiceNumber(""); baseInvoice.setInvoiceType(""); baseInvoice.setAmount(BigDecimal.ZERO); baseInvoice.setTaxAmount(BigDecimal.ZERO); baseInvoice.setTotalAmount(BigDecimal.ZERO); baseInvoice.setBuyerName(""); baseInvoice.setFormName("批量付款申请(一对多)"); baseInvoice.setPaySubject(fkzhxx != null ? fkzhxx : ""); baseInvoice.setPayAccount(fkzh != null ? fkzh : ""); baseInvoice.setBankName(yhqc != null ? yhqc : ""); baseInvoice.setIsLongTerm(sfct != null ? sfct : ""); baseInvoice.setBuyerTaxId(""); baseInvoice.setSellerName(""); baseInvoice.setSellerTaxId(""); baseMapper.insert(baseInvoice); addWorkflowComment(processInstanceId, userId, "报销总金额:"+mainJe); return McR.success(); } // 判断主表状态 - 有发票 if ("是".equals(zt)) { for (Map formComponentValue : formComponentValues) { String id = String.valueOf(formComponentValue.get("id")); if ("DDAttachment_1C1GRYIRFJOG0".equals(id)) { // 获取附件value(它是一个JSON字符串) Object attachmentValue = formComponentValue.get("value"); List attachmentList = null; // 解析附件JSON字符串 if (attachmentValue != null) { String attachmentJsonStr = String.valueOf(attachmentValue); if (StringUtils.isNotBlank(attachmentJsonStr)) { try { attachmentList = JSONObject.parseArray(attachmentJsonStr, Map.class); log.info("解析附件列表成功,共{}个附件", attachmentList != null ? attachmentList.size() : 0); } catch (Exception e) { log.error("解析附件JSON失败: {}", attachmentJsonStr, e); return McR.error("400", "附件格式解析失败"); } } } try { List allResults = new ArrayList<>(); List invoiceList = new ArrayList<>(); // 用于校验的集合 List invoiceNumberList = new ArrayList<>(); Map invoiceNumberToFileName = new HashMap<>(); List invalidBuyerTaxIds = new ArrayList<>(); // 存储第一遍解析的发票数据,避免重复解析 List> parsedInvoiceDataList = new ArrayList<>(); if (attachmentList != null && !attachmentList.isEmpty()) { for (Map attachment : attachmentList) { String fileId = UtilMap.getString(attachment, "fileId"); String fileType = UtilMap.getString(attachment, "fileType"); String fileName = UtilMap.getString(attachment, "fileName"); // 参数校验 if (StringUtils.isBlank(fileId) || StringUtils.isBlank(fileType)) { log.warn("文件信息不完整: fileId={}, fileType={}, fileName={}", fileId, fileType, fileName); continue; } try { String filePath = downloadPath + fileId + "." + fileType; downloadDdFile(processInstanceId, fileId, filePath); String hz = "qw/files/" + fileId + "." + fileType; Map fileMap = new HashMap(); fileMap.put("url", url + hz); fileMap.put("size", new File(filePath).length() / 1024f / 1024f); fileMap.put("isPdf", "pdf".equalsIgnoreCase(fileType)); McR result = processMixedInvoice(fileMap); Object responseData = result.getData(); // 解析发票数据 if (responseData != null) { String jsonStr = JSONObject.toJSONString(responseData); JSONObject jsonObject = JSONObject.parseObject(jsonStr); JSONArray resultArray = jsonObject.getJSONArray("result"); if (resultArray != null && !resultArray.isEmpty()) { JSONObject invoice = resultArray.getJSONObject(0); String invoiceNumber = invoice.getString("serial") != null ? invoice.getString("serial") : ""; String buyerTaxId = invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""; String buyerName = invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""; // 保存解析的数据供后续使用 Map parsedData = new HashMap<>(); parsedData.put("invoice", invoice); parsedData.put("fileId", fileId); parsedData.put("fileName", fileName); parsedData.put("fileType", fileType); parsedData.put("responseData", responseData); parsedInvoiceDataList.add(parsedData); // ========== 校验1:抬头校验 ========== if (StringUtils.isNotBlank(buyerName)) { CompanyTitle existingTitle = companyTitleMapper.selectByTaxId(buyerName); if (existingTitle == null) { String errorInfo = String.format("发票: %s, 购买方名称: %s, 税号: %s", fileName, buyerName, buyerTaxId); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 税号={} 不存在于抬头库, 文件名={}", buyerTaxId, fileName); } } else { String errorInfo = String.format("发票: %s, 购买方税号为空", fileName); invalidBuyerTaxIds.add(errorInfo); log.warn("抬头校验失败: 购买方税号为空, 文件名={}", fileName); } // ========== 校验2:发票号重复校验(与数据库中的发票号比较) ========== if (StringUtils.isNotBlank(invoiceNumber)) { // 检查数据库中是否已存在相同的发票号 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(InvoiceLibrary::getInvoiceNumber, invoiceNumber); InvoiceLibrary existingInvoice = baseMapper.selectOne(queryWrapper); if (existingInvoice != null) { String errorMsg = String.format("发票号重复: %s (当前文件: %s, 已存在于OA审批单: %s)", invoiceNumber, fileName, existingInvoice.getOaId()); log.error(errorMsg); // 终止流程并回写原因 terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } // 同时检查同一审批单内是否有重复(防止同一单据内上传重复发票) if (invoiceNumberList.contains(invoiceNumber)) { String errorMsg = String.format("当前审批单内发票号重复: %s (当前文件: %s, 已存在文件: %s)", invoiceNumber, fileName, invoiceNumberToFileName.get(invoiceNumber)); log.error(errorMsg); // 终止流程并回写原因 terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } invoiceNumberList.add(invoiceNumber); invoiceNumberToFileName.put(invoiceNumber, fileName); } } else { log.warn("发票解析结果为空: fileName={}", fileName); } } else { log.warn("发票OCR识别结果为空: fileName={}", fileName); } } catch (Exception e) { log.error("发票解析异常: fileId={}, fileName={}", fileId, fileName, e); String errorMsg = "发票解析失败: " + e.getMessage(); terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } } } else { log.warn("附件列表为空"); return McR.error("400", "请上传发票附件"); } // ========== 抬头校验失败处理 ========== if (!invalidBuyerTaxIds.isEmpty()) { String errorMsg = "发票抬头有误,以下发票的购买方税号不在公司抬头库中:\n" + String.join("\n", invalidBuyerTaxIds); log.error(errorMsg); // 终止流程 terminateWorkflow(accessToken, processInstanceId, userId, errorMsg); return McR.error("400", errorMsg); } BigDecimal totalAmount = BigDecimal.ZERO; // ========== 第二遍遍历:保存发票数据(校验通过后) ========== for (Map parsedData : parsedInvoiceDataList) { JSONObject invoice = (JSONObject) parsedData.get("invoice"); Object responseData = parsedData.get("responseData"); allResults.add(responseData); BigDecimal amount = invoice.getBigDecimal("amount"); if (amount != null) { totalAmount = totalAmount.add(amount); } // 构建InvoiceLibrary对象 InvoiceLibrary invoiceLibrary = new InvoiceLibrary(); invoiceLibrary.setOaId(processInstanceId); invoiceLibrary.setInvoiceCode(invoice.getString("code") != null ? invoice.getString("code") : ""); invoiceLibrary.setInvoiceNumber(invoice.getString("serial") != null ? invoice.getString("serial") : ""); invoiceLibrary.setInvoiceType(invoice.getString("kindName") != null ? invoice.getString("kindName") : ""); String date = invoice.getString("date"); if (StringUtils.isNotBlank(date)) { try { invoiceLibrary.setInvoiceDate(LocalDate.parse(date.trim())); } catch (Exception e) { log.warn("日期解析失败: {}", date, e); } } invoiceLibrary.setAmount(invoice.getBigDecimal("excludingTax") != null ? invoice.getBigDecimal("excludingTax") : BigDecimal.ZERO); invoiceLibrary.setTaxAmount(invoice.getBigDecimal("tax") != null ? invoice.getBigDecimal("tax") : BigDecimal.ZERO); invoiceLibrary.setTotalAmount(invoice.getBigDecimal("amount") != null ? invoice.getBigDecimal("amount") : BigDecimal.ZERO); invoiceLibrary.setBuyerName(invoice.getString("buyerName") != null ? invoice.getString("buyerName") : ""); invoiceLibrary.setBuyerTaxId(invoice.getString("buyerTaxId") != null ? invoice.getString("buyerTaxId") : ""); invoiceLibrary.setSellerName(invoice.getString("sellerName") != null ? invoice.getString("sellerName") : ""); invoiceLibrary.setSellerTaxId(invoice.getString("sellerTaxId") != null ? invoice.getString("sellerTaxId") : ""); invoiceLibrary.setOaStatus("0"); invoiceLibrary.setInvoiceStatus("0"); invoiceLibrary.setDep(bm != null ? bm : ""); invoiceLibrary.setAccountTitle(bxlb != null ? bxlb : ""); invoiceLibrary.setPayAmount(mainJe != null ? mainJe : BigDecimal.ZERO); invoiceLibrary.setCreatedAt(LocalDateTime.now()); invoiceLibrary.setUpdatedAt(LocalDateTime.now()); invoiceLibrary.setFormName("批量付款申请(一对多)"); invoiceLibrary.setPaySubject(fkzhxx != null ? fkzhxx : ""); invoiceLibrary.setPayAccount(fkzh != null ? fkzh : ""); invoiceLibrary.setBankName(yhqc != null ? yhqc : ""); invoiceLibrary.setIsLongTerm(sfct != null ? sfct : ""); invoiceList.add(invoiceLibrary); } // 保存发票记录 if (!invoiceList.isEmpty()) { for (InvoiceLibrary invoice : invoiceList) { baseMapper.insert(invoice); } log.info("成功保存 {} 条发票记录", invoiceList.size()); } else { log.warn("没有有效的发票数据可保存"); } addWorkflowComment1(processInstanceId, userId, totalAmount); return McR.success(allResults); } catch (Exception e) { log.error("解析子表数据失败", e); return McR.error("300", "解析数据失败: " + e.getMessage()); } } } } return McR.success(); } catch (Exception e) { log.error("处理审批实例失败", e); return McR.error("500", "系统处理失败: " + e.getMessage()); } } @Override public void updateSfctByOaId(Map map) { try { String oaId = UtilMap.getString(map, "oaId"); String sfct = UtilMap.getString(map, "sfct"); String fkzhxx = UtilMap.getString(map, "fkzhxx"); String fkzh = UtilMap.getString(map, "fkzh"); String yhqc = UtilMap.getString(map, "yhqc"); sfct = (sfct == null || sfct.trim().isEmpty()) ? "否" : sfct; // 创建更新条件 LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(InvoiceLibrary::getOaId, oaId) .set(InvoiceLibrary::getIsLongTerm, sfct) .set(InvoiceLibrary::getPayAccount, fkzh) .set(InvoiceLibrary::getBankName, yhqc) .set(InvoiceLibrary::getPaySubject, fkzhxx) .set(InvoiceLibrary::getUpdatedAt, LocalDateTime.now()); int updateCount = baseMapper.update(null, updateWrapper); if (updateCount > 0) { log.info("更新成功: 共更新{}条记录", updateCount); } else { log.warn("未找到符合条件的记录: oaId={}", oaId); } } catch (Exception e) { throw new RuntimeException("更新OA状态失败", e); } } /** * 添加钉钉审批评论 */ private void addWorkflowComment(String processInstanceId, String userId, Object totalAmount) { try { Map body = new HashMap<>(); body.put("processInstanceId", processInstanceId); body.put("text", totalAmount); body.put("commentUserId", userId); String result = UtilHttp.doPost( "https://api.dingtalk.com/v1.0/workflow/processInstances/comments", ddClient.initTokenHeader(), null, body ); log.info("添加审批评论成功: {}", result); } catch (Exception e) { log.error("添加审批评论失败", e); } } private void addWorkflowComment1(String processInstanceId, String userId, BigDecimal totalAmount) { try { Map body = new HashMap<>(); body.put("processInstanceId", processInstanceId); body.put("text", "发票总金额:"+totalAmount); body.put("commentUserId", userId); String result = UtilHttp.doPost( "https://api.dingtalk.com/v1.0/workflow/processInstances/comments", ddClient.initTokenHeader(), null, body ); log.info("添加审批评论成功: {}", result); } catch (Exception e) { log.error("添加审批评论失败", e); } } /** * 从 Map 中获取字符串值 */ private String getStringValue(Map map, String key) { Object value = map.get(key); return value != null ? String.valueOf(value) : ""; } /** * 从 Map 中获取 BigDecimal 值 */ private BigDecimal getBigDecimalValue(Map map, String key) { Object value = map.get(key); if (value == null) { return BigDecimal.ZERO; } if (value instanceof BigDecimal) { return (BigDecimal) value; } try { return new BigDecimal(String.valueOf(value)); } catch (NumberFormatException e) { return BigDecimal.ZERO; } } /** * 空字符串转换 */ private String nullToEmpty(String str) { return str != null ? str : ""; } 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 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"; } 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")) .taxRate(taxRate) .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.defaultString(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)); } private static String guyuanNameRepalce(String name) { name = name.replaceAll(" ", ""); if (name.contains("谷元")) { return UtilString.replaceBracketIsWhole(name); } else { return UtilString.replaceBracketIsSemiangle(name); } } /// 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); } @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; } @GetMapping("/files/{fileId}") public ResponseEntity getFileResource( @PathVariable String fileId, @RequestParam(defaultValue = "download") String option) throws IOException { // 根据fileId获取实际文件路径,这里简化处理,实际可能需要从数据库查询 Path filePath = Paths.get("D://qiwang//files//").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); } }