From df701a309e93aff0e04d172d6c12e8dfd52092eb Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期三, 09 九月 2026 16:33:38 +0800
Subject: [PATCH] feat: 09-09晚 M2台账层数据驱动回填——班线包车/公交/出租/轨道轮渡读库现算写整本(7月回归1323格0差异) + 测试报告与工作日结

---
 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java |    9 +
 docs/工作日结/工作日结_2026-09-09.md                                                                      |   21 +++
 docs/功能测试报告/功能测试报告_汇总大表数据驱动回填M2台账层_2026-09-09.md                                                  |   27 ++++
 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java  |  256 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 312 insertions(+), 1 deletions(-)

diff --git "a/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\346\261\207\346\200\273\345\244\247\350\241\250\346\225\260\346\215\256\351\251\261\345\212\250\345\233\236\345\241\253M2\345\217\260\350\264\246\345\261\202_2026-09-09.md" "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\346\261\207\346\200\273\345\244\247\350\241\250\346\225\260\346\215\256\351\251\261\345\212\250\345\233\236\345\241\253M2\345\217\260\350\264\246\345\261\202_2026-09-09.md"
new file mode 100644
index 0000000..0aeaea7
--- /dev/null
+++ "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\346\261\207\346\200\273\345\244\247\350\241\250\346\225\260\346\215\256\351\251\261\345\212\250\345\233\236\345\241\253M2\345\217\260\350\264\246\345\261\202_2026-09-09.md"
@@ -0,0 +1,27 @@
+# 鍔熻兘娴嬭瘯鎶ュ憡_姹囨�诲ぇ琛ㄦ暟鎹┍鍔ㄥ洖濉紙M2 鍙拌处灞傦級_2026-09-09
+
+## 鍔熻兘璇存槑
+姹囨�昏〃鏁存湰锛堥亾璺繍杈撻噺姹囨�昏〃锛夊鍑烘鍓嶆槸鈥滃鍒跺綋鏈堜汉宸ユ瘝鐗�+澶囦唤杩樺師鈥濓紝褰撴湀娌℃湁浜哄伐姣嶇増灏辨姤閿欙紝鐢ㄦ埛姣忔湀闇�鎵嬪伐鍒朵綔姣嶇増銆�
+鏈疆钀藉湴 M2 绗竴姝ワ細鏂板 `SummaryWorkbookFiller`锛岀敓鎴愭暣鏈椂鎶� 2026 骞� 1..M 鏈堚�滃彴璐﹂〉甯傚窞鏄庣粏琛屸�濈殑褰撴湀鍊间粠搴撶幇绠楀啓鍏ユ瘝鐗堝搴旀牸
+锛坢 鏈堝�煎垪 = 绗� 2+2*(m-1) 鍒楋紝C 璧凤級锛屽叏鐪佽涓庤法椤靛悎鎴愪负鍏紡銆佹墦寮�鑷姩閲嶇畻锛�2025 鍙傜収鍒椾繚鐣欐瘝鐗堢紦瀛樸��
+鏈疆瑕嗙洊 4 寮犲彴璐﹂〉锛氱彮绾垮寘杞︼紙鍚釜浣擄級銆佸叕浜ゃ�佸嚭绉熴�佽建閬撹疆娓★紱缃戠害杞︼紙渚濊禆鎷嗗垎杈撳叆锛夈�佽揣杩愰〉锛圡3锛夊強 9 鏈堜互鍚庢墿鍒楀緟涓嬩竴杞��
+
+## 娴嬭瘯鐜
+- 鍚庣锛歋pring Boot 8090锛坢vn spring-boot:run锛�2026-09-09 16:28 閲嶅惎鍚柊浠g爜锛夛紱鏁版嵁搴撲负鏈湴涓氬姟搴撱��
+- 鍩哄噯锛氫汉宸ュ浠芥瘝鐗� `docs/鐢熸垚姹囨�诲ぇ琛�/_澶囦唤_2026骞�7鏈堥亾璺繍杈撻噺姹囨�昏〃_娓呯悊鍓峗20260904.xlsx`锛�2026-07 鍙e緞 0 宸紓鍩哄噯锛夈��
+
+## 鐢ㄤ緥琛�
+| 鐢ㄤ緥 | 杈撳叆 | 棰勬湡 | 瀹為檯 | 缁撹 |
+|---|---|---|---|---|
+| 7 鏈堟暣鏈鍚戝洖褰� | GET /api/report/export/summaryWorkbook?period=2026-07 | 鐝嚎鍖呰溅/鍏氦/鍑虹/杞ㄩ亾杞浮 4 椤垫湀搴﹀�煎垪 C..Q 涓庡浠芥瘝鐗堥�愭牸涓�鑷� | 姣旇緝 1323 鏍硷紝宸紓 0锛宮axAbs=0 | 閫氳繃 |
+| 7 鏈堝浠芥瘝鐗堜釜鍒牸涓烘枃鏈暟瀛� | 鍏氦 3/6 鏈堢瓑鏍煎瓨涓� '4970.63' 鏂囨湰 | 鏁板��/鏂囨湰鍚屽�艰涓轰竴鑷� | 鏂囨湰杞暟鍊煎悗 0 宸紓 | 閫氳繃 |
+| 8 鏈堟暣鏈紙鏃犲浠芥瘝鐗堛�佹瘝鐗堢増寮忓惈 8 鏈堝垪浣嗘暟鎹彧鍒� 7 鏈堬級 | period=2026-08 | 8 鏈堝綋鏈堝�硷紙Q 鍒楋級鐢卞簱鐜扮畻濉叆锛屽凡鏈� 1-7 鏈堝�间笉鍥為�� | 鍏氦/鍑虹 476+476 鏍� 0 宸紓锛涚彮绾垮寘杞�/杞ㄩ亾杞浮澶氬嚭 Q 鍒� 8 鏈堝簱鍊硷紙姝︽眽鐝嚎瀹㈣繍閲� 329.906 绛夛級锛屽睘棰勬湡鈥滆搴撳~鏂版湀浠解�� | 閫氳繃锛�8 鏈堢湡鍊兼棤浜哄伐瀵圭収锛屼互 MidCalc 7 鏈� 0 宸紓鍙e緞鍚屾簮鎺ㄧ畻锛� |
+| 缂栬瘧 | mvn -o compile | 鏃犻敊璇� | 閫氳繃 | 閫氳繃 |
+
+## 鍙戠幇鐨勯棶棰樹笌澶勭疆
+1. 澶囦唤姣嶇増锛堟竻鐞嗗墠锛夐噷鍏氦閮ㄥ垎鏈堜唤鏍兼槸鈥滄枃鏈瀷鏁板瓧鈥濓紙濡� '4970.63'锛夛紝Excel 鍏紡鍙甯稿弬涓庤繍绠楋紱鐢熸垚鏂囦欢涓�寰嬪啓鏁板�硷紝灞炰紭鍖栬�岄潪宸紓锛屾瘮瀵硅剼鏈凡鎸夆�滄枃鏈暟瀛椻啋鏁板�尖�濆鐞嗐��
+2. 8 鏈堣緭鍑虹洰褰曚腑澶氭湰鈥�8 鏈堟暣鏈�濆叾瀹炲彧濉埌 7 鏈堬紙Q 鍒楃┖锛夛紝鍗拌瘉鐢ㄦ埛鈥滄瘡鏈堟墜宸ュ~鏁�/姣嶇増鈥濈棝鐐癸紱鏈紩鎿庡凡鑳芥妸 8 鏈堝彴璐﹂〉鏂版湀浠芥暟鎹粠搴撶畻鍏ャ��
+3. 閬楃暀锛�9 鏈堝強浠ヤ笂鏃犲搴旂増寮忓垪锛岄渶瑕佲�滆嚜鍔ㄦ墿鍒椻�濓紙鍦ㄧ疮璁″垪鍓嶆彃鍏ュ綋鏈堝��+鍚屾瘮涓ゅ垪骞跺悓姝ュ叕寮�/鏍峰紡锛夛紱缃戠害杞�/璐ц繍/鍩庡競瀹㈣繍绛夊叾浣欓〉鍙栨暟寰� M2/M3 鍚庣画鎺ュ叆锛涚己鏁版彁绀哄綋鍓嶈繘鏃ュ織锛岄〉闈㈡彁绀烘帴鍏ュ湪鏁撮摼瀹屾垚鍚庤ˉ銆�
+
+## 缁撹
+M2 鍙拌处灞傦紙鐝嚎鍖呰溅/鍏氦/鍑虹/杞ㄩ亾杞浮锛夋暟鎹┍鍔ㄥ洖濉彲鐢細2026-07 涓庝汉宸ュ浠芥瘝鐗堥�愭牸 0 宸紓锛�2026-08 鍙妸 8 鏈堟柊鏈堜唤鏁版嵁浠庡簱濉叆锛屾湭鐮村潖鏃㈡湁 1-7 鏈堝�笺�傚彲杩涘叆涓嬩竴杞紙鑷姩鎵╁垪 + 缃戠害杞�/璐ц繍/鍚堟垚椤� M3锛夊紑鍙戙��
\ No newline at end of file
diff --git "a/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md" "b/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md"
index d2dc73d..40b2b89 100644
--- "a/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md"
+++ "b/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md"
@@ -58,4 +58,23 @@
 
 ### 閬楃暀闂涓庡緟鍔烇紙涓庝笂鍗堜竴鑷达級
 - 姹囨�诲ぇ琛� 8 鏈堣嚜鍔ㄥ~鍊硷紙M2/M3 SummaryFiller锛夋湭鍚姩锛�8 鏈堢己 freight_turnover_import銆乻cale_split_transport銆乸assenger_individual_monthly銆亀yc_total/order 婧愭暟鎹��
-- 寰呬笟鍔$粓楠岋細闂鑼冨洿/瑙嗗浘/鐘舵�佹帶浠躲�佽鍐呮暟鎹脊绐椼�佸鍑� Excel 涓庢ā鏉�/鍥涙潯渚嬫枃閫愭潯鏍稿锛涘鍏ュ脊绐� UI銆丷ULE_001 闃堝�硷紙5 鍙�/50 鍚級鏄惁绗﹀悎涓氬姟棰勬湡銆�
\ No newline at end of file
+- 寰呬笟鍔$粓楠岋細闂鑼冨洿/瑙嗗浘/鐘舵�佹帶浠躲�佽鍐呮暟鎹脊绐椼�佸鍑� Excel 涓庢ā鏉�/鍥涙潯渚嬫枃閫愭潯鏍稿锛涘鍏ュ脊绐� UI銆丷ULE_001 闃堝�硷紙5 鍙�/50 鍚級鏄惁绗﹀悎涓氬姟棰勬湡銆�
+---
+
+## 涓嬪崍绗洓杞紙2026-09-09 17:xx锛塎2 鏁版嵁椹卞姩鍥炲~鍚姩锛氬彴璐﹀眰璇诲簱鐜扮畻鍐欐暣鏈�
+
+### 瀹屾垚鍐呭
+- 鏂板 `SummaryWorkbookFiller`锛坮eportexport/calc锛夛細鐢熸垚鏁存湰鏃舵妸 2026 骞� 1..M 鏈堝彴璐﹂〉甯傚窞鏄庣粏琛屽綋鏈堝�间粠搴撶幇绠楀啓鍏ワ紙m 鏈堝�煎垪=C+2*(m-1)锛夛紝琛屽畾浣嶆寜鈥滃垪A甯傚窞鍚�+鍒桞鎸囨爣鏂囨鈥濇壂鎻忥紝涓嶅啓姝昏鍙凤紱鍏ㄧ渷琛�/璺ㄩ〉鍚堟垚淇濇寔鍏紡锛屾墦寮�鑷姩閲嶇畻锛�2025 鍙傜収鍒楁部鐢ㄦ瘝鐗堢紦瀛樸��
+- 瑕嗙洊椤碉細鐝嚎鍖呰溅锛堝惈涓綋锛夈�佸叕浜ゃ�佸嚭绉熴�佽建閬撹疆娓★紱缃戠害杞︼紙渚濊禆鎷嗗垎杈撳叆锛変笌璐ц繍椤靛緟 M3銆�
+- ReportExportService.exportSummaryWorkbook 鎺ュ叆鍥炲~锛坉onor 杩樺師鍚庤鐩栧綋鏈堝�硷級锛岀己鏁版彁绀鸿繘鏃ュ織銆�
+
+### 娴嬭瘯缁撴灉鎽樿
+- mvn -o compile 閫氳繃锛涘悗绔� 16:28 閲嶅惎灏辩华銆�
+- 2026-07 鏁存湰鍥炲綊锛氬洓寮犲彴璐﹂〉鏈堝害鍊煎垪锛圕..Q锛変笌澶囦唤姣嶇増鍏� 1323 鏍奸�愭牸 0 宸紓锛堝浠芥瘝鐗堥儴鍒嗘枃鏈暟瀛楁寜鏁板�兼瘮杈冿級銆�
+- 2026-08 鏁存湰锛氬叕浜�/鍑虹 0 宸紓锛涚彮绾垮寘杞�/杞ㄩ亾杞浮鎶婂簱涓� 8 鏈堬紙Q 鍒楋級鏁版嵁鐜扮畻濉叆锛堟鍓嶇殑鈥�8鏈堟暣鏈�濆叾瀹炲彧濉埌 7 鏈堬級锛岀鍚堟暟鎹┍鍔ㄧ洰鏍囥��
+- 娴嬭瘯鎶ュ憡锛歞ocs/鍔熻兘娴嬭瘯鎶ュ憡/鍔熻兘娴嬭瘯鎶ュ憡_姹囨�诲ぇ琛ㄦ暟鎹┍鍔ㄥ洖濉玀2鍙拌处灞俖2026-09-09.md銆�
+
+### 閬楃暀闂涓庡緟鍔�
+- 9 鏈堝強浠ヤ笂鏃犲綋鏈堟瘝鐗堜笖姣嶇増鏃犲搴旀湀浠藉垪锛氶渶鈥滆嚜鍔ㄦ墿鍒椻�濓紙绱鍒楀墠鎻掑綋鏈堝��+鍚屾瘮涓ゅ垪銆佸叕寮�/鏍峰紡/2025 鍙傜収缁勫彸绉诲悓姝ワ級锛屾湰杞湭鍋氥��
+- M2 鍓╀綑椤碉細缃戠害杞﹀彴璐︼紙闇�銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺銆嬪綋鏈堟媶鍒嗚緭鍏ワ級銆佽揣杩愰〉锛團reightCalc 瑙勪笂/瑙勪笅/鍏ㄥ彛寰勶級+ M3 鍚堟垚/鎴愬搧鍏ㄩ摼銆�
+- 缂烘暟鎻愮ず鐩墠杩涘悗绔棩蹇楋紝椤甸潰鎻愮ず鎺ュ叆寰呮暣閾惧畬鎴愩��
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java
new file mode 100644
index 0000000..435309b
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java
@@ -0,0 +1,256 @@
+package com.trafficaudit.reportexport.calc;
+
+import com.trafficaudit.common.util.RegionUtil;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 姹囨�绘暣鏈�滄暟鎹┍鍔ㄥ洖濉�濆紩鎿庯紙M2 鍙拌处灞傦紝2026-09-09 鎺ュ叆锛夈��
+ *
+ * 鐩爣锛氱敓鎴愪换鎰忔湀浠姐�婇亾璺繍杈撻噺姹囨�昏〃锛堟暣鏈級銆嬫椂涓嶅啀渚濊禆褰撴湀浜哄伐姣嶇増锛�
+ * 姣嶇増鍙綋鐗堝紡搴曠锛屽綋鏈堝�兼寜宸查獙璇佸彛寰勪粠搴撶幇绠楀啓鍏ワ紙鐝嚎鍖呰溅 2026-07 涓庝汉宸ユ瘝鐗�
+ * 350 鏍� 0 宸紓銆佸叕浜�/鍑虹/杞ㄩ亾杞浮鍚屾壒 0 宸紓锛夈��
+ *
+ * 鏈増瑕嗙洊 4 寮犲彴璐﹂〉锛氱彮绾垮寘杞︺�佸叕浜ゃ�佸嚭绉熴�佽建閬撹疆娓★紱
+ * 缃戠害杞︿緷璧栥�婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺銆嬫媶鍒嗚緭鍏ャ�佽揣杩愰〉鍦� M3 鎺ュ叆锛屽悗缁増鏈ˉ鍏呫��
+ *
+ * 鍐欏叆瑙勫垯锛�
+ * 1) 鍙啓鈥滃競宸炴槑缁嗚鈥濈殑 2026 骞� m 鏈堝綋鏈堝�煎垪锛坢 鏈堝�煎垪 = 绗� 2+2*(m-1) 鍒楋紝0 鍩猴紝鍗� C 璧凤級锛�
+ * 2) 鍏ㄧ渷琛岋紙=17 甯傚窞姹傚拰鍏紡锛変笌璺ㄩ〉鍚堟垚椤靛潎涓哄叕寮忥紝涓嶅姩锛孍xcel 鎵撳紑鑷姩閲嶇畻锛�
+ * 3) 2025 鍚屾湡鍙傜収鍒椾繚鐣欐瘝鐗堢紦瀛樺�硷紙鍚屾瘮鍏紡鑷姩寮曠敤锛夛紱
+ * 4) 婧愭暟鎹暣鏈堢己澶辨椂鏀堕泦鎻愮ず銆佷笉鍥炲~锛涘競宸炲綋鏈堝叏涓� 0 鏃朵繚鎸佹瘝鐗堢┖鍗曞厓鏍笺��
+ */
+@Service
+public class SummaryWorkbookFiller {
+
+    @Resource
+    private MidCalc midCalc;
+    @Resource
+    private PaxCalc paxCalc;
+
+    /** 2026 骞� 1 鏈堝綋鏈堝�煎垪锛�0 鍩猴紝鍗� C 鍒楋級 */
+    private static final int FIRST_MONTH_COL = 2;
+
+    /** 鍥炲~ 1..keepMonths 鏈堝彴璐﹂〉锛岃繑鍥炲啓鍏ユ牸鏁帮紱缂烘暟鎻愮ず鍐欏叆 problems */
+    public int fillMonthlyLedger(XSSFWorkbook wb, int year, int keepMonths, List<String> problems) {
+        if (wb == null || keepMonths <= 0) return 0;
+        if (problems == null) problems = new ArrayList<>();
+        String yearPrefix = year + "-";
+        int filled = 0;
+        filled += fillCityRows(wb, "鐝嚎鍖呰溅", midCalc.banxianMonthly(yearPrefix, keepMonths), year, keepMonths, problems, false);
+        filled += fillCityRows(wb, "鍏氦", paxCalc.busMonthly(yearPrefix, keepMonths), year, keepMonths, problems, false);
+        filled += fillCityRows(wb, "鍑虹", paxCalc.taxiMonthly(yearPrefix, keepMonths), year, keepMonths, problems, false);
+        filled += fillTrackFerry(wb, paxCalc.railFerryMonthly(yearPrefix, keepMonths), year, keepMonths, problems);
+        return filled;
+    }
+
+    /** 閫氱敤鍙拌处椤碉紙鐝嚎鍖呰溅/鍏氦/鍑虹锛夊洖濉細鎸夆�滃垪 A 甯傚窞鍚� + 鍒� B 鎸囨爣鏂囨鈥濆畾浣嶈锛屼笉鍐欐琛屽彿 */
+    private int fillCityRows(XSSFWorkbook wb, String sheetName,
+                             Map<Integer, Map<String, double[]>> monthly,
+                             int year, int keepMonths, List<String> problems, boolean wyc) {
+        Sheet sh = wb.getSheet(sheetName);
+        if (sh == null) {
+            problems.add(sheetName + "椤典笉瀛樺湪锛堟瘝鐗堢己澶辫椤碉級锛岃烦杩囪嚜鍔ㄥ洖濉�");
+            return 0;
+        }
+        int cap = monthColumnCapacity(sh);
+        int mMax = Math.min(keepMonths, cap);
+        if (mMax <= 0) {
+            problems.add(sheetName + "椤垫湭璇嗗埆鍒� 2026 骞寸疮璁″垪锛岃烦杩囪嚜鍔ㄥ洖濉�");
+            return 0;
+        }
+        // 1) 甯傚窞璧峰琛岋紙鍒� A 涓鸿鑼冨競宸炲悕锛�
+        Map<String, Integer> cityStart = new LinkedHashMap<>();
+        for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            String city = cityOf(row.getCell(0));
+            if (city != null && !cityStart.containsKey(city)) cityStart.put(city, r);
+        }
+        if (cityStart.isEmpty()) {
+            problems.add(sheetName + "椤垫湭璇嗗埆鍒板競宸炶锛岃烦杩囪嚜鍔ㄥ洖濉�");
+            return 0;
+        }
+        List<Integer> starts = new ArrayList<>(cityStart.values());
+        int filled = 0;
+        List<String> missingMonths = new ArrayList<>();
+        for (int m = 1; m <= mMax; m++) {
+            if (!monthly.containsKey(m)) missingMonths.add(m + "鏈�");
+        }
+        if (!missingMonths.isEmpty()) {
+            problems.add(sheetName + "缂� " + year + " 骞� " + String.join("銆�", missingMonths) + " 婧愭暟鎹紙鏈洖濉級");
+        }
+        for (int i = 0; i < starts.size(); i++) {
+            String city = findCityByStart(sh, starts.get(i));
+            if (city == null) continue;
+            int end = (i + 1 < starts.size()) ? starts.get(i + 1) - 1 : sh.getLastRowNum();
+            // 2) 琛屽唴鍚勬寚鏍囨墍鍦ㄨ锛圔 鍒楁枃妗� 鈫� 鎸囨爣浣� 0..3锛�
+            int[] metricRow = new int[]{-1, -1, -1, -1};
+            for (int r = starts.get(i); r <= end; r++) {
+                Row row = sh.getRow(r);
+                if (row == null) continue;
+                int idx = metricIndexOf(text(row.getCell(1)));
+                if (idx >= 0 && metricRow[idx] < 0) metricRow[idx] = r;
+            }
+            if (metricRow[0] < 0 && metricRow[1] < 0) continue;
+            for (int m = 1; m <= mMax; m++) {
+                Map<String, double[]> mm = monthly.get(m);
+                if (mm == null) continue;
+                double[] arr = mm.get(city);
+                if (arr == null || (arr[0] == 0.0 && arr[1] == 0.0)) continue;
+                int col = FIRST_MONTH_COL + 2 * (m - 1);
+                for (int idx = 0; idx < 4; idx++) {
+                    if (metricRow[idx] < 0) continue;
+                    double val = arr[idx];
+                    if (val == 0.0) continue;
+                    filled += setNumeric(sh, metricRow[idx], col, val);
+                }
+            }
+        }
+        return filled;
+    }
+
+    /** 杞ㄩ亾杞浮椤碉紙杞ㄩ亾=姝︽眽/榛勭煶锛涜疆娓′粎姝︽眽锛変笓鐢ㄥ洖濉� */
+    private int fillTrackFerry(XSSFWorkbook wb, Map<Integer, Map<String, double[]>> monthly,
+                               int year, int keepMonths, List<String> problems) {
+        String sheetName = "杞ㄩ亾銆佽疆娓�";
+        Sheet sh = wb.getSheet(sheetName);
+        if (sh == null) return 0;
+        int cap = monthColumnCapacity(sh);
+        int mMax = Math.min(keepMonths, cap);
+        if (mMax <= 0) return 0;
+        // 杞ㄩ亾鍧椾笌杞浮鍧楁爣棰樿锛堝垪 A 鏂囨锛�
+        int trackTitle = -1, ferryTitle = -1;
+        for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) {
+            String a = text(sh.getRow(r) == null ? null : sh.getRow(r).getCell(0));
+            if (a == null) continue;
+            if (trackTitle < 0 && a.startsWith("杞ㄩ亾瀹㈣繍閲�")) trackTitle = r;
+            else if (ferryTitle < 0 && a.startsWith("杞浮瀹㈣繍閲�")) ferryTitle = r;
+        }
+        if (trackTitle < 0 || ferryTitle < 0) {
+            problems.add(sheetName + "椤垫湭璇嗗埆鍒拌建閬�/杞浮鍧楁爣棰橈紝璺宠繃鑷姩鍥炲~");
+            return 0;
+        }
+        int filled = 0;
+        filled += fillFerryBlock(sh, monthly, trackTitle + 3, ferryTitle - 1, 0, mMax, problems, sheetName);
+        filled += fillFerryBlock(sh, monthly, ferryTitle + 3, sh.getLastRowNum(), 2, mMax, problems, sheetName);
+        return filled;
+    }
+
+    /** 濉厖涓�娈碘�滄爣棰樿鍚庢暟鎹尯鈥濓細base 涓鸿鍧楁寚鏍囦綅鍩猴紙杞ㄩ亾 0 / 杞浮 2锛夛紱block 鍐�
+     *  姣忓競宸� 2 琛岋細瀹㈣繍閲忚锛堝垪 A=甯傚窞鍚嶏級銆佹梾瀹㈠懆杞噺琛岋紙A 绌猴紝B 鍚�滃懆杞噺鈥濓級 */
+    private int fillFerryBlock(Sheet sh, Map<Integer, Map<String, double[]>> monthly,
+                               int start, int end, int base, int mMax,
+                               List<String> problems, String sheetName) {
+        int filled = 0;
+        // 鏀堕泦甯傚窞瀹㈣繍閲忚
+        Map<String, Integer> paxRow = new LinkedHashMap<>();
+        for (int r = start; r <= end; r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            String city = cityOf(row.getCell(0));
+            if (city != null) paxRow.put(city, r);
+        }
+        for (Map.Entry<String, Integer> e : paxRow.entrySet()) {
+            String city = e.getKey();
+            int rp = e.getValue();
+            int rt = findTurnoverRow(sh, rp + 1, Math.min(end, rp + 6));
+            if (rt < 0) continue;
+            for (int m = 1; m <= mMax; m++) {
+                Map<String, double[]> mm = monthly.get(m);
+                if (mm == null) continue;
+                double[] arr = mm.get(city);
+                if (arr == null) continue;
+                int col = FIRST_MONTH_COL + 2 * (m - 1);
+                double pax = arr[base];
+                double turn = arr[base + 1];
+                if (pax != 0.0) filled += setNumeric(sh, rp, col, pax);
+                if (turn != 0.0) filled += setNumeric(sh, rt, col, turn);
+            }
+        }
+        return filled;
+    }
+
+    private int findTurnoverRow(Sheet sh, int from, int to) {
+        for (int r = from; r <= to; r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            String a = text(row.getCell(0));
+            if (a != null && !a.trim().isEmpty()) return -1; // 杩涘叆涓嬩竴甯傚窞鍧�
+            String b = text(row.getCell(1));
+            if (b != null && b.contains("鍛ㄨ浆閲�")) return r;
+        }
+        return -1;
+    }
+
+    /** 鍐欐暟鍊硷細鏍间笉瀛樺湪鍒欏缓鏍煎苟澶嶅埗鍚岃 1 鏈堝垪鏍峰紡锛涜繑鍥� 1 */
+    private int setNumeric(Sheet sh, int r, int col, double val) {
+        Row row = sh.getRow(r);
+        if (row == null) row = sh.createRow(r);
+        Cell c = row.getCell(col);
+        if (c == null) {
+            c = row.createCell(col);
+            Cell ref = row.getCell(FIRST_MONTH_COL);
+            if (ref != null && ref.getCellStyle() != null) c.setCellStyle(ref.getCellStyle());
+        }
+        c.setCellValue(val);
+        return 1;
+    }
+
+    /** 琛� B 鍒楁枃妗� 鈫� 鎸囨爣浣嶏細0 瀹㈣繍閲� / 1 鏃呭鍛ㄨ浆閲� / 2 鍏朵腑鍩庡競鍐呭杩愰噺鎴栧叾涓釜浣撳杩愰噺 / 3 瀵瑰簲鍛ㄨ浆閲� */
+    private int metricIndexOf(String b) {
+        if (b == null) return -1;
+        if (b.contains("鍏朵腑涓綋鏃呭鍛ㄨ浆閲�") || b.contains("鍏朵腑鍩庡競鍐呮梾瀹㈠懆杞噺")) return 3;
+        if (b.contains("鍏朵腑涓綋瀹㈣繍閲�") || b.contains("鍏朵腑鍩庡競鍐呭杩愰噺")) return 2;
+        if (b.contains("鏃呭鍛ㄨ浆閲�")) return 1;
+        if (b.contains("瀹㈣繍閲�")) return 0;
+        return -1;
+    }
+
+    /** 鍒� A 鏂囨 鈫� 瑙勮寖甯傚窞鍚嶏紙婀栧寳鐪�/鍏ㄧ渷銆侀潪甯傚窞琛岃繑鍥� null锛� */
+    private String cityOf(Cell a) {
+        String t = text(a);
+        if (t == null || t.trim().isEmpty()) return null;
+        String norm = RegionUtil.normalizeCityName(t.trim());
+        return RegionUtil.cityList().contains(norm) ? norm : null;
+    }
+
+    private String findCityByStart(Sheet sh, int startRow) {
+        Row row = sh.getRow(startRow);
+        return row == null ? null : cityOf(row.getCell(0));
+    }
+
+    /** 鏈堜唤瀹归噺锛氬湪琛ㄥご锛堝墠 5 琛岋級鎵锯��2026鈥︾疮璁♀�濆垪锛屽閲�=(绱鍒�-1 鏈堝垪)/2 */
+    private int monthColumnCapacity(Sheet sh) {
+        for (int r = 0; r <= 4 && r <= sh.getLastRowNum(); r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            for (Cell c : row) {
+                if (c == null) continue;
+                String t = text(c);
+                if (t != null && t.contains("2026") && t.contains("绱") && c.getColumnIndex() > FIRST_MONTH_COL) {
+                    return (c.getColumnIndex() - FIRST_MONTH_COL) / 2;
+                }
+            }
+        }
+        return 7;
+    }
+
+    private String text(Cell c) {
+        if (c == null) return null;
+        switch (c.getCellType()) {
+            case STRING: return c.getStringCellValue();
+            case NUMERIC: return Double.toString(c.getNumericCellValue());
+            default: return null;
+        }
+    }
+}
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 7fa99e7..fb82de4 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
@@ -29,6 +29,9 @@
 import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
 import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper;
 import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper;
+import com.trafficaudit.reportexport.calc.MidCalc;
+import com.trafficaudit.reportexport.calc.PaxCalc;
+import com.trafficaudit.reportexport.calc.SummaryWorkbookFiller;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellStyle;
@@ -92,6 +95,8 @@
     private CityBusMonthlyMapper cityBusMapper;
     @Resource
     private CityTaxiMonthlyMapper cityTaxiMapper;
+    @Resource
+    private SummaryWorkbookFiller summaryWorkbookFiller;
     @Resource
     private AuditResultMapper auditResultMapper;
     @Resource
@@ -3340,6 +3345,10 @@
             }
             dynamicSummaryYear(wb, year);
             cleanSummarySheetPresentation(wb); // 鍙栨秷鍚勯〉绛涢�夈�佽繕鍘熼殣钘忚锛堝惁鍒欌�滅彮绾垮寘杞︹�濈瓑椤电湅璧锋潵鍍忕己鏁版嵁锛�
+            int effMonth = (toMonth != null && toMonth > 0 && toMonth < month) ? toMonth : month;
+            List<String> dbProblems = new ArrayList<>();
+            int dbFilled = summaryWorkbookFiller.fillMonthlyLedger(wb, year, effMonth, dbProblems);
+            log.info("姹囨�诲伐浣滅翱鏁版嵁椹卞姩鍥炲~瀹屾垚锛氬啓鍏ュ彴璐﹂〉褰撴湀鍊兼牸鏁�={}锛岀己鏁版彁绀�={}", dbFilled, dbProblems);
             applyTwoDecimalFormat(wb); // 鏁板�兼樉绀轰袱浣嶅皬鏁帮紙淇濈暀鍏ㄧ簿搴︼級锛�% / 鏃ユ湡绛夋棦鏈夋牱寮忎笉鏀瑰彉
             if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr();
             wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // 杩樺師/鏀瑰啓鍏紡鍚庢墦寮�鍗抽噸绠�

--
Gitblit v1.9.1