From 53c6f66db6352373f1fe5171dfe42ed0d716141a Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期五, 18 九月 2026 13:37:23 +0800
Subject: [PATCH] fix(中口径): 库内缺数据不再覆盖母版定稿值 + 跨簿重算容错 + 报表期后月份清空

---
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java |  385 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 366 insertions(+), 19 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 e34a275..1f6ca46 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
@@ -56,6 +56,10 @@
 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 cn.hutool.core.io.IoUtil;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -81,6 +85,7 @@
 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;
@@ -314,6 +319,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, "杞﹁締鏁癬瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛�"));
@@ -1050,11 +1058,22 @@
     // ========== 璐ц繍閲忓懆杞噺锛堟ā鏉縚璐ц繍閲忓懆杞噺.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);
@@ -1119,27 +1138,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 鏈� */
@@ -1345,6 +1486,7 @@
         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"});
@@ -1386,7 +1528,8 @@
         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"});
+        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"});
     }
 
     /**
@@ -3144,6 +3287,209 @@
         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;
+        try (java.io.InputStream in = file.getInputStream();
+             XWPFDocument doc = new XWPFDocument(in)) {
+            docPeriod = periodOfDocx(doc);
+            parseWycOrderReportTables(doc, orders, sumHolder);
+        } 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 periodOfDocx(XWPFDocument doc) {
+        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 (XWPFParagraph para : doc.getParagraphs()) {
+            String text = para.getText();
+            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;
+    }
+
+    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) {
+        for (XWPFTable table : doc.getTables()) {
+            List<XWPFTableRow> rows = table.getRows();
+            if (rows.size() < 3) continue;
+            int headerRow = -1;
+            int orderCol = -1;
+            for (int r = 0; r < Math.min(rows.size(), 3); r++) {
+                List<org.apache.poi.xwpf.usermodel.XWPFTableCell> cells = rows.get(r).getTableCells();
+                for (int c = 0; c < cells.size(); c++) {
+                    String norm = flat(cells.get(c).getText());
+                    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<org.apache.poi.xwpf.usermodel.XWPFTableCell> cells = rows.get(r).getTableCells();
+                if (orderCol >= cells.size()) continue;
+                String name = flat(cells.get(0).getText());
+                if (name.isEmpty()) continue;
+                Double val = parsePlainNumber(cells.get(orderCol).getText());
+                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) {
@@ -3513,6 +3859,7 @@
             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