From af70e2f6f71eb24087b72b189212c92d5283f11e Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期四, 10 九月 2026 22:48:38 +0800
Subject: [PATCH] docs: 工作日结补充出版.docx修订记录(Markdown假表格还原为8个真表格 + 4处有误数据修正 + 6页渲染校验)

---
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java |  939 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 803 insertions(+), 136 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 7d2605d..e34a275 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;
@@ -52,6 +58,7 @@
 import org.apache.poi.ss.usermodel.WorkbookFactory;
 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,6 +74,7 @@
 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;
@@ -94,6 +102,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 +146,8 @@
     @Resource
     private AuditResultMapper auditResultMapper;
     @Resource
+    private AuditRunMapper auditRunMapper;
+    @Resource
     private PassengerEnterpriseMonthlyMapper passengerMapper;
     @Resource
     private PassengerIndividualMonthlyMapper passengerIndividualMapper;
@@ -158,6 +171,10 @@
     private CityTaxiMonthlyMapper cityTaxiMapper;
     @Resource
     private CityTaxiAuthMapper cityTaxiAuthMapper;
+    @Resource
+    private WycTotalMonthlyMapper wycTotalMapper;
+    @Resource
+    private WycOrderMonthlyMapper wycOrderMapper;
     @Resource
     private SqlSessionFactory sqlSessionFactory;
 
@@ -403,41 +420,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 +468,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 +624,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 +640,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 +648,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 +687,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 +704,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 +763,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 +787,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 +834,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 +884,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 +935,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 +948,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);
@@ -911,6 +1056,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);
@@ -1166,6 +1317,273 @@
         }
     }
 
+    /** 鏀寔鈥滄寜绫诲瀷娓呯┖鈥濈殑鏁版嵁绫诲瀷 -> 鏁版嵁琛ㄦ搷浣滈敭锛堝悓琛ㄥ悓绫诲埆褰掑苟锛岄伩鍏嶉噸澶嶅垹闄わ級 */
+    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_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"});
+    }
+
+    /**
+     * 娓呯┖鎸囧畾鎶ヨ〃鏈熷凡瀵煎叆鏁版嵁涓庡鏍哥粨鏋滐紝鐢ㄤ簬鏁存湀閲嶅锛涙姇璧勯」鐩富妗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 +1624,43 @@
         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 + "銆嬫暣鐞嗗悗鍐嶅鍏�");
+            }
+        }
+    }
+
     /** 鎸夎〃澶村悕绉板缓绔嬪垪鍙锋槧灏勶紝鍏煎鏂版棫妯℃澘鍒椾綅宸紓 */
     private Map<String, Integer> buildHeaderMap(Row header) {
         Map<String, Integer> colMap = new HashMap<>();
@@ -1217,6 +1672,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 +2260,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 +2278,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;
@@ -1838,10 +2327,12 @@
                 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);
@@ -1966,7 +2457,11 @@
                 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 +2531,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("璐熻矗");
     }
 
     /** 椤圭洰鍚嶈鑼冨寲锛氬幓绌虹櫧/鎹㈣/鎷彿鍐呭/鏍囩偣/銆岄」鐩�嶅悗缂�锛岀敤浜庡崄浜斾簲娓呭崟鍖归厤 */
@@ -2214,12 +2718,18 @@
         }
     }
     /** 鎶曡祫绯荤粺瀵煎嚭瀵煎叆锛堝鏍告瘮瀵瑰熀鍑嗭級锛屽浐瀹�8鍒楋細琛屽彿/鍗曚綅/鏃舵湡/璁″垝鎬绘姇璧�/鑷紑濮嬬疮璁�/鏈勾璁″垝/鑷勾鍒濈疮璁�/褰撴湀瀹屾垚 */
+    private static final int[] INVEST_SYSTEM_HEADER_COLS = {0, 1, 2, 3, 4, 5, 6, 7};
+    private static final String[] INVEST_SYSTEM_HEADER_KEYWORDS =
+        {"琛屽彿", "鍗曚綅", "鏃舵湡", "璁″垝鎬绘姇璧�", "鑷紑濮嬪缓璁�", "鏈勾璁″垝鎶曡祫", "鑷勾鍒�", "褰撴湀瀹屾垚鎶曡祫"};
+
     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, "鎶曡祫绯荤粺瀵煎嚭", "椤圭洰鍚嶇О", "璁″垝鎬绘姇璧�", "鍗曚綅");
+            requireHeaderLayout(sheet, "鎶曡祫绯荤粺瀵煎嚭", "妯℃澘_鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls",
+                INVEST_SYSTEM_HEADER_COLS, INVEST_SYSTEM_HEADER_KEYWORDS);
             for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                 Row row = sheet.getRow(r);
                 if (row == null) continue;
@@ -2257,15 +2767,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 +2882,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 +3024,155 @@
         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;
+    }
+
+    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 +3511,8 @@
             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<>());
             default: throw new RuntimeException("涓嶆敮鎸佺殑瀵煎叆绫诲瀷锛�" + type);
         }
     }

--
Gitblit v1.9.1