From 3b3652e3418ccfbbd21d9f7a2d8769d09c4f18f9 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期一, 12 十月 2026 13:08:54 +0800
Subject: [PATCH] fix(汇总大表): 扩列公式克隆修正(值列相对右移、同比列重写)并落盘 9 月母版 待验收

---
 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java |  214 +++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 181 insertions(+), 33 deletions(-)

diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
index 42e5bf2..fff7842 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -34,6 +34,9 @@
 import com.trafficaudit.reportexport.calc.MidCalc;
 import com.trafficaudit.reportexport.calc.PaxCalc;
 import com.trafficaudit.reportexport.calc.SummaryWorkbookFiller;
+import com.trafficaudit.reportexport.calc.FreightReadinessCheck;
+import com.trafficaudit.reportexport.calc.SummaryMissingDataCheck;
+import com.trafficaudit.reportexport.layout.SummaryLayoutExpander;
 import com.trafficaudit.reportexport.calc.WycSplitCalc;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.Cell;
@@ -101,6 +104,12 @@
     private CityTaxiMonthlyMapper cityTaxiMapper;
     @Resource
     private SummaryWorkbookFiller summaryWorkbookFiller;
+    @Resource
+    private SummaryLayoutExpander summaryLayoutExpander;
+    @Resource
+    private FreightReadinessCheck freightReadinessCheck;
+    @Resource
+    private SummaryMissingDataCheck missingDataCheck;
     @Resource
     private WycSplitCalc wycSplitCalc;
     @Resource
@@ -3622,6 +3631,86 @@
         return exportSummaryWorkbook(period, mode, null, null);
     }
 
+    /**
+     * 姹囨�诲ぇ琛ㄣ�屾寜鏈堟墿鍒椼�嶏紙2026-10-09 Step 1锛夛細鎶� period 褰撴湀姣嶇増鎵╁垪鎴愪笅涓�鏈堢増寮忋��
+     * 鍙仛鐗堝紡鈥斺�斿綋鏈堟暟鎹垪鐣欑┖锛涗笉钀界洏锛岀敱璋冪敤鏂瑰喅瀹氭槸鍚︾‘璁ゅ悗鍙﹀瓨涓轰笅鏈熸瘝鐗堛��
+     */
+    public byte[] expandSummaryWorkbook(String period) throws Exception {
+        if (period == null || !period.contains("-")) {
+            throw new RuntimeException("鎵╁垪浠呮敮鎸佹寜鏈堬紝璇锋寚瀹� period=YYYY-MM");
+        }
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, "month");
+        if (month >= 12) {
+            throw new RuntimeException("鎵╁垪锛�12 鏈堜箣鍚庨渶鏂板紑涓�鏈紝璺ㄥ勾瑙勫垯鍙﹁澶勭悊");
+        }
+        // 2026-10-12锛氭墿鍒椾互銆屽綋鏈熶骇鐗┿�嶄负搴曪紙浜х墿鍚綋鏈熷垪鐨勫�间笌鍏紡锛涙瘝鐗堢殑褰撴湡鍒楁槸绌虹殑锛�
+        // 鐩存帴鎷挎瘝鐗堟墿鍒椾細鎶婂綋鏈熸暟鎹涪鎴愮┖娲烇紝瀵艰嚧绱婕忔湀锛夈�備骇鐗╁彇鑷鍑鸿惤鐩樼紦瀛橈紱缂轰骇鐗╂椂鍥為��姣嶇増骞跺憡璀︺��
+        File source = resolveLatestProduct(period);
+        boolean fromProduct = source != null;
+        if (source == null) {
+            source = resolveSummaryMother(period, "month");
+            if (source != null) {
+                log.warn("姹囨�诲ぇ琛ㄦ墿鍒楋細鏈壘鍒� {} 鐨勪骇鐗╃紦瀛橈紝鍥為��鐢ㄦ瘝鐗堟墿鍒椻�斺�斿巻鍙插垪閲岀己灏戝綋鏈熸暟鎹紝绱鍙兘婕忔湀", period);
+            }
+        }
+        if (source == null) {
+            throw new RuntimeException("鏈壘鍒版眹鎬昏〃姣嶇増鎴栦骇鐗╋紙docs/鐢熸垚姹囨�诲ぇ琛級锛�" + year + "骞�" + month + "鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx");
+        }
+        try (InputStream in = new FileInputStream(source)) {
+            byte[] out = summaryLayoutExpander.expand(org.apache.poi.util.IOUtils.toByteArray(in), year, month + 1);
+            log.info("姹囨�诲ぇ琛ㄦ墿鍒楀畬鎴愶細{}{} -> {}骞磠}鏈堢増寮忥紙鍙仛鐗堝紡銆佸綋鏈熷垪鐣欑┖锛�",
+                    fromProduct ? "浜х墿" : "姣嶇増", source.getName(), year, month + 1);
+            return out;
+        }
+    }
+
+    /** 鍙栬鏈熴�屾渶鏂颁骇鐗┿�嶏細瀵煎嚭钀界洏缂撳瓨 _cache/summaryWorkbook_<period>_month_v*.xlsx锛堟寜淇敼鏃堕棿鍙栨渶鏂帮級 */
+    private File resolveLatestProduct(String period) {
+        try {
+            String rel = summaryTemplateDir;
+            while (rel != null && rel.startsWith("./")) rel = rel.substring(2);
+            String[] roots = {
+                summaryTemplateDir,
+                System.getProperty("user.dir") + "/" + rel,
+                System.getProperty("user.dir") + "/../" + rel
+            };
+            String prefix = "summaryWorkbook_" + period + "_month_";
+            for (String root : roots) {
+                if (root == null || root.trim().isEmpty()) continue;
+                File dir = new File(root, "_cache");
+                if (!dir.isDirectory()) continue;
+                File[] files = dir.listFiles((d, name) -> name.startsWith(prefix) && name.endsWith(".xlsx"));
+                if (files == null || files.length == 0) continue;
+                File latest = files[0];
+                for (File f : files) {
+                    if (f.lastModified() > latest.lastModified()) latest = f;
+                }
+                return latest;
+            }
+            return null;
+        } catch (Exception e) {
+            log.warn("姹囨�诲ぇ琛ㄦ墿鍒楋細鏌ユ壘浜х墿缂撳瓨澶辫触锛堝拷鐣ワ級锛歿}", e.getMessage());
+            return null;
+        }
+    }
+
+    /** 鍙慨姝e綋鏈熸瘝鐗堥噷 7 涓爣棰橀〉鐨勬爣棰樻枃瀛楋紙鎸夋瘝鐗堟湡娆¢噸鍐欙紝鐢ㄤ簬淇婊炲悗鏍囬锛夈�� */
+    public byte[] fixSummaryTitles(String period) throws Exception {
+        if (period == null || !period.contains("-")) {
+            throw new RuntimeException("鏍囬淇浠呮敮鎸佹寜鏈堬紝璇锋寚瀹� period=YYYY-MM");
+        }
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, "month");
+        File mother = resolveSummaryMother(period, "month");
+        if (mother == null) {
+            throw new RuntimeException("鏈壘鍒版眹鎬昏〃姣嶇増锛坉ocs/鐢熸垚姹囨�诲ぇ琛級锛�" + year + "骞�" + month + "鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx");
+        }
+        try (InputStream in = new FileInputStream(mother)) {
+            return summaryLayoutExpander.fixTitles(org.apache.poi.util.IOUtils.toByteArray(in), year, month);
+        }
+    }
+
     /** 鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛圥0-P1 楠ㄦ灦锛屾敮鎸� toMonth 瑁佸壀锛夛細
      *  浠ャ�奩YYY骞碝鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx銆嬶紙M 鐢� period 鍐冲畾锛変负姣嶇増锛屾寜鍚屾湡澶囦唤姣嶇増杩樺師鏈堝害鍧楋紱
      *  toMonth 闈炵┖涓斿皬浜� M 鏃讹紝鎶� 8 涓彴璐�/鍚堟垚椤佃鍒� toMonth 鏈堬紙toMonth+1..M 鐨勫綋鏈堝��/鍚屾瘮鍒楁竻绌恒�佷繚鐣欒〃澶达紝
@@ -3664,6 +3753,8 @@
             int effMonth = (toMonth != null && toMonth > 0 && toMonth < month) ? toMonth : month;
             List<String> dbProblems = new ArrayList<>();
             String effPeriod = String.format("%04d-%02d", year, effMonth);
+            dbProblems.addAll(freightReadinessCheck.check(effPeriod));   // B4锛氳揣杩愬彛寰勫鍑哄墠浣撴锛�6 鏉¤鍒欙級
+            dbProblems.addAll(missingDataCheck.check(effPeriod));        // 缂烘暟娓呭崟锛堣鏈夌殑鏁版病鏈夛級
             reportProgress(progress, 22, "鍥炲~褰撴湀鏁版嵁");
             int dbFilled = summaryWorkbookFiller.fillMonthlyLedger(wb, effPeriod, year, effMonth, dbProblems);
             int yoyFilled = summaryWorkbookFiller.ensureMonthYoyFormulas(wb, year, effMonth);
@@ -4085,9 +4176,8 @@
     private static final int SM_WYC_CX_PAX = 16, SM_WYC_CX_TURN = 17;
     private static final int SM_FREIGHT_TURN = 18;
     private static final int SM_DIM = 19;
-    /** 2026 骞� 1 鏈堝綋鏈堝�煎垪锛�0 鍩猴紝C 鍒楋級锛�2025 骞� 1 鏈堝綋鏈堝�煎垪锛�0 鍩猴紝V 鍒楋級 */
-    private static final int SM_CUR_COL0 = 2;
-    private static final int SM_LAST_COL0 = 21;
+    // 2026-10-09锛氬師鍏堝啓姝荤殑 SM_CUR_COL0=2锛�2026骞�1鏈�=C锛�/ SM_LAST_COL0=21锛�2025骞�1鏈�=V锛夊凡搴熷純锛�
+    // 鏀逛负鎸夎〃澶村姩鎬佸畾浣嶏紙瑙� smMonthCols / smYearMonthOf锛夛紝鍏煎銆屾枃鏈� / 鏃ユ湡搴忓垪鍙� / 鏃ユ湡鏍煎紡銆嶄笁绉嶈〃澶村啓娉曘��
 
     /**
      * 鍒锋柊鍥涘紶璺ㄩ〉鍚堟垚椤碉細褰撳勾绱锛�1..month 鏈堬級銆佸綋鏈堬紙month 鏈堬級鍙婂搴斿悓姣旓紱
@@ -4098,10 +4188,10 @@
         int[] cumMonths = new int[month];
         for (int m = 1; m <= month; m++) cumMonths[m - 1] = m;
         int[] oneMonth = new int[]{month};
-        Map<String, double[]> curCum = smReadLedgerMetrics(wb, true, cumMonths);
-        Map<String, double[]> lastCum = smReadLedgerMetrics(wb, false, cumMonths);
-        Map<String, double[]> curMon = smReadLedgerMetrics(wb, true, oneMonth);
-        Map<String, double[]> lastMon = smReadLedgerMetrics(wb, false, oneMonth);
+        Map<String, double[]> curCum = smReadLedgerMetrics(wb, year, cumMonths);
+        Map<String, double[]> lastCum = smReadLedgerMetrics(wb, year - 1, cumMonths);
+        Map<String, double[]> curMon = smReadLedgerMetrics(wb, year, oneMonth);
+        Map<String, double[]> lastMon = smReadLedgerMetrics(wb, year - 1, oneMonth);
         if (curCum.isEmpty()) {
             log.warn("姹囨�诲ぇ琛ㄥ洓椤靛埛鏂帮細鍙拌处椤垫湭璇诲埌甯傚窞鏁版嵁锛岃烦杩囷紙淇濇寔姣嶇増鍘熷�硷級");
             return 0;
@@ -4130,22 +4220,23 @@
     }
 
     /** 鍙拌处椤电疮璁″彇鏁帮細甯傚窞瑙勮寖鍚� -> 19 缁存寚鏍� */
-    private Map<String, double[]> smReadLedgerMetrics(XSSFWorkbook wb, boolean curYear, int[] months) {
+    private Map<String, double[]> smReadLedgerMetrics(XSSFWorkbook wb, int blockYear, int[] months) {
         Map<String, double[]> out = new LinkedHashMap<>();
-        smReadCityPaxPage(wb, "鍏氦", curYear, months, out, SM_BUS_PAX, SM_BUS_TURN, SM_BUS_CX_PAX, SM_BUS_CX_TURN);
-        smReadCityPaxPage(wb, "鍑虹", curYear, months, out, SM_TAXI_PAX, SM_TAXI_TURN, SM_TAXI_CX_PAX, SM_TAXI_CX_TURN);
-        smReadCityPaxPage(wb, "缃戠害杞�", curYear, months, out, SM_WYC_PAX, SM_WYC_TURN, SM_WYC_CX_PAX, SM_WYC_CX_TURN);
-        smReadRailFerry(wb, curYear, months, out);
-        smReadBanxian(wb, curYear, months, out);
-        smReadFreightTurnover(wb, curYear, months, out);
+        smReadCityPaxPage(wb, "鍏氦", blockYear, months, out, SM_BUS_PAX, SM_BUS_TURN, SM_BUS_CX_PAX, SM_BUS_CX_TURN);
+        smReadCityPaxPage(wb, "鍑虹", blockYear, months, out, SM_TAXI_PAX, SM_TAXI_TURN, SM_TAXI_CX_PAX, SM_TAXI_CX_TURN);
+        smReadCityPaxPage(wb, "缃戠害杞�", blockYear, months, out, SM_WYC_PAX, SM_WYC_TURN, SM_WYC_CX_PAX, SM_WYC_CX_TURN);
+        smReadRailFerry(wb, blockYear, months, out);
+        smReadBanxian(wb, blockYear, months, out);
+        smReadFreightTurnover(wb, blockYear, months, out);
         return out;
     }
 
     /** 鍏氦/鍑虹/缃戠害杞﹂〉锛氭瘡甯傚窞 4 琛岋紙瀹㈣繍閲�/鏃呭鍛ㄨ浆閲�/鍏朵腑鍩庡競鍐呭杩愰噺/鍏朵腑鍩庡競鍐呮梾瀹㈠懆杞噺锛� */
-    private void smReadCityPaxPage(XSSFWorkbook wb, String sheetName, boolean curYear, int[] months,
+    private void smReadCityPaxPage(XSSFWorkbook wb, String sheetName, int blockYear, int[] months,
                                    Map<String, double[]> out, int paxIdx, int turnIdx, int cxPaxIdx, int cxTurnIdx) {
         Sheet sh = wb.getSheet(sheetName);
         if (sh == null) return;
+        Map<Integer, Integer> monthCols = smMonthCols(sh, blockYear);
         int last = sh.getLastRowNum();
         for (int r = sh.getFirstRowNum(); r <= last; r++) {
             Row row = sh.getRow(r);
@@ -4168,10 +4259,10 @@
                     else if (paxRow < 0) paxRow = k;
                 }
             }
-            double totalPax = smSumMonths(sh, paxRow, curYear, months);
-            double totalTurn = smSumMonths(sh, turnRow, curYear, months);
-            double cityPax = smSumMonths(sh, cxPaxRow, curYear, months);
-            double cityTurn = smSumMonths(sh, cxTurnRow, curYear, months);
+            double totalPax = smSumMonths(sh, paxRow, monthCols, months);
+            double totalTurn = smSumMonths(sh, turnRow, monthCols, months);
+            double cityPax = smSumMonths(sh, cxPaxRow, monthCols, months);
+            double cityTurn = smSumMonths(sh, cxTurnRow, monthCols, months);
             double[] arr = out.computeIfAbsent(city, k -> new double[SM_DIM]);
             arr[paxIdx] += cityPax;                 // 鍩庡競瀹㈣繍鍙e緞鍙栤�滃煄甯傚唴鈥�
             arr[turnIdx] += cityTurn;
@@ -4181,9 +4272,10 @@
     }
 
     /** 杞ㄩ亾銆佽疆娓¢〉锛氳建閬撳潡锛堟姹�/榛勭煶锛�+ 杞浮鍧楋紙姝︽眽锛� */
-    private void smReadRailFerry(XSSFWorkbook wb, boolean curYear, int[] months, Map<String, double[]> out) {
+    private void smReadRailFerry(XSSFWorkbook wb, int blockYear, int[] months, Map<String, double[]> out) {
         Sheet sh = wb.getSheet("杞ㄩ亾銆佽疆娓�");
         if (sh == null) return;
+        Map<Integer, Integer> monthCols = smMonthCols(sh, blockYear);
         int last = sh.getLastRowNum();
         int ferryTitle = -1;
         for (int r = sh.getFirstRowNum(); r <= last; r++) {
@@ -4192,12 +4284,12 @@
             String a = smText(row.getCell(0));
             if (a != null && a.contains("杞浮瀹㈣繍閲�")) { ferryTitle = r; break; }
         }
-        smReadRailFerryBlock(sh, curYear, months, out, sh.getFirstRowNum(),
+        smReadRailFerryBlock(sh, monthCols, months, out, sh.getFirstRowNum(),
                 ferryTitle < 0 ? last : ferryTitle - 1, SM_RAIL_PAX);
-        if (ferryTitle >= 0) smReadRailFerryBlock(sh, curYear, months, out, ferryTitle, last, SM_FERRY_PAX);
+        if (ferryTitle >= 0) smReadRailFerryBlock(sh, monthCols, months, out, ferryTitle, last, SM_FERRY_PAX);
     }
 
-    private void smReadRailFerryBlock(Sheet sh, boolean curYear, int[] months, Map<String, double[]> out,
+    private void smReadRailFerryBlock(Sheet sh, Map<Integer, Integer> monthCols, int[] months, Map<String, double[]> out,
                                       int start, int end, int baseIdx) {
         for (int r = start; r <= end; r++) {
             Row row = sh.getRow(r);
@@ -4214,15 +4306,16 @@
                 else if (paxRow < 0) paxRow = k;
             }
             double[] arr = out.computeIfAbsent(city, k -> new double[SM_DIM]);
-            arr[baseIdx] += smSumMonths(sh, paxRow, curYear, months);
-            arr[baseIdx + 1] += smSumMonths(sh, turnRow, curYear, months);
+            arr[baseIdx] += smSumMonths(sh, paxRow, monthCols, months);
+            arr[baseIdx + 1] += smSumMonths(sh, turnRow, monthCols, months);
         }
     }
 
     /** 鐝嚎鍖呰溅椤碉細姣忓競宸� 瀹㈣繍閲� / 鏃呭鍛ㄨ浆閲忥紙鈥滃叾涓釜浣撯�濊涓嶉噸澶嶈鍏ワ級 */
-    private void smReadBanxian(XSSFWorkbook wb, boolean curYear, int[] months, Map<String, double[]> out) {
+    private void smReadBanxian(XSSFWorkbook wb, int blockYear, int[] months, Map<String, double[]> out) {
         Sheet sh = wb.getSheet("鐝嚎鍖呰溅");
         if (sh == null) return;
+        Map<Integer, Integer> monthCols = smMonthCols(sh, blockYear);
         int last = sh.getLastRowNum();
         for (int r = sh.getFirstRowNum(); r <= last; r++) {
             Row row = sh.getRow(r);
@@ -4239,16 +4332,17 @@
                 else if (b.contains("瀹㈣繍閲�") && paxRow < 0) paxRow = k;
             }
             double[] arr = out.computeIfAbsent(city, k -> new double[SM_DIM]);
-            arr[SM_BX_PAX] += smSumMonths(sh, paxRow, curYear, months);
-            arr[SM_BX_TURN] += smSumMonths(sh, turnRow, curYear, months);
+            arr[SM_BX_PAX] += smSumMonths(sh, paxRow, monthCols, months);
+            arr[SM_BX_TURN] += smSumMonths(sh, turnRow, monthCols, months);
         }
     }
 
     /** 璐ц繍椤碉細姣忓競宸� 瑙勪笂璐х墿鍛ㄨ浆閲� + 瑙勪笅璐х墿鍛ㄨ浆閲忥紙鈥滆涓�+瑙勪笅鈥濊鏈韩鏄眰鍜屽叕寮忥紝涓嶇洿鎺ュ彇锛� */
-    private void smReadFreightTurnover(XSSFWorkbook wb, boolean curYear, int[] months, Map<String, double[]> out) {
+    private void smReadFreightTurnover(XSSFWorkbook wb, int blockYear, int[] months, Map<String, double[]> out) {
         Sheet sh = wb.getSheet(" 璐ц繍");
         if (sh == null) sh = wb.getSheet("璐ц繍");
         if (sh == null) return;
+        Map<Integer, Integer> monthCols = smMonthCols(sh, blockYear);
         int last = sh.getLastRowNum();
         for (int r = sh.getFirstRowNum(); r <= last; r++) {
             Row row = sh.getRow(r);
@@ -4265,22 +4359,76 @@
                 else if (b.contains("瑙勪笅璐х墿鍛ㄨ浆閲�")) { if (below < 0) below = k; }
             }
             double[] arr = out.computeIfAbsent(city, k -> new double[SM_DIM]);
-            arr[SM_FREIGHT_TURN] += smSumMonths(sh, above, curYear, months) + smSumMonths(sh, below, curYear, months);
+            arr[SM_FREIGHT_TURN] += smSumMonths(sh, above, monthCols, months) + smSumMonths(sh, below, monthCols, months);
         }
     }
 
-    private double smSumMonths(Sheet sh, int rowIdx, boolean curYear, int[] months) {
-        if (sh == null || rowIdx < 0) return 0;
+    private double smSumMonths(Sheet sh, int rowIdx, Map<Integer, Integer> monthCols, int[] months) {
+        if (sh == null || rowIdx < 0 || monthCols == null) return 0;
         Row row = sh.getRow(rowIdx);
         if (row == null) return 0;
         double s = 0;
         for (int m : months) {
-            int col = (curYear ? SM_CUR_COL0 : SM_LAST_COL0) + 2 * (m - 1);
+            Integer col = monthCols.get(m);
+            if (col == null) continue;
             s += smNum(row.getCell(col));
         }
         return s;
     }
 
+    /** 鍙拌处椤点�屾煇骞� M 鏈堛�嶇殑 0 鍩哄垪鍙凤細鎸夎〃澶村姩鎬佸畾浣嶏紙涓嶅啀鍐欐 C/V锛夛紝鍏煎鏂囨湰 / 鏃ユ湡搴忓垪鍙� / 鏃ユ湡鏍煎紡涓夌鍐欐硶 */
+    private Map<Integer, Integer> smMonthCols(Sheet sh, int wantedYear) {
+        Map<Integer, Integer> cols = new LinkedHashMap<>();
+        if (sh == null) return cols;
+        int lastRow = Math.min(sh.getLastRowNum(), sh.getFirstRowNum() + 4);
+        for (int r = sh.getFirstRowNum(); r <= lastRow; r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            int lastCol = row.getLastCellNum();
+            for (int c = row.getFirstCellNum(); c >= 0 && c < lastCol; c++) {
+                Integer ym = smYearMonthOf(row.getCell(c));
+                if (ym == null || ym / 100 != wantedYear) continue;
+                int mo = ym % 100;
+                if (!cols.containsKey(mo)) cols.put(mo, c);
+            }
+        }
+        if (cols.isEmpty()) {
+            log.warn("姹囨�诲ぇ琛ㄥ洓椤靛埛鏂帮細{} 椤垫湭璇嗗埆鍒� {} 骞寸殑鏈堜唤琛ㄥご鍒楋紙鏀寔鏂囨湰/搴忓垪鍙�/鏃ユ湡涓夌褰㈠紡锛�", sh.getSheetName(), wantedYear);
+        }
+        return cols;
+    }
+
+    /** 琛ㄥご鍗曞厓鏍� -> 骞�*100+鏈堬紱鍙銆孻YYY骞碝鏈堛�嶆枃鏈垨銆屾煇鏈� 1 鍙枫�嶇殑鏃ユ湡搴忓垪鍙凤紝鎺掗櫎绱/鍚屾瘮/鐜瘮 */
+    private Integer smYearMonthOf(Cell c) {
+        if (c == null) return null;
+        try {
+            CellType t = c.getCellType();
+            if (t == CellType.STRING) {
+                String v = c.getStringCellValue();
+                if (v == null) return null;
+                v = v.trim();
+                if (v.isEmpty() || v.contains("绱") || v.contains("鍚屾瘮") || v.contains("鐜瘮")) return null;
+                java.util.regex.Matcher m = java.util.regex.Pattern
+                        .compile("^(\\d{4})\\s*骞碶\s*(\\d{1,2})\\s*鏈�$").matcher(v);
+                if (!m.matches()) return null;
+                int y = Integer.parseInt(m.group(1));
+                int mo = Integer.parseInt(m.group(2));
+                return mo >= 1 && mo <= 12 ? y * 100 + mo : null;
+            }
+            if (t == CellType.NUMERIC) {
+                double v = c.getNumericCellValue();
+                if (v < 36526 || v > 73415) return null;
+                java.util.Calendar cal = java.util.Calendar.getInstance();
+                cal.setTime(org.apache.poi.ss.usermodel.DateUtil.getJavaDate(v));
+                if (cal.get(java.util.Calendar.DAY_OF_MONTH) != 1) return null;
+                return cal.get(java.util.Calendar.YEAR) * 100 + (cal.get(java.util.Calendar.MONTH) + 1);
+            }
+        } catch (Exception ignore) {
+            return null;
+        }
+        return null;
+    }
+
     /** 鍙拌处椤靛崟鍏冩牸鍙栨暟锛堟暟瀛� / 鏁板瓧鏂囨湰 / 鍏紡缂撳瓨锛� */
     private double smNum(Cell c) {
         if (c == null) return 0;

--
Gitblit v1.9.1