|
|
@@ -12,6 +12,8 @@ import com.google.gson.JsonObject;
|
|
|
import io.micrometer.core.instrument.util.StringUtils;
|
|
|
import io.swagger.annotations.ApiModelProperty;
|
|
|
import org.apache.logging.log4j.util.Strings;
|
|
|
+import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
+import org.apache.pdfbox.text.PDFTextStripper;
|
|
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
|
|
import org.apache.poi.ss.usermodel.*;
|
|
|
import org.apache.shiro.SecurityUtils;
|
|
|
@@ -39,6 +41,7 @@ import org.jeecg.modules.saleCode.entity.SaleInterfaceItem;
|
|
|
import org.jeecg.modules.saleCode.mapper.SaleInterfaceItemMapper;
|
|
|
import org.jeecg.modules.saleCode.mapper.SaleInterfaceSyncMapper;
|
|
|
import org.jeecg.modules.saleCode.service.ISaleInterfaceSyncService;
|
|
|
+import org.jeecg.modules.saleCode.util.CoordinateTextStripper;
|
|
|
import org.jeecg.modules.saleCode.util.HttpUtils;
|
|
|
import org.jeecg.modules.saleCode.util.MonthUtil;
|
|
|
import org.jeecg.modules.saleCode.util.PDFTableReader;
|
|
|
@@ -53,6 +56,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
+import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
import java.io.Serializable;
|
|
|
import java.math.BigDecimal;
|
|
|
@@ -340,16 +345,56 @@ public class SaleInterfaceSyncServiceImpl extends ServiceImpl<SaleInterfaceSyncM
|
|
|
return Result.OK("执行成功");
|
|
|
}
|
|
|
|
|
|
- public static void main(String[] args) {
|
|
|
- String dateStr = "2025-04-11T08:33:50.963Z";
|
|
|
+ public static void main(String[] args) throws IOException {
|
|
|
+// String dateStr = "2025-04-11T08:33:50.963Z";
|
|
|
+//
|
|
|
+// Instant instant = Instant.parse(dateStr);
|
|
|
+// Date date = Date.from(instant);
|
|
|
+// BaseShipArchive baseShipArchive = new BaseShipArchive();
|
|
|
+// System.out.println(baseShipArchive.getId());
|
|
|
+
|
|
|
+ // 1. 加载PDF文件
|
|
|
+ PDDocument document = PDDocument.load(new File("E:\\新建文件夹\\RFQ DMS_250237.pdf"));
|
|
|
+ PDFTextStripper stripper = new PDFTextStripper();
|
|
|
+ String text = stripper.getText(document);
|
|
|
+// System.out.println(text);
|
|
|
+
|
|
|
+
|
|
|
+ System.out.println("=============================="); // 输出结果
|
|
|
+ String requestNo = extractField(text, "Request No\\.:", "\\n");
|
|
|
+ System.out.println("1. Request No.: " + requestNo);
|
|
|
+ String Buyer = extractField(text, "Buyer\\:", "\\n");
|
|
|
+ System.out.println("Buyer: " + Buyer);
|
|
|
+ String For = extractField(text, "For\\:", "\\n");
|
|
|
+ System.out.println("For: " + For);
|
|
|
+ String VesselIMOnumber = extractField(text, "Vessel IMO number\\:", "\\n");
|
|
|
+ System.out.println("Vessel IMO number: " + VesselIMOnumber);
|
|
|
+
|
|
|
+ String Equipment = extractField(text, "Equipment\\:", "\\n");
|
|
|
+ System.out.println("Equipment: " + Equipment);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ document.close();
|
|
|
+
|
|
|
|
|
|
- Instant instant = Instant.parse(dateStr);
|
|
|
- Date date = Date.from(instant);
|
|
|
- BaseShipArchive baseShipArchive = new BaseShipArchive();
|
|
|
- System.out.println(baseShipArchive.getId());
|
|
|
}
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
+ * 提取单个字段值
|
|
|
+ */
|
|
|
+ private static String extractField(String text, String fieldName, String delimiter) {
|
|
|
+ Pattern pattern = Pattern.compile(fieldName + "\\s*([^" + delimiter + "]+)");
|
|
|
+ Matcher matcher = pattern.matcher(text);
|
|
|
+ if (matcher.find()) {
|
|
|
+ return matcher.group(1).trim();
|
|
|
+ }
|
|
|
+ return "未找到";
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public synchronized Result<String> actionSyncOrder(Collection<? extends Serializable> idList) {
|
|
|
@@ -672,6 +717,10 @@ public class SaleInterfaceSyncServiceImpl extends ServiceImpl<SaleInterfaceSyncM
|
|
|
case "12":
|
|
|
OMAN(file);
|
|
|
break;
|
|
|
+ case "13":
|
|
|
+// RFQ(file, txt);
|
|
|
+ extractItems(file);
|
|
|
+ break;
|
|
|
default:
|
|
|
return;
|
|
|
}
|
|
|
@@ -1495,6 +1544,105 @@ public class SaleInterfaceSyncServiceImpl extends ServiceImpl<SaleInterfaceSyncM
|
|
|
|
|
|
}
|
|
|
|
|
|
+ public void RFQ(MultipartFile file,String text) throws Exception {
|
|
|
+
|
|
|
+ SaleInterfaceSync saleInterfaceSync = new SaleInterfaceSync();
|
|
|
+ LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
|
|
+ String id = UUIDGenerator.generate();
|
|
|
+ saleInterfaceSync.setId(id);
|
|
|
+ saleInterfaceSync.setCreateBy(sysUser.getUsername());
|
|
|
+
|
|
|
+ String requestNo = extractField(text, "Request No\\.:", "\\n");
|
|
|
+ String Buyer = extractField(text, "Buyer\\:", "\\n");
|
|
|
+ String Buyer2 = extractField(text, "Buyer contact\\:", "\\n");
|
|
|
+ String For = extractField(text, "For\\:", "\\n");
|
|
|
+ String VesselIMOnumber = extractField(text, "Vessel IMO number\\:", "\\n");
|
|
|
+ String Equipment = extractField(text, "Equipment\\:", "\\n");
|
|
|
+
|
|
|
+ saleInterfaceSync.setReferenceNumber(requestNo);
|
|
|
+ if(StringUtils.isNotBlank(Buyer2) && !Buyer2.equals("User")){
|
|
|
+
|
|
|
+ saleInterfaceSync.setBuyerName(Buyer+" "+Buyer2);
|
|
|
+ }else{
|
|
|
+
|
|
|
+ saleInterfaceSync.setBuyerName(Buyer);
|
|
|
+ }
|
|
|
+ saleInterfaceSync.setVesselImo(VesselIMOnumber);
|
|
|
+ saleInterfaceSync.setVesselCode(For);
|
|
|
+ saleInterfaceSync.setSubject(Equipment);
|
|
|
+
|
|
|
+ List<String> fieldList = new ArrayList<>();
|
|
|
+ fieldList.add("No.");
|
|
|
+ fieldList.add("Item name");
|
|
|
+ fieldList.add("Vendor");
|
|
|
+ fieldList.add("Buyer");
|
|
|
+ fieldList.add("Maker's No.");
|
|
|
+ fieldList.add("IMPA");
|
|
|
+ fieldList.add("Maker");
|
|
|
+ fieldList.add("Quantity");
|
|
|
+ fieldList.add("Unit");
|
|
|
+
|
|
|
+ List<String> ignoreList = new ArrayList<>();
|
|
|
+ ignoreList.add("Page");
|
|
|
+ ignoreList.add("Request For Quotation");
|
|
|
+ ignoreList.add("LIMITED as Managers");
|
|
|
+ ignoreList.add("behalf of Owners Abundance");
|
|
|
+ ignoreList.add("Shipsure Version Number");
|
|
|
+ ignoreList.add("Plate / Sheet No");
|
|
|
+ ignoreList.add("Order Line Notes");
|
|
|
+ ignoreList.add("Sub Total");
|
|
|
+ ignoreList.add("Freight Cost");
|
|
|
+ ignoreList.add("Packaging Cost");
|
|
|
+ ignoreList.add("Grand Total");
|
|
|
+ ignoreList.add("(%)");
|
|
|
+ ignoreList.add("1 of");
|
|
|
+ ignoreList.add("2 of");
|
|
|
+ ignoreList.add("3 of");
|
|
|
+ ignoreList.add("4 of");
|
|
|
+ ignoreList.add("5 of");
|
|
|
+ ignoreList.add("6 of");
|
|
|
+ ignoreList.add("7 of");
|
|
|
+ ignoreList.add("8 of");
|
|
|
+
|
|
|
+ String[] extra = {"/"};
|
|
|
+// PdfTable pdfTable = PDFTableReader.handlePdf(file, "Request - Opened");
|
|
|
+ PdfTable pdfTable = PDFTableReader.handlePdf(file, "Items");
|
|
|
+ JSONArray jsonArray = PDFTableReader.getPdfTable(pdfTable, fieldList, ignoreList, extra, "Item name", "Unit", "left", true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ Map<String, JSONObject> willSortedData = new HashMap<>();
|
|
|
+ for(Object data : jsonArray) {
|
|
|
+ willSortedData.put(((JSONObject)data).getString("No."), (JSONObject)data);
|
|
|
+ }
|
|
|
+ List<JSONObject> values = willSortedData.values().stream().sorted(new Comparator<JSONObject>() {
|
|
|
+ @Override
|
|
|
+ public int compare(JSONObject o1, JSONObject o2) {
|
|
|
+ return (o1.getInteger("No.") - o2.getInteger("No."));
|
|
|
+ }}).collect(Collectors.toList());
|
|
|
+
|
|
|
+ jsonArray = JSONArray.parseArray(JSONArray.toJSONString(values));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("去重失败", e);
|
|
|
+ }
|
|
|
+
|
|
|
+ for(Object node : jsonArray) {
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(String.valueOf(node));
|
|
|
+ if(jsonObject.size() >= 0) {
|
|
|
+ SaleInterfaceItem saleInterfaceItem = new SaleInterfaceItem();
|
|
|
+ saleInterfaceItem.setNumber(Integer.valueOf(jsonObject.getString("No.")));
|
|
|
+ saleInterfaceItem.setDescription(jsonObject.getString("Item name"));
|
|
|
+ String formattedStr = jsonObject.getString("Quantity").replace(',', '.');
|
|
|
+ saleInterfaceItem.setQuantity(formattedStr);
|
|
|
+ saleInterfaceItem.setUnitOfMeasure(jsonObject.getString("Unit") == null ? "" : jsonObject.getString("Unit").toUpperCase());
|
|
|
+ saleInterfaceItem.setHeadId(id);
|
|
|
+ saleInterfaceItemMapper.insert(saleInterfaceItem);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ saleInterfaceSyncMapper.insert(saleInterfaceSync);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
public void OMAN(MultipartFile file) throws Exception {
|
|
|
try {
|
|
|
|
|
|
@@ -1730,4 +1878,196 @@ public class SaleInterfaceSyncServiceImpl extends ServiceImpl<SaleInterfaceSyncM
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private static final float Y_TOLERANCE = 5.0f;
|
|
|
+ private static final List<String> UNITS = Arrays.asList("PCE", "PC", "SET", "EA", "PR", "UNIT", "NOS");
|
|
|
+
|
|
|
+ public List<Map<String, Object>> extractItems(MultipartFile file) {
|
|
|
+ try (PDDocument document = PDDocument.load(file.getInputStream())) {
|
|
|
+ CoordinateTextStripper stripper = new CoordinateTextStripper();
|
|
|
+ stripper.setSortByPosition(true);
|
|
|
+ stripper.getText(document);
|
|
|
+
|
|
|
+ List<CoordinateTextStripper.TextChunk> chunks = stripper.getTextChunks();
|
|
|
+
|
|
|
+ // 1. 按 Y 坐标分组(同一行)
|
|
|
+ Map<Float, List<CoordinateTextStripper.TextChunk>> rows = new TreeMap<>();
|
|
|
+ for (CoordinateTextStripper.TextChunk chunk : chunks) {
|
|
|
+ float yKey = Math.round(chunk.getY() / Y_TOLERANCE) * Y_TOLERANCE;
|
|
|
+ rows.computeIfAbsent(yKey, k -> new ArrayList<>()).add(chunk);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 将每行的文本块按 X 坐标排序,得到单元格列表
|
|
|
+ List<List<String>> table = new ArrayList<>();
|
|
|
+ for (List<CoordinateTextStripper.TextChunk> rowChunks : rows.values()) {
|
|
|
+ rowChunks.sort(Comparator.comparing(CoordinateTextStripper.TextChunk::getX));
|
|
|
+ List<String> rowCells = rowChunks.stream()
|
|
|
+ .map(CoordinateTextStripper.TextChunk::getText)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ table.add(rowCells);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 定位 "Items" 标题所在行
|
|
|
+ int itemsRowIndex = -1;
|
|
|
+ for (int i = 0; i < table.size(); i++) {
|
|
|
+ List<String> row = table.get(i);
|
|
|
+ String rowText = String.join("", row);
|
|
|
+ if (rowText.contains("Items")) {
|
|
|
+ itemsRowIndex = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (itemsRowIndex == -1) {
|
|
|
+ throw new RuntimeException("未找到 Items 标题");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 解析 Items 后的每一行
|
|
|
+ List<Map<String, Object>> items = new ArrayList<>();
|
|
|
+ for (int i = itemsRowIndex + 1; i < table.size(); i++) {
|
|
|
+ List<String> row = table.get(i);
|
|
|
+ if (row.isEmpty()) continue;
|
|
|
+
|
|
|
+ String fullRowText = String.join(" ", row).trim();
|
|
|
+ String firstCell = row.get(0).trim();
|
|
|
+
|
|
|
+ // 判断是否为数据行(首单元格以数字开头)
|
|
|
+ if (firstCell.isEmpty() || !Character.isDigit(firstCell.charAt(0))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取序号
|
|
|
+ String no = extractNumberPrefix(firstCell);
|
|
|
+ String description;
|
|
|
+ String quantity;
|
|
|
+ String unit;
|
|
|
+
|
|
|
+ if (row.size() == 1) {
|
|
|
+ // 情形1:整行合并为一个文本块 -> 从完整字符串解析
|
|
|
+ ItemInfo info = parseItemFromText(fullRowText);
|
|
|
+ description = info.description;
|
|
|
+ quantity = info.quantity;
|
|
|
+ unit = info.unit;
|
|
|
+ } else {
|
|
|
+ // 情形2:多列,最后一列为数量+单位
|
|
|
+ String lastCell = row.get(row.size() - 1).trim();
|
|
|
+ ItemInfo lastCellInfo = parseQuantityUnit(lastCell);
|
|
|
+ if (lastCellInfo != null && !lastCellInfo.quantity.isEmpty()) {
|
|
|
+ quantity = lastCellInfo.quantity;
|
|
|
+ unit = lastCellInfo.unit;
|
|
|
+ // 描述为中间列合并
|
|
|
+ StringBuilder descBuilder = new StringBuilder();
|
|
|
+ for (int j = 1; j < row.size() - 1; j++) {
|
|
|
+ descBuilder.append(row.get(j)).append(" ");
|
|
|
+ }
|
|
|
+ description = descBuilder.toString().trim();
|
|
|
+ } else {
|
|
|
+ // 若最后一列不包含数量,则所有剩余列作为描述
|
|
|
+ StringBuilder descBuilder = new StringBuilder();
|
|
|
+ for (int j = 1; j < row.size(); j++) {
|
|
|
+ descBuilder.append(row.get(j)).append(" ");
|
|
|
+ }
|
|
|
+ description = descBuilder.toString().trim();
|
|
|
+ quantity = "1.0";
|
|
|
+ unit = "PCE";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 修正数量为0的情况
|
|
|
+ try {
|
|
|
+ double qtyVal = Double.parseDouble(quantity);
|
|
|
+ if (qtyVal == 0.0) quantity = "1.0";
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 保持原样
|
|
|
+ }
|
|
|
+
|
|
|
+ description = description.trim().replaceAll("\\s+", " ");
|
|
|
+
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
+ item.put("No.", no);
|
|
|
+ item.put("Item name", description);
|
|
|
+ item.put("Vendor cat. No.", "");
|
|
|
+ item.put("Buyer part no", "");
|
|
|
+ item.put("Maker's No.", "");
|
|
|
+ item.put("IMPA", "");
|
|
|
+ item.put("Maker", "");
|
|
|
+ item.put("Quantity", quantity);
|
|
|
+ item.put("Unit", unit);
|
|
|
+ items.add(item);
|
|
|
+ }
|
|
|
+
|
|
|
+ return items;
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new RuntimeException("PDF 解析失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private ItemInfo parseItemFromText(String text) {
|
|
|
+ String no = extractNumberPrefix(text);
|
|
|
+ if (no.isEmpty()) {
|
|
|
+ return new ItemInfo("", text, "1.0", "PCE");
|
|
|
+ }
|
|
|
+ String remaining = text.substring(no.length()).trim();
|
|
|
+
|
|
|
+ ItemInfo qtyInfo = parseQuantityUnit(remaining);
|
|
|
+ if (qtyInfo != null && !qtyInfo.quantity.isEmpty()) {
|
|
|
+ int qtyStart = remaining.lastIndexOf(qtyInfo.quantity + qtyInfo.unit);
|
|
|
+ if (qtyStart >= 0) {
|
|
|
+ String description = remaining.substring(0, qtyStart).trim();
|
|
|
+ return new ItemInfo(no, description, qtyInfo.quantity, qtyInfo.unit);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new ItemInfo(no, remaining, "1.0", "PCE");
|
|
|
+ }
|
|
|
+
|
|
|
+ private ItemInfo parseQuantityUnit(String text) {
|
|
|
+ String upperText = text.toUpperCase();
|
|
|
+ for (String unit : UNITS) {
|
|
|
+ if (upperText.endsWith(unit)) {
|
|
|
+ int unitStart = text.length() - unit.length();
|
|
|
+ String numPart = text.substring(0, unitStart).trim();
|
|
|
+ String quantity = extractTrailingNumber(numPart);
|
|
|
+ if (!quantity.isEmpty()) {
|
|
|
+ return new ItemInfo("", "", quantity, unit);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String extractNumberPrefix(String s) {
|
|
|
+ StringBuilder num = new StringBuilder();
|
|
|
+ for (char c : s.toCharArray()) {
|
|
|
+ if (Character.isDigit(c)) {
|
|
|
+ num.append(c);
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return num.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String extractTrailingNumber(String s) {
|
|
|
+ if (s.isEmpty()) return "";
|
|
|
+ int i = s.length() - 1;
|
|
|
+ while (i >= 0 && (Character.isDigit(s.charAt(i)) || s.charAt(i) == '.' || s.charAt(i) == ',')) {
|
|
|
+ i--;
|
|
|
+ }
|
|
|
+ String number = s.substring(i + 1).trim();
|
|
|
+ number = number.replace(',', '.');
|
|
|
+ return number;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class ItemInfo {
|
|
|
+ String no;
|
|
|
+ String description;
|
|
|
+ String quantity;
|
|
|
+ String unit;
|
|
|
+
|
|
|
+ ItemInfo(String no, String description, String quantity, String unit) {
|
|
|
+ this.no = no;
|
|
|
+ this.description = description;
|
|
|
+ this.quantity = quantity;
|
|
|
+ this.unit = unit;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|