From 2e6830bcf59c01f29f040652b3e5209a7f21238e Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期一, 21 九月 2026 10:23:50 +0800
Subject: [PATCH] feat(投资): 投资系统导出新模板按表头解析 + 审核基准缺失保护 + 十堰源文件清理
---
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java | 1639 +++++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 1,453 insertions(+), 186 deletions(-)
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
index 04b7e2e..446043d 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -2,7 +2,9 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trafficaudit.auditengine.entity.AuditResult;
+import com.trafficaudit.auditengine.entity.AuditRun;
import com.trafficaudit.auditengine.mapper.AuditResultMapper;
+import com.trafficaudit.auditengine.mapper.AuditRunMapper;
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.dataimport.entity.EnergyAuthVehicle;
import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly;
@@ -18,6 +20,8 @@
import com.trafficaudit.dataimport.entity.CityBusMonthly;
import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
import com.trafficaudit.dataimport.entity.CityTaxiAuth;
+import com.trafficaudit.dataimport.entity.WycOrderMonthly;
+import com.trafficaudit.dataimport.entity.WycTotalMonthly;
import com.trafficaudit.dataimport.entity.ScaleSplitTransport;
import com.trafficaudit.dataimport.entity.TransportAuthVehicle;
import com.trafficaudit.dataimport.entity.VehicleTrackMileage;
@@ -36,6 +40,8 @@
import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper;
+import com.trafficaudit.dataimport.mapper.WycOrderMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.WycTotalMonthlyMapper;
import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper;
import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper;
import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper;
@@ -50,8 +56,17 @@
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
+import org.apache.poi.xwpf.usermodel.XWPFTableRow;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
import cn.hutool.core.io.IoUtil;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
@@ -67,12 +82,14 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.trafficaudit.dataimport.dto.ImportResult;
import org.apache.poi.ss.usermodel.DateUtil;
import java.util.Comparator;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.TreeMap;
import java.util.stream.Collectors;
import java.text.SimpleDateFormat;
@@ -94,6 +111,9 @@
/** H204 鐕冩枡1璁¢噺鍗曚綅鐮� -> 涓枃鍗曚綅 */
private static final Map<String, String> ENERGY_FUEL_UNIT_NAMES = new HashMap<>();
+
+ /** 鑳借�楀鍏ユ寜鎶ヨ〃鏈熶簰鏂ワ紝闃叉鍚屾姤琛ㄦ湡骞跺彂閲嶅浜掔浉瑕嗙洊 */
+ private final ConcurrentHashMap<String, Object> energyImportLocks = new ConcurrentHashMap<>();
static {
ENERGY_VEHICLE_TYPE_NAMES.put("01", "鏅�氳揣杞�");
@@ -135,6 +155,8 @@
@Resource
private AuditResultMapper auditResultMapper;
@Resource
+ private AuditRunMapper auditRunMapper;
+ @Resource
private PassengerEnterpriseMonthlyMapper passengerMapper;
@Resource
private PassengerIndividualMonthlyMapper passengerIndividualMapper;
@@ -158,6 +180,10 @@
private CityTaxiMonthlyMapper cityTaxiMapper;
@Resource
private CityTaxiAuthMapper cityTaxiAuthMapper;
+ @Resource
+ private WycTotalMonthlyMapper wycTotalMapper;
+ @Resource
+ private WycOrderMonthlyMapper wycOrderMapper;
@Resource
private SqlSessionFactory sqlSessionFactory;
@@ -297,6 +323,9 @@
entity.setRegionCode(gs(row, colMap, "鎵�灞炲湴鍖�"));
entity.setEnterpriseCode(gs(row, colMap, "浼佷笟浠g爜"));
entity.setEnterpriseName(name.trim());
+ // 鏂扮増妯℃澘鍦ㄣ�屼紒涓氬悕绉般�嶅悗鏂板浜嗕袱鍒楋紙缁熶竴绀句細淇$敤浠g爜/濉姤鍗曚綅锛夛紝鎸夎〃澶村彇鍊硷紝鑰佹ā鏉挎棤璇ュ垪鏃朵负 null
+ entity.setUnifiedCreditCode(gs(row, colMap, "缁熶竴绀句細淇$敤浠g爜"));
+ entity.setReportUnit(gs(row, colMap, "濉姤鍗曚綅"));
entity.setVehicleTotal(gi(row, colMap, "瀹㈣繍杞﹁締_鎬昏"));
entity.setSeatTotal(gi(row, colMap, "鏍稿畾杞藉浣峗鎬昏"));
entity.setVehicleSchedule(gi(row, colMap, "杞﹁締鏁癬瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛�"));
@@ -403,41 +432,20 @@
- /** 鍏矾鏃呭涓綋瀹㈣繍閲�/鍛ㄨ浆閲忥紙浼佷笟绾夸笅姹囨姤鏁版嵁锛屾寜鎵�灞炲湴鍖哄鍏ワ紝渚涘垎甯傚窞琛ㄤ釜浣撹濉厖锛� */
+ /** 鍏矾鏃呭涓綋瀹㈣繍閲�/鍛ㄨ浆閲忥紙瀵煎叆鍚庢寜甯傚窞姹囨�诲~鍏呭垎甯傚窞琛ㄤ釜浣撹锛夛紝鏀寔涓ょ妯℃澘锛�
+ * 鈶� 浼佷笟绾夸笅姹囨姤鏄庣粏锛堣〃澶村惈銆屼紒涓氬悕绉般�嶏級锛氶�愪紒涓氬叆搴擄紱
+ * 鈶� 鐪佸巺甯傚窞姹囨�昏〃銆婃箹鍖楃渷鍏矾鏃呭杩愯緭涓綋鎴锋湀搴︾敓浜ф儏鍐碉紙YYYY.M锛夈�嬶細
+ * sheet 鍐呬负銆屽叏鐪丯鏈堜釜浣撳杩愰噺姹囨�昏〃銆嶆爣棰樺潡 + 銆屽競宸�/涓綋杞﹁締鏁�/鈥�/涓綋瀹㈣繍閲�/涓綋鏃呭鍛ㄨ浆閲忋�嶈〃澶达紝
+ * 鎸夊競宸為�愯鍏ュ簱锛堝悓涓�甯傚窞澶氳濡� 鍘垮唴鐝嚎+璺ㄥ幙鐝嚎 鍧囧叆搴擄紝鎶ヨ〃渚ф寜甯傚窞鑷姩姹囨�伙級銆� */
public ImportResult importPassengerIndividual(MultipartFile file, String period) throws Exception {
List<PassengerIndividualMonthly> rows = new ArrayList<>();
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
- Integer codeCol = colMap.get("鎵�灞炲湴鍖�");
- Integer entCol = colMap.get("浼佷笟浠g爜");
- Integer nameCol = colMap.get("浼佷笟鍚嶇О");
- Integer passCol = null;
- Integer turnCol = null;
- for (Map.Entry<String, Integer> e : colMap.entrySet()) {
- String k = e.getKey();
- if (k != null && k.contains("涓綋") && k.contains("瀹㈣繍閲�")) passCol = e.getValue();
- if (k != null && k.contains("涓綋") && (k.contains("鍛ㄨ浆閲�") || k.contains("鍛ㄨ浆"))) turnCol = e.getValue();
- }
- if (passCol == null && turnCol == null) {
- throw new RuntimeException("鏈壘鍒般�屼釜浣撳杩愰噺/涓綋鏃呭鍛ㄨ浆閲忋�嶅垪锛岃妫�鏌ユā鏉匡紙鍒楀悕闇�鍖呭惈鈥滀釜浣撳杩愰噺鈥濃�滀釜浣撳懆杞噺鈥濓級");
- }
- for (int r = 1; r <= sheet.getLastRowNum(); r++) {
- Row row = sheet.getRow(r);
- if (row == null) continue;
- String name = nameCol != null ? getString(row, nameCol) : null;
- if (name == null || name.trim().isEmpty()) continue;
- Double pass = passCol != null ? getDecimalOrNull(row, passCol) : null;
- Double turn = turnCol != null ? getDecimalOrNull(row, turnCol) : null;
- if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue;
- PassengerIndividualMonthly entity = new PassengerIndividualMonthly();
- entity.setReportPeriod(period);
- entity.setRegionCode(codeCol != null ? getString(row, codeCol) : null);
- entity.setEnterpriseCode(entCol != null ? getString(row, entCol) : null);
- entity.setEnterpriseName(name.trim());
- entity.setPassengerCount(pass);
- entity.setTurnover(turn);
- rows.add(entity);
+ if (colMap.containsKey("浼佷笟鍚嶇О")) {
+ parsePassengerIndividualEnterprise(sheet, colMap, period, rows);
+ } else {
+ parsePassengerIndividualCitySheet(sheet, period, rows);
}
} catch (Exception e) {
log.error("Passenger individual parse error", e);
@@ -472,24 +480,125 @@
return new ImportResult(success, failDetails.size(), failDetails);
}
- /** 鍏矾鏃呭杩愭斂杞﹁締鏁帮紙鐙珛妯℃澘锛氫紒涓氫唬鐮�/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛� */
+ /** 鏃х増涓綋瀹㈣繍閲忔ā鏉匡紙浼佷笟绾夸笅姹囨姤鏄庣粏锛氭墍灞炲湴鍖�/浼佷笟浠g爜/浼佷笟鍚嶇О/鈥︿釜浣撳杩愰噺/鈥︿釜浣撴梾瀹㈠懆杞噺锛� */
+ private void parsePassengerIndividualEnterprise(Sheet sheet, Map<String, Integer> colMap, String period,
+ List<PassengerIndividualMonthly> rows) {
+ Integer codeCol = colMap.get("鎵�灞炲湴鍖�");
+ Integer entCol = colMap.get("浼佷笟浠g爜");
+ Integer nameCol = colMap.get("浼佷笟鍚嶇О");
+ Integer passCol = null;
+ Integer turnCol = null;
+ for (Map.Entry<String, Integer> e : colMap.entrySet()) {
+ String k = e.getKey();
+ if (k != null && k.contains("涓綋") && k.contains("瀹㈣繍閲�")) passCol = e.getValue();
+ if (k != null && k.contains("涓綋") && (k.contains("鍛ㄨ浆閲�") || k.contains("鍛ㄨ浆"))) turnCol = e.getValue();
+ }
+ if (passCol == null && turnCol == null) {
+ throw new RuntimeException("鏈壘鍒般�屼釜浣撳杩愰噺/涓綋鏃呭鍛ㄨ浆閲忋�嶅垪锛岃妫�鏌ユā鏉匡紙鍒楀悕闇�鍖呭惈鈥滀釜浣撳杩愰噺鈥濃�滀釜浣撳懆杞噺鈥濓級");
+ }
+ for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String name = nameCol != null ? getString(row, nameCol) : null;
+ if (name == null || name.trim().isEmpty()) continue;
+ Double pass = passCol != null ? getDecimalOrNull(row, passCol) : null;
+ Double turn = turnCol != null ? getDecimalOrNull(row, turnCol) : null;
+ if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue;
+ PassengerIndividualMonthly entity = new PassengerIndividualMonthly();
+ entity.setReportPeriod(period);
+ entity.setRegionCode(codeCol != null ? getString(row, codeCol) : null);
+ entity.setEnterpriseCode(entCol != null ? getString(row, entCol) : null);
+ entity.setEnterpriseName(name.trim());
+ entity.setPassengerCount(pass);
+ entity.setTurnover(turn);
+ rows.add(entity);
+ }
+ }
+
+ /** 鏂扮増甯傚窞姹囨�绘ā鏉匡細銆婂叏鐪丯鏈堜釜浣撳杩愰噺姹囨�昏〃銆嬫爣棰樿 鈫� 琛ㄥご琛岋紙甯傚窞/鈥�/涓綋瀹㈣繍閲�/涓綋鏃呭鍛ㄨ浆閲忥級鈫� 甯傚窞鏁版嵁琛� */
+ private void parsePassengerIndividualCitySheet(Sheet sheet, String period, List<PassengerIndividualMonthly> rows) {
+ int targetMonth;
+ try {
+ targetMonth = Integer.parseInt(period.split("-")[1]);
+ } catch (Exception e) {
+ throw new RuntimeException("鎶ヨ〃鏈熸牸寮忓簲涓� yyyy-MM锛�" + period);
+ }
+ int titleRow = -1;
+ for (int r = 0; r <= sheet.getLastRowNum(); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String t = getString(row, 0);
+ if (t == null || !t.contains("鍏ㄧ渷") || !t.contains("鏈�")) continue;
+ java.util.regex.Matcher m = java.util.regex.Pattern.compile("鍏ㄧ渷(\\d+)鏈�").matcher(t);
+ if (!m.find()) continue;
+ if (Integer.parseInt(m.group(1)) == targetMonth) { titleRow = r; break; }
+ if (titleRow < 0) titleRow = r; // 鏍囬涓嶅惈鐩爣鏈堜唤鏃舵殏璁扮涓�涓爣棰樺潡鍏滃簳
+ }
+ if (titleRow < 0) {
+ throw new RuntimeException("鏈壘鍒般�屽叏鐪丯鏈堜釜浣撳杩愰噺姹囨�昏〃銆嶆爣棰樺潡锛岃浣跨敤銆婃ā鏉縚婀栧寳鐪佸叕璺梾瀹㈣繍杈撲釜浣撴埛鏈堝害鐢熶骇鎯呭喌锛圷YYY.M锛夈�嬫暣鐞嗗悗瀵煎叆");
+ }
+ Row header = sheet.getRow(titleRow + 1);
+ if (header == null) {
+ throw new RuntimeException("銆屽叏鐪�" + targetMonth + "鏈堚�︽眹鎬昏〃銆嶆爣棰樺悗缂哄皯琛ㄥご琛岋紙甯傚窞/涓綋瀹㈣繍閲�/涓綋鏃呭鍛ㄨ浆閲忥級");
+ }
+ int cityCol = -1, passCol = -1, turnCol = -1;
+ for (Cell c : header) {
+ String h = FORMATTER.formatCellValue(c).trim();
+ if (h.isEmpty()) continue;
+ if (h.contains("甯傚窞")) cityCol = c.getColumnIndex();
+ else if (h.contains("涓綋") && h.contains("瀹㈣繍閲�")) passCol = c.getColumnIndex();
+ else if (h.contains("涓綋") && (h.contains("鍛ㄨ浆閲�") || h.contains("鍛ㄨ浆"))) turnCol = c.getColumnIndex();
+ }
+ if (cityCol < 0 || (passCol < 0 && turnCol < 0)) {
+ throw new RuntimeException("鏈壘鍒般�屽競宸� / 涓綋瀹㈣繍閲� / 涓綋鏃呭鍛ㄨ浆閲忋�嶅垪锛岃浣跨敤绯荤粺妯℃澘鏁寸悊鍚庡鍏�");
+ }
+ int lastRow = Math.min(sheet.getLastRowNum(), titleRow + 40);
+ for (int r = titleRow + 2; r <= lastRow; r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String first = getString(row, cityCol);
+ if (first == null) break;
+ String t = first.trim();
+ if (t.isEmpty() || t.contains("鍚堣") || t.contains("鐜瘮") || t.contains("鍏ㄧ渷")) break;
+ String city = RegionUtil.normalizeCityName(t);
+ if (!RegionUtil.cityList().contains(city)) continue;
+ Double pass = passCol >= 0 ? getDecimalOrNull(row, passCol) : null;
+ Double turn = turnCol >= 0 ? getDecimalOrNull(row, turnCol) : null;
+ if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue;
+ PassengerIndividualMonthly entity = new PassengerIndividualMonthly();
+ entity.setReportPeriod(period);
+ entity.setRegionCode(RegionUtil.codeOfCity(city));
+ entity.setEnterpriseName(city);
+ entity.setPassengerCount(pass);
+ entity.setTurnover(turn);
+ rows.add(entity);
+ }
+ }
+
+ /** 鍏矾鏃呭杩愭斂杞﹁締鏁帮紙鍏煎涓ょ琛ㄥご锛氣憼 浼佷笟浠g爜/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛涒憽 杩愭斂搴撳鍑� OWNERNAME/鎬昏溅杈嗘暟/瀹綅鏁帮紝鏃犱紒涓氫唬鐮佸垪锛� */
public ImportResult importPassengerAuth(MultipartFile file, String period) throws Exception {
List<PassengerAuthVehicle> authList = new ArrayList<>();
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
- Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
- requireHeaderCols(sheet, "鏃呭杩愭斂杞﹁締鏁�", "浼佷笟鍚嶇О", "杞﹁締鏁�", "杞藉浣嶆暟");
+ Map<String, Integer> colMap = buildHeaderMapCompat(sheet.getRow(0));
+ Integer nameCol = firstColIdx(colMap, "浼佷笟鍚嶇О", "OWNERNAME");
+ Integer vehicleCol = firstColIdx(colMap, "杞﹁締鏁�", "鎬昏溅杈嗘暟");
+ Integer seatCol = firstColIdx(colMap, "杞藉浣嶆暟", "瀹綅鏁�");
+ Integer codeCol = firstColIdx(colMap, "浼佷笟浠g爜");
+ if (nameCol == null || vehicleCol == null || seatCol == null) {
+ throw new RuntimeException("鎵�閫夋暟鎹被鍨嬨�愭梾瀹㈣繍鏀胯溅杈嗘暟銆戜笌鏂囦欢鍐呭涓嶇锛氳〃澶撮渶鍚�愪紒涓氬悕绉�/杞﹁締鏁�/杞藉浣嶆暟銆戞垨銆怬WNERNAME/鎬昏溅杈嗘暟/瀹綅鏁般�戯紝璇风‘璁ゆ槸鍚﹂�夐敊浜嗘暟鎹被鍨嬫垨浣跨敤浜嗘棫妯℃澘");
+ }
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
- String name = gs(row, colMap, "浼佷笟鍚嶇О");
+ String name = getString(row, nameCol);
if (name == null || name.trim().isEmpty()) continue;
PassengerAuthVehicle auth = new PassengerAuthVehicle();
auth.setReportPeriod(period);
- auth.setEnterpriseCode(gs(row, colMap, "浼佷笟浠g爜"));
+ auth.setEnterpriseCode(codeCol == null ? null : getString(row, codeCol));
auth.setEnterpriseName(name.trim());
- auth.setVehicleCount(gi(row, colMap, "杞﹁締鏁�"));
- auth.setSeatCount(gi(row, colMap, "杞藉浣嶆暟"));
+ auth.setVehicleCount(getInt(row, vehicleCol));
+ auth.setSeatCount(getInt(row, seatCol));
authList.add(auth);
}
} catch (Exception e) {
@@ -527,6 +636,15 @@
/** 鑳借�楁槑缁嗘瘡缁勫浐瀹�18鍒楋紝杞︾墝鍙疯捣濮嬪垪锛�0-based锛夛細3,21,39,57,75 */
private static final int[] ENERGY_GROUP_COLS = {3, 21, 39, 57, 75};
+ /** 鑳借�楁槑缁嗘ā鏉胯〃澶村叧閿垪锛�0-based锛夛細绋嬪簭鎸夊浐瀹氬垪浣嶈В鏋愶紝瑙f瀽鍓嶆牎楠岃〃澶达紝閿欎綅鐩存帴鎷掔粷 */
+ private static final int[] ENERGY_HEADER_COLS = {3, 4, 5, 21, 39, 57, 75};
+ private static final String[] ENERGY_HEADER_KEYWORDS = {"杞︾墝鍙�", "杞﹁締绫诲瀷", "鐕冩枡绫诲瀷", "杞︾墝鍙�", "杞︾墝鍙�", "杞︾墝鍙�", "杞︾墝鍙�"};
+
+ /** 鑳借�楀鍏ユ寜鎶ヨ〃鏈熶簰鏂ラ攣锛圚204 鏄庣粏涓庤繍鏀夸俊鎭叡鐢紝閬垮厤鍚屾姤琛ㄦ湡骞跺彂閲嶅浜掔浉瑕嗙洊锛� */
+ private Object energyLock(String period) {
+ return energyImportLocks.computeIfAbsent(period == null ? "" : period, k -> new Object());
+ }
+
public ImportResult importEnergyMonthly(MultipartFile file, String period) throws Exception {
List<EnergyRow> rows = new ArrayList<>();
List<EnergyAuthVehicle> authList = new ArrayList<>();
@@ -534,6 +652,7 @@
// sheet0: 绗簩瀛e害璐ц溅鑳借�楁槑缁嗭紙姣忚宸﹀彸鍚�5缁勮溅杈嗭紝姣忕粍鍥哄畾18鍒楋級
Sheet sheet = wb.getSheetAt(0);
requireHeaderKeyword(sheet, "閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐�", "浼佷笟鍚嶇О", "杞︾墝鍙�");
+ requireEnergyHeaderLayout(sheet);
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
@@ -541,7 +660,7 @@
if (enterpriseName == null || enterpriseName.trim().isEmpty()) continue;
for (int group : ENERGY_GROUP_COLS) {
String plate = getString(row, group);
- if (plate == null || plate.trim().isEmpty()) continue;
+ if (plate == null || plate.trim().isEmpty() || "0".equals(plate.trim())) continue;
EnergyVehicleQuarterly e = new EnergyVehicleQuarterly();
e.setReportPeriod(period);
e.setRegionCode(getString(row, 0));
@@ -580,7 +699,7 @@
Row row = authSheet.getRow(r);
if (row == null) continue;
String plate = getString(row, 0);
- if (plate == null || plate.trim().isEmpty()) continue;
+ if (plate == null || plate.trim().isEmpty() || "0".equals(plate.trim())) continue;
EnergyAuthVehicle auth = new EnergyAuthVehicle();
auth.setReportPeriod(period);
auth.setPlateNo(plate.trim());
@@ -597,54 +716,57 @@
}
// 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁锛堜袱琛� + H204 瑙勫垯瀹℃牳缁撴灉锛�
- energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
- .eq(EnergyVehicleQuarterly::getReportPeriod, period));
- energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
- .eq(EnergyAuthVehicle::getReportPeriod, period));
- List<Long> h204RuleIds = new ArrayList<>();
- for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
- if ("H204".equals(rule.getReportType())) h204RuleIds.add(rule.getId());
- }
- if (!h204RuleIds.isEmpty()) {
- auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
- .eq(AuditResult::getReportPeriod, period)
- .in(AuditResult::getRuleId, h204RuleIds));
- }
-
- int success = 0;
- List<String> failDetails = new ArrayList<>();
- for (EnergyRow item : rows) {
- try {
- energyMapper.insert(item.entity);
- success++;
- } catch (Exception e) {
- Throwable cause = e;
- while (cause.getCause() != null) cause = cause.getCause();
- String reason = cause.getMessage();
- if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
- failDetails.add("绗� " + item.excelRow + " 琛岋紙" + item.entity.getEnterpriseName()
- + " " + item.entity.getPlateNo() + "锛夛細" + reason);
- log.error("Energy insert error: row {}, {}", item.excelRow, item.entity.getPlateNo(), e);
+ // 鍔犻攣锛氬悓鎶ヨ〃鏈熷苟鍙戦噸瀵间簰鏂ワ紝闃叉 delete/insert 浜ら敊瀵艰嚧鏁版嵁娈嬬暀鎴栫炕鍊�
+ synchronized (energyLock(period)) {
+ energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+ .eq(EnergyVehicleQuarterly::getReportPeriod, period));
+ energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
+ .eq(EnergyAuthVehicle::getReportPeriod, period));
+ List<Long> h204RuleIds = new ArrayList<>();
+ for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+ if ("H204".equals(rule.getReportType())) h204RuleIds.add(rule.getId());
}
- }
- String errorDetail = String.join("\n", failDetails);
- if (errorDetail.length() > 4000) {
- errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
- }
- recordBatch(file.getOriginalFilename(), "H204", period, rows.size(), success, failDetails.size(), errorDetail);
-
- int authSuccess = 0;
- for (EnergyAuthVehicle auth : authList) {
- try {
- energyAuthMapper.insert(auth);
- authSuccess++;
- } catch (Exception e) {
- log.error("energy auth insert error: {}", auth.getPlateNo(), e);
+ if (!h204RuleIds.isEmpty()) {
+ auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+ .eq(AuditResult::getReportPeriod, period)
+ .in(AuditResult::getRuleId, h204RuleIds));
}
+
+ int success = 0;
+ List<String> failDetails = new ArrayList<>();
+ for (EnergyRow item : rows) {
+ try {
+ energyMapper.insert(item.entity);
+ success++;
+ } catch (Exception e) {
+ Throwable cause = e;
+ while (cause.getCause() != null) cause = cause.getCause();
+ String reason = cause.getMessage();
+ if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+ failDetails.add("绗� " + item.excelRow + " 琛岋紙" + item.entity.getEnterpriseName()
+ + " " + item.entity.getPlateNo() + "锛夛細" + reason);
+ log.error("Energy insert error: row {}, {}", item.excelRow, item.entity.getPlateNo(), e);
+ }
+ }
+ String errorDetail = String.join("\n", failDetails);
+ if (errorDetail.length() > 4000) {
+ errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+ }
+ recordBatch(file.getOriginalFilename(), "H204", period, rows.size(), success, failDetails.size(), errorDetail);
+
+ int authSuccess = 0;
+ for (EnergyAuthVehicle auth : authList) {
+ try {
+ energyAuthMapper.insert(auth);
+ authSuccess++;
+ } catch (Exception e) {
+ log.error("energy auth insert error: {}", auth.getPlateNo(), e);
+ }
+ }
+ recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), authSuccess, 0, null);
+ log.info("Energy monthly imported: {} vehicles, auth {} rows for {}", success, authSuccess, period);
+ return new ImportResult(success, failDetails.size(), failDetails);
}
- recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), authSuccess, 0, null);
- log.info("Energy monthly imported: {} vehicles, auth {} rows for {}", success, authSuccess, period);
- return new ImportResult(success, failDetails.size(), failDetails);
}
/** 鑳借�楄溅杈嗚繍鏀夸俊鎭紙鐙珛妯℃澘锛氳溅鐗屽彿/杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅/鍑嗙壍寮曡川閲忥級 */
@@ -653,11 +775,17 @@
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
requireHeaderKeyword(sheet, "鑳借�楄溅杈嗚繍鏀夸俊鎭�", "杞︾墝鍙�");
+ Row authHeader = sheet.getRow(0);
+ String authH0 = authHeader == null ? null : FORMATTER.formatCellValue(authHeader.getCell(0)).trim();
+ if (authH0 == null || authH0.isEmpty() || !authH0.contains("杞︾墝鍙�")) {
+ throw new RuntimeException("鑳借�楄溅杈嗚繍鏀夸俊鎭〃澶翠笌绯荤粺妯℃澘涓嶄竴鑷达細绗� 1 鍒楀簲涓恒�岃溅鐗屽彿銆嶏紝瀹為檯涓恒��"
+ + (authH0 == null || authH0.isEmpty() ? "绌�" : authH0) + "銆嶃�傝浣跨敤绯荤粺妯℃澘銆婃ā鏉縚鑳借�楄溅杈嗚繍鏀夸俊鎭�.xlsx銆嬫暣鐞嗗悗鍐嶅鍏�");
+ }
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
String plate = getString(row, 0);
- if (plate == null || plate.trim().isEmpty()) continue;
+ if (plate == null || plate.trim().isEmpty() || "0".equals(plate.trim())) continue;
EnergyAuthVehicle auth = new EnergyAuthVehicle();
auth.setReportPeriod(period);
auth.setPlateNo(plate.trim());
@@ -671,30 +799,32 @@
log.error("Energy auth parse error", e);
throw e;
}
- // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁
- energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
- .eq(EnergyAuthVehicle::getReportPeriod, period));
- deleteRuleResults("H204", period);
- int success = 0;
- List<String> failDetails = new ArrayList<>();
- for (EnergyAuthVehicle auth : authList) {
- try {
- energyAuthMapper.insert(auth);
- success++;
- } catch (Exception e) {
- Throwable cause = e;
- while (cause.getCause() != null) cause = cause.getCause();
- String reason = cause.getMessage();
- if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
- failDetails.add("绗� " + (authList.indexOf(auth) + 2) + " 琛岋紙" + auth.getPlateNo() + "锛夛細" + reason);
- log.error("Energy auth insert error: {}", auth.getPlateNo(), e);
+ // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁锛涗笌 H204 鏄庣粏鍏辩敤鎶ヨ〃鏈熼攣锛岄槻骞跺彂浜掔浉瑕嗙洊
+ synchronized (energyLock(period)) {
+ energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
+ .eq(EnergyAuthVehicle::getReportPeriod, period));
+ deleteRuleResults("H204", period);
+ int success = 0;
+ List<String> failDetails = new ArrayList<>();
+ for (EnergyAuthVehicle auth : authList) {
+ try {
+ energyAuthMapper.insert(auth);
+ success++;
+ } catch (Exception e) {
+ Throwable cause = e;
+ while (cause.getCause() != null) cause = cause.getCause();
+ String reason = cause.getMessage();
+ if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+ failDetails.add("绗� " + (authList.indexOf(auth) + 2) + " 琛岋紙" + auth.getPlateNo() + "锛夛細" + reason);
+ log.error("Energy auth insert error: {}", auth.getPlateNo(), e);
+ }
}
+ String errorDetail = String.join("\n", failDetails);
+ if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+ recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), success, failDetails.size(), errorDetail);
+ log.info("Energy auth imported: {} rows for {}", success, period);
+ return new ImportResult(success, failDetails.size(), failDetails);
}
- String errorDetail = String.join("\n", failDetails);
- if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
- recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), success, failDetails.size(), errorDetail);
- log.info("Energy auth imported: {} rows for {}", success, period);
- return new ImportResult(success, failDetails.size(), failDetails);
}
/** H204 琛岃褰曪細淇濈暀 Excel 琛屽彿鐢ㄤ簬澶辫触瀹氫綅 */
@@ -716,25 +846,27 @@
// ========== 杩愭斂杞﹁締 ==========
+ /** 杩愭斂杞﹁締瀵煎叆锛氫笌 H203-2 鏈堟姤涓�鑷达紝鎸夎〃澶村悕绉板畾浣嶅垪锛屽垪椤哄簭鍙樺寲涔熻兘姝g‘瀵煎叆 */
public int importTransportAuth(MultipartFile file, String period) throws Exception {
List<TransportAuthVehicle> list = new ArrayList<>();
int failRows = 0;
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
- requireHeaderKeyword(sheet, "杩愭斂杞﹁締", "浼佷笟鍚嶇О");
+ requireHeaderCols(sheet, "杩愭斂杞﹁締", "浼佷笟鍚嶇О", "鎬荤壍寮曡溅鏁�", "鎬绘寕杞︽暟", "鍏跺畠杞﹁締鏁�", "鎸傝溅鎬诲惃浣�(鍚�)", "鍏跺畠杞﹁締鎬诲惃浣�(鍚�)");
+ Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
- String name = getString(row, 0);
+ String name = gs(row, colMap, "浼佷笟鍚嶇О");
if (name == null || name.trim().isEmpty()) continue;
TransportAuthVehicle entity = new TransportAuthVehicle();
entity.setReportPeriod(period);
entity.setEnterpriseName(name);
- entity.setTractorCount(getInt(row, 1));
- entity.setTrailerCount(getInt(row, 2));
- entity.setOtherCount(getInt(row, 3));
- entity.setTrailerTons(getDecimal(row, 4));
- entity.setOtherTons(getDecimal(row, 5));
+ entity.setTractorCount(gi(row, colMap, "鎬荤壍寮曡溅鏁�"));
+ entity.setTrailerCount(gi(row, colMap, "鎬绘寕杞︽暟"));
+ entity.setOtherCount(gi(row, colMap, "鍏跺畠杞﹁締鏁�"));
+ entity.setTrailerTons(gd(row, colMap, "鎸傝溅鎬诲惃浣�(鍚�)"));
+ entity.setOtherTons(gd(row, colMap, "鍏跺畠杞﹁締鎬诲惃浣�(鍚�)"));
list.add(entity);
}
} catch (Exception e) {
@@ -764,21 +896,23 @@
// ========== 杞ㄨ抗閲岀▼ ==========
+ /** 杞ㄨ抗閲岀▼瀵煎叆锛氫笌 H203-2 鏈堟姤涓�鑷达紝鎸夎〃澶村悕绉板畾浣嶅垪锛屽垪椤哄簭鍙樺寲涔熻兘姝g‘瀵煎叆 */
public int importTrackMileage(MultipartFile file, String period) throws Exception {
List<VehicleTrackMileage> list = new ArrayList<>();
int failRows = 0;
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
- requireHeaderKeyword(sheet, "杞ㄨ抗閲岀▼", "浼佷笟鍚嶇О");
+ requireHeaderCols(sheet, "杞ㄨ抗閲岀▼", "浼佷笟鍚嶇О", "鏈堝害鎬婚噷绋�(鍏噷)", "鏈夐噷绋嬬殑杞﹁締鏁�");
+ Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
- String name = getString(row, 1);
+ String name = gs(row, colMap, "浼佷笟鍚嶇О");
if (name == null || name.trim().isEmpty()) continue;
VehicleTrackMileage entity = new VehicleTrackMileage();
entity.setEnterpriseName(name);
- entity.setMonthlyMileage(getDecimal(row, 2));
- entity.setTrackedVehicles(getInt(row, 3));
+ entity.setMonthlyMileage(gd(row, colMap, "鏈堝害鎬婚噷绋�(鍏噷)"));
+ entity.setTrackedVehicles(gi(row, colMap, "鏈夐噷绋嬬殑杞﹁締鏁�"));
entity.setReportPeriod(period);
list.add(entity);
}
@@ -813,10 +947,11 @@
List<ScaleSplitTransport> list = new ArrayList<>();
int failRows = 0;
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+ int targetMonth = Integer.parseInt(period.split("-")[1]); // 浠呭鍏ョ洰鏍囨湡鎵�鍦ㄦ湀鐨勯〉绛撅紙1-N鏈堟枃浠舵寜鐩爣鏈熷彇褰撴湀/绱锛�
boolean hasPeriodSheet = false;
for (int i = 0; i < wb.getNumberOfSheets(); i++) {
String sn = wb.getSheetAt(i).getSheetName();
- if (sn != null && (sn.contains("褰撴湀") || sn.contains("绱"))) { hasPeriodSheet = true; break; }
+ if (sn != null && (sn.contains("褰撴湀") || sn.contains("鏈堜唤") || sn.contains("绱"))) { hasPeriodSheet = true; break; }
}
if (!hasPeriodSheet) {
throw new RuntimeException("鎵�閫夋暟鎹被鍨嬨�愯涓婅涓嬫媶鍒嗐�戜笌鏂囦欢鍐呭涓嶇锛氭湭鎵惧埌鍚� 褰撴湀/绱 鐨� sheet锛岃纭鏄惁閫夐敊浜嗘暟鎹被鍨�");
@@ -825,9 +960,31 @@
Sheet sheet = wb.getSheetAt(i);
String sheetName = sheet.getSheetName();
String periodType = null;
- if (sheetName.contains("褰撴湀")) periodType = "MONTH";
- else if (sheetName.contains("绱")) periodType = "CUMULATIVE";
+ java.util.regex.Matcher mSheetMonth = java.util.regex.Pattern.compile("(\\d{1,2})\\u6708(\\u4efd|\\u7d2f\\u8ba1)").matcher(sheetName);
+ if (mSheetMonth.find()) {
+ int sheetMonth = Integer.parseInt(mSheetMonth.group(1));
+ if (sheetMonth == targetMonth) {
+ periodType = "浠�".equals(mSheetMonth.group(2)) ? "MONTH" : "CUMULATIVE";
+ }
+ } else {
+ if (sheetName.contains("褰撴湀")) periodType = "MONTH";
+ else if (sheetName.contains("绱")) periodType = "CUMULATIVE";
+ }
if (periodType == null) continue;
+
+ // 鏍¢獙 sheet 甯冨眬锛氱2琛屽簲鍚�屽競宸�/瑙勪笂浼佷笟杩愯緭閲忋�嶏紝绗�3琛屽瓙琛ㄥご搴斿惈銆屽懆杞噺銆嶆垨銆岃揣杩愰噺銆嶏紙鎵╁睍甯冨眬锛�
+ Row headRow = sheet.getRow(1);
+ Row subHeadRow = sheet.getRow(2);
+ boolean layoutOk = headRow != null
+ && getString(headRow, 0) != null && getString(headRow, 0).contains("甯傚窞")
+ && getString(headRow, 1) != null && getString(headRow, 1).contains("瑙勪笂")
+ && subHeadRow != null
+ && ("璐ц繍閲�".equals(getString(subHeadRow, 0))
+ || (getString(subHeadRow, 1) != null && getString(subHeadRow, 1).contains("鍛ㄨ浆閲�")));
+ if (!layoutOk) {
+ throw new RuntimeException("鎵�閫夋暟鎹被鍨嬨�愯涓婅涓嬫媶鍒嗐�戜笌鏂囦欢鍐呭涓嶇锛歴heet銆�" + sheetName
+ + "銆嶆湭鎵惧埌銆屽競宸�/瑙勪笂浼佷笟杩愯緭閲�/鍛ㄨ浆閲忋�嶈〃澶达紝璇风‘璁ゆ槸鍚﹂�夐敊浜嗘暟鎹被鍨�");
+ }
// 妫�娴嬪瓙琛ㄥご锛氭槸鍚﹀寘鍚揣杩愰噺鍒楋紙鎵╁睍甯冨眬锛�
Row subHeader = sheet.getRow(2);
@@ -905,12 +1062,29 @@
// ========== 璐ц繍閲忓懆杞噺锛堟ā鏉縚璐ц繍閲忓懆杞噺.xlsx锛屾瘡鏈堝鍏ワ級 ==========
public int importFreightTurnover(MultipartFile file, String period) throws Exception {
+ try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+ // 澶氬勾搴﹀伐浣滅翱锛堜竴骞翠竴涓� Sheet锛氭暣骞� 1-12 鏈堬紝濡� 21-26 骞村畾绋挎枃浠讹級鈫� 鍏ㄩ儴骞翠唤鍏ュ簱
+ List<String> yearSheets = detectYearSheets(wb);
+ if (!yearSheets.isEmpty()) {
+ return importFreightTurnoverByYear(wb, file, yearSheets);
+ }
+ return importFreightTurnoverByMonth(wb, file, period);
+ }
+ }
+
+ /** 姣忔湀瀵煎叆锛堟ā鏉縚璐ц繍閲忓懆杞噺.xlsx锛夛細宸﹀崐浠婂勾 1-N 鏈堬紝鍙冲崐鍘诲勾 1-12 鏈� */
+ private int importFreightTurnoverByMonth(Workbook wb, MultipartFile file, String period) throws Exception {
List<FreightTurnoverImport> list = new ArrayList<>();
- int failRows = 0;
// 鎶ヨ〃鏈� N 鏈堬細宸﹀崐涓轰粖骞� 1-N 鏈堬紙鍒楁暟涓嶅浐瀹氾級锛屽彸鍗婂浐瀹氫负鍘诲勾 1-12 鏈堬紙绠楀悓姣旂敤锛�
int monthCount = Integer.parseInt(period.split("-")[1]);
- try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+ {
Sheet sheet = wb.getSheetAt(0);
+ Row header = sheet.getRow(0);
+ String h0 = header == null ? null : getString(header, 0);
+ String h1 = header == null ? null : getString(header, 1);
+ if (header == null || h0 == null || !h0.contains("璐ц繍閲�") || h1 == null || !h1.contains("鏈�")) {
+ throw new RuntimeException("鎵�閫夋暟鎹被鍨嬨�愯揣杩愰噺鍛ㄨ浆閲忋�戜笌鏂囦欢鍐呭涓嶇锛氳〃澶村簲涓恒�岃揣杩愰噺(涓囧惃)/1鏈堚��12鏈堛�嶅竷灞�锛岃纭鏄惁閫夐敊浜嗘暟鎹被鍨�");
+ }
boolean cityRowFound = false;
for (int r = 1; r <= Math.min(sheet.getLastRowNum(), 30); r++) {
Row rr = sheet.getRow(r);
@@ -968,27 +1142,149 @@
}
list.add(entity);
}
- } catch (Exception e) {
- throw e;
}
+ Map<String, List<FreightTurnoverImport>> byPeriod = new LinkedHashMap<>();
+ byPeriod.put(period, list);
+ return persistFreightTurnover(byPeriod, file.getOriginalFilename());
+ }
- // 骞傜瓑锛氬悓鎶ヨ〃鏈熷厛鍒犻櫎
- freightTurnoverMapper.delete(new LambdaQueryWrapper<FreightTurnoverImport>()
- .eq(FreightTurnoverImport::getReportPeriod, period));
+ /** 璇嗗埆銆屽骞村害宸ヤ綔绨裤�嶏細姣忎釜 Sheet 涓�骞达紙Sheet 鍚� 2021骞�/2022骞粹�︼級锛屼笖琛ㄥご涓恒�岃揣杩愰噺/1鏈堚��12鏈堛�嶏紱鍚﹀垯杩斿洖绌哄垪琛� */
+ private List<String> detectYearSheets(Workbook wb) {
+ List<String> names = new ArrayList<>();
+ if (wb.getNumberOfSheets() < 2) return names;
+ for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+ Sheet sheet = wb.getSheetAt(i);
+ String nm = sheet.getSheetName() == null ? "" : sheet.getSheetName().trim();
+ if (!nm.matches("\\d{4}\\s*骞�?")) { names.clear(); return names; }
+ Row header = sheet.getRow(0);
+ String h0 = header == null ? null : getString(header, 0);
+ String h1 = header == null ? null : getString(header, 1);
+ if (h0 == null || !h0.contains("璐ц繍閲�") || h1 == null || !h1.contains("鏈�")) {
+ names.clear();
+ return names;
+ }
+ names.add(nm);
+ }
+ return names;
+ }
- int success = 0;
- for (FreightTurnoverImport entity : list) {
- try {
- freightTurnoverMapper.insert(entity);
- success++;
- } catch (Exception e) {
- log.error("freight turnover insert error: {}", entity.getRegionName(), e);
- failRows++;
+ /** 澶氬勾搴﹀伐浣滅翱瀵煎叆锛氶�愬勾涓�鏉℃姤琛ㄦ湡锛堟姤琛ㄦ湡=璇ュ勾鏈�鍚庢湁鏁版嵁鐨勬湀浠斤級锛屼笂骞村悓鏈熷彇涓婁竴骞村害 Sheet */
+ private int importFreightTurnoverByYear(Workbook wb, MultipartFile file, List<String> yearSheets) {
+ Map<Integer, String> sheetByYear = new TreeMap<>();
+ for (String sheetName : yearSheets) {
+ sheetByYear.put(Integer.parseInt(sheetName.replace("骞�", "").trim()), sheetName);
+ }
+ Map<Integer, Map<String, FreightTurnoverImport>> byYear = new TreeMap<>();
+ Map<Integer, Integer> maxMonthByYear = new HashMap<>();
+ for (Map.Entry<Integer, String> entry : sheetByYear.entrySet()) {
+ Map<String, FreightTurnoverImport> parsed = new LinkedHashMap<>();
+ int maxMonth = parseWholeYearSheet(wb.getSheet(entry.getValue()), parsed);
+ if (parsed.isEmpty()) continue;
+ byYear.put(entry.getKey(), parsed);
+ maxMonthByYear.put(entry.getKey(), Math.max(1, Math.min(12, maxMonth)));
+ }
+ Map<String, List<FreightTurnoverImport>> byPeriod = new LinkedHashMap<>();
+ for (Map.Entry<Integer, Map<String, FreightTurnoverImport>> entry : byYear.entrySet()) {
+ int year = entry.getKey();
+ String yearPeriod = year + "-" + String.format("%02d", maxMonthByYear.get(year));
+ Map<String, FreightTurnoverImport> lastYear = byYear.get(year - 1);
+ List<FreightTurnoverImport> rows = new ArrayList<>();
+ for (Map.Entry<String, FreightTurnoverImport> regionEntry : entry.getValue().entrySet()) {
+ FreightTurnoverImport entity = regionEntry.getValue();
+ entity.setReportPeriod(yearPeriod);
+ // 涓婂勾鍚屾湡 = 涓婁竴骞村害 Sheet锛堝骞村害鏂囦欢娌℃湁鍙冲崐銆屽幓骞�1-12鏈堛�嶅尯鍧楋級
+ FreightTurnoverImport last = lastYear == null ? null : lastYear.get(regionEntry.getKey());
+ if (last != null) {
+ for (int m = 1; m <= 12; m++) {
+ setLastFreight(entity, m, getFreight(last, m));
+ setLastTurnover(entity, m, getTurnover(last, m));
+ }
+ }
+ rows.add(entity);
+ }
+ byPeriod.put(yearPeriod, rows);
+ }
+ return persistFreightTurnover(byPeriod, file.getOriginalFilename());
+ }
+
+ /** 瑙f瀽鏁村勾 Sheet锛�1-12 鏈堝垪锛夛細璐ц繍閲忓潡 + 鍛ㄨ浆閲忓潡锛涜繑鍥炴渶鍚庝竴涓湁璐ц繍閲忔暟鎹殑鏈堜唤 */
+ private int parseWholeYearSheet(Sheet sheet, Map<String, FreightTurnoverImport> out) {
+ Map<String, FreightTurnoverImport> freightMap = new LinkedHashMap<>();
+ Map<String, FreightTurnoverImport> turnoverMap = new LinkedHashMap<>();
+ Set<String> seen = new HashSet<>();
+ int maxMonth = 0;
+ for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String name = getString(row, 0);
+ if (name == null || name.trim().isEmpty()) continue;
+ String region = RegionUtil.normalizeCityName(name);
+ boolean isCity = RegionUtil.cityList().contains(region);
+ if (!isCity && !"婀栧寳鐪�".equals(region)) continue; // 琛ㄥご/璇存槑琛�
+ if (seen.contains(region)) {
+ // 璇ュ悕绉板凡鍑虹幇杩� -> 鍛ㄨ浆閲忓尯鍧�
+ FreightTurnoverImport entity = turnoverMap.get(region);
+ if (entity == null) {
+ entity = new FreightTurnoverImport();
+ entity.setRegionName(name.trim());
+ turnoverMap.put(region, entity);
+ }
+ for (int m = 1; m <= 12; m++) {
+ setTurnover(entity, m, getDecimalOrNull(row, m));
+ }
+ } else {
+ seen.add(region);
+ FreightTurnoverImport entity = freightMap.get(region);
+ if (entity == null) {
+ entity = new FreightTurnoverImport();
+ entity.setRegionName(name.trim());
+ freightMap.put(region, entity);
+ }
+ for (int m = 1; m <= 12; m++) {
+ Double v = getDecimalOrNull(row, m);
+ setFreight(entity, m, v);
+ if (v != null && m > maxMonth) maxMonth = m;
+ }
}
}
- recordBatch(file.getOriginalFilename(), "FREIGHT_TURNOVER", period, list.size(), success, failRows, null);
- log.info("Freight turnover imported: {} rows for {}", success, period);
- return success;
+ // 鍚堝苟锛氫竴涓競宸炰竴琛岋紙璐ц繍閲� + 鍛ㄨ浆閲忥級
+ for (Map.Entry<String, FreightTurnoverImport> entry : freightMap.entrySet()) {
+ FreightTurnoverImport entity = entry.getValue();
+ FreightTurnoverImport turnover = turnoverMap.get(entry.getKey());
+ if (turnover != null) {
+ for (int m = 1; m <= 12; m++) {
+ setTurnover(entity, m, getTurnover(turnover, m));
+ }
+ }
+ out.put(entry.getKey(), entity);
+ }
+ return maxMonth;
+ }
+
+ /** 钀藉簱锛氭寜鎶ヨ〃鏈熷厛鍒犲悗鎻掞紙骞傜瓑锛夛紝姣忎釜鎶ヨ〃鏈熶竴鏉″鍏ユ壒娆¤褰曪紝杩斿洖鎴愬姛鏉℃暟 */
+ private int persistFreightTurnover(Map<String, List<FreightTurnoverImport>> byPeriod, String fileName) {
+ int total = 0;
+ for (Map.Entry<String, List<FreightTurnoverImport>> entry : byPeriod.entrySet()) {
+ String p = entry.getKey();
+ List<FreightTurnoverImport> rows = entry.getValue();
+ freightTurnoverMapper.delete(new LambdaQueryWrapper<FreightTurnoverImport>()
+ .eq(FreightTurnoverImport::getReportPeriod, p));
+ int failRows = 0;
+ int success = 0;
+ for (FreightTurnoverImport entity : rows) {
+ try {
+ freightTurnoverMapper.insert(entity);
+ success++;
+ } catch (Exception e) {
+ log.error("freight turnover insert error: {}", entity.getRegionName(), e);
+ failRows++;
+ }
+ }
+ recordBatch(fileName, "FREIGHT_TURNOVER", p, rows.size(), success, failRows, null);
+ log.info("Freight turnover imported: {} rows for {}", success, p);
+ total += success;
+ }
+ return total;
}
/** 瑙f瀽璐ц繍閲忓尯鍧楄锛氬乏鍗� B 璧� N 鍒椾负浠婂勾 1-N 鏈堬紝鍙冲崐涓哄幓骞� 1-12 鏈� */
@@ -1166,6 +1462,275 @@
}
}
+ /** 鏀寔鈥滄寜绫诲瀷娓呯┖鈥濈殑鏁版嵁绫诲瀷 -> 鏁版嵁琛ㄦ搷浣滈敭锛堝悓琛ㄥ悓绫诲埆褰掑苟锛岄伩鍏嶉噸澶嶅垹闄わ級 */
+ private static final Map<String, String> CLEAR_TYPE_TO_OP = new LinkedHashMap<>();
+ /** 鏁版嵁绫诲瀷 -> 鍏宠仈瀹℃牳瑙勫垯绫诲瀷锛堝悓姝ユ竻闄ゅ鏍哥粨鏋滀笌宸插鏍告爣璁帮級 */
+ private static final Map<String, String[]> CLEAR_TYPE_TO_AUDIT = new HashMap<>();
+ /** 鏁版嵁绫诲瀷 -> 鍏宠仈瀵煎叆鎵规 importType锛堝悓姝ユ竻闄ゅ鍏ユ壒娆¤褰曪級 */
+ private static final Map<String, String[]> CLEAR_TYPE_TO_BATCH = new HashMap<>();
+
+ static {
+ CLEAR_TYPE_TO_OP.put("h2032", "H2032");
+ CLEAR_TYPE_TO_OP.put("transportAuth", "TRANSPORT_AUTH");
+ CLEAR_TYPE_TO_OP.put("trackMileage", "TRACK_MILEAGE");
+ CLEAR_TYPE_TO_OP.put("scaleSplit", "SCALE_SPLIT");
+ CLEAR_TYPE_TO_OP.put("freightTurnover", "FREIGHT_TURNOVER");
+ CLEAR_TYPE_TO_OP.put("passengerMonthly", "H2031");
+ CLEAR_TYPE_TO_OP.put("passengerAuth", "PASSENGER_AUTH");
+ CLEAR_TYPE_TO_OP.put("passengerIndividual", "PASSENGER_INDIVIDUAL");
+ CLEAR_TYPE_TO_OP.put("energyMonthly", "H204");
+ CLEAR_TYPE_TO_OP.put("energyAuth", "ENERGY_AUTH");
+ CLEAR_TYPE_TO_OP.put("investment", "INVEST_MONTHLY_PASSENGER");
+ CLEAR_TYPE_TO_OP.put("investCityPassenger", "INVEST_MONTHLY_PASSENGER");
+ CLEAR_TYPE_TO_OP.put("investmentLogistics", "INVEST_MONTHLY_LOGISTICS");
+ CLEAR_TYPE_TO_OP.put("investCityLogistics", "INVEST_MONTHLY_LOGISTICS");
+ CLEAR_TYPE_TO_OP.put("investmentSystem", "INVEST_SYSTEM");
+ CLEAR_TYPE_TO_OP.put("cityBus", "CITY_BUS");
+ CLEAR_TYPE_TO_OP.put("cityTaxi", "CITY_TAXI");
+ CLEAR_TYPE_TO_OP.put("cityTaxiAuth", "CITY_TAXI_AUTH");
+ CLEAR_TYPE_TO_OP.put("wycTotal", "WYC_TOTAL");
+ CLEAR_TYPE_TO_OP.put("wycOrder", "WYC_ORDER");
+ CLEAR_TYPE_TO_OP.put("wycOrderReport", "WYC_ORDER");
+
+ CLEAR_TYPE_TO_AUDIT.put("h2032", new String[]{"H2032"});
+ CLEAR_TYPE_TO_AUDIT.put("transportAuth", new String[]{"H2032"});
+ CLEAR_TYPE_TO_AUDIT.put("trackMileage", new String[]{"H2032"});
+ CLEAR_TYPE_TO_AUDIT.put("scaleSplit", new String[]{"H2032"});
+ CLEAR_TYPE_TO_AUDIT.put("freightTurnover", new String[]{"H2032"});
+ CLEAR_TYPE_TO_AUDIT.put("passengerMonthly", new String[]{"H2031"});
+ CLEAR_TYPE_TO_AUDIT.put("passengerAuth", new String[]{"H2031"});
+ CLEAR_TYPE_TO_AUDIT.put("passengerIndividual", new String[]{"H2031"});
+ CLEAR_TYPE_TO_AUDIT.put("energyMonthly", new String[]{"H204"});
+ CLEAR_TYPE_TO_AUDIT.put("energyAuth", new String[]{"H204"});
+ CLEAR_TYPE_TO_AUDIT.put("investment", new String[]{"INVEST"});
+ CLEAR_TYPE_TO_AUDIT.put("investmentLogistics", new String[]{"INVEST"});
+ CLEAR_TYPE_TO_AUDIT.put("investCityPassenger", new String[]{"INVEST"});
+ CLEAR_TYPE_TO_AUDIT.put("investCityLogistics", new String[]{"INVEST"});
+ CLEAR_TYPE_TO_AUDIT.put("investmentSystem", new String[]{"INVEST"});
+ CLEAR_TYPE_TO_AUDIT.put("cityBus", new String[]{"CITY_BUS"});
+ CLEAR_TYPE_TO_AUDIT.put("cityTaxi", new String[]{"CITY_TAXI"});
+ CLEAR_TYPE_TO_AUDIT.put("cityTaxiAuth", new String[]{"CITY_TAXI"});
+
+ String[] investPassengerBatch = {"INVEST_瀹㈣繍绔欏満", "INVEST_瀹㈣繍绔欏満_CITY"};
+ String[] investLogisticsBatch = {"INVEST_鐗╂祦鍥尯", "INVEST_鐗╂祦鍥尯_CITY"};
+ CLEAR_TYPE_TO_BATCH.put("h2032", new String[]{"H2032"});
+ CLEAR_TYPE_TO_BATCH.put("transportAuth", new String[]{"TRANSPORT_AUTH"});
+ CLEAR_TYPE_TO_BATCH.put("trackMileage", new String[]{"TRACK_MILEAGE"});
+ CLEAR_TYPE_TO_BATCH.put("scaleSplit", new String[]{"SCALE_SPLIT"});
+ CLEAR_TYPE_TO_BATCH.put("freightTurnover", new String[]{"FREIGHT_TURNOVER"});
+ CLEAR_TYPE_TO_BATCH.put("passengerMonthly", new String[]{"H2031"});
+ CLEAR_TYPE_TO_BATCH.put("passengerAuth", new String[]{"PASSENGER_AUTH"});
+ CLEAR_TYPE_TO_BATCH.put("passengerIndividual", new String[]{"PASSENGER_INDIVIDUAL"});
+ CLEAR_TYPE_TO_BATCH.put("energyMonthly", new String[]{"H204"});
+ CLEAR_TYPE_TO_BATCH.put("energyAuth", new String[]{"ENERGY_AUTH"});
+ CLEAR_TYPE_TO_BATCH.put("investment", investPassengerBatch);
+ CLEAR_TYPE_TO_BATCH.put("investCityPassenger", investPassengerBatch);
+ CLEAR_TYPE_TO_BATCH.put("investmentLogistics", investLogisticsBatch);
+ CLEAR_TYPE_TO_BATCH.put("investCityLogistics", investLogisticsBatch);
+ CLEAR_TYPE_TO_BATCH.put("investmentSystem", new String[]{"INVEST_SYSTEM"});
+ CLEAR_TYPE_TO_BATCH.put("cityBus", new String[]{"CITY_BUS"});
+ CLEAR_TYPE_TO_BATCH.put("cityTaxi", new String[]{"CITY_TAXI"});
+ CLEAR_TYPE_TO_BATCH.put("cityTaxiAuth", new String[]{"CITY_TAXI_AUTH"});
+ CLEAR_TYPE_TO_BATCH.put("wycTotal", new String[]{"WYC_TOTAL"});
+ CLEAR_TYPE_TO_BATCH.put("wycOrder", new String[]{"WYC_ORDER", "WYC_ORDER_REPORT"});
+ CLEAR_TYPE_TO_BATCH.put("wycOrderReport", new String[]{"WYC_ORDER_REPORT", "WYC_ORDER"});
+ }
+
+ /**
+ * 娓呯┖鎸囧畾鎶ヨ〃鏈熷凡瀵煎叆鏁版嵁涓庡鏍哥粨鏋滐紝鐢ㄤ簬鏁存湀閲嶅锛涙姇璧勯」鐩富妗d繚鐣欍��
+ *
+ * @param types 闇�娓呯┖鐨勬暟鎹被鍨嬶紙鍓嶇绫诲瀷閿紝濡� h2032/cityBus锛夛紱涓虹┖鍒欐竻绌鸿鎶ヨ〃鏈熷叏閮ㄧ被鍨�
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, Object> clearPeriod(String period, List<String> types) {
+ if (period == null || !period.matches("\\d{4}-\\d{2}")) {
+ throw new IllegalArgumentException("鎶ヨ〃鏈熸牸寮忎笉姝g‘锛屽簲涓� yyyy-MM");
+ }
+ Set<String> selected = new LinkedHashSet<>();
+ if (types != null) {
+ for (String t : types) {
+ if (t != null && !t.trim().isEmpty()) selected.add(t.trim());
+ }
+ }
+ boolean clearAll = selected.isEmpty();
+ int dataRows = 0;
+ int auditRows = 0;
+ int auditRunRows = 0;
+ int batchRows = 0;
+ if (clearAll) {
+ dataRows += h2032Mapper.delete(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+ .eq(H2032EnterpriseMonthly::getReportPeriod, period));
+ dataRows += transportAuthMapper.delete(new LambdaQueryWrapper<TransportAuthVehicle>()
+ .eq(TransportAuthVehicle::getReportPeriod, period));
+ dataRows += trackMileageMapper.delete(new LambdaQueryWrapper<VehicleTrackMileage>()
+ .eq(VehicleTrackMileage::getReportPeriod, period));
+ dataRows += scaleSplitMapper.delete(new LambdaQueryWrapper<ScaleSplitTransport>()
+ .eq(ScaleSplitTransport::getReportPeriod, period));
+ dataRows += freightTurnoverMapper.delete(new LambdaQueryWrapper<FreightTurnoverImport>()
+ .eq(FreightTurnoverImport::getReportPeriod, period));
+ dataRows += passengerMapper.delete(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+ .eq(PassengerEnterpriseMonthly::getReportPeriod, period));
+ dataRows += passengerIndividualMapper.delete(new LambdaQueryWrapper<PassengerIndividualMonthly>()
+ .eq(PassengerIndividualMonthly::getReportPeriod, period));
+ dataRows += passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>()
+ .eq(PassengerAuthVehicle::getReportPeriod, period));
+ dataRows += energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+ .eq(EnergyVehicleQuarterly::getReportPeriod, period));
+ dataRows += energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
+ .eq(EnergyAuthVehicle::getReportPeriod, period));
+ dataRows += investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>()
+ .eq(InvestmentMonthly::getReportPeriod, period));
+ dataRows += investSystemMapper.delete(new LambdaQueryWrapper<InvestmentSystem>()
+ .eq(InvestmentSystem::getReportPeriod, period));
+ dataRows += cityBusMapper.delete(new LambdaQueryWrapper<CityBusMonthly>()
+ .eq(CityBusMonthly::getReportPeriod, period));
+ dataRows += cityTaxiMapper.delete(new LambdaQueryWrapper<CityTaxiMonthly>()
+ .eq(CityTaxiMonthly::getReportPeriod, period));
+ dataRows += cityTaxiAuthMapper.delete(new LambdaQueryWrapper<CityTaxiAuth>()
+ .eq(CityTaxiAuth::getReportPeriod, period));
+ dataRows += wycTotalMapper.delete(new LambdaQueryWrapper<WycTotalMonthly>()
+ .eq(WycTotalMonthly::getReportPeriod, period));
+ dataRows += wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>()
+ .eq(WycOrderMonthly::getReportPeriod, period));
+ auditRows = auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+ .eq(AuditResult::getReportPeriod, period));
+ auditRunRows = auditRunMapper.delete(new LambdaQueryWrapper<AuditRun>()
+ .eq(AuditRun::getReportPeriod, period));
+ batchRows = importBatchMapper.delete(new LambdaQueryWrapper<ImportBatch>()
+ .eq(ImportBatch::getReportPeriod, period));
+ } else {
+ Set<String> ops = new LinkedHashSet<>();
+ Set<String> auditTypes = new LinkedHashSet<>();
+ Set<String> batchTypes = new LinkedHashSet<>();
+ for (String t : selected) {
+ String op = CLEAR_TYPE_TO_OP.get(t);
+ if (op == null) {
+ log.warn("娓呯┖鏁版嵁锛氭湭鐭ユ暟鎹被鍨� {}锛屽凡蹇界暐", t);
+ continue;
+ }
+ ops.add(op);
+ String[] at = CLEAR_TYPE_TO_AUDIT.get(t);
+ if (at != null) java.util.Collections.addAll(auditTypes, at);
+ String[] bt = CLEAR_TYPE_TO_BATCH.get(t);
+ if (bt != null) java.util.Collections.addAll(batchTypes, bt);
+ }
+ if (ops.isEmpty()) {
+ throw new IllegalArgumentException("鏈寚瀹氭湁鏁堢殑鏁版嵁绫诲瀷");
+ }
+ for (String op : ops) {
+ dataRows += clearTypeData(op, period);
+ }
+ if (!auditTypes.isEmpty()) {
+ List<Long> ruleIds = new ArrayList<>();
+ for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+ if (rule.getReportType() != null && auditTypes.contains(rule.getReportType())) {
+ ruleIds.add(rule.getId());
+ }
+ }
+ if (!ruleIds.isEmpty()) {
+ auditRows = auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+ .eq(AuditResult::getReportPeriod, period)
+ .in(AuditResult::getRuleId, ruleIds));
+ }
+ for (String rt : auditTypes) {
+ auditRunRows += auditRunMapper.delete(new LambdaQueryWrapper<AuditRun>()
+ .eq(AuditRun::getReportPeriod, period)
+ .eq(AuditRun::getReportType, rt));
+ }
+ }
+ if (!batchTypes.isEmpty()) {
+ batchRows = importBatchMapper.delete(new LambdaQueryWrapper<ImportBatch>()
+ .eq(ImportBatch::getReportPeriod, period)
+ .in(ImportBatch::getImportType, batchTypes));
+ }
+ }
+ Map<String, Object> stats = new LinkedHashMap<>();
+ stats.put("period", period);
+ stats.put("scope", clearAll ? "all" : "types");
+ if (!clearAll) stats.put("types", new ArrayList<>(selected));
+ stats.put("dataRows", dataRows);
+ stats.put("auditRows", auditRows);
+ stats.put("auditRunRows", auditRunRows);
+ stats.put("batchRows", batchRows);
+ log.info("娓呯┖鎶ヨ〃鏈� {}锛坽}锛夊畬鎴愶細涓氬姟鏁版嵁 {} 琛屻�佸鏍哥粨鏋� {} 琛屻�佸鏍告爣璁� {} 琛屻�佸鍏ユ壒娆� {} 琛�",
+ period, clearAll ? "鍏ㄩ儴绫诲瀷" : String.join(",", selected), dataRows, auditRows, auditRunRows, batchRows);
+ return stats;
+ }
+
+ /** 鎸夆�滄暟鎹〃鎿嶄綔閿�濇竻绌哄搴斾笟鍔¤〃锛堟姇璧勬湀搴︽寜椤圭洰绫诲埆杩囨护锛夛紝杩斿洖鍒犻櫎琛屾暟 */
+ private int clearTypeData(String op, String period) {
+ switch (op) {
+ case "H2032":
+ return h2032Mapper.delete(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+ .eq(H2032EnterpriseMonthly::getReportPeriod, period));
+ case "TRANSPORT_AUTH":
+ return transportAuthMapper.delete(new LambdaQueryWrapper<TransportAuthVehicle>()
+ .eq(TransportAuthVehicle::getReportPeriod, period));
+ case "TRACK_MILEAGE":
+ return trackMileageMapper.delete(new LambdaQueryWrapper<VehicleTrackMileage>()
+ .eq(VehicleTrackMileage::getReportPeriod, period));
+ case "SCALE_SPLIT":
+ return scaleSplitMapper.delete(new LambdaQueryWrapper<ScaleSplitTransport>()
+ .eq(ScaleSplitTransport::getReportPeriod, period));
+ case "FREIGHT_TURNOVER":
+ return freightTurnoverMapper.delete(new LambdaQueryWrapper<FreightTurnoverImport>()
+ .eq(FreightTurnoverImport::getReportPeriod, period));
+ case "H2031":
+ return passengerMapper.delete(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+ .eq(PassengerEnterpriseMonthly::getReportPeriod, period));
+ case "PASSENGER_AUTH":
+ return passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>()
+ .eq(PassengerAuthVehicle::getReportPeriod, period));
+ case "PASSENGER_INDIVIDUAL":
+ return passengerIndividualMapper.delete(new LambdaQueryWrapper<PassengerIndividualMonthly>()
+ .eq(PassengerIndividualMonthly::getReportPeriod, period));
+ case "H204":
+ return energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+ .eq(EnergyVehicleQuarterly::getReportPeriod, period));
+ case "ENERGY_AUTH":
+ return energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
+ .eq(EnergyAuthVehicle::getReportPeriod, period));
+ case "INVEST_MONTHLY_PASSENGER":
+ return deleteInvestmentMonthly(period, "瀹㈣繍绔欏満");
+ case "INVEST_MONTHLY_LOGISTICS":
+ return deleteInvestmentMonthly(period, "鐗╂祦鍥尯");
+ case "INVEST_SYSTEM":
+ return investSystemMapper.delete(new LambdaQueryWrapper<InvestmentSystem>()
+ .eq(InvestmentSystem::getReportPeriod, period));
+ case "CITY_BUS":
+ return cityBusMapper.delete(new LambdaQueryWrapper<CityBusMonthly>()
+ .eq(CityBusMonthly::getReportPeriod, period));
+ case "CITY_TAXI":
+ return cityTaxiMapper.delete(new LambdaQueryWrapper<CityTaxiMonthly>()
+ .eq(CityTaxiMonthly::getReportPeriod, period));
+ case "CITY_TAXI_AUTH":
+ return cityTaxiAuthMapper.delete(new LambdaQueryWrapper<CityTaxiAuth>()
+ .eq(CityTaxiAuth::getReportPeriod, period));
+ case "WYC_TOTAL":
+ return wycTotalMapper.delete(new LambdaQueryWrapper<WycTotalMonthly>()
+ .eq(WycTotalMonthly::getReportPeriod, period));
+ case "WYC_ORDER":
+ return wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>()
+ .eq(WycOrderMonthly::getReportPeriod, period));
+ default:
+ log.warn("娓呯┖鏁版嵁锛氭湭鐭ユ暟鎹〃鎿嶄綔 {}", op);
+ return 0;
+ }
+ }
+
+ /** 鎶曡祫鏈堝害鎸夐」鐩被鍒竻闄わ紙鎶曡祫椤圭洰涓绘。淇濈暀锛� */
+ private int deleteInvestmentMonthly(String period, String category) {
+ List<Long> projectIds = new ArrayList<>();
+ for (InvestmentProject p : investProjectMapper.selectList(new LambdaQueryWrapper<InvestmentProject>()
+ .eq(InvestmentProject::getCategory, category))) {
+ projectIds.add(p.getId());
+ }
+ if (projectIds.isEmpty()) return 0;
+ return investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>()
+ .eq(InvestmentMonthly::getReportPeriod, period)
+ .in(InvestmentMonthly::getProjectId, projectIds));
+ }
+
/** 鏁版嵁鍙樺寲鍚庡垹闄ゆ寚瀹氭姤琛ㄧ被鍨嬫寚瀹氭姤琛ㄦ湡鐨勫鏍哥粨鏋滐紙闇�閲嶆柊鎵ц瀹℃牳锛� */
private void deleteRuleResults(String reportType, String period) {
List<Long> ruleIds = new ArrayList<>();
@@ -1206,6 +1771,72 @@
throw new RuntimeException("鎵�閫夋暟鎹被鍨嬨��" + typeLabel + "銆戜笌鏂囦欢鍐呭涓嶇锛氳〃澶存湭鍖呭惈銆�" + String.join("銆戙��", keywords) + "銆戠瓑鍒楋紝璇风‘璁ゆ槸鍚﹂�夐敊浜嗘暟鎹被鍨�");
}
+ /** 鑳借�楁槑缁嗚〃澶村垪浣嶆牎楠岋細绯荤粺妯℃澘涓衡�滃墠3鍒� + 5缁劽�18鍒椻�濓紝绋嬪簭鎸夊浐瀹氬垪浣嶈В鏋愶紝琛ㄥご閿欎綅浼氬鑷存暣琛ㄩ敊涔憋紝閿欎綅鏃剁洿鎺ユ嫆缁� */
+ private void requireEnergyHeaderLayout(Sheet sheet) {
+ Row header = sheet == null ? null : sheet.getRow(0);
+ if (header == null) {
+ throw new RuntimeException("鑳借�楁槑缁嗚〃澶寸己澶憋紝璇蜂娇鐢ㄧ郴缁熸ā鏉裤�婃ā鏉縚閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙2026锛�.xlsx銆嬫暣鐞嗗悗鍐嶅鍏�");
+ }
+ for (int i = 0; i < ENERGY_HEADER_COLS.length; i++) {
+ int col = ENERGY_HEADER_COLS[i];
+ String keyword = ENERGY_HEADER_KEYWORDS[i];
+ Cell cell = header.getCell(col);
+ String hv = cell == null ? null : FORMATTER.formatCellValue(cell).trim();
+ if (hv == null || hv.isEmpty() || !hv.contains(keyword)) {
+ throw new RuntimeException("琛ㄥご涓庣郴缁熸ā鏉夸笉涓�鑷达細绗� " + (col + 1) + " 鍒楀簲涓恒��" + keyword + "銆嶏紝瀹為檯涓恒��"
+ + (hv == null || hv.isEmpty() ? "绌�" : hv) + "銆嶃�傝浣跨敤绯荤粺妯℃澘銆婃ā鏉縚閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙2026锛�.xlsx銆嬫暣鐞嗗悗鍐嶅鍏�");
+ }
+ }
+ }
+
+ /** 閫氱敤琛ㄥご鍒椾綅鏍¢獙锛氱▼搴忔寜鍥哄畾鍒椾綅瑙f瀽锛岃〃澶撮敊浣嶄細瀵艰嚧鏁磋〃閿欎贡锛岄敊浣嶆椂鐩存帴鎷掔粷瀵煎叆 */
+ private void requireHeaderLayout(Sheet sheet, String typeLabel, String templateHint,
+ int[] cols, String[] keywords) {
+ Row header = sheet == null ? null : sheet.getRow(0);
+ if (header == null) {
+ throw new RuntimeException("鎵�閫夋暟鎹被鍨嬨��" + typeLabel + "銆戞枃浠惰〃澶寸己澶憋紝璇蜂娇鐢ㄧ郴缁熸ā鏉裤��" + templateHint + "銆嬫暣鐞嗗悗鍐嶅鍏�");
+ }
+ for (int i = 0; i < cols.length; i++) {
+ int col = cols[i];
+ String keyword = keywords[i];
+ Cell cell = header.getCell(col);
+ String hv = cell == null ? null : FORMATTER.formatCellValue(cell).trim();
+ if (hv == null || hv.isEmpty() || !hv.contains(keyword)) {
+ throw new RuntimeException("琛ㄥご涓庣郴缁熸ā鏉夸笉涓�鑷达細绗� " + (col + 1) + " 鍒楀簲涓恒��" + keyword + "銆嶏紝瀹為檯涓恒��"
+ + (hv == null || hv.isEmpty() ? "绌�" : hv) + "銆嶃�傛墍閫夋暟鎹被鍨嬨��" + typeLabel + "銆戣浣跨敤绯荤粺妯℃澘銆�" + templateHint + "銆嬫暣鐞嗗悗鍐嶅鍏�");
+ }
+ }
+ }
+
+ /** 鎸夎〃澶村叧閿瓧瀹氫綅鍒楀彿锛氫粠绗� 1 鍒楀悜鍙虫壂锛屽懡涓换涓�鍏抽敭瀛楀嵆杩斿洖璇ュ垪锛涙湭鍛戒腑杩斿洖 -1 */
+ private int findColByKeywords(Row header, String... keywords) {
+ if (header == null) return -1;
+ for (int c = 0; c <= header.getLastCellNum(); c++) {
+ Cell cell = header.getCell(c);
+ if (cell == null) continue;
+ String h = FORMATTER.formatCellValue(cell).trim();
+ if (h.isEmpty()) continue;
+ for (String k : keywords) {
+ if (h.contains(k)) return c;
+ }
+ }
+ return -1;
+ }
+
+ /** 琛ㄥご琛屾枃鏈紙鎶ラ敊鎻愮ず鐢級锛屾渶澶氬彇 12 鍒� */
+ private String headerText(Row header) {
+ if (header == null) return "绌�";
+ StringBuilder sb = new StringBuilder();
+ for (int c = 0; c < Math.min(header.getLastCellNum(), 12); c++) {
+ Cell cell = header.getCell(c);
+ String h = cell == null ? "" : FORMATTER.formatCellValue(cell).trim();
+ if (h.isEmpty()) continue;
+ if (sb.length() > 0) sb.append(" | ");
+ sb.append(h);
+ }
+ return sb.length() == 0 ? "绌�" : sb.toString();
+ }
+
/** 鎸夎〃澶村悕绉板缓绔嬪垪鍙锋槧灏勶紝鍏煎鏂版棫妯℃澘鍒椾綅宸紓 */
private Map<String, Integer> buildHeaderMap(Row header) {
Map<String, Integer> colMap = new HashMap<>();
@@ -1217,6 +1848,33 @@
}
}
return colMap;
+ }
+
+ /** 鍏煎琛ㄥご鏄犲皠锛氬幓寮曞彿/鍘荤┖鐧斤紱绾嫳鏂囪〃澶寸粺涓�澶у啓锛堝 'OWNERNAME' 鈫� OWNERNAME锛夛紝渚夸簬鏂版棫妯℃澘娣风敤 */
+ private Map<String, Integer> buildHeaderMapCompat(Row header) {
+ Map<String, Integer> colMap = new HashMap<>();
+ if (header == null) return colMap;
+ for (Cell cell : header) {
+ if (cell == null) continue;
+ String h = FORMATTER.formatCellValue(cell).replace("'", "").replace("\"", "").trim();
+ if (h.isEmpty()) continue;
+ boolean ascii = true;
+ for (int i = 0; i < h.length(); i++) {
+ if (h.charAt(i) > 127) { ascii = false; break; }
+ }
+ if (ascii) h = h.toUpperCase();
+ if (!colMap.containsKey(h)) colMap.put(h, cell.getColumnIndex());
+ }
+ return colMap;
+ }
+
+ /** 鎸夊埆鍚嶉『搴忓彇绗竴涓瓨鍦ㄧ殑鍒楀彿锛屽叏閮ㄧ己澶辫繑鍥� null */
+ private Integer firstColIdx(Map<String, Integer> colMap, String... aliases) {
+ for (String a : aliases) {
+ Integer idx = colMap.get(a);
+ if (idx != null) return idx;
+ }
+ return null;
}
private int colIdx(Map<String, Integer> colMap, String header) {
@@ -1778,8 +2436,10 @@
if (name == null || name.isEmpty()) return false;
String norm = name.replace(" ", "");
int month = Integer.parseInt(period.substring(5, 7));
+ String normDash = norm.replace('.', '-'); // "2026.8"/"2026.08" -> "2026-8"/"2026-08"
return norm.contains(month + "鏈�") || norm.contains(String.format("%02d", month) + "鏈�")
- || name.contains(period) || name.contains(period.replace("-0", "-"));
+ || name.contains(period) || name.contains(period.replace("-0", "-"))
+ || normDash.contains(period) || normDash.contains(period.replace("-0", "-"));
}
/** 瑙f瀽鍗曚釜甯傚窞鎶ヨ〃锛氶�� sheet锛堟姤琛ㄦ湡鍚嶄紭鍏� + 鏁版嵁琛屾暟锛夛紝鎸夊垪鍚嶆槧灏勬彁鍙栭」鐩 */
@@ -1794,6 +2454,11 @@
if (col == null) continue;
int cnt = countInvestRows(sh, col);
int score = cnt + (cnt > 0 && investSheetMatchesPeriod(sh, period) ? 1000000 : 0);
+ if (cnt > 0 && investSheetMatchesPeriod(sh, period)) {
+ // 褰撴湡鐪熷疄鎶ヨ〃 sheet 鍚嶉�氬父甯﹀勾浠斤紙濡� 2026.8/2026骞�8鏈堬級锛屽巻鍙瞫heet澶氫负绾湀浠藉悕锛�8鏈堬級锛氬甫骞翠唤鐨勪紭鍏�
+ String norm = sh.getSheetName() == null ? "" : sh.getSheetName().replace(" ", "");
+ if (norm.contains(period.substring(0, 4))) score += 100000;
+ }
if (score > bestScore) {
best = sh;
bestCol = col;
@@ -1829,19 +2494,21 @@
}
if (name == null || name.trim().isEmpty()) continue;
String nt = name.trim().replaceAll("[\r\n]+", "");
- if (nt.contains("鍚堣") || nt.contains("鎬昏")) continue;
+ String ntFlat = nt.replaceAll("\\s+", "");
+ if (ntFlat.contains("鍚堣") || ntFlat.contains("鎬昏") || ntFlat.contains("灏忚")) continue;
if (nt.matches("^[涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+銆�.*")) continue;
if (nt.matches("^锛圼涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+锛�.*")) continue;
- if (nt.replace(" ", "").matches(".*灏廫\s*璁�.*")) continue;
if (nt.startsWith("濉姤") || nt.startsWith("鍗曚綅璐熻矗浜�") || nt.matches("^\\d+銆�.*")) continue;
if (hasSeq) {
if (seq == null || seq.trim().isEmpty()) {
// 甯傚窞灏忚妭琛岋紙濡� "姝︽眽" "澶� 闂�"锛夛紝鏇存柊褰撳墠甯傚窞
String c = investmentCity(nt);
- if (c != null) currentCity = c;
+ if (c != null) { currentCity = c; continue; }
+ // 搴忓彿涓虹┖鐨勭湡瀹為」鐩锛堥儴鍒嗗競宸炴湀鎶ヤ笉濉簭鍙凤紝濡傜鍐滄灦鐗╂祦鍥級涔熸寜椤圭洰澶勭悊
+ if (!isLikelyInvestProjectName(nt)) continue;
+ } else if (!seq.trim().matches("\\d+(\\.\\d+)?")) {
continue;
}
- if (!seq.trim().matches("\\d+(\\.\\d+)?")) continue;
} else {
// 鏃犲簭鍙峰垪锛堥殢宸炵瓑锛夛細椤圭洰鍚嶉潪绌哄嵆椤圭洰琛岋紝甯傚窞灏忚妭琛岀敤 investmentCity 璇嗗埆
String c = investmentCity(nt);
@@ -1851,6 +2518,8 @@
project.setProjectName(nt);
project.setCategory(category);
project.setCity(currentCity);
+ String countyCell = cellStr(row, col.get("county"));
+ if (countyCell != null && !countyCell.trim().isEmpty()) project.setCounty(countyCell.trim());
project.setBuilderName(cellStr(row, col.get("builder")));
project.setConstructNature(cellStr(row, col.get("nature")));
project.setStartTime(investTime(cellStr(row, col.get("start"))));
@@ -1886,13 +2555,22 @@
Row row = sh.getRow(r);
if (row == null) continue;
boolean hasName = false;
+ int headerCells = 0;
for (int c = 0; c <= row.getLastCellNum(); c++) {
String v = cellStr(row, c);
- if (v != null && v.contains("椤圭洰鍚嶇О")) { hasName = true; break; }
+ if (v == null) continue;
+ String t = v.trim();
+ if (t.isEmpty() || t.length() > 20) continue; // 璇存槑鎬ч暱鍙ヤ笉绠楄〃澶村瓧娈�
+ String k = investHeaderKey(t);
+ if (k == null) continue;
+ headerCells++;
+ if ("name".equals(k)) hasName = true;
}
- if (!hasName) continue;
+ // 鐪熻〃澶磋鑷冲皯鏈� 2 涓煭琛ㄥご瀛楁锛涘彧鏈変竴鏍奸暱鍙ュ惈銆岄」鐩悕绉般�嶇殑璇存槑椤典細琚帓闄�
+ if (!hasName || headerCells < 2) continue;
Map<Integer, String> colTexts = new HashMap<>();
- for (int rr = r; rr <= Math.min(r + 3, sh.getLastRowNum()); rr++) {
+ int scanEnd = investHeaderScanEnd(sh, r, row);
+ for (int rr = r; rr <= scanEnd; rr++) {
Row rr2 = sh.getRow(rr);
if (rr2 == null) continue;
for (int c = 0; c <= rr2.getLastCellNum(); c++) {
@@ -1903,23 +2581,8 @@
}
Map<String, Integer> col = new HashMap<>();
for (Map.Entry<Integer, String> e : colTexts.entrySet()) {
- int c = e.getKey();
- String v = e.getValue();
- if (v.contains("椤圭洰鍚嶇О")) col.putIfAbsent("name", c);
- else if (v.contains("鎶曡祫寤鸿") || v.contains("寤鸿鍗曚綅") || v.contains("椤圭洰涓氫富")) col.putIfAbsent("builder", c);
- else if (v.contains("寤鸿鎬ц川")) col.putIfAbsent("nature", c);
- else if (v.contains("寮�宸�")) col.putIfAbsent("start", c);
- else if (v.contains("绔e伐") || v.contains("寤烘垚")) col.putIfAbsent("end", c);
- else if ((v.contains("璁″垝鎬绘姇璧�") || v.contains("鎬绘姇璧�")) && !v.contains("绱")) col.putIfAbsent("total", c);
- else if (v.contains("鑷紑濮嬪缓璁�")) col.putIfAbsent("startcum", c);
- else if (v.contains("鏈勾璁″垝")) col.putIfAbsent("yearplan", c);
- else if (v.contains("鑷勾鍒濈疮璁�")) col.putIfAbsent("yearcum", c);
- else if (v.contains("鏈湀瀹屾垚")) col.putIfAbsent("monthdone", c);
- else if (v.contains("寤鸿闃舵")) col.putIfAbsent("stage", c);
- else if (v.contains("褰㈣薄杩涘害")) col.putIfAbsent("desc", c);
- else if (v.contains("宸ュ彲") && !v.contains("椤圭洰鍚嶇О")) col.putIfAbsent("gk", c);
- else if (v.contains("鍒濊") && !v.contains("椤圭洰鍚嶇О")) col.putIfAbsent("cs", c);
- else if (v.contains("寤虹瓚闈㈢Н") && !v.contains("鏂板")) col.putIfAbsent("area", c);
+ String k = investHeaderKey(e.getValue());
+ if (k != null) col.putIfAbsent(k, e.getKey());
}
if (!col.containsKey("name")) continue;
for (int c = 0; c <= row.getLastCellNum(); c++) {
@@ -1936,14 +2599,85 @@
int md = col.get("yearcum") + 1;
if (md <= row.getLastCellNum()) col.put("monthdone", md);
}
+ // desc 缂哄け鏃舵寜 stage+1 鍏滃簳锛氱渷鏍囥�岄」鐩缓璁鹃樁娈点�佽繘搴︽儏鍐点�嶆槸 L/M 鍚堝苟琛ㄥご锛�
+ // 瀛濇劅/鎭╂柦/鏋楀尯/闅忓窞绛夊競宸炲彧鍦� L 鍐欍�屽缓璁鹃樁娈点�嶃�丮 鐩存帴鍐欒繘搴︽枃瀛楄�屼笉鍐欐爣棰�
+ if (!col.containsKey("desc") && col.containsKey("stage") && col.get("stage") >= 0) {
+ int dc = col.get("stage") + 1;
+ if (dc <= row.getLastCellNum()) col.put("desc", dc);
+ }
for (String k : new String[]{"builder", "nature", "start", "end", "total", "startcum",
- "yearplan", "yearcum", "monthdone", "stage", "desc", "gk", "cs", "area"}) {
+ "yearplan", "yearcum", "monthdone", "stage", "desc", "gk", "cs", "area", "county"}) {
col.putIfAbsent(k, -1);
}
col.put("headerRow", r);
return col;
}
return null;
+ }
+
+ /** 鍗曚釜琛ㄥご鍗曞厓鏍� -> 瀛楁閿紙null 琛ㄧず涓嶆槸琛ㄥご瀛楁锛夛紱渚涜〃澶磋鍒ゅ畾涓庡垪鏄犲皠鍏辩敤 */
+ private String investHeaderKey(String v) {
+ if (v == null) return null;
+ String flat = v.replace(" ", "");
+ if (v.contains("椤圭洰鍚嶇О")) return "name";
+ // 銆屽幙锛堝競銆佸尯锛夈�嶇殑鍚勭鍐欐硶锛氬崐/鍏ㄨ鎷彿銆侀】鍙�/閫楀彿銆佹湁鏃犳嫭鍙凤紝缁熶竴褰掍竴鍖栧悗鍐嶅垽瀹�
+ String flatCounty = flat.replace("锛�", "(").replace("锛�", ")").replace("銆�", "").replace("锛�", "").replace(",", "");
+ if (flatCounty.contains("鍘�(甯傚尯)") || flatCounty.contains("鍘垮競鍖�") || flatCounty.contains("鍖哄幙")) return "county";
+ if (v.contains("鎶曡祫寤鸿") || v.contains("寤鸿鍗曚綅") || v.contains("椤圭洰涓氫富")) return "builder";
+ if (v.contains("寤鸿鎬ц川")) return "nature";
+ if (v.contains("寮�宸�")) return "start";
+ if (v.contains("绔e伐") || v.contains("寤烘垚")) return "end";
+ if ((v.contains("璁″垝鎬绘姇璧�") || v.contains("鎬绘姇璧�")) && !v.contains("绱")) return "total";
+ if (v.contains("鑷紑濮嬪缓璁�")) return "startcum";
+ if (v.contains("鏈勾璁″垝")) return "yearplan";
+ if (v.contains("鑷勾鍒濈疮璁�")) return "yearcum";
+ if (v.contains("鏈湀瀹屾垚")) return "monthdone";
+ if (v.contains("寤鸿闃舵")) return "stage";
+ if (v.contains("褰㈣薄杩涘害") || v.contains("寤鸿杩涘睍")) return "desc";
+ if (v.contains("宸ュ彲") && !v.contains("椤圭洰鍚嶇О")) return "gk";
+ if (v.contains("鍒濊") && !v.contains("椤圭洰鍚嶇О")) return "cs";
+ if (v.contains("寤虹瓚闈㈢Н") && !v.contains("鏂板")) return "area";
+ return null;
+ }
+
+ /** 琛ㄥご鏂囨湰鎵弿涓嬬晫锛氶粯璁よ〃澶磋 +3锛涜嫢銆岄」鐩悕绉般�嶅垪棣栦釜鏁版嵁琛屾洿闈犱笅锛屽垯鎵╁睍鍒版暟鎹鍓嶄竴琛岋紙涓婇檺 +12锛夈��
+ * 鑳屾櫙锛氬捀瀹�/鍗佸牥绛夊競宸炴妸銆屽缓璁鹃樁娈�/褰㈣薄杩涘害銆嶆爣绛惧啓鍦ㄨ〃澶翠笅绗� 4~8 琛岋紝鍥哄畾 +3 浼氭紡鍒楋紙褰㈣薄杩涘害璇讳笉鍒帮級銆� */
+ private int investHeaderScanEnd(Sheet sh, int headerRow, Row headerRowObj) {
+ int hardEnd = Math.min(headerRow + 12, sh.getLastRowNum());
+ int nameCol = -1;
+ if (headerRowObj != null) {
+ for (int c = 0; c <= headerRowObj.getLastCellNum(); c++) {
+ String v = cellStr(headerRowObj, c);
+ if (v != null && v.contains("椤圭洰鍚嶇О")) { nameCol = c; break; }
+ }
+ }
+ int firstData = findFirstInvestDataRow(sh, headerRow, nameCol);
+ int scanEnd = (firstData > headerRow)
+ ? Math.min(hardEnd, firstData - 1)
+ : Math.min(headerRow + 3, sh.getLastRowNum());
+ if (scanEnd < headerRow) scanEnd = headerRow;
+ return scanEnd;
+ }
+
+ /** 銆岄」鐩悕绉般�嶅垪棣栦釜鏁版嵁琛屼笅鏍囷紙璺宠繃琛ㄥご缁涓庣┖琛岋級锛涙壘涓嶅埌杩斿洖 -1 */
+ private int findFirstInvestDataRow(Sheet sh, int headerRow, int nameCol) {
+ if (nameCol < 0) return -1;
+ for (int r = headerRow + 1; r <= sh.getLastRowNum(); r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ String name = cellStr(row, nameCol);
+ if (name == null || name.trim().isEmpty()) continue;
+ String nt = name.trim().replaceAll("[\r\n]+", "");
+ if (nt.contains("椤圭洰鍚嶇О")) continue;
+ String flat = nt.replaceAll("\\s+", "");
+ if (flat.contains("鍚堣") || flat.contains("鎬昏") || flat.contains("灏忚")) continue;
+ if (nt.matches("^[涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+銆�.*")) continue;
+ if (nt.matches("^锛圼涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+锛�.*")) continue;
+ if (nt.startsWith("濉姤") || nt.startsWith("鍗曚綅璐熻矗浜�") || nt.matches("^\\d+銆�.*")) continue;
+ if (investmentCity(nt) != null) continue;
+ return r;
+ }
+ return -1;
}
/** 缁熻 sheet 鍐呴」鐩暟鎹鏁帮紙鐢ㄤ簬澶� sheet 鍘嗗彶鏂囦欢閫夊綋鏈� sheet锛� */
@@ -1958,15 +2692,19 @@
String name = cellStr(row, nameCol);
if (name == null || name.trim().isEmpty()) continue;
String nt = name.trim().replaceAll("[\r\n]+", "");
- if (nt.contains("鍚堣") || nt.contains("鎬昏")) continue;
+ String ntFlat = nt.replaceAll("\\s+", "");
+ if (ntFlat.contains("鍚堣") || ntFlat.contains("鎬昏") || ntFlat.contains("灏忚")) continue;
if (nt.matches("^[涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+銆�.*")) continue;
- if (nt.replace(" ", "").matches(".*灏廫\s*璁�.*")) continue;
if (nt.startsWith("濉姤") || nt.startsWith("鍗曚綅璐熻矗浜�") || nt.matches("^\\d+銆�.*")) continue;
if (!hasSeq) {
if (nt.length() > 4) count++;
} else {
String seq = cellStr(row, seqCol);
- if (seq != null && seq.trim().matches("\\d+(\\.\\d+)?")) count++;
+ if (seq != null && seq.trim().matches("\\d+(\\.\\d+)?")) {
+ count++;
+ } else if ((seq == null || seq.trim().isEmpty()) && isLikelyInvestProjectName(nt)) {
+ count++;
+ }
}
}
return count;
@@ -2036,6 +2774,15 @@
log.error("load five year project names error", e);
}
return names;
+ }
+
+ /** 搴忓彿涓虹┖鐨勯潪甯傚窞琛屾槸鍚﹀儚椤圭洰琛岋細闀垮害>4 涓旈潪澶囨敞/璇存槑绛夋潅椤� */
+ private boolean isLikelyInvestProjectName(String nt) {
+ if (nt == null || nt.length() <= 4) return false;
+ if (investmentCity(nt) != null) return false;
+ return !nt.startsWith("澶囨敞") && !nt.startsWith("璇存槑") && !nt.startsWith("娉細")
+ && !nt.startsWith("濉〃") && !nt.startsWith("瀹℃牳") && !nt.startsWith("缁熻")
+ && !nt.startsWith("璐熻矗");
}
/** 椤圭洰鍚嶈鑼冨寲锛氬幓绌虹櫧/鎹㈣/鎷彿鍐呭/鏍囩偣/銆岄」鐩�嶅悗缂�锛岀敤浜庡崄浜斾簲娓呭崟鍖归厤 */
@@ -2213,26 +2960,42 @@
this.monthly = monthly;
}
}
- /** 鎶曡祫绯荤粺瀵煎嚭瀵煎叆锛堝鏍告瘮瀵瑰熀鍑嗭級锛屽浐瀹�8鍒楋細琛屽彿/鍗曚綅/鏃舵湡/璁″垝鎬绘姇璧�/鑷紑濮嬬疮璁�/鏈勾璁″垝/鑷勾鍒濈疮璁�/褰撴湀瀹屾垚 */
+ /** 鎶曡祫绯荤粺瀵煎嚭瀵煎叆锛堝鏍告瘮瀵瑰熀鍑嗭級銆�
+ * 鍒椾綅**鎸夎〃澶村叧閿瓧瀹氫綅**锛屽吋瀹逛袱绉嶆ā鏉匡細
+ * - 鑰佹ā鏉裤�婃ā鏉縚鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls銆嬶細琛屽彿/鍗曚綅/鏃舵湡/璁″垝鎬绘姇璧�/鑷紑濮嬪缓璁剧疮璁�/鏈勾璁″垝鎶曡祫/鑷勾鍒濈疮璁�/褰撴湀瀹屾垚鎶曡祫
+ * - 鏂版ā鏉裤��2026骞碭鏈堟姇璧勭郴缁熼」鐩槑缁�.xls銆嬶紙鐢ㄦ埛 2026-09-21 鏇存崲锛夛細琛屽彿/鎶曡祫椤圭洰/鏃舵湡/椤圭洰鎵�澶勯樁娈�/璁″垝鎬绘姇璧�/鏈勾璁″垝鎶曡祫/鑷紑濮嬪缓璁捐嚦鏈勾锛堟湀锛夊簳-绱瀹屾垚鎶曡祫/褰撴湀瀹屾垚鎶曡祫
+ * 鏂版ā鏉�**娌℃湁銆岃嚜骞村垵绱銆嶅垪** 鈫� year_cum 鐣欑┖锛堝鏍告椂鎸夈�屽熀鍑嗙己澶便�嶈烦杩囷紝涓嶅啀褰撴垚 0 鍙備笌姣斿锛夈�� */
public int importInvestmentSystem(MultipartFile file, String period) throws Exception {
List<InvestmentSystem> list = new ArrayList<>();
int failRows = 0;
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
- requireHeaderKeyword(sheet, "鎶曡祫绯荤粺瀵煎嚭", "椤圭洰鍚嶇О", "璁″垝鎬绘姇璧�", "鍗曚綅");
+ requireHeaderKeyword(sheet, "鎶曡祫绯荤粺瀵煎嚭", "鎶曡祫椤圭洰", "椤圭洰鍚嶇О", "鍗曚綅", "璁″垝鎬绘姇璧�");
+ Row header = sheet.getRow(0);
+ int cName = findColByKeywords(header, "鎶曡祫椤圭洰", "椤圭洰鍚嶇О", "鍗曚綅");
+ int cTotal = findColByKeywords(header, "璁″垝鎬绘姇璧�");
+ int cYearPlan = findColByKeywords(header, "鏈勾璁″垝鎶曡祫");
+ int cStartCum = findColByKeywords(header, "鑷紑濮嬪缓璁�");
+ int cYearCum = findColByKeywords(header, "鑷勾鍒�");
+ int cMonthDone = findColByKeywords(header, "褰撴湀瀹屾垚鎶曡祫");
+ if (cName < 0 || cTotal < 0 || cYearPlan < 0 || cStartCum < 0 || cMonthDone < 0) {
+ throw new RuntimeException("鎵�閫夋暟鎹被鍨嬨�愭姇璧勭郴缁熷鍑恒�戣〃澶村垪涓嶅畬鏁达細闇�瑕併�屾姇璧勯」鐩�(鎴栧崟浣�)銆佽鍒掓�绘姇璧勩�佹湰骞磋鍒掓姇璧勩��"
+ + "鑷紑濮嬪缓璁锯�︾疮璁″畬鎴愭姇璧勩�佸綋鏈堝畬鎴愭姇璧勩�嶇瓑鍒楋紝瀹為檯琛ㄥご涓恒��" + headerText(header)
+ + "銆嶃�傝浣跨敤绯荤粺妯℃澘銆�2026骞碭鏈堟姇璧勭郴缁熼」鐩槑缁嗐�嬫垨銆婃ā鏉縚鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦)銆嬫暣鐞嗗悗鍐嶅鍏�");
+ }
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
- String name = getString(row, 1);
+ String name = getString(row, cName);
if (name == null || name.trim().isEmpty()) continue;
InvestmentSystem sys = new InvestmentSystem();
sys.setReportPeriod(period);
sys.setProjectName(name.trim());
- sys.setTotalInvestment(getDecimal(row, 3));
- sys.setStartCum(getDecimal(row, 4));
- sys.setYearPlan(getDecimal(row, 5));
- sys.setYearCum(getDecimal(row, 6));
- sys.setMonthDone(getDecimal(row, 7));
+ sys.setTotalInvestment(getDecimal(row, cTotal));
+ sys.setStartCum(getDecimal(row, cStartCum));
+ sys.setYearPlan(getDecimal(row, cYearPlan));
+ sys.setYearCum(cYearCum < 0 ? null : getDecimal(row, cYearCum));
+ sys.setMonthDone(getDecimal(row, cMonthDone));
list.add(sys);
}
} catch (Exception e) {
@@ -2257,15 +3020,18 @@
}
/** 鍩庡競鍏氦鏈堟姤瀵煎叆锛堝煄甯傚叕鍏变氦閫氭湀搴﹁繍钀ユ儏鍐碉紝浼佷笟绾ф槑缁嗭紝鍙栧叕鍏辨苯鐢佃溅瀛楁锛� */
+ /** 鍩庡競鍏氦锛圲203锛夋ā鏉垮叧閿垪浣嶏紙0-based锛夛細鎵�灞炲湴鍖�/浼佷笟浠g爜/浼佷笟鍚嶇О/杩愯惀杞︽暟/瀹㈣繍閲�/杞ㄩ亾瀹㈣繍閲�/杞浮瀹㈣繍閲�/骞冲潎杩愯窛/濉〃浜�/鑱旂郴鐢佃瘽/鎶ュ嚭鏃ユ湡 */
+ private static final int[] CITY_BUS_HEADER_COLS = {0, 1, 2, 5, 8, 13, 17, 19, 24, 25, 26};
+ private static final String[] CITY_BUS_HEADER_KEYWORDS =
+ {"鎵�灞炲湴鍖�", "浼佷笟浠g爜", "浼佷笟鍚嶇О", "鍏叡姹界數杞杩愯惀杞︽暟", "鍏叡姹界數杞瀹㈣繍閲�", "杞ㄩ亾浜ら�歘瀹㈣繍閲�",
+ "鍩庡競瀹㈣繍杞浮_瀹㈣繍閲�", "鍏叡姹界數杞﹀钩鍧囪繍璺�", "濉〃浜�", "鑱旂郴鐢佃瘽", "鎶ュ嚭鏃ユ湡"};
+
public ImportResult importCityBus(MultipartFile file, String period) throws Exception {
List<CityBusRow> rows = new ArrayList<>();
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
- Row header = sheet.getRow(0);
- if (header == null || getString(header, 2) == null
- || !getString(header, 2).contains("浼佷笟鍚嶇О")) {
- throw new RuntimeException("鏈壘鍒拌〃澶磋锛堥渶鍖呭惈 浼佷笟鍚嶇О 鍒楋級");
- }
+ requireHeaderLayout(sheet, "鍩庡競鍏氦", "妯℃澘_浜や紒缁烾203琛╛鍩庡競鍏叡浜ら�氭湀搴﹁繍钀ユ儏鍐碉紙2026骞�7鏈堬級.xls",
+ CITY_BUS_HEADER_COLS, CITY_BUS_HEADER_KEYWORDS);
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
@@ -2369,15 +3135,18 @@
}
/** 宸℃父鍑虹鏈堟姤瀵煎叆锛堝競宸炵骇锛�17 琛岋紱鍚牳瀹炶鏄�/涓婃姤璇存槑/淇敼璁板綍锛� */
+ /** 宸℃父鍑虹锛圲402锛夋ā鏉垮叧閿垪浣嶏紙0-based锛夛細鎵�灞炲湴鍖�/浼佷笟浠g爜/浼佷笟鍚嶇О/杞藉杞︽鎬绘暟/瀹㈣繍閲�/鏃呭鍛ㄨ浆閲�/杩愯惀杞﹁締鏁�/骞冲潎杩愯窛/濉〃浜�/鑱旂郴鐢佃瘽/鎶ュ嚭鏃ユ湡 */
+ private static final int[] CITY_TAXI_HEADER_COLS = {0, 1, 2, 4, 6, 8, 10, 11, 15, 16, 17};
+ private static final String[] CITY_TAXI_HEADER_KEYWORDS =
+ {"鎵�灞炲湴鍖�", "浼佷笟浠g爜", "浼佷笟鍚嶇О", "杞藉杞︽鎬绘暟", "瀹㈣繍閲�", "鏃呭鍛ㄨ浆閲�", "杩愯惀杞﹁締鏁�",
+ "宸℃父鍑虹姹借溅骞冲潎杩愯窛", "濉〃浜�", "鑱旂郴鐢佃瘽", "鎶ュ嚭鏃ユ湡"};
+
public ImportResult importCityTaxi(MultipartFile file, String period) throws Exception {
List<TaxiRow> rows = new ArrayList<>();
try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = wb.getSheetAt(0);
- Row header = sheet.getRow(0);
- if (header == null || getString(header, 2) == null
- || !getString(header, 2).contains("浼佷笟鍚嶇О")) {
- throw new RuntimeException("鏈壘鍒拌〃澶磋锛堥渶鍖呭惈 浼佷笟鍚嶇О 鍒楋級");
- }
+ requireHeaderLayout(sheet, "宸℃父鍑虹", "妯℃澘_浜よ缁烾402琛�-宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤(2026).xls",
+ CITY_TAXI_HEADER_COLS, CITY_TAXI_HEADER_KEYWORDS);
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
@@ -2508,6 +3277,501 @@
recordBatch(file.getOriginalFilename(), "CITY_TAXI_AUTH", period, list.size(), success, fail, null);
log.info("CityTaxiAuth imported: {} rows for {}", success, period);
return success;
+ }
+
+
+ // ========== 缃戠害杞︽湀搴﹁緭鍏ュ鍏ワ紙鍏ㄧ渷 pin / 璁㈠崟锛� ==========
+
+ /** 缃戠害杞︽�绘暟鏈堝害杈撳叆锛氭ā鏉縚缃戠害杞︽�绘暟.xlsx锛坰heet1锛汚 鍒�=鏈堜唤鏃ユ湡锛汢=鍏ㄧ渷瀹㈣繍閲� C=鍏朵腑鍩庡競鍐� D=鍛ㄨ浆閲� E=鍏朵腑鍩庡競鍐呭懆杞噺锛夈�傛暣琛ㄦ寜鎶ヨ〃鏈熻鐩栥�� */
+ public synchronized int importWycTotal(MultipartFile file, String period) throws Exception {
+ Map<String, WycTotalMonthly> rows = new LinkedHashMap<>();
+ try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+ Sheet sheet = wb.getSheetAt(0);
+ for (int r = 2; r <= sheet.getLastRowNum(); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String rp = periodOfCell(row.getCell(0));
+ if (rp == null) continue;
+ double pk = dbl(row, 1), pck = dbl(row, 2), pz = dbl(row, 3), pcz = dbl(row, 4);
+ if (pk <= 0 && pck <= 0 && pz <= 0 && pcz <= 0) continue;
+ WycTotalMonthly item = new WycTotalMonthly();
+ item.setReportPeriod(rp);
+ item.setPinK(pk > 0 ? pk : null);
+ item.setPinCK(pck > 0 ? pck : null);
+ item.setPinZ(pz > 0 ? pz : null);
+ item.setPinCZ(pcz > 0 ? pcz : null);
+ rows.put(rp, item);
+ }
+ }
+ if (rows.isEmpty()) throw new RuntimeException("鏈瘑鍒埌鏈夋晥鏈堜唤琛岋細A 鍒楀簲涓烘棩鏈燂紙濡� 2026-07锛夛紝B-E 涓哄叏鐪佸杩愰噺/鍩庡競鍐�/鍛ㄨ浆閲�/鍩庡競鍐呭懆杞噺");
+ int ok = upsertWycTotal(rows);
+ log.info("WycTotal imported: {} periods ok={}", rows.size(), ok);
+ return ok;
+ }
+
+ /** 缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺锛氭ā鏉縚缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx 棣栦釜鏁版嵁 sheet锛圓=鏈堜唤锛汢-E=鍏ㄧ渷 pin锛汧=璁㈠崟鍚堣锛汫-W=17 甯傚窞璁㈠崟锛� */
+ public synchronized int importWycOrder(MultipartFile file, String period) throws Exception {
+ Map<String, WycOrderMonthly> rows = new LinkedHashMap<>();
+ try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+ Sheet sheet = wb.getSheetAt(0);
+ int head = -1;
+ for (int r = 0; r <= Math.min(sheet.getLastRowNum(), 4); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String a = getString(row, 0);
+ if (a != null && a.contains("鏈堜唤")) { head = r; break; }
+ }
+ if (head < 0) throw new RuntimeException("鏈壘鍒拌〃澶达細A 鍒楅渶鍚�滄湀浠解��");
+ for (int r = head + 1; r <= sheet.getLastRowNum(); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String rp = periodOfCell(row.getCell(0));
+ if (rp == null) continue;
+ double pk = dbl(row, 1), pck = dbl(row, 2), pz = dbl(row, 3), pcz = dbl(row, 4), sum = dbl(row, 5);
+ double citySum = 0;
+ for (int k = 0; k < 17; k++) citySum += dbl(row, 6 + k);
+ if (pk <= 0 && pck <= 0 && pz <= 0 && pcz <= 0 && sum <= 0 && citySum <= 0) continue;
+ WycOrderMonthly it = new WycOrderMonthly();
+ it.setReportPeriod(rp);
+ it.setPinK(pk > 0 ? pk : null);
+ it.setPinCK(pck > 0 ? pck : null);
+ it.setPinZ(pz > 0 ? pz : null);
+ it.setPinCZ(pcz > 0 ? pcz : null);
+ if (sum <= 0 && citySum > 0) sum = citySum;
+ it.setOrderSum(sum > 0 ? sum : null);
+ it.setOrderWuhan(dblN(row, 6));
+ it.setOrderHuangshi(dblN(row, 7));
+ it.setOrderShiyan(dblN(row, 8));
+ it.setOrderYichang(dblN(row, 9));
+ it.setOrderXiangyang(dblN(row, 10));
+ it.setOrderEzhou(dblN(row, 11));
+ it.setOrderJingmen(dblN(row, 12));
+ it.setOrderXiaogan(dblN(row, 13));
+ it.setOrderJingzhou(dblN(row, 14));
+ it.setOrderHuanggang(dblN(row, 15));
+ it.setOrderXianning(dblN(row, 16));
+ it.setOrderSuizhou(dblN(row, 17));
+ it.setOrderEnshi(dblN(row, 18));
+ it.setOrderXiantao(dblN(row, 19));
+ it.setOrderQianjiang(dblN(row, 20));
+ it.setOrderTianmen(dblN(row, 21));
+ it.setOrderShennong(dblN(row, 22));
+ rows.put(rp, it);
+ }
+ }
+ if (rows.isEmpty()) throw new RuntimeException("鏈瘑鍒埌鏈夋晥鏈堜唤琛岋細A 鍒楀簲涓� yyyy-MM锛孊-E 鍏ㄧ渷 pin / F 鍚堣 / G-W 17 甯傚窞璁㈠崟");
+ int ok = upsertWyc(rows);
+ log.info("WycOrder imported: {} periods ok={}", rows.size(), ok);
+ return ok;
+ }
+
+ private int upsertWyc(Map<String, WycOrderMonthly> rows) {
+ int ok = 0, fail = 0;
+ for (WycOrderMonthly item : rows.values()) {
+ try {
+ wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>().eq(WycOrderMonthly::getReportPeriod, item.getReportPeriod()));
+ wycOrderMapper.insert(item);
+ recordBatch("缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺", "WYC_ORDER", item.getReportPeriod(), 1, 1, 0, null);
+ ok++;
+ } catch (Exception e) {
+ fail++;
+ log.error("WycOrder insert error period={}", item.getReportPeriod(), e);
+ }
+ }
+ return ok;
+ }
+
+ private int upsertWycTotal(Map<String, WycTotalMonthly> rows) {
+ int ok = 0, fail = 0;
+ for (WycTotalMonthly item : rows.values()) {
+ try {
+ wycTotalMapper.delete(new LambdaQueryWrapper<WycTotalMonthly>().eq(WycTotalMonthly::getReportPeriod, item.getReportPeriod()));
+ wycTotalMapper.insert(item);
+ recordBatch("缃戠害杞︽�绘暟", "WYC_TOTAL", item.getReportPeriod(), 1, 1, 0, null);
+ ok++;
+ } catch (Exception e) {
+ fail++;
+ log.error("WycTotal insert error period={}", item.getReportPeriod(), e);
+ }
+ }
+ return ok;
+ }
+
+ // ========== 缃戠害杞﹁鍗曡繍琛岀洃娴嬪垎鏋愭姤鍛婏紙docx锛夊鍏� ==========
+
+ /** 缃戠害杞﹁鍗曟姤鍛婂鍏ョ粨鏋滐紙鎶ヨ〃鏈�/璁㈠崟鍚堣/瑕嗙洊甯傚窞鏁�/缂哄け甯傚窞锛� */
+ public static class WycOrderReportResult {
+ private final String period;
+ private final double orderSum;
+ private final int cityCount;
+ private final List<String> missingCities;
+
+ public WycOrderReportResult(String period, double orderSum, int cityCount, List<String> missingCities) {
+ this.period = period;
+ this.orderSum = orderSum;
+ this.cityCount = cityCount;
+ this.missingCities = missingCities;
+ }
+
+ public String getPeriod() { return period; }
+ public double getOrderSum() { return orderSum; }
+ public int getCityCount() { return cityCount; }
+ public List<String> getMissingCities() { return missingCities; }
+ }
+
+ /**
+ * 銆婄綉绾﹁溅琛屼笟YYYY骞碝鏈堣繍琛岀洃娴嬫儏鍐靛垎鏋愭姤鍛娿�嬶紙docx锛夊鍏ワ細
+ * 瑙f瀽鎶ュ憡闄勪欢涓�屽湴甯傚悕绉� | 鈥� | 璁㈠崟鏁版�绘暟銆嶈〃鐨� 17 甯傚窞璁㈠崟鏁帮紝鍐欏叆 wyc_order_monthly 鐨�
+ * F 璁㈠崟鍚堣涓� G-W 17 甯傚窞璁㈠崟鍒楋紱鍏ㄧ渷 pin锛圔-E锛夋部鐢ㄨ鏈熷凡鏈夎锛岃嫢鏃犺鏈熻鍒欎粠
+ * wyc_total_monthly 鍚屾姤琛ㄦ湡澶嶅埗銆傛姤琛ㄦ湡浼樺厛鍙栨姤鍛婃爣棰樺勾鏈堬紝鍙栦笉鍒版椂鐢ㄩ〉闈㈡墍閫夋姤琛ㄦ湡銆�
+ */
+ public synchronized WycOrderReportResult importWycOrderReport(MultipartFile file, String period) throws Exception {
+ Map<String, Double> orders = new LinkedHashMap<>();
+ double[] sumHolder = new double[]{0.0};
+ String docPeriod;
+ byte[] bytes;
+ try (java.io.InputStream in = file.getInputStream()) {
+ bytes = IoUtil.readBytes(in);
+ }
+ boolean isZip = bytes.length > 3 && (bytes[0] & 0xFF) == 0x50 && (bytes[1] & 0xFF) == 0x4B;
+ boolean isWordMl = !isZip && looksLikeWordMl(bytes);
+ try {
+ if (isZip && !isWordPackage(bytes)) {
+ throw new RuntimeException("杩欎笉鏄� Word 鏂囨。锛堝帇缂╁寘閲屾病鏈� word/document.xml锛夛紝"
+ + "澶氬崐鏄� Excel 鎴栧叾瀹冩牸寮忋�傝涓婁紶缃戠害杞﹁鍗曞垎鏋愭姤鍛婄殑 .docx锛堟垨 Word 2003 XML锛�");
+ }
+ if (isZip) {
+ try (XWPFDocument doc = new XWPFDocument(new java.io.ByteArrayInputStream(bytes))) {
+ docPeriod = periodOfParagraphs(paragraphsOfDocx(doc));
+ parseWycOrderReportTables(doc, orders, sumHolder);
+ }
+ } else if (isWordMl) {
+ // 缃戠害杞︾洃绠″钩鍙颁笅鍙戠殑鎶ュ憡甯歌 WPS/Word 瀛樻垚銆學ord 2003 XML锛堝钩閾� XML锛夈�嶅悗鍐嶆敼鍚� .docx锛�
+ // 杩欑鏂囦欢涓嶆槸 zip 鍖咃紝XWPFDocument 鎵撲笉寮�锛堟姤 "File is not a zip file"锛夛紝闇�鎸� XML 鐩存帴瑙f瀽銆�
+ String xml = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
+ Document dom = parseXmlSafely(xml);
+ docPeriod = periodOfParagraphs(paragraphsOfDocxXml(dom));
+ parseWycOrderReportTablesXml(dom, orders, sumHolder);
+ } else if (isOle2(bytes)) {
+ throw new RuntimeException("杩欐槸 Word 97-2003 鑰佹牸寮忥紙.doc锛夛紝璇风敤 WPS/Word 鎵撳紑鍚庛�屽彟瀛樹负銆峝ocx 鏍煎紡鍐嶅鍏�");
+ } else {
+ throw new RuntimeException("鏃犳硶璇嗗埆鐨勬枃浠舵牸寮忥細鏃笉鏄� docx锛坺ip 鍖咃級锛屼篃涓嶆槸 Word 2003 XML 鎴� .doc");
+ }
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException("鏃犳硶璇诲彇 Word 鏂囨。锛岃涓婁紶 .docx 鏍煎紡鐨勩�婄綉绾﹁溅琛屼笟YYYY骞碝鏈堣繍琛岀洃娴嬫儏鍐靛垎鏋愭姤鍛娿�嬶紱"
+ + "鑻ヤ笂浼犵殑鍏跺疄鏄� Excel/PDF锛岃鏀圭敤瀵瑰簲妯℃澘瀵煎叆銆傦紙鍘熷閿欒锛�" + e.getMessage() + "锛�");
+ }
+ if (orders.isEmpty()) {
+ throw new RuntimeException("鏈В鏋愬埌浠讳綍甯傚窞璁㈠崟鏁帮紝璇风‘璁や笂浼犵殑鏄�婄綉绾﹁溅琛屼笟YYYY骞碝鏈堣繍琛岀洃娴嬫儏鍐靛垎鏋愭姤鍛娿��");
+ }
+ String rp = null;
+ if (docPeriod != null && docPeriod.matches("\\d{4}-\\d{2}")) rp = docPeriod;
+ else if (period != null && period.matches("\\d{4}-\\d{2}")) rp = period;
+ if (rp == null) {
+ throw new RuntimeException("鏃犳硶纭畾鎶ヨ〃鏈燂細鎶ュ憡鏍囬鏈惈銆孻YYY骞碝鏈堛�嶏紝涓旈〉闈㈡湭閫夋嫨鎶ヨ〃鏈�");
+ }
+
+ List<String> missing = new ArrayList<>();
+ double citySum = 0.0;
+ for (String city : RegionUtil.cityList()) {
+ Double v = orders.get(city);
+ if (v == null) missing.add(city);
+ else citySum += v;
+ }
+ double sum = sumHolder[0] > 0 ? sumHolder[0] : citySum;
+
+ WycOrderMonthly it = wycOrderMapper.selectOne(new LambdaQueryWrapper<WycOrderMonthly>()
+ .eq(WycOrderMonthly::getReportPeriod, rp).last("LIMIT 1"));
+ if (it == null) it = new WycOrderMonthly();
+ it.setReportPeriod(rp);
+ it.setOrderSum(sum > 0 ? sum : null);
+ it.setOrderWuhan(orderOf(orders, "姝︽眽甯�"));
+ it.setOrderHuangshi(orderOf(orders, "榛勭煶甯�"));
+ it.setOrderShiyan(orderOf(orders, "鍗佸牥甯�"));
+ it.setOrderYichang(orderOf(orders, "瀹滄槍甯�"));
+ it.setOrderXiangyang(orderOf(orders, "瑗勯槼甯�"));
+ it.setOrderEzhou(orderOf(orders, "閯傚窞甯�"));
+ it.setOrderJingmen(orderOf(orders, "鑽嗛棬甯�"));
+ it.setOrderXiaogan(orderOf(orders, "瀛濇劅甯�"));
+ it.setOrderJingzhou(orderOf(orders, "鑽嗗窞甯�"));
+ it.setOrderHuanggang(orderOf(orders, "榛勫唸甯�"));
+ it.setOrderXianning(orderOf(orders, "鍜稿畞甯�"));
+ it.setOrderSuizhou(orderOf(orders, "闅忓窞甯�"));
+ it.setOrderEnshi(orderOf(orders, "鎭╂柦宸�"));
+ it.setOrderXiantao(orderOf(orders, "浠欐甯�"));
+ it.setOrderQianjiang(orderOf(orders, "娼滄睙甯�"));
+ it.setOrderTianmen(orderOf(orders, "澶╅棬甯�"));
+ it.setOrderShennong(orderOf(orders, "绁炲啘鏋舵灄鍖�"));
+
+ if (it.getPinK() == null || it.getPinCK() == null || it.getPinZ() == null || it.getPinCZ() == null) {
+ WycTotalMonthly tot = wycTotalMapper.selectOne(new LambdaQueryWrapper<WycTotalMonthly>()
+ .eq(WycTotalMonthly::getReportPeriod, rp).last("LIMIT 1"));
+ if (tot != null) {
+ if (it.getPinK() == null) it.setPinK(tot.getPinK());
+ if (it.getPinCK() == null) it.setPinCK(tot.getPinCK());
+ if (it.getPinZ() == null) it.setPinZ(tot.getPinZ());
+ if (it.getPinCZ() == null) it.setPinCZ(tot.getPinCZ());
+ }
+ }
+
+ wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>().eq(WycOrderMonthly::getReportPeriod, rp));
+ wycOrderMapper.insert(it);
+ recordBatch(file.getOriginalFilename(), "WYC_ORDER_REPORT", rp, 1, 1, 0, null);
+ log.info("WycOrderReport imported: period={} sum={} cities={} missing={}", rp, sum, orders.size(), missing);
+ return new WycOrderReportResult(rp, sum, orders.size(), missing);
+ }
+
+ /** 鎶ュ憡鏍囬涓殑鎶ヨ〃鏈燂細缃戠害杞﹁涓�2026骞�7鏈� -> 2026-07锛涘彇涓嶅埌杩斿洖 null */
+ private String periodOfParagraphs(List<String> paragraphs) {
+ java.util.regex.Pattern titlePat = java.util.regex.Pattern.compile("缃戠害杞﹁涓歕\s*(\\d{4})\\s*骞碶\s*(\\d{1,2})\\s*鏈�");
+ java.util.regex.Pattern anyPat = java.util.regex.Pattern.compile("(\\d{4})\\s*骞碶\s*(\\d{1,2})\\s*鏈�");
+ String fallback = null;
+ int scanned = 0;
+ for (String text : paragraphs) {
+ if (text == null) continue;
+ String flat = text.replaceAll("\\s+", "");
+ if (flat.isEmpty()) continue;
+ java.util.regex.Matcher m = titlePat.matcher(flat);
+ if (m.find()) return ymd(m.group(1), m.group(2));
+ if (fallback == null && scanned < 10) {
+ java.util.regex.Matcher m2 = anyPat.matcher(flat);
+ if (m2.find()) fallback = ymd(m2.group(1), m2.group(2));
+ }
+ scanned++;
+ }
+ return fallback;
+ }
+
+ /** docx 娈佃惤鏂囨湰锛堜繚鎸佸師濮嬮『搴忥級 */
+ private List<String> paragraphsOfDocx(XWPFDocument doc) {
+ List<String> out = new ArrayList<>();
+ for (XWPFParagraph para : doc.getParagraphs()) out.add(para.getText());
+ return out;
+ }
+
+ /** Word 2003 XML锛圵ordML锛夋钀芥枃鏈細<w:p> 涓嬫墍鏈� <w:t> 鎷兼帴鍚庡帇鎺夌┖鐧� */
+ private List<String> paragraphsOfDocxXml(Document dom) {
+ List<String> out = new ArrayList<>();
+ for (Element p : elementsByLocalName(dom, "p")) out.add(textOfXmlElement(p));
+ return out;
+ }
+
+ /** 鏄惁涓� Word 2003 XML锛圵ordML 骞抽摵 XML锛夆�斺�旇繖绉嶆枃浠跺父琚敼鍚嶆垚 .docx */
+ private boolean looksLikeWordMl(byte[] bytes) {
+ int n = Math.min(bytes.length, 4096);
+ String head = new String(bytes, 0, n, java.nio.charset.StandardCharsets.UTF_8);
+ return head.contains("wordml") && head.contains("<w:wordDocument");
+ }
+
+ /** zip 鍖呭唴鏄惁鍚� word/document.xml锛堢敤浜庡尯鍒� docx 涓� xlsx/鍏跺畠 OPC 鍖咃級 */
+ private boolean isWordPackage(byte[] bytes) {
+ try (java.util.zip.ZipInputStream zin =
+ new java.util.zip.ZipInputStream(new java.io.ByteArrayInputStream(bytes))) {
+ java.util.zip.ZipEntry e;
+ while ((e = zin.getNextEntry()) != null) {
+ String n = e.getName();
+ if ("word/document.xml".equals(n)) return true;
+ }
+ } catch (Exception ignore) {
+ return false;
+ }
+ return false;
+ }
+
+ /** 鏄惁涓� OLE2 澶嶅悎鏂囨。锛圵ord 97-2003 .doc锛� */
+ private boolean isOle2(byte[] b) {
+ return b.length > 8 && (b[0] & 0xFF) == 0xD0 && (b[1] & 0xFF) == 0xCF
+ && (b[2] & 0xFF) == 0x11 && (b[3] & 0xFF) == 0xE0;
+ }
+
+ /** 瀹夊叏瑙f瀽 XML锛氱鐢ㄥ閮ㄥ疄浣�/DTD锛堟姤鍛婃潵鑷閮ㄥ钩鍙帮紝闃� XXE锛� */
+ private Document parseXmlSafely(String xml) throws Exception {
+ javax.xml.parsers.DocumentBuilderFactory f = javax.xml.parsers.DocumentBuilderFactory.newInstance();
+ f.setNamespaceAware(true);
+ try { f.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); } catch (Exception ignore) { }
+ try { f.setFeature("http://xml.org/sax/features/external-general-entities", false); } catch (Exception ignore) { }
+ try { f.setFeature("http://xml.org/sax/features/external-parameter-entities", false); } catch (Exception ignore) { }
+ javax.xml.parsers.DocumentBuilder b = f.newDocumentBuilder();
+ b.setEntityResolver(new org.xml.sax.EntityResolver() {
+ @Override
+ public org.xml.sax.InputSource resolveEntity(String publicId, String systemId) {
+ return new org.xml.sax.InputSource(new java.io.StringReader(""));
+ }
+ });
+ return b.parse(new org.xml.sax.InputSource(new java.io.StringReader(xml)));
+ }
+
+ /** 鎸� localName 鍙栧叏閮ㄥ悗浠e厓绱狅紙鍛藉悕绌洪棿鏃犲叧锛屽吋瀹� WordML 涓� docx 鐨� w: 鍓嶇紑锛� */
+ private List<Element> elementsByLocalName(Document dom, String localName) {
+ List<Element> out = new ArrayList<>();
+ NodeList list = dom.getElementsByTagNameNS("*", localName);
+ for (int i = 0; i < list.getLength(); i++) out.add((Element) list.item(i));
+ return out;
+ }
+
+ /** 鍙彇鐩存帴瀛愬厓绱狅紙閬垮厤宓屽琛ㄦ牸/宓屽灞炴�у厓绱犺绠楄繘鏉ワ級 */
+ private List<Element> childElementsByLocalName(Element parent, String localName) {
+ List<Element> out = new ArrayList<>();
+ NodeList kids = parent.getChildNodes();
+ for (int i = 0; i < kids.getLength(); i++) {
+ Node n = kids.item(i);
+ if (n.getNodeType() == Node.ELEMENT_NODE && localName.equals(n.getLocalName())) out.add((Element) n);
+ }
+ return out;
+ }
+
+ /** 鍏冪礌鍐呮墍鏈� <w:t> 鏂囨湰鎷兼帴骞跺帇鎺夌┖鐧� */
+ private String textOfXmlElement(Element el) {
+ StringBuilder sb = new StringBuilder();
+ NodeList ts = el.getElementsByTagNameNS("*", "t");
+ for (int i = 0; i < ts.getLength(); i++) sb.append(ts.item(i).getTextContent());
+ return flat(sb.toString());
+ }
+
+ private String ymd(String y, String mo) {
+ try {
+ int month = Integer.parseInt(mo);
+ if (month < 1 || month > 12) return null;
+ return String.format("%04d-%02d", Integer.parseInt(y), month);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /** 瑙f瀽鎶ュ憡涓殑銆屽湴甯傚悕绉� | 鈥� | 璁㈠崟鏁版�绘暟銆嶈〃锛宱rders=甯傚窞璁㈠崟锛宻umHolder[0]=鍚堣琛岃鍗曟暟 */
+ private void parseWycOrderReportTables(XWPFDocument doc, Map<String, Double> orders, double[] sumHolder) {
+ List<List<List<String>>> tables = new ArrayList<>();
+ for (XWPFTable table : doc.getTables()) {
+ List<List<String>> rows = new ArrayList<>();
+ for (XWPFTableRow row : table.getRows()) {
+ List<String> cells = new ArrayList<>();
+ for (org.apache.poi.xwpf.usermodel.XWPFTableCell c : row.getTableCells()) cells.add(flat(c.getText()));
+ rows.add(cells);
+ }
+ tables.add(rows);
+ }
+ matchWycOrderTable(tables, orders, sumHolder);
+ }
+
+ /** Word 2003 XML锛圵ordML锛夊垎鏀細<w:tbl>/<w:tr>/<w:tc> 鎶芥垚鏂囨湰琛ㄥ悗璧板悓涓�濂楀尮閰嶉�昏緫 */
+ private void parseWycOrderReportTablesXml(Document dom, Map<String, Double> orders, double[] sumHolder) {
+ List<List<List<String>>> tables = new ArrayList<>();
+ for (Element tbl : elementsByLocalName(dom, "tbl")) {
+ List<List<String>> rows = new ArrayList<>();
+ for (Element tr : childElementsByLocalName(tbl, "tr")) {
+ List<String> cells = new ArrayList<>();
+ for (Element tc : childElementsByLocalName(tr, "tc")) cells.add(textOfXmlElement(tc));
+ rows.add(cells);
+ }
+ tables.add(rows);
+ }
+ matchWycOrderTable(tables, orders, sumHolder);
+ }
+
+ /**
+ * 鍦ㄣ�屾枃鏈寲琛ㄦ牸銆嶉噷鎵鹃檮浠�1锛堣〃澶村惈銆岃鍗曟暟鎬绘暟銆�/銆岃鍗曟暟銆嶏級骞跺彇 17 甯傚窞璁㈠崟鏁颁笌鍚堣銆�
+ * 鍏ュ弬 tables = 琛� -> 琛� -> 鍗曞厓鏍兼枃鏈紙宸插帇绌虹櫧锛夛紝涓庢潵婧愭牸寮忔棤鍏炽��
+ */
+ private void matchWycOrderTable(List<List<List<String>>> tables, Map<String, Double> orders, double[] sumHolder) {
+ for (List<List<String>> rows : tables) {
+ if (rows.size() < 3) continue;
+ int headerRow = -1;
+ int orderCol = -1;
+ for (int r = 0; r < Math.min(rows.size(), 3); r++) {
+ List<String> cells = rows.get(r);
+ for (int c = 0; c < cells.size(); c++) {
+ String norm = cells.get(c);
+ if (norm.contains("璁㈠崟鏁版�绘暟") || "璁㈠崟鏁�".equals(norm)) {
+ headerRow = r;
+ orderCol = c;
+ break;
+ }
+ }
+ if (headerRow >= 0) break;
+ }
+ if (headerRow < 0 || orderCol < 0) continue;
+
+ Map<String, Double> found = new LinkedHashMap<>();
+ double citySum = 0.0;
+ boolean hasTotal = false;
+ for (int r = headerRow + 1; r < rows.size(); r++) {
+ List<String> cells = rows.get(r);
+ if (cells.isEmpty() || orderCol >= cells.size()) continue;
+ String name = cells.get(0);
+ if (name.isEmpty()) continue;
+ Double val = parsePlainNumber(cells.get(orderCol));
+ if (val == null) continue;
+ String city = RegionUtil.normalizeCityName(name);
+ if (city != null && RegionUtil.cityList().contains(city)) {
+ found.put(city, val);
+ citySum += val;
+ } else if (name.contains("鍚堣") || name.contains("鎬昏") || name.contains("鍏ㄧ渷") || name.contains("婀栧寳")) {
+ hasTotal = true;
+ sumHolder[0] = val;
+ }
+ }
+ if (!found.isEmpty()) {
+ orders.clear();
+ orders.putAll(found);
+ if (!hasTotal) sumHolder[0] = citySum;
+ return;
+ }
+ }
+ }
+
+ private String flat(String s) {
+ return s == null ? "" : s.replaceAll("\\s+", "").trim();
+ }
+
+ /** 銆�11,183,267 鍗曘�嶈繖绫绘枃鏈彇鏁板�� */
+ private Double parsePlainNumber(String s) {
+ if (s == null) return null;
+ String t = s.replaceAll("[,\\s]", "").trim();
+ if (t.isEmpty()) return null;
+ java.util.regex.Matcher m = java.util.regex.Pattern.compile("-?\\d+(?:\\.\\d+)?").matcher(t);
+ if (!m.find()) return null;
+ try {
+ return Double.parseDouble(m.group());
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ private Double orderOf(Map<String, Double> orders, String city) {
+ Double v = orders.get(city);
+ return (v == null || v <= 0) ? null : v;
+ }
+ private String periodOfCell(Cell c) {
+ if (c == null) return null;
+ if (c.getCellType() == CellType.NUMERIC) {
+ try {
+ double v = c.getNumericCellValue();
+ if (v >= 40000 && v <= 60000) {
+ java.util.Date d = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(v);
+ java.text.SimpleDateFormat f = new java.text.SimpleDateFormat("yyyy-MM");
+ return f.format(d);
+ }
+ } catch (Exception ignore) { }
+ }
+ String s = null;
+ try { s = c.getStringCellValue(); } catch (Exception ignore) { }
+ if (s != null) {
+ java.util.regex.Matcher m = java.util.regex.Pattern.compile("(\\d{4})[-/.骞碷(\\d{1,2})").matcher(s.trim());
+ if (m.find()) return String.format("%04d-%02d", Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)));
+ }
+ return null;
+ }
+
+ private double dbl(Row row, int idx) {
+ Double v = getDecimal(row, idx);
+ return v == null ? 0.0 : v;
+ }
+
+ private Double dblN(Row row, int idx) {
+ Double v = getDecimal(row, idx);
+ return (v == null || v <= 0) ? null : v;
}
/** 杩愭斂鍑虹杞﹀競宸炴彁鍙栵細閬撹矾杩愯緭璇佸瓧鍙封啋妗f鍙封啋缁忚惀鏉冨彿鈫掍笟鎴峰湴鍧�锛屽彇 6 浣嶅尯鍒掍唬鐮� */
@@ -2846,6 +4110,9 @@
case "trackMileage": return new ImportResult(importTrackMileage(file, period), 0, new ArrayList<>());
case "freightTurnover": return new ImportResult(importFreightTurnover(file, period), 0, new ArrayList<>());
case "scaleSplit": return new ImportResult(importScaleSplit(file, period), 0, new ArrayList<>());
+ case "wycTotal": return new ImportResult(importWycTotal(file, period), 0, new ArrayList<>());
+ case "wycOrder": return new ImportResult(importWycOrder(file, period), 0, new ArrayList<>());
+ case "wycOrderReport": return new ImportResult(importWycOrderReport(file, period).getCityCount(), 0, new ArrayList<>());
default: throw new RuntimeException("涓嶆敮鎸佺殑瀵煎叆绫诲瀷锛�" + type);
}
}
--
Gitblit v1.9.1