From 8719902780eadfc55d4d159d5a111b433f065137 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期五, 18 九月 2026 11:34:33 +0800
Subject: [PATCH] 功能(中口径): 四维口径统一 + 修独立中口径明细扩列月份总行为空
---
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java | 695 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 660 insertions(+), 35 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 ceea4f1..16dd5e5 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
@@ -13,6 +13,7 @@
import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly;
import com.trafficaudit.dataimport.entity.ScaleSplitTransport;
+import com.trafficaudit.dataimport.entity.WycOrderMonthly;
import com.trafficaudit.auditengine.entity.AuditResult;
import com.trafficaudit.auditengine.entity.AuditRun;
import com.trafficaudit.auditengine.mapper.AuditResultMapper;
@@ -29,9 +30,11 @@
import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper;
import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper;
+import com.trafficaudit.dataimport.mapper.WycOrderMonthlyMapper;
import com.trafficaudit.reportexport.calc.MidCalc;
import com.trafficaudit.reportexport.calc.PaxCalc;
import com.trafficaudit.reportexport.calc.SummaryWorkbookFiller;
+import com.trafficaudit.reportexport.calc.WycSplitCalc;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
@@ -98,6 +101,10 @@
private CityTaxiMonthlyMapper cityTaxiMapper;
@Resource
private SummaryWorkbookFiller summaryWorkbookFiller;
+ @Resource
+ private WycSplitCalc wycSplitCalc;
+ @Resource
+ private WycOrderMonthlyMapper wycOrderMapper;
@Resource
private AuditResultMapper auditResultMapper;
@Resource
@@ -1179,17 +1186,16 @@
return lv == 0.0 ? null : (v - lv) / lv;
}
- /** 涓彛寰勬湀鍚屾瘮锛歬=-1 琛ㄧず鎬昏锛堢彮绾�+鍏氦涔嬪拰锛屽嚭绉�/缃戠害杞︽棤婧愭暟鎹寜 0锛夛紱鏃犲幓骞存暟鎹暀绌� */
+ /** 涓彛寰勬湀鍚屾瘮锛歬=-1 琛ㄧず鎬昏锛堢彮绾�+鍏氦+鍑虹鍩庝埂+缃戠害杞﹀煄涔� 鍥涢」涔嬪拰锛夛紱鏃犲幓骞存暟鎹暀绌� */
private void fillMidYoyCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month,
String area, int k, boolean volume) {
double v = 0.0, lv = 0.0;
if (k == -1) {
for (int kk = 1; kk <= 4; kk++) {
- if (kk >= 3) continue;
v += midClassVal(mid, year, month, area, kk, volume);
lv += midClassVal(mid, year - 1, month, area, kk, volume);
}
- } else if (k <= 2) {
+ } else {
v = midClassVal(mid, year, month, area, k, volume);
lv = midClassVal(mid, year - 1, month, area, k, volume);
}
@@ -1197,7 +1203,7 @@
if (v != 0.0 && lv != 0.0) c.setCellValue(round((v - lv) / lv, 4));
}
- /** 涓彛寰勭疮璁″悓姣斿垪锛�1..months 鏈堢疮璁★紱k=-1 鎬昏 = 鐝嚎+鍏氦锛� */
+ /** 涓彛寰勭疮璁″悓姣斿垪锛�1..months 鏈堢疮璁★紱鍥涚淮榻愬叏锛� */
private void fillMidCumYoyColumn(Sheet sheet, Map<Integer, Map<String, double[][]>> mid, int year,
int months, int colIdx) {
List<String> areas = midTemplateAreas();
@@ -1217,7 +1223,7 @@
String area = areas.get(blockOffset / 5);
double v = 0.0, lv = 0.0;
for (int m = 1; m <= months; m++) {
- if (k <= 2) {
+ {
v += midClassVal(mid, year, m, area, k, volume);
lv += midClassVal(mid, year - 1, m, area, k, volume);
}
@@ -2181,20 +2187,15 @@
boolean extended = fillMonths > 6;
if (extended) extendMonthlyColumns(sheet, 2, currentYear, 4, sheet.getLastRowNum());
List<String> areas = midTemplateAreas();
+ // 2026-09-18锛氭敼涓恒�屾寜鍗曞厓鏍肩被鍨嬫棤鍏炽�嶉亶鍘嗏�斺�旀墿鍒楋紙7~12 鏈堬級鍑烘潵鐨勫�煎垪鍦ㄦā鏉块噷鏄┖鏍硷紝
+ // 鏃у疄鐜板彧澶勭悊 CellType.FORMULA 鐨勬牸锛屽鑷� 7 鏈堝強浠ュ悗鏈堜唤姘歌繙濉笉涓婏紙8 鏈堟暣鍒� 0銆佺疮璁″彧鍒� 6 鏈堬級銆�
for (Row row : sheet) {
if (row == null) continue;
- for (Cell c : row) {
- if (c.getCellType() != CellType.FORMULA) continue;
- String f = c.getCellFormula();
- if (f == null) continue;
- boolean crossBook = f.contains("[");
- if (!crossBook && !isYoyMonthCol(c.getColumnIndex() + 1)) continue; // 鍐呴儴鍏紡锛堝悎璁�/绱锛変繚鐣欓噸绠楋紱鍚屾瘮鍒� #REF! 瑕嗗啓涓烘暟鍊�
- if (crossBook && !is2026MonthCol(c.getColumnIndex() + 1)) {
- keepCached(c); // 2025 骞村垪淇濈暀妯℃澘缂撳瓨鍊�
- continue;
- }
+ for (int c0 = 0; c0 < Math.max(row.getLastCellNum(), 0); c0++) {
+ Cell c = row.getCell(c0);
+ if (c == null) continue;
int r = c.getRowIndex() + 1;
- int col = c.getColumnIndex() + 1;
+ int col = c0 + 1;
int blockOffset = -1;
boolean volume = false;
if (r >= 5 && r <= 94) {
@@ -2205,31 +2206,48 @@
volume = false;
}
if (blockOffset < 0) {
- keepCached(c); // r1/r2 澶囨敞绛�
+ if (c.getCellType() == CellType.FORMULA) keepCached(c); // r1/r2 澶囨敞銆侀〉澶栬法绨垮紩鐢ㄧ瓑
continue;
}
- if (blockOffset % 5 == 0) {
- if (isYoyMonthCol(col)) {
- fillMidYoyCell(c, mid, currentYear, monthOfCol(col), areas.get(blockOffset / 5), -1, volume);
- } else {
- keepCached(c); // 鎬昏鍐呴儴鍏紡锛堥槻寰★級
+ boolean isVal = is2026MonthCol(col);
+ boolean isYoy = isYoyMonthCol(col);
+ if (!isVal && !isYoy) {
+ // 2025 骞村垪绛夛細璺ㄧ翱鍏紡鍙栫紦瀛樺�兼柇閾撅紱鍐呴儴鍏紡锛堝悎璁�/绱锛変繚鐣欙紝绋嶅悗 recalc 閲嶇畻
+ if (c.getCellType() == CellType.FORMULA) {
+ String f = c.getCellFormula();
+ if (f != null && f.contains("[")) keepCached(c);
}
continue;
}
- int k = blockOffset % 5; // 1=鐝嚎 2=鍏氦 3=鍑虹 4=缃戠害杞�
String area = areas.get(blockOffset / 5);
- if (is2026MonthCol(col)) {
+ if (blockOffset % 5 == 0) {
+ if (isYoy) {
+ fillMidYoyCell(c, mid, currentYear, monthOfCol(col), area, -1, volume);
+ } else if (monthOfCol(col) > 6) {
+ // 鎵╁垪鍑烘潵鐨� 7~12 鏈堟�昏鍊煎垪锛氭ā鏉块噷鏈棤姝ゅ垪锛屽厠闅嗗悗鏃㈡病鏈夊叕寮忎篃娌℃湁缂撳瓨鍊硷紝
+ // 鐩存帴鍐欏簱鍐呭洓缁村悎璁★紙缁村害0锛夈��1~6 鏈堜繚鐣欐ā鏉跨紦瀛樺�硷紝閬垮厤涓㈡帀妯℃澘閲岀殑瀹氱鏁般��
+ double total = midClassVal(mid, currentYear, monthOfCol(col), area, 0, volume);
+ c.setBlank();
+ if (total != 0.0) c.setCellValue(round(total, 4));
+ } else if (c.getCellType() == CellType.FORMULA) {
+ keepCached(c); // 1~6 鏈堟�昏鍊煎垪 = 妯℃澘缂撳瓨鍊硷紝淇濈暀
+ }
+ continue;
+ }
+ int k = blockOffset % 5; // 1=鐝嚎 2=鍏氦 3=鍑虹鍩庝埂 4=缃戠害杞﹀煄涔�
+ if (isVal) {
fillMidCell(c, mid, currentYear, monthOfCol(col), area, k, volume);
- } else if (isYoyMonthCol(col)) {
- fillMidYoyCell(c, mid, currentYear, monthOfCol(col), area, k, volume);
} else {
- keepCached(c); // 2025 骞村垪淇濈暀妯℃澘缂撳瓨鍊�
+ fillMidYoyCell(c, mid, currentYear, monthOfCol(col), area, k, volume);
}
}
}
// 绱鍚屾瘮鍒楋紙妯℃澘 #REF! 鈫� 搴撳唴鍘诲勾 1..N 鏈堢疮璁″悓姣旀暟鍊硷級
fillMidCumYoyColumn(sheet, mid, currentYear, fillMonths, extended ? 27 : 15);
- recalc(wb);
+ // evaluateAll 浼氬湪璺ㄧ翱鍏紡锛圫heet2 鐨� [1]xx!A1锛夊鏁存湰涓柇锛岀疮璁″垪 AA 灏辨案杩滄嬁涓嶅埌鏂扮紦瀛樺�硷紝
+ // 鏀圭敤閫愭牸瀹归敊鐨� recalcWorkbookFormulas锛氬潖鏍艰烦杩囥�佸ソ鏍肩収绠椼��
+ int[] recalc = recalcWorkbookFormulas(wb);
+ log.info("涓彛寰勬槑缁嗗叕寮忛噸绠楀畬鎴愶細鎴愬姛 {} 鏍硷紝澶辫触 {} 鏍�", recalc[0], recalc[1]);
clearFormulaErrorsAll(wb); // 鍘诲勾鍚屾湡鏄庣粏椤垫畫鐣� #REF! 鍚屾瘮鍏紡 鈫� 娓呯┖
return toBytes(wb);
}
@@ -2256,12 +2274,8 @@
return (col - 1) / 2;
}
- /** 濉腑鍙e緞鏁版嵁鍗曞厓鏍硷細鍑虹/缃戠害杞�(3/4)鏄惧紡 0锛岀彮绾�/鍏氦濉簱鍐呭�硷紙鏃犲�兼竻绌猴級 */
+ /** 濉腑鍙e緞鏁版嵁鍗曞厓鏍硷細鐝嚎/鍏氦/鍑虹鍩庝埂/缃戠害杞﹀煄涔� 鍥涚淮缁熶竴鍙栧簱鍐呭�硷紙鏃犲�兼竻绌猴級 */
private void fillMidCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) {
- if (k == 3 || k == 4) {
- writeExplicitZero(c); // 鍑虹/缃戠害杞︽殏鏃犳暟鎹簮锛屾樉寮� 0
- return;
- }
double v = midClassVal(mid, year, month, area, k, volume);
// POI setCellValue(double) 瀵瑰叕寮忓崟鍏冩牸鍙洿鏂扮紦瀛樹笉绉婚櫎鍏紡锛屽繀椤诲厛 setBlank 鍐嶅啓鍊�
c.setBlank();
@@ -2369,7 +2383,52 @@
addMidClass(mid, key, city, 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang()));
addMidClass(mid, key, "婀栧寳鐪�", 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang()));
}
+ // 缁村害3锛氬煄闄呭煄涔″贰娓稿嚭绉� = 鍙拌处椤点�屽嚭绉�!鎬婚噺 鈭� 鍑虹!鍩庡競鍐呫�嶏紙婧� city_taxi_monthly锛�
+ for (CityTaxiMonthly t : cityTaxiMapper.selectList(null)) {
+ if (t.getReportPeriod() == null || t.getCity() == null) continue;
+ int key = midPeriodKey(t.getReportPeriod());
+ if (key <= 0) continue;
+ String city = RegionUtil.normalizeCityName(t.getCity());
+ if (city == null) continue;
+ double pass = nz(t.getPassengerVolume()) - nz(t.getPassengerCity());
+ double turn = nz(t.getTurnover()) - nz(t.getTurnoverCity());
+ addMidClass(mid, key, city, 3, pass, turn);
+ addMidClass(mid, key, "婀栧寳鐪�", 3, pass, turn);
+ }
+ // 缁村害4锛氬煄闄呭煄涔$綉绾﹁溅 = 缃戠害杞︽媶鍒嗙粨鏋滐紙涓庡彴璐︺�岀綉绾﹁溅銆嶉〉鍚屾簮鍚屾硶锛�
+ for (WycOrderMonthly o : wycOrderMapper.selectList(null)) {
+ String per = o.getReportPeriod();
+ if (per == null) continue;
+ int key = midPeriodKey(per);
+ if (key <= 0) continue;
+ WycSplitCalc.WycResult wr;
+ try {
+ wr = wycSplitCalc.calc(per);
+ } catch (Exception e) {
+ continue; // 璇ユ湡缂鸿鍗�/pin 鎴栧嚭绉熸簮鏁版嵁锛岃烦杩囷紙涓嶅奖鍝嶅叾瀹冩湡锛�
+ }
+ for (Map.Entry<String, WycSplitCalc.WycMetrics> en : wr.getByCity().entrySet()) {
+ WycSplitCalc.WycMetrics wm = en.getValue();
+ addMidClass(mid, key, en.getKey(), 4, wm.getSuburbanPax(), wm.getSuburbanTurnover());
+ addMidClass(mid, key, "婀栧寳鐪�", 4, wm.getSuburbanPax(), wm.getSuburbanTurnover());
+ }
+ }
return mid;
+ }
+
+ /** "yyyy-MM" -> yyyy*100+MM锛涢潪娉曡繑鍥� 0锛堜笌 loadMidClassMap 鐨� key 鍙e緞涓�鑷达級 */
+ private int midPeriodKey(String period) {
+ if (period == null) return 0;
+ String[] parts = period.trim().split("-");
+ if (parts.length != 2) return 0;
+ try {
+ int y = Integer.parseInt(parts[0]);
+ int m = Integer.parseInt(parts[1]);
+ if (y <= 0 || m < 1 || m > 12) return 0;
+ return y * 100 + m;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
}
/** 鍐欏叆鍒嗙被鍊煎苟绱姞鎬婚噺锛堢淮搴�0锛� */
@@ -3353,17 +3412,89 @@
int yoyFilled = summaryWorkbookFiller.ensureMonthYoyFormulas(wb, year, effMonth);
int monthFormulaFilled = summaryWorkbookFiller.ensureMonthFormulas(wb, year, effMonth);
int rankRefreshed = refreshSummaryRankSheets(wb, effPeriod, "month");
+ int cumulativeRefreshed = refreshSummaryCumulativeSheets(wb, year, effMonth); // 鍥涘紶璺ㄩ〉鍚堟垚椤甸殢鎶ヨ〃鏈熷埛鏂�
int widthApplied = applySummaryReferenceLayout(wb);
log.info("姹囨�诲伐浣滅翱鏁版嵁椹卞姩鍥炲~瀹屾垚锛氬啓鍏ュ綋鏈堝�兼牸鏁�={}锛岃ˉ鍐欏綋鏈堝悓姣斿叕寮忔牸鏁�={}锛岃ˉ鍐欏綋鏈堝悎璁″叕寮忔牸鏁�={}锛�"
- + "鍒锋柊鎺掑悕椤垫暟鍊兼牸鏁�={}锛屾寜銆婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃銆嬪榻愬垪瀹藉垪鏁�={}锛岀己鏁版彁绀�={}",
- dbFilled, yoyFilled, monthFormulaFilled, rankRefreshed, widthApplied, dbProblems);
+ + "鍒锋柊鎺掑悕椤垫暟鍊兼牸鏁�={}锛屽埛鏂板洓椤碉紙鍩庡競姹囨��/鍩庡競鍒嗗競宸�/涓彛寰勫垎鏋�/閬撹矾杩愯緭鍛ㄨ浆閲忥級鏁板�兼牸鏁�={}锛�"
+ + "鎸夈�婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃銆嬪榻愬垪瀹藉垪鏁�={}锛岀己鏁版彁绀�={}",
+ dbFilled, yoyFilled, monthFormulaFilled, rankRefreshed, cumulativeRefreshed, widthApplied, dbProblems);
applyTwoDecimalFormat(wb); // 鏁板�兼樉绀轰袱浣嶅皬鏁帮紙淇濈暀鍏ㄧ簿搴︼級锛�% / 鏃ユ湡绛夋棦鏈夋牱寮忎笉鏀瑰彉
+ int inlineFixed = normalizeStaleInlineStrings(wb); // 娓呮帀銆岄潪 inlineStr 鍗存畫鐣� <is>銆嶇殑闈炴硶鏍硷紝鍚﹀垯 Excel 鎷掔粷鎵撳紑
+ if (inlineFixed > 0) log.warn("姹囨�诲伐浣滅翱娓呮帀娈嬬暀 inlineStr 闈炴硶鏍� {} 涓紙姣嶇増鐢� openpyxl 鍐欏嚭鏃剁殑鍏稿瀷闂锛�", inlineFixed);
+ int[] recalc = recalcWorkbookFormulas(wb); // 鍏紡閲嶇畻骞跺啓鍥炵紦瀛樺�硷細鍏ㄧ渷琛�(危甯傚窞)/绱鍒�/鎺掑悕 SUM 鎵嶈兘琚瑙堜笌浜屾澶勭悊璇诲埌
+ log.info("姹囨�诲伐浣滅翱鍏紡閲嶇畻瀹屾垚锛氭垚鍔� {} 鏍硷紝澶辫触 {} 鏍硷紙澶辫触鏍间繚鐣欐瘝鐗堟棫缂撳瓨鍊硷紝鎵撳紑鏃舵寜 fullCalcOnLoad 閲嶇畻锛�", recalc[0], recalc[1]);
if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr();
wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // 杩樺師/鏀瑰啓鍏紡鍚庢墦寮�鍗抽噸绠�
return toBytes(wb);
} finally {
ZipSecureFile.setMinInflateRatio(savedZipRatio);
}
+ }
+
+ /**
+ * 姹囨�诲伐浣滅翱鍏紡閲嶇畻锛歅OI 鍐欐枃浠朵笉甯︾紦瀛樺�硷紝瀵艰嚧銆屽叏鐪佽锛埼�17甯傚窞锛夈�嶃�岀疮璁″垪銆嶃�屾帓鍚� SUM銆�
+ * 鍦ㄩ瑙�/浜屾澶勭悊閲屽彧鐪嬪埌姣嶇増涓婁竴鏈熺殑鏃у�硷紙2026-09-18 鐢ㄦ埛鎶モ��8鏈堟病鏁版嵁 / 鍏ㄧ渷琛屽仠鍦ㄤ笂涓�鏈熲�濈殑鏍瑰洜涔嬩竴锛夈��
+ * 閫愭牸 evaluate 骞舵妸缁撴灉鍐欏洖缂撳瓨鍊硷紱涓埆涓嶆敮鎸佸嚱鏁�/澶栭儴寮曠敤鏍煎け璐ュ垯璺宠繃骞惰鏁帮紝涓嶉樆濉炲鍑恒��
+ */
+ private int[] recalcWorkbookFormulas(XSSFWorkbook wb) {
+ if (wb == null) return new int[]{0, 0};
+ org.apache.poi.ss.usermodel.FormulaEvaluator ev;
+ try {
+ ev = wb.getCreationHelper().createFormulaEvaluator();
+ } catch (Exception e) {
+ log.warn("姹囨�诲伐浣滅翱鍏紡閲嶇畻锛氬垱寤烘眰鍊煎櫒澶辫触锛岃烦杩囷紙{}锛�", e.getMessage());
+ return new int[]{0, 0};
+ }
+ int ok = 0, fail = 0;
+ java.util.List<String> samples = new java.util.ArrayList<>();
+ for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+ Sheet sh = wb.getSheetAt(i);
+ if (sh == null) continue;
+ for (Row row : sh) {
+ if (row == null) continue;
+ for (Cell c : row) {
+ if (c == null || c.getCellType() != CellType.FORMULA) continue;
+ try {
+ ev.evaluateFormulaCell(c); // 娉ㄦ剰锛歟valuate() 鏃犲壇浣滅敤锛屽繀椤荤敤 evaluateFormulaCell 鎵嶄細鎶婄粨鏋滃啓鍥炵紦瀛樺��
+ ok++;
+ } catch (Exception ex) {
+ fail++;
+ if (samples.size() < 10) {
+ samples.add(sh.getSheetName() + "!" + c.getAddress() + " :: " + ex.getClass().getSimpleName());
+ }
+ }
+ }
+ }
+ }
+ if (!samples.isEmpty()) log.warn("姹囨�诲伐浣滅翱鍏紡閲嶇畻澶辫触鏍蜂緥锛歿}", samples);
+ return new int[]{ok, fail};
+ }
+
+ /**
+ * 娓呮帀銆岄潪 inlineStr 鍗存畫鐣� <is>銆嶇殑闈炴硶鍗曞厓鏍笺��
+ *
+ * 姣嶇増鑻ョ敱 openpyxl 鍐欏嚭锛屾枃鏈崟鍏冩牸鐢� <is>锛坕nlineStr锛夊瓨鍌紱POI 鐨� setCellValue 鍙敼鍐� t 涓� <v>锛�
+ * 涓嶄細娓呮帀鏃� <is>锛屼簬鏄骇鍑� <c t="n"><v>67.0</v><is><t>67.00</t></is></c> 杩欑闈炴硶鏍硷紝
+ * Excel 鍒ゅ畾涓恒�屾棤娉曡鍙栫殑鍐呭銆嶈�岀洿鎺ユ嫆缁濇墦寮�锛圕OM 琛ㄧ幇涓恒�屼笉鑳藉彇寰楃被 Workbooks 鐨� Open 灞炴�с�嶏級銆�
+ * 2026-07 瀵煎嚭浠跺嵆鍥犳鎵撲笉寮�锛堛�婂叕浜ゃ��17 鏍� +銆婅建閬撱�佽疆娓°��2 鏍硷級銆傝惤鐩樺墠缁熶竴娓呯悊锛屽箓绛夈��
+ */
+ private int normalizeStaleInlineStrings(XSSFWorkbook wb) {
+ int fixed = 0;
+ for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+ org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet ctw = wb.getSheetAt(i).getCTWorksheet();
+ if (ctw == null || ctw.getSheetData() == null) continue;
+ for (org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRow row : ctw.getSheetData().getRowArray()) {
+ for (org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell c : row.getCArray()) {
+ if (!c.isSetIs()) continue;
+ STCellType.Enum t = c.getT();
+ if (t == null || !STCellType.INLINE_STR.equals(t)) {
+ c.unsetIs();
+ fixed++;
+ }
+ }
+ }
+ }
+ return fixed;
}
/**
@@ -3654,6 +3785,496 @@
}
/** 瀹氫綅鐗堝紡鍙傜収浠� docs/鐢熸垚姹囨�诲ぇ琛�/鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛堜紭鍏堢骇鍚屾瘝鐗堬細閰嶇疆鐩綍 鈫� user.dir 鐩稿 鈫� 涓婄骇锛� */
+ // ==================== 姹囨�诲ぇ琛細鍥涘紶鈥滆法椤靛悎鎴愨�濋〉闅忔姤琛ㄦ湡鍒锋柊 ====================
+ // 鍩庡競姹囨�� / 鍩庡競鍒嗗競宸� / 涓彛寰勫垎鏋� / 閬撹矾杩愯緭鍛ㄨ浆閲� 姝ゅ墠鏁撮〉娌跨敤姣嶇増闈欐�佸�硷紙鏍囬鍋滃湪 1-7 鏈堬級銆�
+ // 鏈鎸� 2026-09-13 宸查獙璇佸彛寰勶紙涓� 8 鏈堟瘝鐗堥�愭牸 0 宸紓锛変粠姣嶇増鍙拌处椤电幇绠楋細
+ // 鍩庡競瀹㈣繍閲� = 鍩庡競鍐呭叕浜� + 鍩庡競鍐呭贰娓稿嚭绉� + 鍩庡競鍐呯綉绾﹁溅 + 杞ㄩ亾 + 杞浮
+ // 閬撹矾瀹㈣繍鍛ㄨ浆閲� = 鍏矾鐝嚎 + 鍩庨檯鍩庝埂鍏氦 + 鍩庨檯鍩庝埂宸℃父鍑虹 + 鍩庨檯鍩庝埂缃戠害杞�
+ // 閬撹矾璐ц繍鍛ㄨ浆閲� = 璐ц繍椤� 甯傚窞 瑙勪笂璐х墿鍛ㄨ浆閲� + 瑙勪笅璐х墿鍛ㄨ浆閲�
+ // 2026 骞� 1..N 鏈堝彇鍙拌处椤� 2026 鍒楋紙C/E/G鈥︼級锛屽幓骞村悓鏈熷彇姣嶇増缂撳瓨 2025 鍒楋紙V/X/Z鈥︼級鈥斺�斿簱閲屾病鏈� 2025 骞村煄甯傚杩愭暟鎹��
+
+ /** 鍩庡競瀹㈣繍鍙e緞鎸囨爣浣嶏紙0..17 鍩庡競瀹㈣繍锛�18 璐ц繍鍛ㄨ浆閲忥級 */
+ private static final int SM_BUS_PAX = 0, SM_BUS_TURN = 1;
+ private static final int SM_TAXI_PAX = 2, SM_TAXI_TURN = 3;
+ private static final int SM_WYC_PAX = 4, SM_WYC_TURN = 5;
+ private static final int SM_RAIL_PAX = 6, SM_RAIL_TURN = 7;
+ private static final int SM_FERRY_PAX = 8, SM_FERRY_TURN = 9;
+ private static final int SM_BX_PAX = 10, SM_BX_TURN = 11;
+ private static final int SM_BUS_CX_PAX = 12, SM_BUS_CX_TURN = 13;
+ private static final int SM_TAXI_CX_PAX = 14, SM_TAXI_CX_TURN = 15;
+ 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;
+
+ /**
+ * 鍒锋柊鍥涘紶璺ㄩ〉鍚堟垚椤碉細褰撳勾绱锛�1..month 鏈堬級銆佸綋鏈堬紙month 鏈堬級鍙婂搴斿悓姣旓紱
+ * 2026 涓� 2025 鍚屾湡閮藉彇鑷瘝鐗堝彴璐﹂〉缂撳瓨锛岃繑鍥炲啓鍏ュ崟鍏冩牸鏁般��
+ */
+ private int refreshSummaryCumulativeSheets(XSSFWorkbook wb, int year, int month) {
+ if (wb == null || month <= 0) return 0;
+ 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);
+ if (curCum.isEmpty()) {
+ log.warn("姹囨�诲ぇ琛ㄥ洓椤靛埛鏂帮細鍙拌处椤垫湭璇诲埌甯傚窞鏁版嵁锛岃烦杩囷紙淇濇寔姣嶇増鍘熷�硷級");
+ return 0;
+ }
+ double[] pCurCum = smProvinceSum(curCum);
+ double[] pLastCum = smProvinceSum(lastCum);
+ double[] pCurMon = smProvinceSum(curMon);
+ double[] pLastMon = smProvinceSum(lastMon);
+ int n = 0;
+ n += smWriteCitySummary(wb, year, month, pCurCum, pLastCum, pCurMon, pLastMon);
+ n += smWriteCityByRegion(wb, month, curCum, lastCum, curMon, lastMon);
+ n += smWriteMidAnalysis(wb, month, pCurCum, pLastCum, pCurMon, pLastMon);
+ n += smWriteTurnoverSheets(wb, year, month, curCum, lastCum, curMon, lastMon);
+ return n;
+ }
+
+ /** 17 甯傚窞鍚堣锛堜笉鍚彴璐﹂〉鈥滃叏鐪佲�濆叕寮忚锛� */
+ private double[] smProvinceSum(Map<String, double[]> m) {
+ double[] p = new double[SM_DIM];
+ for (String c : RegionUtil.cityList()) {
+ double[] a = m.get(c);
+ if (a == null) continue;
+ for (int i = 0; i < SM_DIM; i++) p[i] += a[i];
+ }
+ return p;
+ }
+
+ /** 鍙拌处椤电疮璁″彇鏁帮細甯傚窞瑙勮寖鍚� -> 19 缁存寚鏍� */
+ private Map<String, double[]> smReadLedgerMetrics(XSSFWorkbook wb, boolean curYear, 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);
+ return out;
+ }
+
+ /** 鍏氦/鍑虹/缃戠害杞﹂〉锛氭瘡甯傚窞 4 琛岋紙瀹㈣繍閲�/鏃呭鍛ㄨ浆閲�/鍏朵腑鍩庡競鍐呭杩愰噺/鍏朵腑鍩庡競鍐呮梾瀹㈠懆杞噺锛� */
+ private void smReadCityPaxPage(XSSFWorkbook wb, String sheetName, boolean curYear, int[] months,
+ Map<String, double[]> out, int paxIdx, int turnIdx, int cxPaxIdx, int cxTurnIdx) {
+ Sheet sh = wb.getSheet(sheetName);
+ if (sh == null) return;
+ int last = sh.getLastRowNum();
+ for (int r = sh.getFirstRowNum(); r <= last; r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ String city = smCityOf(row.getCell(0));
+ if (city == null) continue;
+ int paxRow = -1, turnRow = -1, cxPaxRow = -1, cxTurnRow = -1;
+ for (int k = r; k <= Math.min(last, r + 4); k++) {
+ Row kr = sh.getRow(k);
+ if (kr == null) continue;
+ String b = smText(kr.getCell(1));
+ if (b == null || b.contains("涓綋")) continue;
+ boolean inner = b.contains("鍩庡競鍐�");
+ boolean turn = b.contains("鍛ㄨ浆閲�");
+ if (inner) {
+ if (turn) { if (cxTurnRow < 0) cxTurnRow = k; }
+ else if (cxPaxRow < 0) cxPaxRow = k;
+ } else {
+ if (turn) { if (turnRow < 0) turnRow = k; }
+ 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[] arr = out.computeIfAbsent(city, k -> new double[SM_DIM]);
+ arr[paxIdx] += cityPax; // 鍩庡競瀹㈣繍鍙e緞鍙栤�滃煄甯傚唴鈥�
+ arr[turnIdx] += cityTurn;
+ arr[cxPaxIdx] += totalPax - cityPax; // 鍩庨檯鍩庝埂 = 鎬婚噺 - 鍩庡競鍐�
+ arr[cxTurnIdx] += totalTurn - cityTurn;
+ }
+ }
+
+ /** 杞ㄩ亾銆佽疆娓¢〉锛氳建閬撳潡锛堟姹�/榛勭煶锛�+ 杞浮鍧楋紙姝︽眽锛� */
+ private void smReadRailFerry(XSSFWorkbook wb, boolean curYear, int[] months, Map<String, double[]> out) {
+ Sheet sh = wb.getSheet("杞ㄩ亾銆佽疆娓�");
+ if (sh == null) return;
+ int last = sh.getLastRowNum();
+ int ferryTitle = -1;
+ for (int r = sh.getFirstRowNum(); r <= last; r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ String a = smText(row.getCell(0));
+ if (a != null && a.contains("杞浮瀹㈣繍閲�")) { ferryTitle = r; break; }
+ }
+ smReadRailFerryBlock(sh, curYear, 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);
+ }
+
+ private void smReadRailFerryBlock(Sheet sh, boolean curYear, int[] months, Map<String, double[]> out,
+ int start, int end, int baseIdx) {
+ for (int r = start; r <= end; r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ String city = smCityOf(row.getCell(0));
+ if (city == null) continue;
+ int paxRow = -1, turnRow = -1;
+ for (int k = r; k <= Math.min(end, r + 3); k++) {
+ Row kr = sh.getRow(k);
+ if (kr == null) continue;
+ String b = smText(kr.getCell(1));
+ if (b == null) continue;
+ if (b.contains("鍛ㄨ浆閲�")) { if (turnRow < 0) turnRow = k; }
+ 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);
+ }
+ }
+
+ /** 鐝嚎鍖呰溅椤碉細姣忓競宸� 瀹㈣繍閲� / 鏃呭鍛ㄨ浆閲忥紙鈥滃叾涓釜浣撯�濊涓嶉噸澶嶈鍏ワ級 */
+ private void smReadBanxian(XSSFWorkbook wb, boolean curYear, int[] months, Map<String, double[]> out) {
+ Sheet sh = wb.getSheet("鐝嚎鍖呰溅");
+ if (sh == null) return;
+ int last = sh.getLastRowNum();
+ for (int r = sh.getFirstRowNum(); r <= last; r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ String city = smCityOf(row.getCell(0));
+ if (city == null) continue;
+ int paxRow = -1, turnRow = -1;
+ for (int k = r; k <= Math.min(last, r + 4); k++) {
+ Row kr = sh.getRow(k);
+ if (kr == null) continue;
+ String b = smText(kr.getCell(1));
+ if (b == null || b.contains("涓綋")) continue;
+ if (b.contains("鍛ㄨ浆閲�")) { if (turnRow < 0) turnRow = k; }
+ 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);
+ }
+ }
+
+ /** 璐ц繍椤碉細姣忓競宸� 瑙勪笂璐х墿鍛ㄨ浆閲� + 瑙勪笅璐х墿鍛ㄨ浆閲忥紙鈥滆涓�+瑙勪笅鈥濊鏈韩鏄眰鍜屽叕寮忥紝涓嶇洿鎺ュ彇锛� */
+ private void smReadFreightTurnover(XSSFWorkbook wb, boolean curYear, int[] months, Map<String, double[]> out) {
+ Sheet sh = wb.getSheet(" 璐ц繍");
+ if (sh == null) sh = wb.getSheet("璐ц繍");
+ if (sh == null) return;
+ int last = sh.getLastRowNum();
+ for (int r = sh.getFirstRowNum(); r <= last; r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ String city = smCityOf(row.getCell(0));
+ if (city == null) continue;
+ int above = -1, below = -1;
+ for (int k = r; k <= Math.min(last, r + 6); k++) {
+ Row kr = sh.getRow(k);
+ if (kr == null) continue;
+ String b = smText(kr.getCell(1));
+ if (b == null || b.contains("鍏朵腑")) continue;
+ if (b.contains("瑙勪笂璐х墿鍛ㄨ浆閲�")) { if (above < 0) above = k; }
+ 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);
+ }
+ }
+
+ private double smSumMonths(Sheet sh, int rowIdx, boolean curYear, int[] months) {
+ if (sh == null || rowIdx < 0) 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);
+ s += smNum(row.getCell(col));
+ }
+ return s;
+ }
+
+ /** 鍙拌处椤靛崟鍏冩牸鍙栨暟锛堟暟瀛� / 鏁板瓧鏂囨湰 / 鍏紡缂撳瓨锛� */
+ private double smNum(Cell c) {
+ if (c == null) return 0;
+ try {
+ CellType t = c.getCellType();
+ if (t == CellType.NUMERIC) return c.getNumericCellValue();
+ if (t == CellType.FORMULA) {
+ return c.getCachedFormulaResultType() == CellType.NUMERIC ? c.getNumericCellValue() : 0;
+ }
+ if (t == CellType.STRING) {
+ String s = c.getStringCellValue();
+ if (s == null) return 0;
+ s = s.replace(",", "").replace("%", "").trim();
+ if (s.isEmpty()) return 0;
+ return Double.parseDouble(s);
+ }
+ } catch (Exception ignore) {
+ return 0;
+ }
+ return 0;
+ }
+
+ private String smText(Cell c) {
+ if (c == null) return null;
+ try {
+ return c.getCellType() == CellType.STRING ? c.getStringCellValue() : null;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /** 鍒� A 鏂囨 -> 瑙勮寖甯傚窞鍚嶏紙鈥滃叏鐪�/婀栧寳鐪佲�濅笌闈炲競宸炶杩斿洖 null锛� */
+ private String smCityOf(Cell a) {
+ String t = smText(a);
+ if (t == null || t.trim().isEmpty()) return null;
+ String norm = RegionUtil.normalizeCityName(t.trim());
+ if (norm == null) return null;
+ return RegionUtil.cityList().contains(norm) ? norm : null;
+ }
+
+ private double smYoy(double cur, double last) {
+ return last == 0.0 ? 0.0 : (cur - last) / last;
+ }
+
+ /** 鍐欏�硷細鍏紡鏍间笉鍔紙淇濈暀姣嶇増鍏紡锛屾墦寮�鍗抽噸绠楋級锛�0 涓斿師涓虹┖鏍煎垯涓嶆柊澧� */
+ private int smSetVal(Sheet sh, int r0, int c0, double v) {
+ Row row = sh.getRow(r0);
+ if (row == null) return 0;
+ Cell c = row.getCell(c0);
+ if (c != null && c.getCellType() == CellType.FORMULA) return 0;
+ if (c == null) {
+ if (v == 0.0) return 0;
+ c = row.createCell(c0);
+ Cell left = row.getCell(c0 - 1);
+ if (left != null) c.setCellStyle(left.getCellStyle());
+ } else if (v == 0.0 && c.getCellType() == CellType.BLANK) {
+ return 0;
+ }
+ c.setBlank();
+ c.setCellValue(v);
+ return 1;
+ }
+
+ private void smSetTitle(Sheet sh, int r0, int c0, String text) {
+ if (sh == null) return;
+ Row row = sh.getRow(r0);
+ if (row == null) return;
+ Cell c = row.getCell(c0);
+ if (c == null || c.getCellType() == CellType.FORMULA) return;
+ c.setBlank();
+ c.setCellValue(text);
+ }
+
+ /** 鍩庡競姹囨�婚〉锛氬潡1锛�1..N 鏈堢疮璁★級琛� 3-9锛屽潡2锛堝綋鏈堬級琛� 14-20锛涘垪 B/C/D/E = 瀹㈣繍閲�/鍚屾瘮/鍛ㄨ浆閲�/鍚屾瘮 */
+ private int smWriteCitySummary(XSSFWorkbook wb, int year, int month,
+ double[] pCurCum, double[] pLastCum, double[] pCurMon, double[] pLastMon) {
+ Sheet sh = wb.getSheet("鍩庡競姹囨��");
+ if (sh == null) return 0;
+ smSetTitle(sh, 0, 0, year + "骞�1-" + month + "鏈堝叏鐪佺疮璁″畬鎴愬煄甯傚杩愰噺鎯呭喌");
+ smSetTitle(sh, 11, 0, year + "骞�" + month + "鏈堝叏鐪佸畬鎴愬煄甯傚杩愰噺鎯呭喌");
+ int n = 0;
+ int[] rows = {2, 3, 4, 5, 6, 7, 8};
+ for (int i = 0; i < rows.length; i++) {
+ double[] v = smCitySummaryRow(pCurCum, pLastCum, i);
+ n += smSetVal(sh, rows[i], 1, v[0]);
+ n += smSetVal(sh, rows[i], 2, v[2]);
+ n += smSetVal(sh, rows[i], 3, v[1]);
+ n += smSetVal(sh, rows[i], 4, v[3]);
+ }
+ for (int i = 0; i < rows.length; i++) {
+ double[] v = smCitySummaryRow(pCurMon, pLastMon, i);
+ n += smSetVal(sh, rows[i] + 11, 1, v[0]);
+ n += smSetVal(sh, rows[i] + 11, 2, v[2]);
+ n += smSetVal(sh, rows[i] + 11, 3, v[1]);
+ n += smSetVal(sh, rows[i] + 11, 4, v[3]);
+ }
+ return n;
+ }
+
+ /** i=0 鎬诲杩愰噺 1 鍏氦 2 鍑虹杞� 3 宸℃父鍑虹 4 缃戠害杞� 5 杞ㄩ亾 6 杞浮锛涜繑鍥� [瀹㈣繍閲�, 鍛ㄨ浆閲�, 瀹㈣繍閲忓悓姣�, 鍛ㄨ浆閲忓悓姣擼 */
+ private double[] smCitySummaryRow(double[] p, double[] pl, int i) {
+ double pax, turn, lPax, lTurn;
+ if (i == 0) {
+ pax = p[SM_BUS_PAX] + p[SM_TAXI_PAX] + p[SM_WYC_PAX] + p[SM_RAIL_PAX] + p[SM_FERRY_PAX];
+ turn = p[SM_BUS_TURN] + p[SM_TAXI_TURN] + p[SM_WYC_TURN] + p[SM_RAIL_TURN] + p[SM_FERRY_TURN];
+ lPax = pl[SM_BUS_PAX] + pl[SM_TAXI_PAX] + pl[SM_WYC_PAX] + pl[SM_RAIL_PAX] + pl[SM_FERRY_PAX];
+ lTurn = pl[SM_BUS_TURN] + pl[SM_TAXI_TURN] + pl[SM_WYC_TURN] + pl[SM_RAIL_TURN] + pl[SM_FERRY_TURN];
+ } else if (i == 1) {
+ pax = p[SM_BUS_PAX]; turn = p[SM_BUS_TURN]; lPax = pl[SM_BUS_PAX]; lTurn = pl[SM_BUS_TURN];
+ } else if (i == 2) {
+ pax = p[SM_TAXI_PAX] + p[SM_WYC_PAX]; turn = p[SM_TAXI_TURN] + p[SM_WYC_TURN];
+ lPax = pl[SM_TAXI_PAX] + pl[SM_WYC_PAX]; lTurn = pl[SM_TAXI_TURN] + pl[SM_WYC_TURN];
+ } else if (i == 3) {
+ pax = p[SM_TAXI_PAX]; turn = p[SM_TAXI_TURN]; lPax = pl[SM_TAXI_PAX]; lTurn = pl[SM_TAXI_TURN];
+ } else if (i == 4) {
+ pax = p[SM_WYC_PAX]; turn = p[SM_WYC_TURN]; lPax = pl[SM_WYC_PAX]; lTurn = pl[SM_WYC_TURN];
+ } else if (i == 5) {
+ pax = p[SM_RAIL_PAX]; turn = p[SM_RAIL_TURN]; lPax = pl[SM_RAIL_PAX]; lTurn = pl[SM_RAIL_TURN];
+ } else {
+ pax = p[SM_FERRY_PAX]; turn = p[SM_FERRY_TURN]; lPax = pl[SM_FERRY_PAX]; lTurn = pl[SM_FERRY_TURN];
+ }
+ return new double[]{pax, turn, smYoy(pax, lPax), smYoy(turn, lTurn)};
+ }
+
+ /** 鍩庡競鍒嗗競宸為〉锛�4 鍧楋紙绱瀹㈣繍閲�/绱鍛ㄨ浆閲�/褰撴湀瀹㈣繍閲�/褰撴湀鍛ㄨ浆閲忥級锛屾瘡鍧� 鍏ㄧ渷 1 琛� + 17 甯傚窞 */
+ private int smWriteCityByRegion(XSSFWorkbook wb, int month,
+ Map<String, double[]> curCum, Map<String, double[]> lastCum,
+ Map<String, double[]> curMon, Map<String, double[]> lastMon) {
+ Sheet sh = wb.getSheet("鍩庡競鍒嗗競宸�");
+ if (sh == null) return 0;
+ smSetTitle(sh, 0, 0, "鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞1-" + month + "鏈堢疮璁℃儏鍐�");
+ smSetTitle(sh, 22, 0, "鍩庡競瀹㈣繍瀹㈣繍鍛ㄨ浆閲忓垎甯傚窞1-" + month + "鏈堢疮璁℃儏鍐�");
+ smSetTitle(sh, 45, 0, "鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞" + month + "鏈堟儏鍐�");
+ smSetTitle(sh, 67, 0, "鍩庡競瀹㈣繍瀹㈣繍鍛ㄨ浆閲忓垎甯傚窞" + month + "鏈堟儏鍐�");
+ int n = 0;
+ n += smWriteCityByRegionBlock(sh, 4, true, curCum, lastCum);
+ n += smWriteCityByRegionBlock(sh, 26, false, curCum, lastCum);
+ n += smWriteCityByRegionBlock(sh, 49, true, curMon, lastMon);
+ n += smWriteCityByRegionBlock(sh, 71, false, curMon, lastMon);
+ return n;
+ }
+
+ private int smWriteCityByRegionBlock(Sheet sh, int provRow, boolean pax,
+ Map<String, double[]> cur, Map<String, double[]> last) {
+ int n = 0;
+ double[] provCur = smProvinceSum(cur);
+ double[] provLast = smProvinceSum(last);
+ List<String> cities = RegionUtil.cityList();
+ for (int i = -1; i < cities.size(); i++) {
+ int r = provRow + i + 1;
+ double[] c = i < 0 ? provCur : cur.get(cities.get(i));
+ double[] l = i < 0 ? provLast : last.get(cities.get(i));
+ if (c == null) c = new double[SM_DIM];
+ if (l == null) l = new double[SM_DIM];
+ double[] g = smCityGroupValues(c, l, pax);
+ n += smSetVal(sh, r, 1, g[0]);
+ n += smSetVal(sh, r, 4, g[6]);
+ n += smSetVal(sh, r, 6, g[1]);
+ n += smSetVal(sh, r, 9, g[7]);
+ n += smSetVal(sh, r, 11, g[2]);
+ n += smSetVal(sh, r, 14, g[8]);
+ n += smSetVal(sh, r, 16, g[3]);
+ n += smSetVal(sh, r, 19, g[9]);
+ n += smSetVal(sh, r, 21, g[4]);
+ n += smSetVal(sh, r, 22, g[10]);
+ n += smSetVal(sh, r, 23, g[5]);
+ n += smSetVal(sh, r, 24, g[11]);
+ }
+ return n;
+ }
+
+ /** 6 缁勫�� + 6 缁勫閫燂細[鎬婚噺, 鍏氦, 鍑虹, 缃戠害杞�, 杞ㄩ亾, 杞浮, 鎬婚噺澧為��, 鍏氦鈥�, 杞浮] */
+ private double[] smCityGroupValues(double[] c, double[] l, boolean pax) {
+ int busI = pax ? SM_BUS_PAX : SM_BUS_TURN;
+ int taxiI = pax ? SM_TAXI_PAX : SM_TAXI_TURN;
+ int wycI = pax ? SM_WYC_PAX : SM_WYC_TURN;
+ int railI = pax ? SM_RAIL_PAX : SM_RAIL_TURN;
+ int ferryI = pax ? SM_FERRY_PAX : SM_FERRY_TURN;
+ double bus = c[busI], taxi = c[taxiI], wyc = c[wycI], rail = c[railI], ferry = c[ferryI];
+ double lBus = l[busI], lTaxi = l[taxiI], lWyc = l[wycI], lRail = l[railI], lFerry = l[ferryI];
+ double total = bus + taxi + wyc + rail + ferry;
+ double lTotal = lBus + lTaxi + lWyc + lRail + lFerry;
+ return new double[]{total, bus, taxi, wyc, rail, ferry,
+ smYoy(total, lTotal), smYoy(bus, lBus), smYoy(taxi, lTaxi), smYoy(wyc, lWyc),
+ smYoy(rail, lRail), smYoy(ferry, lFerry)};
+ }
+
+ /** 涓彛寰勫垎鏋愰〉锛氬潡1锛堢疮璁★級琛� 3-7锛屽潡2锛堝綋鏈堬級琛� 12-16锛涘垪 B/C/F/G = 瀹㈣繍閲�/鍚屾瘮/鍛ㄨ浆閲�/鍚屾瘮 */
+ private int smWriteMidAnalysis(XSSFWorkbook wb, int month,
+ double[] pCurCum, double[] pLastCum, double[] pCurMon, double[] pLastMon) {
+ Sheet sh = wb.getSheet("涓彛寰勫垎鏋�");
+ if (sh == null) return 0;
+ smSetTitle(sh, 0, 0, "1-" + month + "鏈堜腑鍙e緞瀹㈣繍閲忓強鍛ㄨ浆閲�");
+ smSetTitle(sh, 9, 0, month + "鏈堜腑鍙e緞瀹㈣繍閲忓強鍛ㄨ浆閲�");
+ int n = 0;
+ int[] rows = {2, 3, 4, 5, 6};
+ for (int i = 0; i < rows.length; i++) {
+ double[] v = smMidRow(pCurCum, pLastCum, i);
+ n += smSetVal(sh, rows[i], 1, v[0]);
+ n += smSetVal(sh, rows[i], 2, v[2]);
+ n += smSetVal(sh, rows[i], 5, v[1]);
+ n += smSetVal(sh, rows[i], 6, v[3]);
+ }
+ for (int i = 0; i < rows.length; i++) {
+ double[] v = smMidRow(pCurMon, pLastMon, i);
+ n += smSetVal(sh, rows[i] + 9, 1, v[0]);
+ n += smSetVal(sh, rows[i] + 9, 2, v[2]);
+ n += smSetVal(sh, rows[i] + 9, 5, v[1]);
+ n += smSetVal(sh, rows[i] + 9, 6, v[3]);
+ }
+ return n;
+ }
+
+ /** i=0 鎬� 1 鍏矾鐝嚎 2 鍩庨檯鍩庝埂鍏氦 3 鍩庨檯鍩庝埂宸℃父鍑虹 4 鍩庨檯鍩庝埂缃戠害杞︼紱杩斿洖 [瀹㈣繍閲�, 鍛ㄨ浆閲�, 瀹㈣繍閲忓悓姣�, 鍛ㄨ浆閲忓悓姣擼 */
+ private double[] smMidRow(double[] p, double[] pl, int i) {
+ int[] paxIdx = {SM_BX_PAX, SM_BX_PAX, SM_BUS_CX_PAX, SM_TAXI_CX_PAX, SM_WYC_CX_PAX};
+ int[] turnIdx = {SM_BX_TURN, SM_BX_TURN, SM_BUS_CX_TURN, SM_TAXI_CX_TURN, SM_WYC_CX_TURN};
+ double pax, turn, lPax, lTurn;
+ if (i == 0) {
+ pax = p[SM_BX_PAX] + p[SM_BUS_CX_PAX] + p[SM_TAXI_CX_PAX] + p[SM_WYC_CX_PAX];
+ turn = p[SM_BX_TURN] + p[SM_BUS_CX_TURN] + p[SM_TAXI_CX_TURN] + p[SM_WYC_CX_TURN];
+ lPax = pl[SM_BX_PAX] + pl[SM_BUS_CX_PAX] + pl[SM_TAXI_CX_PAX] + pl[SM_WYC_CX_PAX];
+ lTurn = pl[SM_BX_TURN] + pl[SM_BUS_CX_TURN] + pl[SM_TAXI_CX_TURN] + pl[SM_WYC_CX_TURN];
+ } else {
+ pax = p[paxIdx[i]]; turn = p[turnIdx[i]];
+ lPax = pl[paxIdx[i]]; lTurn = pl[turnIdx[i]];
+ }
+ return new double[]{pax, turn, smYoy(pax, lPax), smYoy(turn, lTurn)};
+ }
+
+ /** 閬撹矾杩愯緭鍛ㄨ浆閲忛〉锛氬潡1锛堢疮璁★級琛� 4-21锛屽潡2锛堝綋鏈堬級琛� 26-43锛涘彧鍐欏�煎垪锛圔/E/V 绛夊叕寮忎繚鐣欙級 */
+ private int smWriteTurnoverSheets(XSSFWorkbook wb, int year, int month,
+ Map<String, double[]> curCum, Map<String, double[]> lastCum,
+ Map<String, double[]> curMon, Map<String, double[]> lastMon) {
+ Sheet sh = wb.getSheet("閬撹矾杩愯緭鍛ㄨ浆閲�");
+ if (sh == null) return 0;
+ smSetTitle(sh, 0, 0, year + "骞�1-" + month + "鏈堝叏鐪佺疮璁″畬鎴愪氦閫氬懆杞噺鎯呭喌");
+ smSetTitle(sh, 22, 0, year + "骞�" + month + "鏈堝叏鐪佸畬鎴愪氦閫氬懆杞噺鎯呭喌");
+ int n = 0;
+ n += smWriteTurnoverBlock(sh, 3, curCum, lastCum);
+ n += smWriteTurnoverBlock(sh, 25, curMon, lastMon);
+ return n;
+ }
+
+ private int smWriteTurnoverBlock(Sheet sh, int provRow, Map<String, double[]> cur, Map<String, double[]> last) {
+ int n = 0;
+ double[] provCur = smProvinceSum(cur);
+ double[] provLast = smProvinceSum(last);
+ List<String> cities = RegionUtil.cityList();
+ for (int i = -1; i < cities.size(); i++) {
+ int r = provRow + i + 1;
+ double[] c = i < 0 ? provCur : cur.get(cities.get(i));
+ double[] l = i < 0 ? provLast : last.get(cities.get(i));
+ if (c == null) c = new double[SM_DIM];
+ if (l == null) l = new double[SM_DIM];
+ double freight = c[SM_FREIGHT_TURN], lFreight = l[SM_FREIGHT_TURN];
+ double pax = c[SM_BUS_PAX] + c[SM_TAXI_PAX] + c[SM_WYC_PAX] + c[SM_RAIL_PAX] + c[SM_FERRY_PAX];
+ double lPax = l[SM_BUS_PAX] + l[SM_TAXI_PAX] + l[SM_WYC_PAX] + l[SM_RAIL_PAX] + l[SM_FERRY_PAX];
+ double road = c[SM_BX_TURN] + c[SM_BUS_CX_TURN] + c[SM_TAXI_CX_TURN] + c[SM_WYC_CX_TURN];
+ double lRoad = l[SM_BX_TURN] + l[SM_BUS_CX_TURN] + l[SM_TAXI_CX_TURN] + l[SM_WYC_CX_TURN];
+ n += smSetVal(sh, r, 6, freight);
+ n += smSetVal(sh, r, 9, smYoy(freight, lFreight));
+ n += smSetVal(sh, r, 11, pax);
+ n += smSetVal(sh, r, 14, smYoy(pax, lPax));
+ n += smSetVal(sh, r, 16, road);
+ n += smSetVal(sh, r, 19, smYoy(road, lRoad));
+ n += smSetVal(sh, r, 22, lFreight);
+ n += smSetVal(sh, r, 23, lPax);
+ n += smSetVal(sh, r, 24, lRoad);
+ }
+ return n;
+ }
+
+
private File resolveSummaryLayoutRef() {
String rel = summaryTemplateDir;
while (rel != null && rel.startsWith("./")) rel = rel.substring(2);
@@ -3962,6 +4583,9 @@
if (bs == null) continue;
// 涓彛寰勬帓鍚嶉〉 A..H 闄堟棫宸﹀潡宸蹭簬 2026-09-09 鎷嶆澘鏁村潡鍒犻櫎锛氳鍖哄煙姘镐笉浠庡浠芥瘝鐗堝洖濉紝閬垮厤鈥滅敓鎴愬張澶嶆椿鈥�
boolean skipMidRankLeftBlock = "涓彛寰勬帓鍚�".equals(ds.getSheetName());
+ // 銆� 璐ц繍銆嬮〉 2025..2021 骞村害鍧楋紙T 鍒楄捣锛屽惈鍚勫勾绱/绱鍚屾瘮鍒楋級鍦ㄦ瘝鐗堥噷鏄�屽競宸炴淳鐢熷叕寮忋�嶏紝
+ // 澶囦唤姣嶇増鍚屽尯浠嶆槸鏃х殑鍐欐甯搁噺锛堝叏鐪佽涓嬬瓑锛夛紝鍥炲~浼氳 2021/2022/2023 绱鍙堝涓嶄笂锛堚�滅敓鎴愬張澶嶆椿鈥濓級锛屾晠璇ュ尯涓嶅洖濉��
+ boolean skipFreightHistoryBlocks = " 璐ц繍".equals(ds.getSheetName());
for (int r = ds.getFirstRowNum(); r <= ds.getLastRowNum(); r++) {
Row dr = ds.getRow(r);
if (dr == null) continue;
@@ -3969,6 +4593,7 @@
for (Cell dc : dr) {
if (dc == null) continue;
if (skipMidRankLeftBlock && dc.getColumnIndex() < 8) continue;
+ if (skipFreightHistoryBlocks && dc.getColumnIndex() >= 19) continue; // T 鍒楀強浠ュ悗 = 2025..2021 骞村害鍧�
CellType t = dc.getCellType();
if (t == CellType.BLANK) continue;
String dtx = summaryCellText(dc);
--
Gitblit v1.9.1