|
@@ -1,7 +1,20 @@
|
|
|
package cn.newfeifan.mall.module.distri.service.ptsettlement;
|
|
|
|
|
|
+import cn.hutool.core.io.IoUtil;
|
|
|
+import cn.newfeifan.mall.framework.common.exception.ErrorCode;
|
|
|
+import cn.newfeifan.mall.module.distri.controller.admin.shopsettlement.vo.ShopSettlementRespVO;
|
|
|
+import cn.newfeifan.mall.module.distri.controller.admin.shopsettlement.vo.ShopSettlementSaveReqVO;
|
|
|
+import cn.newfeifan.mall.module.distri.dal.dataobject.shopsettlement.data.ShopSettlementErrorData;
|
|
|
+import cn.newfeifan.mall.module.distri.dal.dataobject.shopsettlement.data.ShopSettlementPDFData;
|
|
|
+import cn.newfeifan.mall.module.distri.dal.dataobject.shopsettlement.file.BufferedImageMultipartFile;
|
|
|
+import cn.newfeifan.mall.module.distri.enums.PtSettlemntStatusEnum;
|
|
|
+import cn.newfeifan.mall.module.distri.service.shopsettlement.ShopSettlementService;
|
|
|
+import cn.newfeifan.mall.module.infra.service.file.FileService;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
import javax.annotation.Resource;
|
|
|
+
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
|
|
|
import cn.newfeifan.mall.module.distri.controller.admin.ptsettlement.vo.*;
|
|
@@ -14,6 +27,22 @@ import cn.newfeifan.mall.module.distri.dal.mysql.ptsettlement.PtSettlementMapper
|
|
|
import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
import static cn.newfeifan.mall.module.distri.enums.ErrorCodeConstants.*;
|
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+
|
|
|
+import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
+import org.apache.pdfbox.rendering.PDFRenderer;
|
|
|
+import org.apache.pdfbox.text.PDFTextStripper;
|
|
|
+
|
|
|
+import java.awt.image.BufferedImage;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* 平台每日结算信息表,记录店铺的结算信息 Service 实现类
|
|
|
*
|
|
@@ -26,6 +55,12 @@ public class PtSettlementServiceImpl implements PtSettlementService {
|
|
|
@Resource
|
|
|
private PtSettlementMapper ptSettlementMapper;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private ShopSettlementService shopSettlementService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FileService fileService;
|
|
|
+
|
|
|
@Override
|
|
|
public Long createPtSettlement(PtSettlementSaveReqVO createReqVO) {
|
|
|
// 插入
|
|
@@ -74,4 +109,348 @@ public class PtSettlementServiceImpl implements PtSettlementService {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public List<ShopSettlementErrorData> importPdf(MultipartFile multipartFile, Long id) {
|
|
|
+ // 定义日期格式,只包含年月日
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+
|
|
|
+ // 校验文件格式
|
|
|
+ if (!isPdf(multipartFile)) {
|
|
|
+ ErrorCode ERROR = new ErrorCode(1_002_030_026, "选择的文件不是pdf格式");
|
|
|
+ throw exception(ERROR);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取选择日期,店铺的结算信息
|
|
|
+ List<ShopSettlementPDFData> shopSettList = shopSettlementService.selectListByPtId(id);
|
|
|
+
|
|
|
+ if (shopSettList.isEmpty()) {
|
|
|
+ ErrorCode ERROR = new ErrorCode(1_002_030_027, "选择的行中没有结算单记录");
|
|
|
+ throw exception(ERROR);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 用来记录错误的回执单信息
|
|
|
+ List<ShopSettlementErrorData> errorList = new ArrayList<>();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 将MultipartFile转换为临时文件
|
|
|
+ Path tempFile = Files.createTempFile("temp", ".pdf");
|
|
|
+ multipartFile.transferTo(tempFile.toFile());
|
|
|
+
|
|
|
+ // 加载指定路径的PDF文档
|
|
|
+ PDDocument document = PDDocument.load(tempFile.toFile());
|
|
|
+
|
|
|
+ // 创建PDF渲染器,用于将PDF页面渲染为图像
|
|
|
+ PDFRenderer pdfRenderer = new PDFRenderer(document);
|
|
|
+
|
|
|
+ // 创建PDF文本剥离器,用于从PDF页面中提取文本
|
|
|
+ PDFTextStripper textStripper = new PDFTextStripper();
|
|
|
+
|
|
|
+ // 遍历文档中的每一页
|
|
|
+ for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) {
|
|
|
+
|
|
|
+ // 设置开始和结束页码,以便仅从当前页提取文本
|
|
|
+ textStripper.setStartPage(pageIndex + 1);
|
|
|
+ textStripper.setEndPage(pageIndex + 1);
|
|
|
+
|
|
|
+ // 提取当前页的文本
|
|
|
+ String pageText = textStripper.getText(document);
|
|
|
+
|
|
|
+ // 检查页面是否包含错误信息
|
|
|
+ if (containsErrorInfo(pageText)) {
|
|
|
+
|
|
|
+ // 账号
|
|
|
+ String accountNumber = getErrorAccountNumber(pageText, true);
|
|
|
+ // 户名
|
|
|
+ String accountName = getErrorAccountName(pageText, true);
|
|
|
+ // 金额
|
|
|
+ String money = getMoney(pageText);
|
|
|
+ // 订单流水号
|
|
|
+ String orderNO = getOrderNO(pageText);
|
|
|
+
|
|
|
+ ShopSettlementErrorData error = ShopSettlementErrorData.builder()
|
|
|
+ .accountNumber(accountNumber)
|
|
|
+ .accountName(accountName)
|
|
|
+ .money(money)
|
|
|
+ .orderNO(orderNO)
|
|
|
+ .build();
|
|
|
+
|
|
|
+ errorList.add(error);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断备注的账单日期是否与结算单为同一天
|
|
|
+ String time = getOrderTime(pageText);
|
|
|
+ if (time != null && !time.isEmpty()) {
|
|
|
+ for (ShopSettlementPDFData shopSettlementPDFData : shopSettList) {
|
|
|
+ // 判断日期是否同样
|
|
|
+ isSameDate(shopSettlementPDFData, formatter, time);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (ShopSettlementPDFData shopSettlementPDFData : shopSettList) {
|
|
|
+ // 账号
|
|
|
+ String accountNumber = getErrorAccountNumber(pageText, false);
|
|
|
+ // 户名
|
|
|
+ String accountName = getErrorAccountName(pageText, false);
|
|
|
+ // 金额
|
|
|
+ String money = getMoney(pageText);
|
|
|
+
|
|
|
+ if (shopSettlementPDFData.getShop().getAccountNumber().equals(accountNumber) &&
|
|
|
+ shopSettlementPDFData.getShop().getAccountName().equals(accountName) &&
|
|
|
+ shopSettlementPDFData.getShopTotalHighAmount().divide(new BigDecimal("100"), 2, RoundingMode.FLOOR).toString().equals(money)) {
|
|
|
+
|
|
|
+ BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300);
|
|
|
+ int desiredHeight = 1200; // 设定你想要的高度
|
|
|
+
|
|
|
+ // 获取原始宽度和高度
|
|
|
+ int originalWidth = image.getWidth();
|
|
|
+ int originalHeight = image.getHeight();
|
|
|
+
|
|
|
+ // 计算裁剪区域的宽度和高度
|
|
|
+ int cropWidth = originalWidth;
|
|
|
+ int cropHeight = desiredHeight;
|
|
|
+
|
|
|
+ // 确保裁剪区域不会超出原图范围
|
|
|
+ if (cropHeight > originalHeight) {
|
|
|
+ cropHeight = originalHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算裁剪区域的起始点,这里我们假设从顶部开始裁剪
|
|
|
+ int x = 0;
|
|
|
+ int y = 0;
|
|
|
+
|
|
|
+ // 裁剪图片
|
|
|
+ BufferedImage croppedImage = image.getSubimage(x, y, cropWidth, cropHeight);
|
|
|
+
|
|
|
+ String fileName = "image.png";
|
|
|
+ String contentType = "image/png";
|
|
|
+
|
|
|
+ MultipartFile file = new BufferedImageMultipartFile(croppedImage, fileName, contentType);
|
|
|
+
|
|
|
+ // 上传文件
|
|
|
+ String fileUrl = fileService.createFile(file.getOriginalFilename(), null, IoUtil.readBytes(file.getInputStream()));
|
|
|
+
|
|
|
+ ShopSettlementSaveReqVO bean = BeanUtils.toBean(shopSettlementPDFData, ShopSettlementSaveReqVO.class);
|
|
|
+ bean.setAttachment(fileUrl);
|
|
|
+
|
|
|
+ // 保存修改结算单
|
|
|
+ shopSettlementService.updateShopSettlement(bean);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回退错误的回执单导致的结算单信息
|
|
|
+ if (!errorList.isEmpty()) {
|
|
|
+
|
|
|
+ for (ShopSettlementErrorData shopSettlementErrorData : errorList) {
|
|
|
+
|
|
|
+ for (ShopSettlementPDFData shopSettlementPDFData : shopSettList) {
|
|
|
+
|
|
|
+ if (shopSettlementPDFData.getShop().getAccountNumber().equals(shopSettlementErrorData.getAccountNumber()) &&
|
|
|
+ shopSettlementPDFData.getShop().getAccountName().equals(shopSettlementErrorData.getAccountName()) &&
|
|
|
+ shopSettlementPDFData.getShopTotalHighAmount().divide(new BigDecimal("100"), 2, RoundingMode.FLOOR).toString().equals(shopSettlementErrorData.getMoney())
|
|
|
+ ) {
|
|
|
+ ShopSettlementSaveReqVO bean = BeanUtils.toBean(shopSettlementPDFData, ShopSettlementSaveReqVO.class);
|
|
|
+ bean.setAttachment(null);
|
|
|
+
|
|
|
+ shopSettlementErrorData.setShopSettlement(BeanUtils.toBean(bean, ShopSettlementRespVO.class));
|
|
|
+
|
|
|
+ shopSettlementService.updateShopSettlement(bean);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean status = true;
|
|
|
+ for (ShopSettlementPDFData shopSettlementPDFData : shopSettList) {
|
|
|
+ if (shopSettlementPDFData.getAttachment() == null) {
|
|
|
+ status = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ PtSettlementDO ptSettlementDO = ptSettlementMapper.selectById(id);
|
|
|
+ if (status) {
|
|
|
+ ptSettlementDO.setStatus(PtSettlemntStatusEnum.SETTLEMENT.getStatus());
|
|
|
+ } else {
|
|
|
+ ptSettlementDO.setStatus(PtSettlemntStatusEnum.SETTLEMENT_ERROR.getStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果错误的回执单数量和结算单数量一致,则说明全部失败
|
|
|
+ if (errorList.size() == shopSettList.size()) {
|
|
|
+ ptSettlementDO.setStatus(PtSettlemntStatusEnum.SETTLEMENT_SUCCESS.getStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新pt结算单状态
|
|
|
+ ptSettlementMapper.updateById(ptSettlementDO);
|
|
|
+
|
|
|
+ // 关闭PDF文档
|
|
|
+ document.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ ErrorCode ERROR = new ErrorCode(1_002_030_027, "处理PDF文件异常: " + e.getMessage());
|
|
|
+ throw exception(ERROR);
|
|
|
+ }
|
|
|
+
|
|
|
+ return errorList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断备注的账单日期是否与结算单为同一天
|
|
|
+ *
|
|
|
+ * @param shopSettlementPDFData 结算单
|
|
|
+ * @param formatter 日期格式
|
|
|
+ * @param time 日期
|
|
|
+ */
|
|
|
+ private void isSameDate(ShopSettlementPDFData shopSettlementPDFData, DateTimeFormatter formatter, String time) {
|
|
|
+ // 使用定义的格式器格式化LocalDateTime对象
|
|
|
+ String formattedDate = shopSettlementPDFData.getCreateTime().minusDays(1).format(formatter);
|
|
|
+
|
|
|
+ if (!time.equals(formattedDate)) {
|
|
|
+ ErrorCode ERROR = new ErrorCode(1_002_030_028, "上传的PDF文件中有回执单日期与结算单日期不同!");
|
|
|
+ throw exception(ERROR);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取回执单的备注:账单日期
|
|
|
+ *
|
|
|
+ * @param pageText 回执单文本
|
|
|
+ * @return 账单日期
|
|
|
+ */
|
|
|
+ private String getOrderTime(String pageText) {
|
|
|
+ int timeIndex = pageText.indexOf("账单日期:");
|
|
|
+
|
|
|
+ if (timeIndex == -1) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ timeIndex = timeIndex + "账单日期:".length();
|
|
|
+
|
|
|
+ int timeEndIndex = pageText.indexOf("温馨提示");
|
|
|
+
|
|
|
+ return pageText.substring(timeIndex, timeEndIndex).trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取错误的回执单流水号
|
|
|
+ *
|
|
|
+ * @param pageText 回执单文本
|
|
|
+ * @return 流水号
|
|
|
+ */
|
|
|
+ private String getOrderNO(String pageText) {
|
|
|
+
|
|
|
+ int NOIndex = pageText.indexOf("关联正向交易账务流水号:");
|
|
|
+ NOIndex = NOIndex + "关联正向交易账务流水号:".length();
|
|
|
+
|
|
|
+ int NOEndIndex = pageText.indexOf("温馨提示");
|
|
|
+
|
|
|
+ return pageText.substring(NOIndex, NOEndIndex).trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取错误的回执单户名
|
|
|
+ *
|
|
|
+ * @param pageText 回执单文本
|
|
|
+ * @param isError 是否是错误类型
|
|
|
+ * @return 户名
|
|
|
+ */
|
|
|
+ private String getErrorAccountName(String pageText, boolean isError) {
|
|
|
+
|
|
|
+ if (isError) {
|
|
|
+ int nameIndex = pageText.indexOf("户名");
|
|
|
+ nameIndex = nameIndex + "户名".length();
|
|
|
+
|
|
|
+ int nameEndIndex = pageText.indexOf("收");
|
|
|
+
|
|
|
+ return pageText.substring(nameIndex, nameEndIndex).trim();
|
|
|
+ } else {
|
|
|
+ int nameIndex = pageText.lastIndexOf("户名");
|
|
|
+ nameIndex = nameIndex + "户名".length();
|
|
|
+
|
|
|
+ int nameEndIndex = pageText.indexOf("账号");
|
|
|
+
|
|
|
+ return pageText.substring(nameIndex, nameEndIndex).trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取错误的回执单账号
|
|
|
+ *
|
|
|
+ * @param pageText 回执单文本
|
|
|
+ * @param isError 是否是错误类型
|
|
|
+ * @return 账号
|
|
|
+ */
|
|
|
+ private String getErrorAccountNumber(String pageText, boolean isError) {
|
|
|
+
|
|
|
+ // 错误的回执单和正确的回执单截取的位置不同
|
|
|
+ if (isError) {
|
|
|
+ int accountNumberIndex = pageText.indexOf("账号");
|
|
|
+ accountNumberIndex = accountNumberIndex + "账号".length();
|
|
|
+
|
|
|
+ int accountNumberEndIndex = pageText.lastIndexOf("账号");
|
|
|
+
|
|
|
+ return pageText.substring(accountNumberIndex, accountNumberEndIndex).trim();
|
|
|
+ } else {
|
|
|
+ int accountNumberIndex = pageText.lastIndexOf("账号");
|
|
|
+ accountNumberIndex = accountNumberIndex + "账号".length();
|
|
|
+
|
|
|
+ int accountNumberEndIndex = pageText.indexOf("机构");
|
|
|
+
|
|
|
+ return pageText.substring(accountNumberIndex, accountNumberEndIndex).trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取回执单中的金额
|
|
|
+ *
|
|
|
+ * @param pageText 回执单文本
|
|
|
+ * @return 金额
|
|
|
+ */
|
|
|
+ private String getMoney(String pageText) {
|
|
|
+ int rmbIndex = pageText.indexOf("人民币");
|
|
|
+ rmbIndex = rmbIndex + "人民币".length();
|
|
|
+ int payTimeIndex = pageText.indexOf("交易时间");
|
|
|
+
|
|
|
+ return pageText.substring(rmbIndex, payTimeIndex).trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查给定的页面文本是否包含错误信息的字样
|
|
|
+ *
|
|
|
+ * @param pageText 要检查的页面文本
|
|
|
+ * @return 如果页面文本包含"关联正向交易账务流水号",则返回true;否则返回false
|
|
|
+ */
|
|
|
+ private static boolean containsErrorInfo(String pageText) {
|
|
|
+ return pageText.contains("关联正向交易账务流水号");
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isPdf(MultipartFile file) {
|
|
|
+ // Check file extension
|
|
|
+ String originalFilename = file.getOriginalFilename();
|
|
|
+ if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".pdf")) {
|
|
|
+ System.out.println("文件不是PDF格式,扩展名不符合要求");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+// // Optionally, check file header for added security
|
|
|
+// try {
|
|
|
+// byte[] headerBytes = new byte[4];
|
|
|
+// try (InputStream is = file.getInputStream()) {
|
|
|
+// is.read(headerBytes, 0, headerBytes.length);
|
|
|
+// }
|
|
|
+// String header = new String(headerBytes);
|
|
|
+// if (!header.equals("%PDF-")) {
|
|
|
+// System.out.println("文件不是PDF格式,文件头不符合要求");
|
|
|
+// return false;
|
|
|
+// }
|
|
|
+// } catch (IOException e) {
|
|
|
+// System.out.println("读取文件头时发生错误:" + e.getMessage());
|
|
|
+// return false;
|
|
|
+// }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
}
|