From da614c0b85465f92453405928534d5fa2adc3c8c Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期五, 11 九月 2026 16:43:08 +0800
Subject: [PATCH] feat: 网约车订单运行监测报告(docx)导入 + 汇总大表8月数据驱动回填与版式对齐
---
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java | 1210 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 1,187 insertions(+), 23 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 945a762..c8ae866 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;
@@ -44,6 +47,7 @@
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -92,6 +96,8 @@
@Resource
private CityTaxiMonthlyMapper cityTaxiMapper;
@Resource
+ private SummaryWorkbookFiller summaryWorkbookFiller;
+ @Resource
private AuditResultMapper auditResultMapper;
@Resource
private AuditRunMapper auditRunMapper;
@@ -106,6 +112,10 @@
/** 鍏矾鏃呭杈撳嚭妯℃澘鐩綍锛坅pplication.yml passenger.template-dir锛� */
@Value("${passenger.template-dir:docs/鍏矾鏃呭+鑳借��/杈撳嚭}")
private String passengerTemplateDir;
+
+ /** 姹囨�诲伐浣滅翱妯℃澘鐩綍锛坅pplication.yml summary.template-dir锛� */
+ @Value("${summary.template-dir:docs/鐢熸垚姹囨�诲ぇ琛▆")
+ private String summaryTemplateDir;
/** 璐ц繍鎶ヨ〃妯℃澘鐩綍锛坅pplication.yml freight.template-dir锛� */
@Value("${freight.template-dir:docs/璐ц繍}")
@@ -171,6 +181,43 @@
rows = addReadyCount(() -> energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod)), "鑳借�楄溅杈嗗搴︽暟鎹�", notes);
auditTypes.add("H204");
break;
+ case "wycSplit": {
+ rows = addReadyCount(() -> cityTaxiMapper.selectCount(periodQw(period, mode, CityTaxiMonthly::getReportPeriod)), "宸℃父鍑虹鏈堟姤", notes);
+ try {
+ Map<String, Object> inp = loadWycMonthlyInput(period, mode);
+ if (inp == null) {
+ rows = 0;
+ notes.add("銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬬己 " + period + " 琛岋紙闇�鍏堢淮鎶ゅ綋鏈堣鍗曚笌鍏ㄧ渷 pin锛�");
+ } else {
+ double pinK = (Double) inp.get("pinK"), pinCK = (Double) inp.get("pinCK");
+ double pinZ = (Double) inp.get("pinZ"), pinCZ = (Double) inp.get("pinCZ");
+ double orderSum = (Double) inp.get("orderSum");
+ if (pinK <= 0 || pinCK <= 0 || pinZ <= 0 || pinCZ <= 0 || orderSum <= 0) {
+ rows = 0;
+ notes.add("缃戠害杞﹁緭鍏� " + period + " 琛屼笉瀹屾暣锛堣鍗�/鍏ㄧ渷 pin 缂哄け锛�");
+ }
+ }
+ } catch (Exception ex) {
+ rows = 0;
+ notes.add("缃戠害杞﹁緭鍏ヨ鍙栧け璐ワ細" + ex.getMessage());
+ }
+ break;
+ }
+ case "summaryWorkbook": {
+ rows = addReadyCount(() -> cityTaxiMapper.selectCount(periodQw(period, mode, CityTaxiMonthly::getReportPeriod)), "宸℃父鍑虹鏈堟姤", notes)
+ + addReadyCount(() -> cityBusMapper.selectCount(periodQw(period, mode, CityBusMonthly::getReportPeriod)), "鍩庡競鍏氦鏈堟姤", notes);
+ if (rows <= 0) notes.add("姹囨�昏〃渚濊禆鐨勫叕浜�/鍑虹鏈堟姤鏁版嵁缂哄け");
+ try {
+ if (resolveSummaryMother(period, mode) == null) {
+ rows = 0;
+ notes.add("docs/鐢熸垚姹囨�诲ぇ琛� 涓嬬己灏戝彲澶嶅埗鐨勬瘝鐗堟眹鎬昏〃");
+ }
+ } catch (Exception ex) {
+ rows = 0;
+ notes.add("姣嶇増瀹氫綅澶辫触锛�" + ex.getMessage());
+ }
+ break;
+ }
default:
rows = 0;
notes.add("鏈煡鎶ヨ〃绫诲瀷锛�" + t);
@@ -181,6 +228,7 @@
auditRows += auditRowsOf(period, mode, at);
audited = audited || auditedMarked(period, mode, at);
}
+ if (auditTypes.isEmpty()) audited = true;
it.put("rows", rows);
it.put("ready", rows > 0);
it.put("auditRows", auditRows);
@@ -212,7 +260,8 @@
for (String t : types) {
if (isFreightType(t)) freight = true;
else if (isPassengerType(t)) passenger = true;
- else if ("cityTaxiDetail".equals(t) || isCityPassengerType(t)) { cityTaxi = true; cityBus = true; }
+ else if ("cityTaxiDetail".equals(t) || "wycSplit".equals(t)) { cityTaxi = true; }
+ else if (isCityPassengerType(t) || "summaryWorkbook".equals(t)) { cityTaxi = true; cityBus = true; }
else if (isInvestType(t)) invest = true;
else if ("energySummary".equals(t)) energy = true;
}
@@ -256,7 +305,46 @@
List<Integer> missing = new ArrayList<>();
List<Integer> coverage = new ArrayList<>();
if (Boolean.FALSE.equals(it.get("audited"))) problems.add("璇ユ姤琛ㄦ湡灏氭湭瀹℃牳閫氳繃");
- if (isFreightType(t)) {
+ if ("wycSplit".equals(t)) {
+ coverage.addAll(taxiCover);
+ missing = missingMonths(taxiCover, month);
+ try {
+ Map<String, Object> inp = loadWycMonthlyInput(period, mode);
+ if (inp == null) {
+ problems.add("缂虹綉绾﹁溅杈撳叆锛氳鍦ㄣ�婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬭ˉ " + period + " 琛�");
+ } else {
+ double pinK = (Double) inp.get("pinK"), pinCK = (Double) inp.get("pinCK");
+ double pinZ = (Double) inp.get("pinZ"), pinCZ = (Double) inp.get("pinCZ");
+ double orderSum = (Double) inp.get("orderSum");
+ if (pinK <= 0 || pinCK <= 0 || pinZ <= 0 || pinCZ <= 0 || orderSum <= 0) {
+ problems.add("缃戠害杞﹁緭鍏� " + period + " 琛屼笉瀹屾暣锛堣鍗�/鍏ㄧ渷 pin 缂哄け锛�");
+ } else {
+ addSum(summary, "鍏ㄧ渷缃戠害杞﹀杩愰噺", pinK, "涓囦汉");
+ addSum(summary, "鍏朵腑鍩庡競鍐呭杩愰噺", pinCK, "涓囦汉");
+ addSum(summary, "鍏ㄧ渷缃戠害杞﹀懆杞噺", pinZ, "涓囦汉鍏噷");
+ addSum(summary, "鍏朵腑鍩庡競鍐呭懆杞噺", pinCZ, "涓囦汉鍏噷");
+ addSum(summary, "閮ㄧ骇璁㈠崟鍚堣", orderSum, "鍗�");
+ }
+ }
+ } catch (Exception ex) {
+ problems.add("缃戠害杞﹁緭鍏ヨ鍙栧け璐ワ細" + ex.getMessage());
+ }
+ } else if ("summaryWorkbook".equals(t)) {
+ java.util.Set<Integer> cov = new java.util.TreeSet<>();
+ cov.addAll(busCover);
+ cov.addAll(taxiCover);
+ coverage.addAll(cov);
+ missing = missingMonths(cov, month);
+ try {
+ if (resolveSummaryMother(period, mode) == null) {
+ problems.add("docs/鐢熸垚姹囨�诲ぇ琛� 涓嬬己灏戝彲澶嶅埗鐨勬瘝鐗堟眹鎬昏〃");
+ } else {
+ summaryWorkbookPreview(period, yearNum, month, summary, problems);
+ }
+ } catch (Exception ex) {
+ problems.add(ex.getMessage());
+ }
+ } else if (isFreightType(t)) {
coverage.addAll(freightCover);
missing = missingMonths(freightCover, month);
freightPreview(period, month, summary, problems);
@@ -355,6 +443,118 @@
return sb.toString();
}
+ /** 姹囨�绘暣鏈叧閿寚鏍囬瑙堬細璐ц繍閲�/鍛ㄨ浆閲忋�佸煄甯傚杩愩�佷腑鍙e緞鐨� 褰撴湀銆�1..M 绱銆佸綋鏈堝悓姣斻�佺疮璁″悓姣旓紙鍚屾瘮鍙栧簱鍐呭幓骞村悓鏈燂紝缂哄垯鏄剧ず鈥旓級 */
+ private void summaryWorkbookPreview(String period, int year, int month,
+ List<Map<String, Object>> summary, List<String> problems) {
+ String yearPrefix = year + "-";
+ // ---- 璐ц繍锛堥噺=妯℃澘_璐ц繍閲忓懆杞噺鍏ㄧ渷琛岋紱鍛ㄨ浆閲�=瑙勪笂瑙勪笅鎷嗗垎琛ㄥ叏鐪佽锛� ----
+ FreightTurnoverImport ft = loadFreightTurnover(period).get("婀栧寳鐪�");
+ Double fm = ft == null ? null : freightMonth(ft, month);
+ Double fcum = ft == null ? null : freightCum(ft, month);
+ Double fy = ft == null ? null : freightYoy(ft, month);
+ Double fcy = ft == null ? null : freightCumYoy(ft, month);
+ addKpi(summary, "鍏ㄧ渷璐ц繍閲�", fm, fcum, fy, fcy, "涓囧惃");
+ if (fm != null && fy == null) {
+ problems.add((year - 1) + "-" + String.format("%02d", month) + " 鍚屾湡璐ц繍閲忔暟鎹湭瀵煎叆搴擄紝璐ц繍閲忓綋鏈�/绱鍚屾瘮鏆備笉鏄剧ず锛屽緟 2025 骞村畾绋挎暟鎹鍏ュ悗鑷姩鍑虹幇");
+ }
+ ScaleSplitTransport tm = loadProvinceMonthMap(period).get(month);
+ ScaleSplitTransport tc = getProvinceCumulative(period);
+ Double tvM = tm == null ? null : tm.getTotalTurnover();
+ Double tvC = tc == null ? null : tc.getTotalTurnover();
+ Double tvMY = tm == null ? null : getYoyMetric(tm, "turnover", "total");
+ Double tvCY = tc == null ? null : getYoyMetric(tc, "turnover", "total");
+ addKpi(summary, "鍏ㄧ渷璐х墿鍛ㄨ浆閲�", tvM, tvC, tvMY, tvCY, "涓囧惃鍏噷");
+ if (fm == null) problems.add("妯℃澘_璐ц繍閲忓懆杞噺缂� " + period + " 褰撴湀鏁版嵁锛岃揣杩愰噺浠呰兘鎻愪緵鎴嚦鏈�杩戝鍏ユ湀鐨勭疮璁�");
+ if (tvM == null) problems.add("瑙勪笂瑙勪笅鎷嗗垎缂� " + period + " 褰撴湀鏁版嵁锛屽懆杞噺/瑙勪笂瑙勪笅褰撴湀缂猴紝绱涓烘埅鑷虫渶杩戝鍏ユ湀");
+
+ // ---- 鍩庡競瀹㈣繍锛堝叕浜�+鍑虹+杞ㄩ亾+杞浮 鍏ㄧ渷灏忚锛涙寚鏍囦綅 0/2/4/6=瀹㈣繍锛�1/3/5/7=鍛ㄨ浆锛� ----
+ double[] cumM = cityCumProv(yearPrefix, month);
+ double[] cumPrev = cityCumProv(yearPrefix, month - 1);
+ double[] monthVec = diff8(cumM, cumPrev);
+ double cityPaxM = sumIdx(monthVec, new int[]{0, 2, 4, 6});
+ double cityTurnM = sumIdx(monthVec, new int[]{1, 3, 5, 7});
+ double cityPaxC = sumIdx(cumM, new int[]{0, 2, 4, 6});
+ double cityTurnC = sumIdx(cumM, new int[]{1, 3, 5, 7});
+ double[] lastCumM = cityCumProv((year - 1) + "-", month);
+ double[] lastCumPrev = cityCumProv((year - 1) + "-", month - 1);
+ double[] lastVec = diff8(lastCumM, lastCumPrev);
+ double lastPaxM = sumIdx(lastVec, new int[]{0, 2, 4, 6});
+ double lastPaxC = sumIdx(lastCumM, new int[]{0, 2, 4, 6});
+ double lastTurnM = sumIdx(lastVec, new int[]{1, 3, 5, 7});
+ double lastTurnC = sumIdx(lastCumM, new int[]{1, 3, 5, 7});
+ addKpiRaw(summary, "鍩庡競瀹㈣繍閲�", cityPaxM, cityPaxC, lastPaxM, lastPaxC, "涓囦汉娆�");
+ addKpiRaw(summary, "鍩庡競瀹㈣繍鍛ㄨ浆閲�", cityTurnM, cityTurnC, lastTurnM, lastTurnC, "涓囦汉鍏噷");
+
+ // ---- 涓彛寰勶紙鍏矾鏃呭浼佷笟 H203-1 鍏ㄧ渷锛屽崟浣嶆崲绠� /10000锛� ----
+ Map<Integer, Map<String, PassengerAgg>> data = loadPassengerAggMap();
+ double paxM = 0.0, paxC = 0.0, turnM = 0.0, turnC = 0.0;
+ double lastPaxM2 = 0.0, lastPaxC2 = 0.0, lastTurnM2 = 0.0, lastTurnC2 = 0.0;
+ for (int m = 1; m <= month; m++) {
+ PassengerAgg agg = aggOf(data, year, m, "婀栧寳鐪�");
+ if (agg != null) {
+ double pax = agg.passengerTotal / 10000.0;
+ double turn = agg.turnoverTotal / 10000.0;
+ if (m == month) { paxM = pax; turnM = turn; }
+ paxC += pax;
+ turnC += turn;
+ }
+ PassengerAgg last = aggOf(data, year - 1, m, "婀栧寳鐪�");
+ if (last != null) {
+ double lp = last.passengerTotal / 10000.0;
+ double lt = last.turnoverTotal / 10000.0;
+ if (m == month) { lastPaxM2 = lp; lastTurnM2 = lt; }
+ lastPaxC2 += lp;
+ lastTurnC2 += lt;
+ }
+ }
+ addKpiRaw(summary, "涓彛寰勫杩愰噺", paxM, paxC, lastPaxM2, lastPaxC2, "涓囦汉娆�");
+ addKpiRaw(summary, "涓彛寰勫懆杞噺", turnM, turnC, lastTurnM2, lastTurnC2, "涓囦汉鍏噷");
+
+ if (paxM <= 0 && paxC <= 0 && cityPaxC <= 0 && fm == null) {
+ problems.add("璐ц繍閲忓懆杞噺/瑙勪笂瑙勪笅/鍏矾鏃呭/鍩庡競瀹㈣繍褰撴湀鏁版嵁鍧囩己澶�");
+ }
+ if (cityPaxC > 0 && lastPaxC <= 0) {
+ problems.add((year - 1) + "-" + String.format("%02d", month) + " 鍚屾湡鍩庡競瀹㈣繍鏁版嵁鏈鍏ュ簱锛屽煄甯傚杩愬悓姣旀殏涓嶆樉绀猴紝寰� 2025 骞村畾绋挎暟鎹鍏ュ悗鑷姩鍑虹幇");
+ }
+ if (paxC > 0 && lastPaxC2 <= 0) {
+ problems.add((year - 1) + "-" + String.format("%02d", month) + " 鍚屾湡鍏矾鏃呭鏁版嵁鏈鍏ュ簱锛屼腑鍙e緞鍚屾瘮鏆備笉鏄剧ず锛屽緟 2025 骞村畾绋挎暟鎹鍏ュ悗鑷姩鍑虹幇");
+ }
+ }
+
+ /** 鍩庡競瀹㈣繍鍏ㄧ渷绱鍚戦噺锛�8 鎸囨爣锛氬叕浜ゅ杩�/鍛ㄨ浆銆佸嚭绉熷杩�/鍛ㄨ浆銆佽建閬撳杩�/鍛ㄨ浆銆佽疆娓″杩�/鍛ㄨ浆锛� */
+ private double[] cityCumProv(String yearPrefix, int month) {
+ if (month <= 0) return new double[8];
+ return loadCityPassengerCumulative(yearPrefix, month).getOrDefault("鍏ㄧ渷", new double[8]);
+ }
+
+ private double[] diff8(double[] a, double[] b) {
+ double[] out = new double[8];
+ for (int i = 0; i < 8; i++) out[i] = a[i] - b[i];
+ return out;
+ }
+
+ private double sumIdx(double[] arr, int[] idx) {
+ double sum = 0.0;
+ for (int i : idx) sum += (i < arr.length ? arr[i] : 0.0);
+ return sum;
+ }
+
+ /** 鐧惧垎姣斿悓姣旓紙鍊煎凡涓烘瘮鐜� 0.xx锛夛細杈撳嚭鍥涜 褰撴湀/绱/褰撴湀鍚屾瘮/绱鍚屾瘮 */
+ private void addKpi(List<Map<String, Object>> summary, String name, Double m, Double cum, Double yoyM, Double yoyC, String unit) {
+ if (m != null) addSum(summary, name + "路褰撴湀", round(m, 2), unit);
+ if (cum != null) addSum(summary, name + "路绱", round(cum, 2), unit);
+ if (yoyM != null) addSum(summary, name + "路褰撴湀鍚屾瘮", round(yoyM * 100.0, 1), "%");
+ if (yoyC != null) addSum(summary, name + "路绱鍚屾瘮", round(yoyC * 100.0, 1), "%");
+ }
+
+ /** 鍘熷鏁板�肩増锛堟湀/绱涓� 0 瑙嗕负鏃犳暟鎹笉鍑鸿锛� */
+ private void addKpiRaw(List<Map<String, Object>> summary, String name, double m, double cum, double lastM, double lastC, String unit) {
+ if (m > 0) addSum(summary, name + "路褰撴湀", round(m, 2), unit);
+ if (cum > 0) addSum(summary, name + "路绱", round(cum, 2), unit);
+ if (m > 0 && lastM > 0) addSum(summary, name + "路褰撴湀鍚屾瘮", round((m - lastM) / lastM * 100.0, 1), "%");
+ if (cum > 0 && lastC > 0) addSum(summary, name + "路绱鍚屾瘮", round((cum - lastC) / lastC * 100.0, 1), "%");
+ }
+
private void addSum(List<Map<String, Object>> summary, String label, Double value, String unit) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("label", label);
@@ -369,7 +569,7 @@
Map<String, Double> h2032Cum = loadH2032FreightCumulative(period);
ScaleSplitTransport provCum = getProvinceCumulative(period);
Double total = freightCum(ft.get("婀栧寳鐪�"), month);
- Double totalWan = total == null ? null : round(total / 10000.0, 4);
+ Double totalWan = total == null ? null : round(total, 4); // 妯℃澘鍒楀凡鏄竾鍚紝涓嶅啀 梅10000
double above = 0.0;
for (Double v : h2032Cum.values()) if (v != null) above += v;
double aboveWan = round(above / 10000.0, 4);
@@ -665,17 +865,58 @@
totalYoy.put(city, freightCumYoy(ftMap.get(city), month));
}
+
+ // ===== 绗簩椤碉紙褰撴湀锛夋暟鎹細瑙勪笂=H2032 褰撴湀銆佸悎璁�=妯℃澘_璐ц繍閲忓懆杞噺褰撴湀鍒椼�佽涓�=宸�硷紱鍚屾瘮=鍘诲勾鍚屾湀 =====
+ Map<String, Map<Integer, Double>> h2032MonthCur = loadH2032FreightByMonth(period);
+ String lastYearPeriod = (Integer.parseInt(year) - 1) + period.substring(4);
+ Map<String, Map<Integer, Double>> h2032MonthLast = loadH2032FreightByMonth(lastYearPeriod);
+ Map<String, Double> monthAbove = new HashMap<>();
+ Map<String, Double> monthBelow = new HashMap<>();
+ Map<String, Double> monthTotal = new HashMap<>();
+ Map<String, Double> monthAboveYoy = new HashMap<>();
+ Map<String, Double> monthBelowYoy = new HashMap<>();
+ Map<String, Double> monthTotalYoy = new HashMap<>();
+ double provMonthAbove = 0.0, provMonthAboveLast = 0.0;
+ for (String city : RegionUtil.cityList()) {
+ Double ma = monthFreightWan(h2032MonthCur, city, month);
+ Double maLast = monthFreightWan(h2032MonthLast, city, month);
+ Double mt = freightMonth(ftMap.get(city), month);
+ Double mtLast = lastFreightMonth(ftMap.get(city), month);
+ if (ma != null) provMonthAbove += ma;
+ if (maLast != null) provMonthAboveLast += maLast;
+ monthAbove.put(city, ma);
+ monthTotal.put(city, mt);
+ monthBelow.put(city, mt == null ? null : round(mt - (ma == null ? 0.0 : ma), 4));
+ monthAboveYoy.put(city, growthYoy(ma, maLast));
+ Double mb = monthBelow.get(city);
+ Double mbLast = mtLast == null ? null : round(mtLast - (maLast == null ? 0.0 : maLast), 4);
+ monthBelowYoy.put(city, growthYoy(mb, mbLast));
+ monthTotalYoy.put(city, freightYoy(ftMap.get(city), month));
+ }
+ Double provMonthAboveWan = provMonthAbove == 0.0 ? null : round(provMonthAbove, 4);
+ Double provMonthTotal = freightMonth(ftMap.get("婀栧寳鐪�"), month);
+ Double provMonthBelow = provMonthTotal == null ? null : round(provMonthTotal - provMonthAbove, 4);
+ Double provMonthAboveYoy = growthYoy(provMonthAbove == 0.0 ? null : provMonthAbove, provMonthAboveLast);
+ Double provMonthTotalLast = lastFreightMonth(ftMap.get("婀栧寳鐪�"), month);
+ Double provMonthBelowLast = provMonthTotalLast == null ? null
+ : round(provMonthTotalLast - provMonthAboveLast, 4);
+ Double provMonthBelowYoy = growthYoy(provMonthBelow, provMonthBelowLast);
+ Double provMonthTotalYoy = freightYoy(ftMap.get("婀栧寳鐪�"), month);
+
// 浠� docs/璐ц繍/鐢熸垚_璐ц繍閲忔帓鍚�.xlsx 涓哄簳绋匡細淇濈暀琛ㄥご/鍚堝苟/鍒楀/鏍峰紡涓� Q/R 鍗犳瘮鍏紡锛屼粎鏇挎崲鏁版嵁
File template = resolveFreightTemplate("鐢熸垚_璐ц繍閲忔帓鍚�.xlsx");
try (InputStream in = new FileInputStream(template);
XSSFWorkbook wb = new XSSFWorkbook(in)) {
Sheet sheet = wb.getSheetAt(0);
sheet.getRow(0).getCell(0).setCellValue(year + "骞�" + cumRange(month) + "鍏ㄧ渷鍒嗗競宸炵疮璁″畬鎴愬叕璺揣杩愰噺鎯呭喌");
- sheet.getRow(22).getCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚鍏矾璐ц繍閲忔儏鍐�");
+ sheet.getRow(22).getCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞瀹屾垚鍏矾璐ц繍閲忔儏鍐�");
fillFreightRankBlock(sheet, 3, above, below, total, aboveYoy, belowYoy, totalYoy,
provAboveWan, provBelow, provTotal, provAboveYoy, provBelowYoy, provTotalYoy);
- fillFreightRankBlock(sheet, 25, above, below, total, aboveYoy, belowYoy, totalYoy,
- provAboveWan, provBelow, provTotal, provAboveYoy, provBelowYoy, provTotalYoy);
+ renameRankMonthHeader(sheet, 24);
+ fillFreightRankBlock(sheet, 25, monthAbove, monthBelow, monthTotal,
+ monthAboveYoy, monthBelowYoy, monthTotalYoy,
+ provMonthAboveWan, provMonthBelow, provMonthTotal,
+ provMonthAboveYoy, provMonthBelowYoy, provMonthTotalYoy);
try {
wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
} catch (Exception e) {
@@ -693,15 +934,27 @@
Map<String, ScaleSplitTransport> cumMap = loadCumulativeMap(period);
ScaleSplitTransport province = getProvinceCumulative(period);
+ // 褰撴湀锛堢浜岄〉锛夛細鍛ㄨ浆閲忔媶鍒嗚〃 MONTH 琛岋紙鍚綋鏈堝悓姣旓級
+ Map<String, ScaleSplitTransport> monthTurnover = new HashMap<>();
+ for (ScaleSplitTransport rec : scaleSplitMapper.selectList(
+ new LambdaQueryWrapper<ScaleSplitTransport>()
+ .eq(ScaleSplitTransport::getPeriodType, "MONTH")
+ .eq(ScaleSplitTransport::getReportPeriod, period))) {
+ String name = RegionUtil.normalizeCityName(rec.getRegionName());
+ if (name != null) monthTurnover.put(name, rec);
+ }
+ ScaleSplitTransport provinceMonth = monthTurnover.remove("婀栧寳鐪�");
+
// 浠� docs/璐ц繍/鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx 涓哄簳绋匡細淇濈暀琛ㄥご/鍚堝苟/鍒楀/鏍峰紡锛屼粎鏇挎崲鏁版嵁锛決/R 鍗犳瘮涓烘ā鏉跨紦瀛樺�硷紝鎸夋柊鏁版嵁閲嶇畻
File template = resolveFreightTemplate("鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx");
try (InputStream in = new FileInputStream(template);
XSSFWorkbook wb = new XSSFWorkbook(in)) {
Sheet sheet = wb.getSheetAt(0);
sheet.getRow(0).getCell(0).setCellValue(year + "骞�" + cumRange(month) + "鍏ㄧ渷鍒嗗競宸炵疮璁″畬鎴愬叕璺揣鐗╄繍杈撳懆杞噺鎯呭喌");
- sheet.getRow(22).getCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚鍏矾璐х墿杩愯緭鍛ㄨ浆閲忔儏鍐�");
+ sheet.getRow(22).getCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞瀹屾垚鍏矾璐х墿杩愯緭鍛ㄨ浆閲忔儏鍐�");
fillTurnoverRankBlock(sheet, 3, cumMap, province);
- fillTurnoverRankBlock(sheet, 25, cumMap, province);
+ renameRankMonthHeader(sheet, 24);
+ fillTurnoverRankBlock(sheet, 25, monthTurnover, provinceMonth);
return toBytes(wb);
}
}
@@ -901,14 +1154,15 @@
return org.apache.poi.ss.util.CellReference.convertNumToColString(colIdx);
}
- /** 鏃呭鍒嗗競宸炴湀鍊硷紙0=瀹㈣繍閲�/1=鍛ㄨ浆閲�/2=涓綋瀹㈣繍閲�/3=涓綋鍛ㄨ浆閲忥級 */
+ /** 鏃呭鍒嗗競宸炴湀鍊硷紙0=瀹㈣繍閲�/1=鍛ㄨ浆閲�/2=涓綋瀹㈣繍閲�/3=涓綋鍛ㄨ浆閲忥紱0/1=浼佷笟H2031+涓綋锛�2026-09-03 鍙e緞纭锛� */
private double passengerMonthVal(Map<Integer, Map<String, PassengerAgg>> data, Map<Integer, Map<String, double[]>> indi,
int year, int month, String region, int metricIdx) {
- if (metricIdx <= 1) {
+ if (metricIdx == 0 || metricIdx == 1) { // 瀹㈣繍閲�/鍛ㄨ浆閲忔�昏锛氫紒涓�+涓綋
PassengerAgg agg = aggOf(data, year, month, region);
- Double val = agg == null ? null
+ double ent = agg == null ? 0.0
: (metricIdx == 0 ? agg.passengerTotal / 10000.0 : agg.turnoverTotal / 10000.0);
- return val == null ? 0.0 : val;
+ double ind = individualVal(indi, year, month, region, metricIdx == 0 ? 0 : 1);
+ return ent + ind;
}
return individualVal(indi, year, month, region, metricIdx - 2);
}
@@ -992,6 +1246,31 @@
}
setFreightCell(row, cumCol, cumValue);
setFreightCell(row, cumCol + 1, cumYoy);
+ }
+
+ /** H2032 瑙勪笂鏌愭湀鍚堣锛堝惃鈫掍竾鍚級锛涙棤璇ユ湀鏁版嵁杩斿洖 null */
+ private Double monthFreightWan(Map<String, Map<Integer, Double>> byCityMonth, String city, int month) {
+ Map<Integer, Double> byMonth = byCityMonth == null ? null : byCityMonth.get(city);
+ Double v = byMonth == null ? null : byMonth.get(month);
+ return v == null ? null : round(v / 10000.0, 4);
+ }
+
+ /** 鍚屾瘮 = (鏈湡-鍩烘湡)/鍩烘湡锛涗换涓�鏈熺己澶辨垨鍩烘湡涓� 0 杩斿洖 null */
+ private Double growthYoy(Double cur, Double last) {
+ if (cur == null || last == null || last == 0.0) return null;
+ return (cur - last) / last;
+ }
+
+ /** 鎺掑悕琛ㄧ浜岄〉鍒楀ご銆岀疮璁″畬鎴愩�嶁啋銆屽綋鏈堝畬鎴愩�嶏紙浠呭鍑哄唴瀛樻浛鎹紝妯℃澘鏂囦欢涓嶆敼锛� */
+ private void renameRankMonthHeader(Sheet sheet, int headerRowIdx) {
+ Row row = sheet.getRow(headerRowIdx);
+ if (row == null) return;
+ for (int col : new int[]{1, 6, 11}) {
+ Cell c = row.getCell(col);
+ if (c == null || c.getCellType() != CellType.STRING) continue;
+ String s = c.getStringCellValue();
+ if (s != null && s.contains("绱瀹屾垚")) c.setCellValue(s.replace("绱瀹屾垚", "褰撴湀瀹屾垚"));
+ }
}
/** 璐ц繍閲忔帓鍚嶅潡濉厖锛氭ā鏉� 1-based r4 璧凤紙鍏ㄧ渷+17甯傚窞锛夛紝Q/R 鍗犳瘮鍏紡淇濈暀涓嶅姩 */
@@ -1163,9 +1442,12 @@
/** H2032瑙勪笂: city -> month -> 璐ц繍閲�(鍚�) */
private Map<String, Map<Integer, Double>> loadH2032FreightByMonth(String period) {
Map<String, Map<Integer, Double>> result = new HashMap<>();
+ String yearPrefix = period.length() >= 4 ? period.substring(0, 4) : null;
List<H2032EnterpriseMonthly> list = h2032Mapper.selectList(null);
for (H2032EnterpriseMonthly record : list) {
if (record.getReportPeriod() == null || record.getReportPeriod().compareTo(period) > 0) continue;
+ // H2032 琛ㄥ悓鏃跺瓨鏈夊線骞存湀鎶ワ紝缁熻褰撳勾瑙勪笂鍊兼椂鍙彇涓庢姤琛ㄦ湡鍚屽勾鐨勮褰曪紝閬垮厤鍘诲勾鏁版嵁鍙犲姞
+ if (yearPrefix != null && !record.getReportPeriod().startsWith(yearPrefix)) continue;
String city = RegionUtil.cityByCode(record.getRegionCode());
if (city == null) continue;
int month = parseMonth(record.getReportPeriod());
@@ -1178,9 +1460,11 @@
/** H2032瑙勪笂绱: city -> 璐ц繍閲�(鍚�) */
private Map<String, Double> loadH2032FreightCumulative(String period) {
Map<String, Double> result = new HashMap<>();
+ String yearPrefix = period.length() >= 4 ? period.substring(0, 4) : null;
List<H2032EnterpriseMonthly> list = h2032Mapper.selectList(null);
for (H2032EnterpriseMonthly record : list) {
if (record.getReportPeriod() == null || record.getReportPeriod().compareTo(period) > 0) continue;
+ if (yearPrefix != null && !record.getReportPeriod().startsWith(yearPrefix)) continue;
String city = RegionUtil.cityByCode(record.getRegionCode());
if (city == null) continue;
double freight = record.getFreightTotal() == null ? 0.0 : record.getFreightTotal();
@@ -1945,6 +2229,7 @@
// 绱鍚屾瘮鍒楋紙妯℃澘 #REF! 鈫� 搴撳唴鍘诲勾 1..N 鏈堢疮璁″悓姣旀暟鍊硷級
fillMidCumYoyColumn(sheet, mid, currentYear, fillMonths, extended ? 27 : 15);
recalc(wb);
+ clearFormulaErrorsAll(wb); // 鍘诲勾鍚屾湡鏄庣粏椤垫畫鐣� #REF! 鍚屾瘮鍏紡 鈫� 娓呯┖
return toBytes(wb);
}
}
@@ -2022,7 +2307,7 @@
/**
* 涓彛寰勫垎绫绘湀搴︽暟鎹細骞�*100+鏈� -> (甯傚窞/鍏ㄧ渷 -> double[5][2])
- * 缁村害 0=鎬婚噺 1=鍏矾鐝嚎(h2031) 2=鍩庨檯鍩庝埂鍏氦(cityBus) 3=宸℃父鍑虹 4=缃戠害杞︼紱[瀹㈣繍閲�(涓囦汉), 鍛ㄨ浆閲�(涓囦汉鍏噷)]
+ * 缁村害 0=鎬婚噺 1=鍏矾鐝嚎(h2031+涓綋) 2=鍩庨檯鍩庝埂鍏氦(cityBus) 3=宸℃父鍑虹 4=缃戠害杞︼紱[瀹㈣繍閲�(涓囦汉), 鍛ㄨ浆閲�(涓囦汉鍏噷)]
*/
private Map<Integer, Map<String, double[][]>> loadMidClassMap() {
Map<Integer, Map<String, double[][]>> mid = new HashMap<>();
@@ -2044,6 +2329,25 @@
+ nz(e.getPassengerClass4()) + nz(e.getPassengerCharter())) / 10000.0;
double turn = (nz(e.getTurnoverClass1()) + nz(e.getTurnoverClass2()) + nz(e.getTurnoverClass3())
+ nz(e.getTurnoverClass4()) + nz(e.getTurnoverCharter())) / 10000.0;
+ addMidClass(mid, key, city, 1, pass, turn);
+ addMidClass(mid, key, "婀栧寳鐪�", 1, pass, turn);
+ }
+ for (PassengerIndividualMonthly e : passengerIndividualMapper.selectList(null)) { // 涓綋骞跺叆鍏矾鐝嚎/鎬婚噺锛�09-03 鍙e緞锛�
+ if (e.getReportPeriod() == null) continue;
+ String[] parts = e.getReportPeriod().split("-");
+ if (parts.length != 2) continue;
+ int y, m;
+ try {
+ y = Integer.parseInt(parts[0]);
+ m = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException ex) {
+ continue;
+ }
+ String city = RegionUtil.cityByCode(e.getRegionCode());
+ if (city == null) continue;
+ int key = y * 100 + m;
+ double pass = nz(e.getPassengerCount()) / 10000.0;
+ double turn = nz(e.getTurnover()) / 10000.0;
addMidClass(mid, key, city, 1, pass, turn);
addMidClass(mid, key, "婀栧寳鐪�", 1, pass, turn);
}
@@ -2195,11 +2499,20 @@
title.getCell(0).setCellValue(currentYear + "骞�1-" + maxMonth + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌");
}
}
+ // 鍙冲潡锛堝綋鏈堬級鏍囬 K1
+ if (title != null && title.getCell(10) != null) {
+ title.getCell(10).setCellValue(currentYear + "骞�" + maxMonth + "鏈堝叏鐪佸垎甯傚窞瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌");
+ }
// 鍏ㄧ渷琛� r3锛欱3/F3 涓� SUM 鍏紡淇濈暀锛汥3/H3 鍚屾瘮濉�硷紙0-based 鍒� 3/7锛�
setValOrBlank(sheet, 2, 3, yoyOf(midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, true),
midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, 0, true)));
setValOrBlank(sheet, 2, 7, yoyOf(midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, false),
midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, 0, false)));
+ // 鍙冲潡鍏ㄧ渷琛� N3/R3锛氬綋鏈堝悓姣旓紙L3/P3 涓� SUM 鍏紡淇濈暀锛�
+ setValOrBlank(sheet, 2, 13, yoyOf(midClassVal(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, true),
+ midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", 0, true)));
+ setValOrBlank(sheet, 2, 17, yoyOf(midClassVal(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, false),
+ midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", 0, false)));
// 甯傚窞琛� r4-20锛欱/F 绱鍊笺�丏/H 鍚屾瘮锛汣/E/G/I 鎺掑悕鍏紡淇濈暀
List<String> cities = RegionUtil.CITY_LIST;
for (int i = 0; i < cities.size(); i++) {
@@ -2213,6 +2526,15 @@
setValOrBlank(sheet, r0, 3, yoyOf(pass, lastPass));
setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
setValOrBlank(sheet, r0, 7, yoyOf(turn, lastTurn));
+ // 鍙冲潡褰撴湀锛歀/P 鍊笺�丯/R 褰撴湀鍚屾瘮锛圡/O/Q/S 鎺掑悕鍏紡淇濈暀锛�
+ double mPass = midClassVal(mid, currentYear, maxMonth, city, 0, true);
+ double mTurn = midClassVal(mid, currentYear, maxMonth, city, 0, false);
+ double mLastPass = midClassVal(mid, currentYear - 1, maxMonth, city, 0, true);
+ double mLastTurn = midClassVal(mid, currentYear - 1, maxMonth, city, 0, false);
+ setValOrBlank(sheet, r0, 11, mPass == 0 ? null : round(mPass, 4));
+ setValOrBlank(sheet, r0, 13, mPass == 0 ? null : yoyOf(mPass, mLastPass));
+ setValOrBlank(sheet, r0, 15, mTurn == 0 ? null : round(mTurn, 4));
+ setValOrBlank(sheet, r0, 17, mTurn == 0 ? null : yoyOf(mTurn, mLastTurn));
}
recalc(wb);
return toBytes(wb);
@@ -2277,6 +2599,36 @@
setValOrBlank(sheet, r0, 2, yoyOf(pass, lastPass));
setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
setValOrBlank(sheet, r0, 6, yoyOf(turn, lastTurn));
+ }
+ // 涓嬪崐鍧楋紙褰撴湀锛夛細鏍囬琛�9銆佹暟鎹11-15锛�0-based 8銆�10-14锛�
+ Row bottomTitle = sheet.getRow(8);
+ if (bottomTitle != null && bottomTitle.getCell(0) != null) {
+ bottomTitle.getCell(0).setCellValue(currentYear + "骞�" + maxMonth + "鏈堜腑鍙e緞瀹㈣繍閲忓強鍛ㄨ浆閲�");
+ }
+ for (int i = 0; i < names.length; i++) {
+ int r0 = 10 + i;
+ double pass = midClassVal(mid, currentYear, maxMonth, "婀栧寳鐪�", dims[i], true);
+ double turn = midClassVal(mid, currentYear, maxMonth, "婀栧寳鐪�", dims[i], false);
+ double lastPass = midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", dims[i], true);
+ double lastTurn = midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", dims[i], false);
+ setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
+ setValOrBlank(sheet, r0, 2, pass == 0 ? null : yoyOf(pass, lastPass));
+ setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
+ setValOrBlank(sheet, r0, 6, turn == 0 ? null : yoyOf(turn, lastTurn));
+ }
+ // 涓嬪崐鍧楀崰姣斿叕寮忕敱鏍蜂緥鎸囧悜涓婂崐鍧楋紙B$3/F$3锛夛紝鏀瑰啓涓烘湰鍧楁�昏锛圔$11/F$11锛夛紝琛�12-15
+ for (int i = 1; i < names.length; i++) {
+ int r0 = 10 + i;
+ Row rr = sheet.getRow(r0);
+ if (rr == null) continue;
+ Cell dc = rr.getCell(3);
+ if (dc != null && dc.getCellType() == CellType.FORMULA) {
+ dc.setCellFormula("B" + (r0 + 1) + "/B$11");
+ }
+ Cell hc = rr.getCell(7);
+ if (hc != null && hc.getCellType() == CellType.FORMULA) {
+ hc.setCellFormula("F" + (r0 + 1) + "/F$11");
+ }
}
recalc(wb);
return toBytes(wb);
@@ -2372,6 +2724,7 @@
fillEnergyTemplateSheet(s, agg);
}
recalc(wb);
+ clearFormulaErrorsAll(wb); // 鏃犲幓骞存暟鎹殑鍚屾瘮/澧為�熷叕寮� #DIV/0! 鈫� 娓呯┖
return toBytes(wb);
}
}
@@ -2508,7 +2861,7 @@
public byte[] exportCityBusDetail(String period, String mode) throws Exception {
int year = Integer.parseInt(period.split("-")[0]);
int month = monthOf(period, mode);
- return exportCityByTemplate("瀵煎叆妯℃澘_鍩庡競鍏氦.xlsx", period, mode,
+ return exportCityByTemplate("鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period, mode,
loadCityBusByMonth(year + "-", month),
loadCityBusByMonth((year - 1) + "-", month));
}
@@ -2562,7 +2915,7 @@
public byte[] exportCityTaxiDetail(String period, String mode) throws Exception {
int year = Integer.parseInt(period.split("-")[0]);
int month = monthOf(period, mode);
- return exportCityByTemplate("瀵煎叆妯℃澘_宸℃父鍑虹.xlsx", period, mode,
+ return exportCityByTemplate("鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period, mode,
loadCityTaxiByMonth(year + "-", month),
loadCityTaxiByMonth((year - 1) + "-", month));
}
@@ -2606,7 +2959,7 @@
int year = Integer.parseInt(period.split("-")[0]);
int month = monthOf(period, mode);
Map<Integer, Map<String, double[]>> monthCity = loadCityRailFerryByMonth(year + "-", month);
- File template = resolveTemplate("瀵煎叆妯℃澘_杞ㄩ亾銆佽疆娓�.xlsx");
+ File template = resolveTemplate("鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
try (InputStream in = new FileInputStream(template);
XSSFWorkbook wb = new XSSFWorkbook(in)) {
Sheet sheet = wb.getSheetAt(0);
@@ -2616,6 +2969,8 @@
// 杞浮锛氬杩愰噺琛� 13(鍏ㄧ渷)/15(姝︽眽)锛涙寚鏍� 2=杞浮瀹㈣繍閲� 3=杞浮鍛ㄨ浆閲�
fillRailFerryBlock(sheet, monthCity, month, new String[][]{{"鍏ㄧ渷", "12"}, {"姝︽眽甯�", "14"}}, 2);
recalc(wb);
+ clearFormulaErrorCells(sheet, 3, sheet.getLastRowNum(), 1, 39);
+ fixCityMonthHeaderText(sheet);
return toBytes(wb);
}
}
@@ -2704,6 +3059,7 @@
int year = Integer.parseInt(period.split("-")[0]);
int month = monthOf(period, mode);
Map<String, double[]> cum = loadCityPassengerCumulative(year + "-", month);
+ Map<String, double[]> monthMap = loadCityPassengerMonthMap(year + "-", month);
File template = resolveTemplate(templateFileName);
try (InputStream in = new FileInputStream(template);
XSSFWorkbook wb = new XSSFWorkbook(in)) {
@@ -2712,9 +3068,24 @@
List<String> areas = new java.util.ArrayList<>();
areas.add("鍏ㄧ渷");
areas.addAll(RegionUtil.cityList());
- fillCitySumBlock(sheet, cum, areas, 4, 0); // 瀹㈣繍閲忓尯锛堝崟浣嶏細涓囦汉娆★紝POI 0 鍩猴級
- fillCitySumBlock(sheet, cum, areas, 26, 1); // 鍛ㄨ浆閲忓尯锛堝崟浣嶏細涓囦汉娆″叕閲岋紝POI 0 鍩猴級
+ fillCitySumBlock(sheet, cum, areas, 4, 0); // 绱瀹㈣繍閲忓尯锛堝崟浣嶏細涓囦汉娆★紝POI 0 鍩猴紝琛�5-22锛�
+ fillCitySumBlock(sheet, cum, areas, 26, 1); // 绱鍛ㄨ浆閲忓尯锛堝崟浣嶏細涓囦汉娆″叕閲岋紝POI 0 鍩猴紝琛�27-44锛�
+ // 褰撴湀鍧楁爣棰橈紙0鍩鸿44/66锛変笌鏁版嵁锛�0鍩鸿48-65/70-87锛�
+ Row pTitle = sheet.getRow(44);
+ if (pTitle != null && pTitle.getCell(0) != null) {
+ pTitle.getCell(0).setCellValue("鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞" + month + "鏈堟儏鍐�");
+ }
+ Row tTitle = sheet.getRow(66);
+ if (tTitle != null && tTitle.getCell(0) != null) {
+ tTitle.getCell(0).setCellValue("鍩庡競瀹㈣繍瀹㈣繍鍛ㄨ浆閲忓垎甯傚窞" + month + "鏈堟儏鍐�");
+ }
+ fillCitySumBlock(sheet, monthMap, areas, 48, 0); // 褰撴湀瀹㈣繍閲忓尯锛堣49-66锛�
+ fillCitySumBlock(sheet, monthMap, areas, 70, 1); // 褰撴湀鍛ㄨ浆閲忓尯锛堣71-88锛�
+ // 褰撴湀鍧楁帓鍚�/鍗犳瘮鍏紡鐢辩疮璁″潡澶嶅埗鑰屾潵锛孯ANK 鑼冨洿涓庡崰姣斿垎姣嶉渶鏀规寚褰撴湀鍧楄嚜韬
+ repairCityMonthRankShare(sheet, 50, 66, 49);
+ repairCityMonthRankShare(sheet, 72, 88, 71);
recalc(wb);
+ clearFormulaErrorsAll(wb); // 鏃犲幓骞寸疮璁$殑鍗犳瘮/澧為�熷叕寮� #DIV/0! 鈫� 娓呯┖
return toBytes(wb);
}
}
@@ -2744,6 +3115,33 @@
setDataCell(sheet, rowIdx, 24, 0);
rowIdx++;
}
+ }
+
+ /** 褰撴湀鍧楁帓鍚�/鍗犳瘮鍏紡淇锛氭ā鏉垮綋鏈堝潡鐢辩疮璁″潡澶嶅埗鑰屾潵锛孯ANK 鑼冨洿涓庡崰姣斿垎姣嶉渶鏀规寚褰撴湀鍧楄嚜韬 */
+ private void repairCityMonthRankShare(Sheet sheet, int firstDataRow1, int lastDataRow1, int provRow1) {
+ String[] groups = {"B", "G", "L", "Q"};
+ String[] growth = {"E", "J", "O", "T"};
+ for (int r = firstDataRow1; r <= lastDataRow1; r++) {
+ int r0 = r - 1;
+ for (int g = 0; g < 4; g++) {
+ String base = groups[g];
+ String grow = growth[g];
+ setFormulaIfFormula(sheet, r0, 2 + g * 5,
+ "RANK(" + base + r + "," + base + "$" + firstDataRow1 + ":" + base + "$" + lastDataRow1 + ")");
+ setFormulaIfFormula(sheet, r0, 3 + g * 5, base + r + "/" + base + "$" + provRow1);
+ setFormulaIfFormula(sheet, r0, 5 + g * 5,
+ "RANK(" + grow + r + "," + grow + "$" + firstDataRow1 + ":" + grow + "$" + lastDataRow1 + ")");
+ }
+ }
+ }
+
+ /** 鍗曞厓鏍煎師涓哄叕寮忔椂鏀瑰啓鍏紡锛堟帓鍚�/鍗犳瘮鍒楋級锛岄潪鍏紡鍗曞厓鏍间笉鍔紙淇濇寔妯℃澘寮忔牱锛� */
+ private void setFormulaIfFormula(Sheet sheet, int rowIdx, int colIdx, String formula) {
+ Row row = sheet.getRow(rowIdx);
+ if (row == null) return;
+ Cell c = row.getCell(colIdx);
+ if (c == null || c.getCellType() != CellType.FORMULA) return;
+ c.setCellFormula(formula);
}
/** 鏌愬勾 1..limit 鏈堝煄甯傚杩愮疮璁★細city 鈫� [鍏氦瀹㈣繍閲�,鍏氦鍛ㄨ浆閲�,鍑虹瀹㈣繍閲�,鍑虹鍛ㄨ浆閲�,杞ㄩ亾瀹㈣繍閲�,杞ㄩ亾鍛ㄨ浆閲�,杞浮瀹㈣繍閲�,杞浮鍛ㄨ浆閲廬锛屽惈"鍏ㄧ渷" */
@@ -2784,6 +3182,23 @@
return cum;
}
+ /** 鏌愬勾鍗曟湀鍩庡競瀹㈣繍鏁版嵁锛歝ity 鈫� 8 缁存暟缁勶紙鍙e緞鍚岀疮璁★級锛屽惈"鍏ㄧ渷"锛�= month 绱 - (month-1) 绱 */
+ private Map<String, double[]> loadCityPassengerMonthMap(String yearPrefix, int month) {
+ Map<String, double[]> cum = loadCityPassengerCumulative(yearPrefix, month);
+ if (month <= 1) return cum;
+ Map<String, double[]> pre = loadCityPassengerCumulative(yearPrefix, month - 1);
+ Map<String, double[]> out = new HashMap<>();
+ for (Map.Entry<String, double[]> e : cum.entrySet()) {
+ double[] prev = pre.get(e.getKey());
+ double[] d = new double[8];
+ for (int i = 0; i < 8; i++) {
+ d[i] = e.getValue()[i] - (prev == null ? 0 : prev[i]);
+ }
+ out.put(e.getKey(), d);
+ }
+ return out;
+ }
+
// ==================== 鍩庡競瀹㈣繍 鍏ㄧ渷姹囨�伙紙鍩庡競姹囨�籣妯℃澘锛� ====================
/** 鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx锛堜互 鍩庡競姹囨�籣妯℃澘.xlsx 涓哄簳绋匡紱2026 娈靛~搴撳唴 1..N 鏈堢疮璁★紝鍚屾瘮娓呯┖锛�2025/2024 娈典繚鐣欐ā鏉垮巻鍙插弬鑰冨�硷級 */
@@ -2795,7 +3210,7 @@
double taxiPax = prov[2], taxiTurn = prov[3];
double railPax = prov[4], railTurn = prov[5];
double ferryPax = prov[6], ferryTurn = prov[7];
- File template = resolveTemplate("瀵煎叆妯℃澘_鍩庡競姹囨��.xlsx");
+ File template = resolveTemplate("鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx");
try (InputStream in = new FileInputStream(template);
XSSFWorkbook wb = new XSSFWorkbook(in)) {
Sheet sheet = wb.getSheetAt(0);
@@ -2819,7 +3234,36 @@
setDataCell(sheet, r, 2, 0);
setDataCell(sheet, r, 4, 0);
}
+ // 搴曢儴鍗曟湀琛紙鏂扮増寮忥級锛氭爣棰樿11锛�0鍩�10锛夈�佹暟鎹13-19锛�0鍩�12-18锛夛紝濉綋鏈堝畬鎴愰噺
+ Row mTitle = sheet.getRow(10);
+ if (mTitle != null && mTitle.getCell(0) != null) {
+ mTitle.getCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸畬鎴愬煄甯傚杩愰噺鎯呭喌");
+ }
+ double[] mProv = loadCityPassengerMonthMap(year + "-", month).getOrDefault("鍏ㄧ渷", new double[8]);
+ double mBusPax = mProv[0], mBusTurn = mProv[1];
+ double mTaxiPax = mProv[2], mTaxiTurn = mProv[3];
+ double mRailPax = mProv[4], mRailTurn = mProv[5];
+ double mFerryPax = mProv[6], mFerryTurn = mProv[7];
+ setDataCell(sheet, 12, 1, mBusPax + mTaxiPax + mRailPax + mFerryPax); // B13 鎬诲杩愰噺
+ setDataCell(sheet, 12, 3, mBusTurn + mTaxiTurn + mRailTurn + mFerryTurn); // D13 鎬诲懆杞噺
+ setDataCell(sheet, 13, 1, mBusPax); // B14 鍩庡競鍏氦
+ setDataCell(sheet, 13, 3, mBusTurn); // D14
+ setDataCell(sheet, 14, 1, mTaxiPax); // B15 鍩庡競鍑虹杞�
+ setDataCell(sheet, 14, 3, mTaxiTurn); // D15
+ setDataCell(sheet, 15, 1, mTaxiPax); // B16 鍏朵腑宸℃父鍑虹
+ setDataCell(sheet, 15, 3, mTaxiTurn); // D16
+ setZeroCell(sheet, 16, 1); // B17 鍩庡競缃戠害杞�
+ setZeroCell(sheet, 16, 3); // D17
+ setDataCell(sheet, 17, 1, mRailPax); // B18 杞ㄩ亾
+ setDataCell(sheet, 17, 3, mRailTurn); // D18
+ setDataCell(sheet, 18, 1, mFerryPax); // B19 杞浮
+ setDataCell(sheet, 18, 3, mFerryTurn);// D19
+ for (int r = 12; r <= 18; r++) { // 鍗曟湀琛� C/E 鍚屾瘮锛氭棤涓婂勾鍗曟湀婧� 鈫� 娓呯┖鏍蜂緥
+ setDataCell(sheet, r, 2, 0);
+ setDataCell(sheet, r, 4, 0);
+ }
recalc(wb);
+ clearFormulaErrorsAll(wb); // 鏃犲幓骞存暟鎹殑鍚屾瘮鍏紡 #DIV/0! 鈫� 娓呯┖
return toBytes(wb);
}
}
@@ -2833,8 +3277,8 @@
addZipEntry(zos, "鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", exportCityBusDetail(period, mode));
addZipEntry(zos, "鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", exportCityTaxiDetail(period, mode));
addZipEntry(zos, "鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", exportCityRailFerryDetail(period, mode));
- addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競鍒嗗競宸�.xlsx", period, mode));
- addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode));
+ addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", exportCityPassengerCitySum("鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", period, mode));
+ addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", exportCityPassengerCitySum("鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode));
addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx", exportCityPassengerSummary(period, mode));
}
return bos.toByteArray();
@@ -2861,6 +3305,609 @@
addZipEntry(zos, "鐢熸垚_涓彛寰勫垎鏋�.xlsx", exportPassengerMidAnalysis(period, mode));
}
return bos.toByteArray();
+ }
+
+ /**
+ * 鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛圥0-P1 楠ㄦ灦锛夛細鏁存湰澶嶅埗姣嶇増銆奩YYY骞碝鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx銆嬶紝
+ * 淇濈暀鍏ㄩ儴椤电/鐗堝紡/鍏紡/2025 骞寸紦瀛樺�硷紝浠呮妸琛ㄥご骞翠唤鍔ㄦ�佸寲涓虹洰鏍囧勾锛涙暟鎹~鍏呮寜 P2 璧烽�愭鎺ュ叆銆�
+ */
+ public byte[] exportSummaryWorkbook(String period, String mode) throws Exception {
+ return exportSummaryWorkbook(period, mode, null);
+ }
+
+ /** 鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛圥0-P1 楠ㄦ灦锛屾敮鎸� toMonth 瑁佸壀锛夛細
+ * 浠ャ�奩YYY骞碝鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx銆嬶紙M 鐢� period 鍐冲畾锛変负姣嶇増锛屾寜鍚屾湡澶囦唤姣嶇増杩樺師鏈堝害鍧楋紱
+ * toMonth 闈炵┖涓斿皬浜� M 鏃讹紝鎶� 8 涓彴璐�/鍚堟垚椤佃鍒� toMonth 鏈堬紙toMonth+1..M 鐨勫綋鏈堝��/鍚屾瘮鍒楁竻绌恒�佷繚鐣欒〃澶达紝
+ * 绱涓庣疮璁″悓姣斿叕寮忔敼涓烘寜 toMonth 鏈堥噸绠楋級锛岀敤浜庘�滃厛鐢熸垚 1-6 鏈堛��1-7 鏈堟墿鍒椻�濋獙璇併�� */
+ public byte[] exportSummaryWorkbook(String period, String mode, Integer toMonth) throws Exception {
+ int year = Integer.parseInt(period.contains("-") ? period.split("-")[0] : period);
+ int month = period.contains("-") ? monthOf(period, mode) : 12;
+ File template = resolveSummaryMother(period, mode);
+ if (template == null) {
+ throw new RuntimeException("鏈壘鍒版眹鎬昏〃姣嶇増銆�" + year + "骞�" + month + "鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx銆嬶紙docs/鐢熸垚姹囨�诲ぇ琛級");
+ }
+ double savedZipRatio = ZipSecureFile.getMinInflateRatio();
+ ZipSecureFile.setMinInflateRatio(0.0001); // 鏂扮増姣嶇増 styles.xml 鐢� openpyxl 楂樺帇缂╋紙0.0099<0.01锛夎Е鍙� POI Zip-bomb 璇姤锛岃鍙栧悗杩樺師
+ try (InputStream in = new FileInputStream(template);
+ XSSFWorkbook wb = new XSSFWorkbook(in)) {
+ File donor = resolveSummaryDonor(year, month);
+ if (donor != null) {
+ try (InputStream din = new FileInputStream(donor); XSSFWorkbook dwb = new XSSFWorkbook(din)) {
+ int restored = restoreSummaryMonthlyBlocks(wb, dwb);
+ log.info("姹囨�诲伐浣滅翱鏈堝害鍧楄繕鍘熷畬鎴愶紝琛ラ綈/瑕嗙洊鍗曞厓鏍兼暟={}锛堟瘝鐗堬細{}锛屽浠芥瘝鐗堬細{}锛�", restored, template.getName(), donor.getName());
+ }
+ } else {
+ log.warn("姹囨�诲伐浣滅翱鏈壘鍒板悓鏈熷浠芥瘝鐗堬紙_澶囦唤_{}骞磠}鏈堚�︼級锛岃緭鍑哄皢淇濇寔姣嶇増鐜扮姸锛堝彲鑳戒负 1-4 鏈堥鏋讹級", year, month);
+ }
+ if (toMonth != null && toMonth > 0 && toMonth < month) {
+ int trimmed = trimSummaryMonthlySheets(wb, toMonth);
+ log.info("姹囨�诲伐浣滅翱瑁佸壀鑷� {} 鏈堝畬鎴愶紝娓呯┖/鏀瑰啓鍗曞厓鏍兼暟={}", toMonth, trimmed);
+ }
+ dynamicSummaryYear(wb, year);
+ cleanSummarySheetPresentation(wb); // 鍙栨秷鍚勯〉绛涢�夈�佽繕鍘熼殣钘忚锛堝惁鍒欌�滅彮绾垮寘杞︹�濈瓑椤电湅璧锋潵鍍忕己鏁版嵁锛�
+ int effMonth = (toMonth != null && toMonth > 0 && toMonth < month) ? toMonth : month;
+ List<String> dbProblems = new ArrayList<>();
+ String effPeriod = String.format("%04d-%02d", year, effMonth);
+ int dbFilled = summaryWorkbookFiller.fillMonthlyLedger(wb, effPeriod, year, effMonth, dbProblems);
+ int yoyFilled = summaryWorkbookFiller.ensureMonthYoyFormulas(wb, year, effMonth);
+ int monthFormulaFilled = summaryWorkbookFiller.ensureMonthFormulas(wb, year, effMonth);
+ int widthApplied = applySummaryReferenceLayout(wb);
+ log.info("姹囨�诲伐浣滅翱鏁版嵁椹卞姩鍥炲~瀹屾垚锛氬啓鍏ュ綋鏈堝�兼牸鏁�={}锛岃ˉ鍐欏綋鏈堝悓姣斿叕寮忔牸鏁�={}锛岃ˉ鍐欏綋鏈堝悎璁″叕寮忔牸鏁�={}锛�"
+ + "鎸夈�婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃銆嬪榻愬垪瀹藉垪鏁�={}锛岀己鏁版彁绀�={}",
+ dbFilled, yoyFilled, monthFormulaFilled, widthApplied, dbProblems);
+ applyTwoDecimalFormat(wb); // 鏁板�兼樉绀轰袱浣嶅皬鏁帮紙淇濈暀鍏ㄧ簿搴︼級锛�% / 鏃ユ湡绛夋棦鏈夋牱寮忎笉鏀瑰彉
+ if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr();
+ wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // 杩樺師/鏀瑰啓鍏紡鍚庢墦寮�鍗抽噸绠�
+ return toBytes(wb);
+ } finally {
+ ZipSecureFile.setMinInflateRatio(savedZipRatio);
+ }
+ }
+
+ /**
+ * 鐗堝紡瀵归綈锛氭寜 docs/鐢熸垚姹囨�诲ぇ琛�/銆婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx銆嬶紙鐢ㄦ埛鎻愪緵鐨勭増寮忓弬鐓э級閫愬垪濂楃敤鍒楀涓庨殣钘忔爣璁帮紝
+ * 浣跨敓鎴愪欢涓庡弬鐓т欢鈥滄牸寮忓畬鍏ㄤ竴鑷粹�濓紝骞惰姣嶇増娌″畾涔夊搴︾殑鈥滃悗缁墿鍒椻�濓紙濡� 8 鏈堝垪锛変篃鏈変笌 8 鏈堜竴鑷寸殑鍒楀銆�
+ * 鍙傜収浠剁己澶辨椂闈欓粯璺宠繃锛堣繑鍥� 0锛夛紝涓嶅奖鍝嶅師鏈夊鍑恒��
+ */
+ private int applySummaryReferenceLayout(XSSFWorkbook wb) {
+ File ref;
+ try {
+ ref = resolveSummaryLayoutRef();
+ } catch (Exception e) {
+ return 0;
+ }
+ if (ref == null) return 0;
+ double savedZipRatio = ZipSecureFile.getMinInflateRatio();
+ ZipSecureFile.setMinInflateRatio(0.0001);
+ int applied = 0;
+ try (InputStream in = new FileInputStream(ref); XSSFWorkbook rwb = new XSSFWorkbook(in)) {
+ for (int i = 0; i < rwb.getNumberOfSheets(); i++) {
+ org.apache.poi.xssf.usermodel.XSSFSheet rs = rwb.getSheetAt(i);
+ if (rs == null) continue;
+ org.apache.poi.xssf.usermodel.XSSFSheet ts = wb.getSheet(rs.getSheetName());
+ if (ts == null) continue;
+ int maxCol = Math.min(usedColumnCount(rs), usedColumnCount(ts) + 4);
+ if (maxCol <= 0) continue;
+ double[] width = new double[maxCol];
+ boolean[] hasWidth = new boolean[maxCol];
+ boolean[] hidden = new boolean[maxCol];
+ for (org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCols cols : rs.getCTWorksheet().getColsArray()) {
+ for (org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol col : cols.getColArray()) {
+ long min = col.getMin();
+ long max = col.getMax();
+ for (long c = min; c <= max; c++) {
+ int idx = (int) (c - 1);
+ if (idx < 0 || idx >= maxCol) {
+ if (idx >= maxCol) break;
+ continue;
+ }
+ if (col.isSetWidth()) {
+ width[idx] = col.getWidth();
+ hasWidth[idx] = true;
+ }
+ hidden[idx] = col.isSetHidden() && col.getHidden();
+ }
+ }
+ }
+ for (int c = 0; c < maxCol; c++) {
+ if (hasWidth[c]) {
+ int w = (int) Math.round(width[c] * 256d);
+ if (ts.getColumnWidth(c) != w) {
+ ts.setColumnWidth(c, w);
+ applied++;
+ }
+ }
+ if (hidden[c] != ts.isColumnHidden(c)) ts.setColumnHidden(c, hidden[c]);
+ }
+ }
+ log.info("姹囨�诲伐浣滅翱鐗堝紡瀵归綈銆婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃銆嬪畬鎴愶細濂楃敤鍒楀 {} 鍒楋紙鍙傜収浠讹細{}锛�", applied, ref.getName());
+ } catch (Exception e) {
+ log.warn("姹囨�诲伐浣滅翱鐗堝紡瀵归綈澶辫触锛堝拷鐣ワ紝缁х画鎸夋瘝鐗堢増寮忚緭鍑猴級锛歿}", e.getMessage());
+ return 0;
+ } finally {
+ ZipSecureFile.setMinInflateRatio(savedZipRatio);
+ }
+ return applied;
+ }
+
+ /** 涓�椤靛凡鐢ㄥ埌鐨勬渶澶у垪鏁帮紙0 鍩鸿鏁帮級 */
+ private int usedColumnCount(org.apache.poi.ss.usermodel.Sheet sh) {
+ int max = 0;
+ for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) {
+ org.apache.poi.ss.usermodel.Row row = sh.getRow(r);
+ if (row == null) continue;
+ if (row.getLastCellNum() > max) max = row.getLastCellNum();
+ }
+ return max;
+ }
+
+ /** 瀹氫綅鐗堝紡鍙傜収浠� docs/鐢熸垚姹囨�诲ぇ琛�/鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛堜紭鍏堢骇鍚屾瘝鐗堬細閰嶇疆鐩綍 鈫� user.dir 鐩稿 鈫� 涓婄骇锛� */
+ private File resolveSummaryLayoutRef() {
+ 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
+ };
+ for (String root : roots) {
+ if (root == null || root.trim().isEmpty()) continue;
+ File f = new File(root, "鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx");
+ if (f.isFile()) return f;
+ }
+ return null;
+ }
+
+ /** 姹囨�昏〃姣嶇増瀹氫綅锛氫粎绮剧‘鍖归厤 {year}骞磠month}鏈堚�︼紙P0-P1 楠ㄦ灦鏈熶笉鍏佽璺ㄦ湀/璺ㄥ勾闈欓粯鍥為��锛�
+ * 鐩爣鏈熸棤瀵瑰簲姣嶇増鏃舵槑纭姤閿欙紝鏈堜唤鎵╁睍瑙勫垯寰� P0 姣嶇増瀹氱鍚庡啀鏀惧紑锛� */
+ private File resolveSummaryMother(String period, String mode) throws Exception {
+ String year = period != null && period.contains("-") ? period.split("-")[0] : (period == null ? "" : period);
+ int month = period != null && period.contains("-") ? monthOf(period, mode) : 12;
+ String name = year + "骞�" + month + "鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx";
+ return resolveAnyTemplate(summaryTemplateDir, name);
+ }
+
+ // ==================== 缃戠害杞﹀杩愰噺鎷嗗垎琛紙鏈堝害锛� ====================
+
+ /** 缃戠害杞﹀杩愰噺鎷嗗垎琛細杈撳叆=閮ㄧ骇璁㈠崟锛堢綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx G-W锛�+ 鍏ㄧ渷 pin锛圔-E锛�+ 宸℃父鍑虹鏈堟姤锛圖B锛夛紱
+ * 绠楁硶涓庛�婄綉绾﹁溅鎷嗗垎.xlsx銆嬫湀椤典竴鑷达細瀹㈣繍閲�=璁㈠崟鍗犳瘮脳鍏ㄧ渷鎬婚噺锛涘煄甯傚唴=宸℃父鍑虹鍩庡競鍐呭崰姣斾唬鐞嗐�佹姹変繚鎸佽嚜韬叾浣欐寜鍓╀綑鍒嗘憡锛�
+ * 鍛ㄨ浆閲�=瀹㈣繍閲徝楀贰娓稿嚭绉熷钩鍧囪繍璺濆崰姣斿垎鎽婂叏鐪佸懆杞噺 pin锛屽煄甯傚唴鍛ㄨ浆閲忓悓娉曘�� */
+ public byte[] exportWycSplit(String period, String mode) throws Exception {
+ if (period == null || !period.contains("-")) {
+ throw new RuntimeException("缃戠害杞﹀杩愰噺鎷嗗垎琛ㄤ粎鏀寔鎸夋湀鐢熸垚锛岃閫夋嫨鍏蜂綋鏈堜唤");
+ }
+ int year = Integer.parseInt(period.split("-")[0]);
+ int month = parseMonth(period);
+ Map<String, Object> inp = loadWycMonthlyInput(period, mode);
+ if (inp == null) {
+ throw new RuntimeException("銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬬己灏戞姤琛ㄦ湡 " + period + " 鐨勬暟鎹锛岃鍏堢淮鎶ゅ綋鏈堣鍗曚笌鍏ㄧ渷 pin");
+ }
+ double pinK = (Double) inp.get("pinK");
+ double pinCK = (Double) inp.get("pinCK");
+ double pinZ = (Double) inp.get("pinZ");
+ double pinCZ = (Double) inp.get("pinCZ");
+ double orderSum = (Double) inp.get("orderSum");
+ double[] orderArr = (double[]) inp.get("orders");
+ if (pinK <= 0 || pinCK <= 0 || pinZ <= 0 || pinCZ <= 0 || orderSum <= 0) {
+ throw new RuntimeException("缃戠害杞﹁緭鍏� " + period + " 琛屼笉瀹屾暣锛氬叏鐪� pin / 璁㈠崟缂哄け锛岃鏍稿銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆�");
+ }
+ List<String> cities = RegionUtil.cityList();
+ int n = cities.size();
+ if (orderArr == null || orderArr.length < n) {
+ throw new RuntimeException("缃戠害杞﹁鍗曞垪鏁颁笉瓒筹紙搴斾负 17 甯傚窞璁㈠崟鍒楋級");
+ }
+ Map<String, double[]> taxi = taxiMonthlyOf(period);
+ java.util.List<String> missingTaxi = new java.util.ArrayList<>();
+ double[] Dtax = new double[n];
+ double[] Etax = new double[n];
+ double[] avgD = new double[n];
+ double[] avgDc = new double[n];
+ for (int i = 0; i < n; i++) {
+ double[] a = taxi.get(cities.get(i));
+ if (a == null || a[0] <= 0) {
+ missingTaxi.add(cities.get(i));
+ continue;
+ }
+ Dtax[i] = a[0];
+ Etax[i] = a[2];
+ avgD[i] = a[4];
+ avgDc[i] = a[5];
+ }
+ if (!missingTaxi.isEmpty()) {
+ throw new RuntimeException("宸℃父鍑虹鏈堟姤缂哄皯浠ヤ笅甯傚窞鏁版嵁锛�" + String.join("銆�", missingTaxi));
+ }
+ double[] C = new double[n];
+ double[] H = new double[n];
+ double[] I = new double[n];
+ double[] Gl = new double[n];
+ double[] Hl = new double[n];
+ double[] Il = new double[n];
+ double sumG = 0;
+ double sumDl = 0;
+ double sumEl = 0;
+ for (int i = 0; i < n; i++) {
+ C[i] = orderArr[i] / orderSum * pinK;
+ double F = Etax[i] / Dtax[i];
+ double G = C[i] * F;
+ sumG += G;
+ double Dl = C[i] * avgD[i];
+ double El = H[i] * avgDc[i];
+ sumDl += Dl;
+ sumEl += El;
+ }
+ // 鍩庡競鍐咃細姝︽眽淇濇寔鑷韩锛屽叾浣欐寜 G 鍓╀綑鍗犳瘮鍒嗘憡鑷冲叏鐪佸煄甯傚唴 pin
+ for (int i = 0; i < n; i++) {
+ if (i == 0) {
+ H[i] = C[i] * (Etax[i] / Dtax[i]);
+ } else {
+ H[i] = (C[i] * (Etax[i] / Dtax[i])) / (sumG - C[0] * (Etax[0] / Dtax[0])) * (pinCK - C[0] * (Etax[0] / Dtax[0]));
+ }
+ I[i] = C[i] - H[i];
+ }
+ // 鍛ㄨ浆閲忥細鎸� 瀹㈣繍閲徝楀贰娓稿嚭绉熷钩鍧囪繍璺� 鍗犳瘮鍒嗘憡鍏ㄧ渷鍛ㄨ浆閲� pin
+ double whG = C[0] * (Etax[0] / Dtax[0]);
+ double whDl = C[0] * avgD[0];
+ double[] El2 = new double[n];
+ double sumEl2 = 0;
+ for (int i = 0; i < n; i++) {
+ double Dl = C[i] * avgD[i];
+ double El2v = H[i] * avgDc[i];
+ El2[i] = El2v;
+ sumEl2 += El2v;
+ Gl[i] = Dl / sumDl * pinZ;
+ }
+ for (int i = 0; i < n; i++) {
+ if (i == 0) {
+ Hl[i] = Gl[i];
+ } else {
+ Hl[i] = El2[i] / (sumEl2 - El2[0]) * (pinCZ - Gl[0]);
+ }
+ Il[i] = Gl[i] - Hl[i];
+ }
+ return writeWycSplitWorkbook(period, cities, orderArr, C, Dtax, Etax, H, I, avgD, avgDc, Gl, Hl, Il, pinK, pinCK, pinZ, pinCZ, orderSum);
+ }
+
+ private byte[] writeWycSplitWorkbook(String period, List<String> cities, double[] orders, double[] C, double[] Dtax,
+ double[] Etax, double[] H, double[] I, double[] avgD, double[] avgDc,
+ double[] Gl, double[] Hl, double[] Il,
+ double pinK, double pinCK, double pinZ, double pinCZ, double orderSum) throws Exception {
+ XSSFWorkbook wb = new XSSFWorkbook();
+ XSSFSheet sh = wb.createSheet(period + "鎷嗗垎");
+ int n = cities.size();
+ // 瀹㈣繍閲忓潡
+ Row t = sh.createRow(0);
+ t.createCell(0).setCellValue("缃戠害杞﹀杩愰噺鎷嗗垎锛堜竾浜猴級 鎶ヨ〃鏈燂細" + period);
+ String[] head1 = {"甯傚窞", "璁㈠崟鎬绘暟锛堝崟锛�", "缃戠害杞﹀杩愰噺锛堜竾浜猴級", "宸℃父鍑虹瀹㈣繍閲忥紙涓囦汉锛�", "宸℃父鍑虹鍩庡競鍐呭杩愰噺锛堜竾浜猴級", "鍩庡競鍐呭崰姣�", "缃戠害杞﹀煄甯傚唴瀹㈣繍閲忥紙涓囦汉锛�", "缃戠害杞﹀煄闄呭煄涔″杩愰噺锛堜竾浜猴級"};
+ Row h1 = sh.createRow(1);
+ for (int j = 0; j < head1.length; j++) h1.createCell(j).setCellValue(head1[j]);
+ int r = 2;
+ writeWycRow(sh, r++, "鍏ㄧ渷", orderSum, pinK, sum(Dtax), sum(Etax), pinK > 0 ? pinCK / pinK : 0, pinCK, pinK - pinCK);
+ for (int i = 0; i < n; i++) {
+ writeWycRow(sh, r++, cities.get(i), orders[i], C[i], Dtax[i], Etax[i], Dtax[i] > 0 ? Etax[i] / Dtax[i] : 0, H[i], I[i]);
+ }
+ r += 1;
+ Row t2 = sh.createRow(r++);
+ t2.createCell(0).setCellValue("鏃呭鍛ㄨ浆閲忔媶鍒嗭紙涓囦汉鍏噷锛�");
+ String[] head2 = {"甯傚窞", "鍑虹杞﹀钩鍧囪繍璺濓紙鍏噷锛�", "鍑虹杞﹀煄甯傚唴骞冲潎杩愯窛锛堝叕閲岋級", "缃戠害杞︽梾瀹㈠懆杞噺锛堜竾浜哄叕閲岋級", "缃戠害杞﹀煄甯傚唴鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛�", "缃戠害杞﹀煄闄呭煄涔℃梾瀹㈠懆杞噺锛堜竾浜哄叕閲岋級"};
+ Row h2 = sh.createRow(r++);
+ for (int j = 0; j < head2.length; j++) h2.createCell(j).setCellValue(head2[j]);
+ writeWycRow2(sh, r++, "鍏ㄧ渷", 0, 0, pinZ, pinCZ, pinZ - pinCZ);
+ for (int i = 0; i < n; i++) {
+ writeWycRow2(sh, r++, cities.get(i), avgD[i], avgDc[i], Gl[i], Hl[i], Il[i]);
+ }
+ r += 1;
+ String[] notes = {
+ "娉細1) 瀹㈣繍閲� C = 璁㈠崟鍗犳瘮 脳 鍏ㄧ渷缃戠害杞﹀杩愰噺锛涘煄甯傚唴 H锛氭姹�=鑷韩锛屽叾浣欐寜 G 鍓╀綑鍗犳瘮鍒嗘憡鑷冲叏鐪佸煄甯傚唴 pin锛涘煄闄呭煄涔� = 鎬婚噺 - 鍩庡競鍐呫��",
+ " 2) 鍛ㄨ浆閲� G = 瀹㈣繍閲� 脳 宸℃父鍑虹骞冲潎杩愯窛 鐨勫崰姣� 脳 鍏ㄧ渷缃戠害杞﹀懆杞噺 pin锛涘煄甯傚唴鍛ㄨ浆閲忓悓娉曪紙鐢ㄥ煄甯傚唴骞冲潎杩愯窛锛夈��",
+ " 3) 杈撳叆锛氳鍗� = 褰撴湀鍒嗘瀽鎶ュ憡闄勪欢1锛�17 甯傚窞璁㈠崟鏁帮級锛涘叏鐪� pin = 缃戠害杞︽�绘暟.xlsx锛涘贰娓稿嚭绉� = 宸℃父鍑虹鏈堟姤锛堢郴缁熷鍏ワ紝鍚钩鍧囪繍璺濓級銆�",
+ " 4) 姝︽眽鏃犲煄闄呭煄涔★紱鏁板�间繚鐣欏叏绮惧害锛岀晫闈�/鏂囦欢鏄剧ず涓や綅灏忔暟銆�"
+ };
+ for (String nt : notes) {
+ Row nr = sh.createRow(r++);
+ nr.createCell(0).setCellValue(nt);
+ }
+ for (int j = 0; j < 8; j++) sh.setColumnWidth(j, 18 * 256);
+ return toBytes(wb);
+ }
+
+ private void writeWycRow(Sheet sh, int r, String name, double ord, double k, double dt, double et, double f, double h, double i) {
+ Row row = sh.createRow(r);
+ row.createCell(0).setCellValue(name);
+ row.createCell(1).setCellValue(ord);
+ row.createCell(2).setCellValue(k);
+ row.createCell(3).setCellValue(dt);
+ row.createCell(4).setCellValue(et);
+ row.createCell(5).setCellValue(f);
+ row.createCell(6).setCellValue(h);
+ row.createCell(7).setCellValue(i);
+ }
+
+ private void writeWycRow2(Sheet sh, int r, String name, double ad, double adc, double g, double h, double i) {
+ Row row = sh.createRow(r);
+ row.createCell(0).setCellValue(name);
+ row.createCell(1).setCellValue(ad);
+ row.createCell(2).setCellValue(adc);
+ row.createCell(3).setCellValue(g);
+ row.createCell(4).setCellValue(h);
+ row.createCell(5).setCellValue(i);
+ }
+
+ private double sum(double[] a) {
+ double s = 0;
+ for (double v : a) s += v;
+ return s;
+ }
+
+ /** 璇诲彇 docs/鍩庡競瀹㈣繍/缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx锛氭寜 period(yyyy-MM) 琛屽彇 pin(B-E) 涓� 17 甯傚窞璁㈠崟(G-W) */
+ private Map<String, Object> loadWycMonthlyInput(String period, String mode) throws Exception {
+ String target = period;
+ File f;
+ try {
+ f = resolveAnyTemplate(templateDir, "缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx");
+ } catch (Exception e) {
+ throw new RuntimeException("鏈壘鍒般�婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬶紝璇锋斁鍒� docs/鍩庡競瀹㈣繍 鐩綍");
+ }
+ try (InputStream in = new FileInputStream(f);
+ org.apache.poi.ss.usermodel.Workbook wb = org.apache.poi.ss.usermodel.WorkbookFactory.create(in)) {
+ org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(0);
+ org.apache.poi.ss.usermodel.DataFormatter df = new org.apache.poi.ss.usermodel.DataFormatter();
+ List<String> cities = RegionUtil.cityList();
+ int n = cities.size();
+ for (int i = 1; i <= sh.getLastRowNum(); i++) {
+ Row row = sh.getRow(i);
+ if (row == null) continue;
+ Cell c0 = row.getCell(0);
+ if (c0 == null) continue;
+ String m = df.formatCellValue(c0).trim();
+ if (m == null || m.isEmpty()) continue;
+ if (m.length() > 7) m = m.substring(0, 7);
+ if (!target.equals(m)) continue;
+ Map<String, Object> map = new HashMap<>();
+ map.put("pinK", cellNum(row, 1, df, 0.0));
+ map.put("pinCK", cellNum(row, 2, df, 0.0));
+ map.put("pinZ", cellNum(row, 3, df, 0.0));
+ map.put("pinCZ", cellNum(row, 4, df, 0.0));
+ double[] orders = new double[n];
+ double orderSum = 0;
+ for (int k = 0; k < n; k++) {
+ orders[k] = cellNum(row, 6 + k, df, 0.0);
+ orderSum += orders[k];
+ }
+ if (orderSum <= 0) {
+ orderSum = cellNum(row, 5, df, 0.0); // F 鍒楀悎璁″厹搴�
+ }
+ map.put("orders", orders);
+ map.put("orderSum", orderSum);
+ return map;
+ }
+ return null;
+ }
+ }
+
+ private double cellNum(Row row, int idx, org.apache.poi.ss.usermodel.DataFormatter df, double def) {
+ Cell c = row.getCell(idx);
+ if (c == null) return def;
+ if (c.getCellType() == CellType.NUMERIC) return c.getNumericCellValue();
+ String s = df.formatCellValue(c).replace(",", "").trim();
+ if (s == null || s.isEmpty()) return def;
+ try {
+ return Double.parseDouble(s);
+ } catch (Exception e) {
+ return def;
+ }
+ }
+
+ /** 宸℃父鍑虹鏈堟姤锛圖B锛夋寜鏈堝姞杞斤細甯傚窞 -> [瀹㈣繍閲�, 鍛ㄨ浆閲�, 鍩庡競鍐呭杩愰噺, 鍩庡競鍐呭懆杞噺, 骞冲潎杩愯窛, 鍩庡競鍐呭钩鍧囪繍璺漖 */
+ private Map<String, double[]> taxiMonthlyOf(String period) {
+ Map<String, double[]> map = new java.util.LinkedHashMap<>();
+ List<CityTaxiMonthly> rows = cityTaxiMapper.selectList(
+ new LambdaQueryWrapper<CityTaxiMonthly>().eq(CityTaxiMonthly::getReportPeriod, period));
+ for (CityTaxiMonthly r : rows) {
+ String city = RegionUtil.normalizeCityName(r.getCity());
+ if (city == null || city.isEmpty()) city = "鏈煡";
+ double[] a = map.computeIfAbsent(city, k -> new double[6]);
+ a[0] += nz(r.getPassengerVolume());
+ a[1] += nz(r.getTurnover());
+ a[2] += nz(r.getPassengerCity());
+ a[3] += nz(r.getTurnoverCity());
+ if (r.getAvgDistance() != null && r.getAvgDistance() > 0) a[4] = r.getAvgDistance();
+ if (r.getAvgDistanceCity() != null && r.getAvgDistanceCity() > 0) a[5] = r.getAvgDistanceCity();
+ }
+ for (double[] a : map.values()) {
+ if (a[4] <= 0 && a[0] > 0) a[4] = round(a[1] / a[0], 2);
+ if (a[5] <= 0 && a[2] > 0) a[5] = round(a[3] / a[2], 2);
+ }
+ return map;
+ }
+
+ /** 瀹氫綅鍚屾湡澶囦唤姣嶇増锛堟竻鐞嗗墠鐨勫畬鏁存湀姣嶇増锛夛紝鐢ㄤ簬鎶婇鏋舵瘝鐗堢己澶辩殑 5-7 鏈堟湀搴﹀潡鎸夊浠借繕鍘燂紱
+ * 涓� resolveAnyTemplate 鐩稿悓绛栫暐锛氬湪閰嶇疆鐩綍銆乽ser.dir 鐩稿鐩綍銆佷笂绾х洰褰曚笁涓�欓�夋牴涓壂鎻� */
+ private File resolveSummaryDonor(int year, int month) {
+ String rel = summaryTemplateDir;
+ while (rel.startsWith("./")) rel = rel.substring(2);
+ String[] roots = {
+ summaryTemplateDir,
+ System.getProperty("user.dir") + "/" + rel,
+ System.getProperty("user.dir") + "/../" + rel
+ };
+ for (String root : roots) {
+ if (root == null || root.trim().isEmpty()) continue;
+ File dir = new File(root);
+ File[] files = dir.listFiles((d, name) ->
+ name.startsWith("_澶囦唤_") && name.contains(year + "骞�" + month + "鏈�") && name.contains("閬撹矾杩愯緭閲忔眹鎬昏〃"));
+ if (files != null && files.length > 0) return files[0];
+ }
+ return null;
+ }
+
+ /** 鎸夊悓鏈熷浠芥瘝鐗堣繕鍘熸湀搴﹀潡锛氬嚒澶囦唤鏈夊唴瀹硅�屾瘝鐗堝悓鏍肩己澶�/鍐呭涓嶅悓锛堟竻鐞嗘椂琚鎺夌殑 5-7 鏈堝�间笌绱鍏紡锛夌殑鍗曞厓鏍硷紝鏁存牸鍥炲~骞惰鐩� */
+ private int restoreSummaryMonthlyBlocks(XSSFWorkbook wb, XSSFWorkbook donor) {
+ int restored = 0;
+ for (int i = 0; i < donor.getNumberOfSheets(); i++) {
+ Sheet ds = donor.getSheetAt(i);
+ if (ds == null) continue;
+ Sheet bs = wb.getSheet(ds.getSheetName());
+ if (bs == null) continue;
+ // 涓彛寰勬帓鍚嶉〉 A..H 闄堟棫宸﹀潡宸蹭簬 2026-09-09 鎷嶆澘鏁村潡鍒犻櫎锛氳鍖哄煙姘镐笉浠庡浠芥瘝鐗堝洖濉紝閬垮厤鈥滅敓鎴愬張澶嶆椿鈥�
+ boolean skipMidRankLeftBlock = "涓彛寰勬帓鍚�".equals(ds.getSheetName());
+ for (int r = ds.getFirstRowNum(); r <= ds.getLastRowNum(); r++) {
+ Row dr = ds.getRow(r);
+ if (dr == null) continue;
+ Row br = bs.getRow(r);
+ for (Cell dc : dr) {
+ if (dc == null) continue;
+ if (skipMidRankLeftBlock && dc.getColumnIndex() < 8) continue;
+ CellType t = dc.getCellType();
+ if (t == CellType.BLANK) continue;
+ String dtx = summaryCellText(dc);
+ if (dtx == null || dtx.isEmpty()) continue;
+ Cell bc = (br == null) ? null : br.getCell(dc.getColumnIndex());
+ if (bc != null && dtx.equals(summaryCellText(bc))) continue;
+ Row tr = (bc != null) ? br : bs.getRow(r);
+ if (tr == null) tr = bs.createRow(r);
+ Cell tc = (bc != null) ? bc : tr.getCell(dc.getColumnIndex());
+ if (tc == null) tc = tr.createCell(dc.getColumnIndex());
+ tc.setBlank();
+ applyDonorNumberFormat(wb, tc, dc);
+ if (t == CellType.FORMULA) {
+ String f = dc.getCellFormula();
+ if (f != null) tc.setCellFormula(f);
+ } else if (t == CellType.STRING) {
+ tc.setCellValue(dc.getStringCellValue());
+ } else if (t == CellType.NUMERIC) {
+ tc.setCellValue(dc.getNumericCellValue());
+ } else if (t == CellType.BOOLEAN) {
+ tc.setCellValue(dc.getBooleanCellValue());
+ }
+ restored++;
+ }
+ }
+ }
+ return restored;
+ }
+
+ /** 杩樺師鍥炲~鏃跺悓姝� donor锛堝凡鍋氱増寮忕粺涓�锛夌殑鏁板瓧鏍煎紡锛屼繚璇佸悓姣�/鏁板��/鎺掑悕鏍兼樉绀烘牸寮忎笌姣嶇増涓�鑷� */
+ private void applyDonorNumberFormat(XSSFWorkbook wb, Cell dst, Cell src) {
+ if (wb == null || dst == null || src == null) return;
+ try {
+ String fmt = src.getCellStyle().getDataFormatString();
+ if (fmt == null || fmt.isEmpty() || "General".equalsIgnoreCase(fmt)) return;
+ org.apache.poi.ss.usermodel.DataFormat df = wb.createDataFormat();
+ short idx = df.getFormat(fmt);
+ CellStyle cur = dst.getCellStyle();
+ if (cur != null && idx == cur.getDataFormat()) return;
+ CellStyle ns = wb.createCellStyle();
+ if (cur != null) ns.cloneStyleFrom(cur);
+ ns.setDataFormat(idx);
+ dst.setCellStyle(ns);
+ } catch (Exception ignore) {
+ // 涓埆鏍煎紡寮傚父涓嶉樆濉炶繕鍘�
+ }
+ }
+ /** 鍗曞厓鏍煎唴瀹规枃鏈寲锛堝叕寮忓甫 = 鍓嶇紑锛夛紝鐢ㄤ簬璺ㄧ翱姣斿 */
+ private String summaryCellText(Cell c) {
+ if (c == null) return null;
+ switch (c.getCellType()) {
+ case FORMULA: {
+ String f = c.getCellFormula();
+ return f == null ? "" : "=" + f;
+ }
+ case STRING:
+ return c.getStringCellValue();
+ case NUMERIC:
+ return Double.toString(c.getNumericCellValue());
+ case BOOLEAN:
+ return Boolean.toString(c.getBooleanCellValue());
+ default:
+ return "";
+ }
+ }
+
+ /** 鎶� 8 涓彴璐�/鍚堟垚椤佃鍓埌 keepMonths 鏈堬細
+ * 鏈堝害鍊煎垪 2026 骞� m 鏈� = 绗� 3+2*(m-1) 鍒楋紙C/E/G/I/K/M/O锛夛紝鍚屾瘮鍒椾负鍏跺悗涓�鍒楋紱
+ * 绱 Q 鍒� = C+E+...+O锛岀疮璁″悓姣� R 鍒楀垎姣� = 2025 骞村悓鏈堝垪 T/V/X/Z/AB/AD/AF銆�
+ * 瑁佸壀 = 鏁版嵁琛岋紙鍚疮璁″叕寮忎笖浠讳竴 2026 鏈堝害鍒楁湁鍐呭锛変腑 keepMonths+1..7 鏈堢殑褰撴湀鍊�/鍚屾瘮鍗曞厓鏍兼竻绌猴紙琛ㄥご淇濈暀锛夛紝
+ * 骞舵妸璇ヨ绱鍏紡鍘绘帀瀵瑰簲 +鍒楋紝绱鍚屾瘮鍒嗘瘝鍘绘帀瀵瑰簲 2025 鍒椼�� */
+ private int trimSummaryMonthlySheets(XSSFWorkbook wb, int keepMonths) {
+ String[] scope = {" 璐ц繍", "鐝嚎鍖呰溅", "鍩庡競瀹㈣繍", "鍏氦", "鍑虹", "缃戠害杞�", "杞ㄩ亾銆佽疆娓�", "涓彛寰勬槑缁�"};
+ java.util.Set<String> scopeSet = new java.util.HashSet<>();
+ for (String s : scope) scopeSet.add(s);
+ int touched = 0;
+ for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+ Sheet sh = wb.getSheetAt(i);
+ if (sh == null || !scopeSet.contains(sh.getSheetName())) continue;
+ for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ Cell q = row.getCell(16); // Q 鍒� = 绱
+ if (q == null || q.getCellType() != CellType.FORMULA) continue;
+ boolean hasMonthly = false;
+ for (int m = 1; m <= 7 && !hasMonthly; m++) {
+ Cell v = row.getCell(2 + 2 * (m - 1)); // C,E,G,I,K,M,O
+ if (v != null && v.getCellType() != CellType.BLANK) hasMonthly = true;
+ }
+ if (!hasMonthly) continue; // 鎺掗櫎椤甸/椤典腑璺ㄩ〉绛炬牎楠屽叕寮忚锛堟棤鏈堝害鍊硷級
+ int excelRow = r + 1; // 鍏紡鍐呭紩鐢ㄤ负 Excel 1-based 琛屽彿
+ for (int m = keepMonths + 1; m <= 7; m++) {
+ int valueCol = 2 + 2 * (m - 1); // 2026 褰撴湀鍊煎垪
+ int yoyCol = valueCol + 1; // 褰撴湀鍚屾瘮鍒�
+ String vLetter = colLetter(valueCol);
+ Cell vc = row.getCell(valueCol);
+ if (vc != null) {
+ vc.setBlank();
+ touched++;
+ }
+ Cell yc = row.getCell(yoyCol);
+ if (yc != null) {
+ yc.setBlank();
+ touched++;
+ }
+ String fq = q.getCellFormula();
+ if (fq != null && fq.contains("+" + vLetter + excelRow)) {
+ q.setCellFormula(fq.replace("+" + vLetter + excelRow, ""));
+ touched++;
+ }
+ Cell rc = row.getCell(17); // R 鍒� = 绱鍚屾瘮
+ if (rc != null && rc.getCellType() == CellType.FORMULA) {
+ String fr = rc.getCellFormula();
+ int lastCol = 19 + 2 * (m - 1); // 2025 鍚屾湀鍒� T..AF锛�0-based锛歍=19,AF=31锛�
+ String lLetter = colLetter(lastCol);
+ String txt = fr;
+ if (txt != null && txt.contains("," + lLetter + excelRow)) {
+ txt = txt.replace("," + lLetter + excelRow, "");
+ touched++;
+ }
+ if (txt != null && txt.contains("+" + lLetter + excelRow)) {
+ txt = txt.replace("+" + lLetter + excelRow, "");
+ touched++;
+ }
+ if (txt != null && !txt.equals(fr)) rc.setCellFormula(txt);
+ }
+ }
+ }
+ }
+ return touched;
+ }
+
+ /** 姹囨�诲伐浣滅翱骞翠唤鍔ㄦ�佸寲锛氫粎鎶� 2026骞� 鍓嶇紑鏇挎崲涓虹洰鏍囧勾锛�2025/2024 鍙傝�冨垪涓嶅姩锛� */
+ private void dynamicSummaryYear(XSSFWorkbook wb, int year) {
+ String oldPrefix = "2026骞�";
+ String newPrefix = year + "骞�";
+ if (oldPrefix.equals(newPrefix)) return;
+ 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.STRING) continue;
+ String v = c.getStringCellValue();
+ if (v != null && v.contains(oldPrefix)) {
+ c.setCellValue(v.replace(oldPrefix, newPrefix));
+ }
+ }
+ }
+ }
}
/** 鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪.zip锛氶瀹夋帓 + 鍗佷簲浜� + 浜垮厓 + 缁忔祹寮哄幙 */
@@ -2903,6 +3950,7 @@
case "passengerMidDetail": return exportPassengerMidDetail(period, mode);
case "passengerMidRank": return exportPassengerMidRank(period, mode);
case "passengerMidAnalysis": return exportPassengerMidAnalysis(period, mode);
+ case "summaryWorkbook": return exportSummaryWorkbook(period, mode);
case "energySummary": return exportEnergySummary(period);
case "investPlan": return investPlan(period, mode);
case "investFiveYearLogistics": return investFiveYearLogistics(period, mode);
@@ -2911,9 +3959,10 @@
case "cityBusDetail": return exportCityBusDetail(period, mode);
case "cityTaxiDetail": return exportCityTaxiDetail(period, mode);
case "cityRailFerryDetail": return exportCityRailFerryDetail(period, mode);
- case "cityPassengerCitySum": return exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競鍒嗗競宸�.xlsx", period, mode);
- case "cityPassengerDetailSum": return exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode);
+ case "cityPassengerCitySum": return exportCityPassengerCitySum("鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", period, mode);
+ case "cityPassengerDetailSum": return exportCityPassengerCitySum("鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode);
case "cityPassengerSummary": return exportCityPassengerSummary(period, mode);
+ case "wycSplit": return exportWycSplit(period, mode);
default: return null;
}
}
@@ -2938,6 +3987,8 @@
REPORT_FILE_NAMES.put("cityPassengerCitySum", "鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx");
REPORT_FILE_NAMES.put("cityPassengerDetailSum", "鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx");
REPORT_FILE_NAMES.put("cityPassengerSummary", "鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx");
+ REPORT_FILE_NAMES.put("summaryWorkbook", "鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx");
+ REPORT_FILE_NAMES.put("wycSplit", "鐢熸垚_缃戠害杞﹀杩愰噺鎷嗗垎琛�.xlsx");
}
private void addZipEntry(java.util.zip.ZipOutputStream zos, String name, byte[] data) throws Exception {
@@ -2996,7 +4047,53 @@
} catch (Exception e) {
log.warn("鍩庡競瀹㈣繍妯℃澘鍏紡姹傚�煎け璐�: {}", e.getMessage());
}
+ clearFormulaErrorCells(sheet, 4, sheet.getLastRowNum(), 3, 39);
+ fixCityMonthHeaderText(sheet);
return toBytes(wb);
+ }
+ }
+
+ /** 娓呯┖姹傚�肩粨鏋滀负閿欒鐨勫叕寮忓崟鍏冩牸锛堟ā鏉胯法绨垮悓姣斿叕寮忔畫鐣� #REF! 鏃剁Щ闄ゅ叕寮忥紝閬垮厤瀵煎嚭鏄剧ず閿欒锛� */
+ private void clearFormulaErrorCells(Sheet sheet, int startRow, int endRow, int startCol, int endCol) {
+ for (int r = startRow; r <= endRow; r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ for (int c = startCol; c <= endCol; c++) {
+ Cell cell = row.getCell(c);
+ if (cell == null || cell.getCellType() != CellType.FORMULA) continue;
+ try {
+ if (cell.getCachedFormulaResultType() == CellType.ERROR) cell.setBlank();
+ } catch (Exception ignore) {
+ // 缂撳瓨绫诲瀷璇诲彇澶辫触鏃跺拷鐣�
+ }
+ }
+ }
+ }
+
+ /** 娓呯┖宸ヤ綔绨垮叏閮ㄥ伐浣滆〃鍐呮眰鍊肩粨鏋滀负閿欒鐨勫叕寮忓崟鍏冩牸锛堣法绨�/璺ㄨ〃 #REF!銆佺己鍘诲勾鏁版嵁 #DIV/0! 绛夛級 */
+ private void clearFormulaErrorsAll(XSSFWorkbook wb) {
+ for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+ Sheet s = wb.getSheetAt(i);
+ clearFormulaErrorCells(s, 2, s.getLastRowNum(), 1, 60);
+ }
+ }
+
+ /** 鍩庡競瀹㈣繍绫绘ā鏉胯〃澶存湀浠藉垪鑻ヤ负鏈牸寮忓寲鏃ユ湡搴忓垪鍙凤紙濡� 46082锛夛紝鏀瑰啓涓恒�孻YYY骞碝鏈堛�嶆枃鏈� */
+ private void fixCityMonthHeaderText(Sheet sheet) {
+ for (int r = 0; r <= Math.min(sheet.getLastRowNum(), 20); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ Cell nameCell = row.getCell(0);
+ String nameText = nameCell == null ? null : cellText(nameCell);
+ if (nameText == null || !nameText.contains("鍦板尯")) continue;
+ for (int c = 2; c <= 40; c += 2) {
+ Cell cell = row.getCell(c);
+ if (cell == null || cell.getCellType() != CellType.NUMERIC) continue;
+ double v = cell.getNumericCellValue();
+ if (v < 40000 || v > 60000) continue;
+ java.util.Date d = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(v);
+ cell.setCellValue(new java.text.SimpleDateFormat("yyyy骞碝鏈�").format(d));
+ }
}
}
@@ -3142,6 +4239,7 @@
}
private byte[] toBytesHssf(HSSFWorkbook wb) throws Exception {
+ applyTwoDecimalFormat(wb);
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
wb.write(out);
wb.close();
@@ -3875,7 +4973,73 @@
private double nz(Double v) {
return v == null ? 0.0 : v;
}
+ /** 鐢熸垚琛ㄦ暟鍊煎垪缁熶竴鏍峰紡锛氫繚鐣欏叏绮惧害鏁板�硷紝鏄剧ず涓や綅灏忔暟锛涙暣鏁板�间笌 %/鏃ユ湡/绉戝璁℃暟绛夋棦鏈夋牱寮忎笉鏀瑰彉 */
+ private void applyTwoDecimalFormat(org.apache.poi.ss.usermodel.Workbook wb) {
+ if (wb == null) return;
+ org.apache.poi.ss.usermodel.DataFormat df = wb.createDataFormat();
+ short fmtTwo = df.getFormat("0.00");
+ for (int s = 0; s < wb.getNumberOfSheets(); s++) {
+ org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(s);
+ if (sh == null) continue;
+ for (org.apache.poi.ss.usermodel.Row row : sh) {
+ if (row == null) continue;
+ for (org.apache.poi.ss.usermodel.Cell cell : row) {
+ if (cell == null) continue;
+ try {
+ CellType ct = cell.getCellType();
+ if (ct != CellType.NUMERIC && ct != CellType.FORMULA) continue;
+ if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) continue;
+ boolean integral = false;
+ if (ct == CellType.NUMERIC) {
+ integral = Math.rint(cell.getNumericCellValue()) == cell.getNumericCellValue();
+ } else if (cell.getCachedFormulaResultType() == CellType.NUMERIC) {
+ integral = Math.rint(cell.getNumericCellValue()) == cell.getNumericCellValue();
+ }
+ if (integral) continue; // 鏁存暟涓嶆樉绀� .00锛堝惈鍏紡缁撴灉涓烘暣鏁扮殑鏍硷級
+ org.apache.poi.ss.usermodel.CellStyle cs = cell.getCellStyle();
+ if (cs == null) continue;
+ String f = cs.getDataFormatString();
+ if (f == null) f = "";
+ if (f.contains("%") || f.contains("E") || f.contains("0.00")) continue;
+ boolean plain = f.isEmpty() || "General".equals(f) || "general".equals(f)
+ || "0".equals(f) || "#,##0".equals(f) || "0.0".equals(f) || "#,##0.0".equals(f);
+ if (!plain) continue;
+ org.apache.poi.ss.usermodel.CellStyle ns = wb.createCellStyle();
+ ns.cloneStyleFrom(cs);
+ ns.setDataFormat(fmtTwo);
+ cell.setCellStyle(ns);
+ } catch (Exception ignore) {
+ // 涓埆鍗曞厓鏍兼牸寮忓紓甯镐笉闃诲瀵煎嚭
+ }
+ }
+ }
+ }
+ }
+
+ /** 姹囨�昏〃瀵煎嚭鍚庢寜鍐呭鑷姩鍔犲鍒楀锛堝彧鍔犲涓嶇缉绐勶紱璺宠繃鍚堝苟鍗曞厓鏍兼爣棰橈紝闃查暱鏍囬鎶婂崟鍒楁拺鐖嗭級 */
+ /** 姹囨�诲ぇ琛ㄨ緭鍑哄墠娓呯悊姣嶇増娈嬬暀涓嬫潵鐨勭瓫閫変笌琛岄殣钘忥細绛涢�夌澶�/闅愯棌琛屼細璁╂暣椤电湅璧锋潵鈥滅己鏁版嵁鈥� */
+ private void cleanSummarySheetPresentation(org.apache.poi.ss.usermodel.Workbook wb) {
+ if (wb == null) return;
+ for (int s = 0; s < wb.getNumberOfSheets(); s++) {
+ org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(s);
+ if (sh == null) continue;
+ if (sh instanceof org.apache.poi.xssf.usermodel.XSSFSheet) {
+ org.apache.poi.xssf.usermodel.XSSFSheet xs = (org.apache.poi.xssf.usermodel.XSSFSheet) sh;
+ if (xs.getCTWorksheet().isSetAutoFilter()) xs.getCTWorksheet().unsetAutoFilter();
+ for (org.apache.poi.ss.usermodel.Row row : xs) {
+ if (row == null) continue;
+ org.apache.poi.xssf.usermodel.XSSFRow xr = (org.apache.poi.xssf.usermodel.XSSFRow) row;
+ if (xr.getCTRow().isSetHidden()) xr.getCTRow().unsetHidden();
+ }
+ } else {
+ sh.setAutoFilter(null);
+ }
+ }
+ }
+
+
private byte[] toBytes(XSSFWorkbook wb) throws Exception {
+ applyTwoDecimalFormat(wb);
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
wb.write(out);
wb.close();
--
Gitblit v1.9.1