|
@@ -10,22 +10,25 @@ import com.malk.server.aliwork.YDParam;
|
|
|
import com.malk.server.common.McException;
|
|
import com.malk.server.common.McException;
|
|
|
import com.malk.server.common.McR;
|
|
import com.malk.server.common.McR;
|
|
|
import com.malk.service.aliwork.YDClient;
|
|
import com.malk.service.aliwork.YDClient;
|
|
|
-import com.malk.utils.UtilMap;
|
|
|
|
|
-import com.malk.utils.UtilMc;
|
|
|
|
|
-import com.malk.utils.UtilNumber;
|
|
|
|
|
|
|
+import com.malk.utils.*;
|
|
|
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
|
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
|
|
|
|
+import lombok.SneakyThrows;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import net.coobird.thumbnailator.Thumbnails;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
+import org.apache.poi.util.IOUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
|
|
-import java.math.BigDecimal;
|
|
|
|
|
-import java.util.List;
|
|
|
|
|
-import java.util.Map;
|
|
|
|
|
-import java.util.Optional;
|
|
|
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
|
|
+import java.io.InputStream;
|
|
|
|
|
+import java.net.URL;
|
|
|
|
|
+import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -43,6 +46,7 @@ public class IVController {
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private YDClient ydClient;
|
|
private YDClient ydClient;
|
|
|
|
|
|
|
|
|
|
+ /// 优先获取字段, 新版本接口已支持字段返回
|
|
|
private String findValue(List<Map<String, String>> infos, String... names) {
|
|
private String findValue(List<Map<String, String>> infos, String... names) {
|
|
|
for (String name : names) {
|
|
for (String name : names) {
|
|
|
Optional optional = infos.stream().filter(info -> info.get("Name").equals(name)).findAny();
|
|
Optional optional = infos.stream().filter(info -> info.get("Name").equals(name)).findAny();
|
|
@@ -53,30 +57,79 @@ public class IVController {
|
|
|
return "";
|
|
return "";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 兼容历史配置, 格式 (谷元)
|
|
|
|
|
+ private String guyuanNameRepalce(String name) {
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // prd 校验发票抬头, 购买方范围
|
|
|
|
|
+ private void validateBuyer(String BuyerName, String tips) {
|
|
|
|
|
+ List<String> corpNames = Arrays.asList(
|
|
|
|
|
+ "谷元(上海)文化科技有限责任公司",
|
|
|
|
|
+ "上海爱鱼文化传媒有限公司",
|
|
|
|
|
+ "上海渔米可禧文化传媒有限公司",
|
|
|
|
|
+ "上海巧豆文化传媒有限公司",
|
|
|
|
|
+ "钰鸿文化创意(上海)有限公司",
|
|
|
|
|
+ "上海攸元文化科技有限公司",
|
|
|
|
|
+ "上海盈田演出经纪有限公司",
|
|
|
|
|
+ "上海小蝠文化科技有限公司",
|
|
|
|
|
+ "沐田居海(厦门)文化传媒有限公司",
|
|
|
|
|
+ "北京元环文化科技有限责任公司",
|
|
|
|
|
+ "厦门攸元文化科技有限公司",
|
|
|
|
|
+ "厦门亨有文化科技有限公司",
|
|
|
|
|
+ "厦门银雀思汀文化传媒有限公司",
|
|
|
|
|
+ "上海渝泽信息科技有限公司",
|
|
|
|
|
+ "厦门神谷飞流影视传媒有限公司",
|
|
|
|
|
+ "厦门谷钛数字科技有限公司",
|
|
|
|
|
+ "上海观情科技有限公司");
|
|
|
|
|
+ McException.assertAccessException(!corpNames.contains(BuyerName), tips + ", 购买方名称不合法!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * 混票识别
|
|
|
|
|
|
|
+ * 混票识别 [旧版]
|
|
|
*/
|
|
*/
|
|
|
@PostMapping("invoice-iv")
|
|
@PostMapping("invoice-iv")
|
|
|
McR invoice_iv(@RequestBody Map<String, String> data) throws TencentCloudSDKException {
|
|
McR invoice_iv(@RequestBody Map<String, String> data) throws TencentCloudSDKException {
|
|
|
|
|
|
|
|
McException.assertParamException_Null(data, "url");
|
|
McException.assertParamException_Null(data, "url");
|
|
|
- String imageUrl = ydClient.convertTemporaryUrl(data.get("url"));
|
|
|
|
|
- log.info("混票识别, 免登地址, {}", imageUrl);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ String image = ydClient.convertTemporaryUrl(data.get("url"));
|
|
|
|
|
+ log.info("混票识别, 免登地址, {}", image);
|
|
|
|
|
+ // 非PDF, 且内存大于3M, 压缩后上传
|
|
|
|
|
+ if (!UtilMap.getBoolean(data, "isPdf") && UtilMap.getFloat(data, "size") > 3.0f) {
|
|
|
|
|
+ image = imageUrlConvertBase64(image);
|
|
|
|
|
+ }
|
|
|
// ppExt: 通用字段定义
|
|
// ppExt: 通用字段定义
|
|
|
- List<Map> invoices = (List<Map>) txyInvoice.doMixedInvoiceOCR(imageUrl).get("MixedInvoiceItems");
|
|
|
|
|
|
|
+ List<Map> invoices = (List<Map>) txyInvoice.doMixedInvoiceOCR(image).get("MixedInvoiceItems");
|
|
|
List<McInvoiceDto> result = invoices.stream().map(item -> {
|
|
List<McInvoiceDto> result = invoices.stream().map(item -> {
|
|
|
String kind = TXYConf.TYPE_INVOICE.get(item.get("Type").toString());
|
|
String kind = TXYConf.TYPE_INVOICE.get(item.get("Type").toString());
|
|
|
List<Map<String, String>> infos = (List<Map<String, String>>) item.get("SingleInvoiceInfos");
|
|
List<Map<String, String>> infos = (List<Map<String, String>>) item.get("SingleInvoiceInfos");
|
|
|
|
|
|
|
|
McInvoiceDto.assertSuccess(item, kind); // 响应断言
|
|
McInvoiceDto.assertSuccess(item, kind); // 响应断言
|
|
|
-
|
|
|
|
|
String invoiceName = findValue(infos, "发票名称");
|
|
String invoiceName = findValue(infos, "发票名称");
|
|
|
if (kind.equals("全电发票")) {
|
|
if (kind.equals("全电发票")) {
|
|
|
- kind = invoiceName.contains("增值税专用发票") ? "增值税电子专用发票" : "增值税电子普通发票";
|
|
|
|
|
|
|
+ kind = invoiceName.contains("增值税专用发票") ? "全电专用发票" : "全电普通发票";
|
|
|
}
|
|
}
|
|
|
if (kind.equals("增值税发票")) {
|
|
if (kind.equals("增值税发票")) {
|
|
|
kind = invoiceName.contains("增值税专用发票") ? "增值税专用发票" : "增值税普通发票";
|
|
kind = invoiceName.contains("增值税专用发票") ? "增值税专用发票" : "增值税普通发票";
|
|
|
|
|
+ if (invoiceName.contains("增值税电子")) {
|
|
|
|
|
+ kind = invoiceName.contains("专用发票") ? "增值税电子专用发票" : "增值税电子普通发票";
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
McInvoiceDto invoiceDto = McInvoiceDto.builder()
|
|
McInvoiceDto invoiceDto = McInvoiceDto.builder()
|
|
|
.name(invoiceName)
|
|
.name(invoiceName)
|
|
@@ -87,11 +140,12 @@ public class IVController {
|
|
|
.serial(findValue(infos, "发票号码", "编号", "电子客票号码", "票据号码").replace("No", "")) // 发票, 非税发票
|
|
.serial(findValue(infos, "发票号码", "编号", "电子客票号码", "票据号码").replace("No", "")) // 发票, 非税发票
|
|
|
.date(findValue(infos, "开票日期").replace("年", "-").replace("月", "-").replace("日", ""))
|
|
.date(findValue(infos, "开票日期").replace("年", "-").replace("月", "-").replace("日", ""))
|
|
|
.checkCode(findValue(infos, "校验码"))
|
|
.checkCode(findValue(infos, "校验码"))
|
|
|
- .amount(new BigDecimal(UtilNumber.replaceCurrencyCHY(findValue(infos, "小写金额", "价税合计(小写)", "合计金额", "票价", "金额")))) // 发票, 全电票, 行程单, 火车票, 过路过桥费
|
|
|
|
|
- .excludingTax(new BigDecimal(findValue(infos, "金额", "票价", "小写金额"))) // 行程单, 火车票, 定额发票
|
|
|
|
|
- .buyerName(findValue(infos, "购买方名称", "交款人")) // 发票, 非税发票
|
|
|
|
|
|
|
+ .amount(UtilNumber.replaceCurrencyCHYToDecimal(findValue(infos, "小写金额", "价税合计(小写)", "合计金额", "票价", "金额"))) // 发票, 全电票, 行程单, 火车票, 过路过桥费
|
|
|
|
|
+ .excludingTax(UtilNumber.replaceCurrencyCHYToDecimal(findValue(infos, "合计金额", "金额", "票价", "小写金额"))) // [ppExt: 多明细行时, 优先取值合计] 行程单, 火车票, 定额发票
|
|
|
|
|
+ .tax(UtilNumber.replaceCurrencyCHYToDecimal(findValue(infos, "合计税额"))) // 增值税发票
|
|
|
|
|
+ .buyerName(guyuanNameRepalce(findValue(infos, "购买方名称", "交款人"))) // 发票, 非税发票
|
|
|
.buyerTaxId(findValue(infos, "购买方识别号", "购买方统一社会信用代码/纳税人识别号", "交款人统一社会信用代码")) // 发票, 全电票, 非税发票
|
|
.buyerTaxId(findValue(infos, "购买方识别号", "购买方统一社会信用代码/纳税人识别号", "交款人统一社会信用代码")) // 发票, 全电票, 非税发票
|
|
|
- .sellerName(findValue(infos, "销售方名称", "填开单位")) // 行程单
|
|
|
|
|
|
|
+ .sellerName(guyuanNameRepalce(findValue(infos, "销售方名称", "填开单位"))) // 行程单
|
|
|
.sellerTaxId(findValue(infos, "销售方识别号", "销售方统一社会信用代码/纳税人识别号", "销售单位代号")) // 发票, 全电票, 行程单
|
|
.sellerTaxId(findValue(infos, "销售方识别号", "销售方统一社会信用代码/纳税人识别号", "销售单位代号")) // 发票, 全电票, 行程单
|
|
|
.passengerName(findValue(infos, "旅客姓名", "姓名")) // 行程单, 火车票
|
|
.passengerName(findValue(infos, "旅客姓名", "姓名")) // 行程单, 火车票
|
|
|
.seatType(findValue(infos, "座位等级", "席别")) // 行程单, 火车票
|
|
.seatType(findValue(infos, "座位等级", "席别")) // 行程单, 火车票
|
|
@@ -136,6 +190,7 @@ public class IVController {
|
|
|
String invoiceNo = dto.getSerial(); // 唯一标识, 发票号码
|
|
String invoiceNo = dto.getSerial(); // 唯一标识, 发票号码
|
|
|
|
|
|
|
|
String serial = "第【" + (index + 1) + "】张发票";
|
|
String serial = "第【" + (index + 1) + "】张发票";
|
|
|
|
|
+ validateBuyer(dto.getBuyerName(), serial + "有疑问");
|
|
|
McException.assertAccessException(StringUtils.isBlank(invoiceNo), serial + ", 识别结果为空, 请检查!");
|
|
McException.assertAccessException(StringUtils.isBlank(invoiceNo), serial + ", 识别结果为空, 请检查!");
|
|
|
YDParam ydParam = YDParam.builder()
|
|
YDParam ydParam = YDParam.builder()
|
|
|
.formUuid("FORM-W2A66Z910O9B3LP9C6IYUDPRVWY62DO0YHIILY")
|
|
.formUuid("FORM-W2A66Z910O9B3LP9C6IYUDPRVWY62DO0YHIILY")
|
|
@@ -147,20 +202,94 @@ public class IVController {
|
|
|
}
|
|
}
|
|
|
// prd 仅仅识别增值税普通发票
|
|
// prd 仅仅识别增值税普通发票
|
|
|
if (dto.getKindName().contains("普通发票")) {
|
|
if (dto.getKindName().contains("普通发票")) {
|
|
|
|
|
+ String serialTips = serial + "有疑问";
|
|
|
try {
|
|
try {
|
|
|
// ppExt: 识别与验真后抬头对比
|
|
// ppExt: 识别与验真后抬头对比
|
|
|
- Map rsp = txyInvoice.VatInvoiceVerifyNew(dto.getKindName(), dto.getCode(), invoiceNo, dto.getDate(), String.valueOf(dto.getAmount()), dto.getCheckCode(), String.valueOf(dto.getExcludingTax()));
|
|
|
|
|
|
|
+ Map rsp = txyInvoice.VatInvoiceVerifyNew(dto.getKindName(), dto.getCode(), invoiceNo, dto.getDate(), String.valueOf(dto.getAmount()), dto.getCheckCode(), String.valueOf(dto.getExcludingTax()), serialTips);
|
|
|
Map invoice = (Map) rsp.get("Invoice");
|
|
Map invoice = (Map) rsp.get("Invoice");
|
|
|
- McException.assertAccessException(!dto.getBuyerName().equals(invoice.get("BuyerName")), "发票有疑问, 购买方名称不匹配!");
|
|
|
|
|
- McException.assertAccessException(!dto.getBuyerTaxId().equals(invoice.get("BuyerTaxCode")), "发票有疑问, 购买方税号不匹配!");
|
|
|
|
|
- McException.assertAccessException(!dto.getSellerName().equals(invoice.get("SellerName")), "发票有疑问, 销售方名称不匹配!");
|
|
|
|
|
- McException.assertAccessException(!dto.getSellerTaxId().equals(invoice.get("SellerTaxCode")), "发票有疑问, 销售方税号不匹配!");
|
|
|
|
|
|
|
+ McException.assertAccessException(!dto.getBuyerName().equals(guyuanNameRepalce(invoice.get("BuyerName").toString())), serialTips + ", 购买方名称不匹配!");
|
|
|
|
|
+ McException.assertAccessException(!dto.getBuyerTaxId().equals(invoice.get("BuyerTaxCode")), serialTips + ", 购买方税号不匹配!");
|
|
|
|
|
+ McException.assertAccessException(!dto.getSellerName().equals(guyuanNameRepalce(invoice.get("SellerName").toString())), serialTips + ", 销售方名称不匹配!");
|
|
|
|
|
+ McException.assertAccessException(!dto.getSellerTaxId().equals(invoice.get("SellerTaxCode")), serialTips + ", 销售方税号不匹配!");
|
|
|
} catch (TencentCloudSDKException e) {
|
|
} catch (TencentCloudSDKException e) {
|
|
|
- McException.exceptionAccess(serial + e.getMessage());
|
|
|
|
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
|
|
+ // prd: 上传发票为假发票时,提示:该发票有疑问,请联系财务人员
|
|
|
|
|
+ String message = e.getMessage();
|
|
|
|
|
+ if (message.contains("发票不存在")) {
|
|
|
|
|
+ message = "有疑问,请联系财务人员";
|
|
|
|
|
+ }
|
|
|
|
|
+ McException.exceptionAccess(serial + message);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}));
|
|
}));
|
|
|
|
|
|
|
|
return McR.success("发票查重, 验真响应");
|
|
return McR.success("发票查重, 验真响应");
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发票状态更新: 服务注册 // todo 通用服务, { 组件Id: 值, 组件id: 值 }
|
|
|
|
|
+ */
|
|
|
|
|
+ @PostMapping("invoice-up")
|
|
|
|
|
+ McR invoice_va(HttpServletRequest request) {
|
|
|
|
|
+ Map data = UtilServlet.getParamMap(request);
|
|
|
|
|
+ log.info("发票状态更新: 服务注册, {}", data);
|
|
|
|
|
+
|
|
|
|
|
+ String compId = UtilMap.getString(data, "compId");
|
|
|
|
|
+ String status = UtilMap.getString(data, "status");
|
|
|
|
|
+
|
|
|
|
|
+ // 读取关联表单
|
|
|
|
|
+ List<String> associationForm = (List<String>) JSON.parse(UtilMap.getString(data, "multiAssociation"));
|
|
|
|
|
+ List<String> formInstanceIds = new ArrayList<>();
|
|
|
|
|
+ for (String record : associationForm) {
|
|
|
|
|
+ // 解析关联表单
|
|
|
|
|
+ List<Map> associationData = (List<Map>) JSON.parse(record);
|
|
|
|
|
+ formInstanceIds.addAll(associationData.stream().map(form -> UtilMap.getString(form, "instanceId")).collect(Collectors.toList()));
|
|
|
|
|
+ }
|
|
|
|
|
+ // 宜搭批量更新
|
|
|
|
|
+ Map update = UtilMap.map(compId, status);
|
|
|
|
|
+ if (compId.equals("selectField_liihyrt6")) {
|
|
|
|
|
+ update.put("radioField_liw7rb2q", "否"); // 提交后, 更新是否退回标识为否
|
|
|
|
|
+ }
|
|
|
|
|
+ ydClient.operateData(YDParam.builder()
|
|
|
|
|
+ .formUuid("FORM-W2A66Z910O9B3LP9C6IYUDPRVWY62DO0YHIILY")
|
|
|
|
|
+ .formInstanceIdList(formInstanceIds)
|
|
|
|
|
+ .updateFormDataJson(JSON.toJSONString(update))
|
|
|
|
|
+ .build(), YDConf.FORM_OPERATION.multi_update);
|
|
|
|
|
+
|
|
|
|
|
+ return McR.success();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发票状态更新: 退回提交
|
|
|
|
|
+ */
|
|
|
|
|
+ @PostMapping("invoice-zy")
|
|
|
|
|
+ McR invoice_zy(@RequestBody Map data) {
|
|
|
|
|
+ log.info("发票状态更新: 退回提交, {}", data);
|
|
|
|
|
+
|
|
|
|
|
+ List<String> pre_ids = (List<String>) data.get("pre_ids"); // 释放修改前
|
|
|
|
|
+ List<String> cur_ids = (List<String>) data.get("cur_ids"); // 占用修改后
|
|
|
|
|
+
|
|
|
|
|
+ // [前端调用添加] 退回为监听宜搭dom事件, 先执行接口调用, 才会校验宜搭必填, 过滤无效调用
|
|
|
|
|
+ if (cur_ids.size() == 0) {
|
|
|
|
|
+ return McR.success();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map pre_update = (Map) data.get("pre_update");
|
|
|
|
|
+ Map cur_update = (Map) data.get("cur_update");
|
|
|
|
|
+
|
|
|
|
|
+ // 宜搭批量更新
|
|
|
|
|
+ ydClient.operateData(YDParam.builder()
|
|
|
|
|
+ .formUuid("FORM-W2A66Z910O9B3LP9C6IYUDPRVWY62DO0YHIILY")
|
|
|
|
|
+ .formInstanceIdList(pre_ids)
|
|
|
|
|
+ .updateFormDataJson(JSON.toJSONString(pre_update))
|
|
|
|
|
+ .build(), YDConf.FORM_OPERATION.multi_update);
|
|
|
|
|
+
|
|
|
|
|
+ ydClient.operateData(YDParam.builder()
|
|
|
|
|
+ .formUuid("FORM-W2A66Z910O9B3LP9C6IYUDPRVWY62DO0YHIILY")
|
|
|
|
|
+ .formInstanceIdList(cur_ids)
|
|
|
|
|
+ .updateFormDataJson(JSON.toJSONString(cur_update))
|
|
|
|
|
+ .build(), YDConf.FORM_OPERATION.multi_update);
|
|
|
|
|
+
|
|
|
|
|
+ return McR.success();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|