From 595b31273d583fbe367b3bfbebb6fc02e318abc0 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期二, 22 九月 2026 16:51:25 +0800
Subject: [PATCH] fix(投资): 明细表取数三处静默缺陷 + 导入两处丢数/选错页签缺陷
---
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 226 insertions(+), 14 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 03e2803..42e5bf2 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
@@ -2681,9 +2681,19 @@
* 鐢熸垚_涓彛寰勬帓鍚�.xlsx锛氬崟鍧楃疮璁★紙閫� 1 涓湀=褰撴湀锛岄�� 1-6 鏈�=绱锛夛紝RANK/SUM 鍏紡淇濈暀銆�
*/
public byte[] exportPassengerMidRank(String period, String mode) throws Exception {
+ return exportPassengerMidRank(period, mode, null);
+ }
+
+ /**
+ * 鏂规 A锛�2026-09-21锛夛細涓彛寰勬帓鍚嶇殑绱鍚屾瘮鍩烘暟涓庘�滀腑鍙e緞鏄庣粏鈥濅繚鎸佸悓婧愩��
+ * 鏄庣粏椤电殑 2025 骞村垪鏄瘝鐗堥潤鎬佸畾绋垮�硷紝鎺掑悕椤靛師鍏堟煡搴撶幇绠楋紝涓よ�呭湪 2025-03/04 缃戠害杞︽媶鍒嗕笂涓嶅悓銆�
+ * summaryWb 闈炵┖鏃剁洿鎺ュ鐢ㄥ綋鍓嶆鍦ㄧ敓鎴愮殑姹囨�诲伐浣滅翱锛涚嫭绔嬪鍑烘椂鍐嶆墦寮�瀵瑰簲姣嶇増銆�
+ */
+ private byte[] exportPassengerMidRank(String period, String mode, XSSFWorkbook summaryWb) throws Exception {
int maxMonth = monthOf(period, mode);
int currentYear = Integer.parseInt(period.substring(0, 4));
File template = resolvePassengerTemplate("鐢熸垚_涓彛寰勬帓鍚�.xlsx");
+ Map<String, double[]> lastYearCumBase = loadMidRankStaticBase(period, mode, summaryWb, currentYear, maxMonth);
try (InputStream in = new FileInputStream(template);
XSSFWorkbook wb = new XSSFWorkbook(in)) {
Sheet sheet = wb.getSheetAt(0);
@@ -2703,8 +2713,8 @@
title.getCell(10).setCellValue(currentYear + "骞�" + maxMonth + "鏈堝叏鐪佸垎甯傚窞瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌");
}
// 鍏ㄧ渷琛� r3锛欱3/F3 涓� SUM 鍏紡淇濈暀锛汥3/H3 鍚屾瘮濉�硷紙0-based 鍒� 3/7锛�
- setValOrBlank(sheet, 2, 3, midCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, true));
- setValOrBlank(sheet, 2, 7, midCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, false));
+ setValOrBlank(sheet, 2, 3, midRankCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, true, lastYearCumBase));
+ setValOrBlank(sheet, 2, 7, midRankCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, false, lastYearCumBase));
// 鍙冲潡鍏ㄧ渷琛� N3/R3锛氬綋鏈堝悓姣旓紙L3/P3 涓� SUM 鍏紡淇濈暀锛�
setValOrBlank(sheet, 2, 13, midMonthYoy(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, true));
setValOrBlank(sheet, 2, 17, midMonthYoy(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, false));
@@ -2715,17 +2725,13 @@
int r0 = 3 + i;
double pass = midCumClass(mid, currentYear, city, maxMonth, 0, true);
double turn = midCumClass(mid, currentYear, city, maxMonth, 0, false);
- double lastPass = midCumClass(mid, currentYear - 1, city, maxMonth, 0, true);
- double lastTurn = midCumClass(mid, currentYear - 1, city, maxMonth, 0, false);
setValOrBlank(sheet, r0, 1, pass == 0 ? null : pass);
- setValOrBlank(sheet, r0, 3, midCumYoy(mid, currentYear, city, maxMonth, 0, true));
+ setValOrBlank(sheet, r0, 3, midRankCumYoy(mid, currentYear, city, maxMonth, true, lastYearCumBase));
setValOrBlank(sheet, r0, 5, turn == 0 ? null : turn);
- setValOrBlank(sheet, r0, 7, midCumYoy(mid, currentYear, city, maxMonth, 0, false));
+ setValOrBlank(sheet, r0, 7, midRankCumYoy(mid, currentYear, city, maxMonth, false, lastYearCumBase));
// 鍙冲潡褰撴湀锛歀/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 : mPass);
setValOrBlank(sheet, r0, 13, mPass == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, city, 0, true));
setValOrBlank(sheet, r0, 15, mTurn == 0 ? null : mTurn);
@@ -2734,6 +2740,112 @@
recalc(wb);
return toBytes(wb);
}
+ }
+
+ /** 璇诲彇姣嶇増銆婁腑鍙e緞鏄庣粏銆嬬殑 2025 骞撮�愭湀闈欐�佸�硷紝姹囨�绘垚鎺掑悕椤垫墍闇�鐨勫幓骞村悓鏈熺疮璁″熀鏁般�� */
+ private Map<String, double[]> readMidDetailLastYearCumulative(XSSFWorkbook sourceWb, int currentYear, int maxMonth) {
+ Map<String, double[]> out = new LinkedHashMap<>();
+ if (sourceWb == null || maxMonth <= 0) return out;
+ Sheet sheet = sourceWb.getSheet("涓彛寰勬槑缁�");
+ if (sheet == null) return out;
+ Map<Integer, Integer> monthCols = new LinkedHashMap<>();
+ java.util.regex.Pattern monthHeader = java.util.regex.Pattern.compile("^(\\d{4})骞�(\\d{1,2})鏈�$");
+ int headerEnd = Math.min(sheet.getLastRowNum(), sheet.getFirstRowNum() + 5);
+ for (int r = sheet.getFirstRowNum(); r <= headerEnd; r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ for (int c = row.getFirstCellNum(); c >= 0 && c < row.getLastCellNum(); c++) {
+ String text = cellText(row.getCell(c));
+ if (text == null) continue;
+ java.util.regex.Matcher matcher = monthHeader.matcher(text.trim());
+ if (!matcher.matches()) continue;
+ int y = Integer.parseInt(matcher.group(1));
+ int m = Integer.parseInt(matcher.group(2));
+ if (y == currentYear - 1 && m >= 1 && m <= maxMonth) monthCols.putIfAbsent(m, c);
+ }
+ }
+ if (monthCols.size() < maxMonth) return out;
+ for (int r = sheet.getFirstRowNum(); r <= sheet.getLastRowNum(); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String city = RegionUtil.normalizeCityName(cellText(row.getCell(0)));
+ if (city == null || !RegionUtil.CITY_LIST.contains(city)) continue;
+ String metric = cellText(row.getCell(1));
+ if (metric == null) continue;
+ int idx = metric.contains("鎬绘梾瀹㈠懆杞噺") ? 1 : (metric.contains("鎬诲杩愰噺") ? 0 : -1);
+ if (idx < 0) continue;
+ double sum = 0.0;
+ boolean complete = true;
+ for (int m = 1; m <= maxMonth; m++) {
+ Cell c = row.getCell(monthCols.get(m));
+ if (c == null) { complete = false; break; }
+ if (c.getCellType() == CellType.NUMERIC) {
+ sum += c.getNumericCellValue();
+ } else if (c.getCellType() == CellType.FORMULA && c.getCachedFormulaResultType() == CellType.NUMERIC) {
+ sum += c.getNumericCellValue();
+ } else {
+ complete = false;
+ break;
+ }
+ }
+ double[] values = out.computeIfAbsent(city, k -> new double[]{Double.NaN, Double.NaN});
+ values[idx] = complete ? sum : Double.NaN;
+ }
+ double[] province = new double[2];
+ for (String city : RegionUtil.CITY_LIST) {
+ double[] values = out.get(city);
+ if (values == null || Double.isNaN(values[0]) || Double.isNaN(values[1])) return new LinkedHashMap<>();
+ province[0] += values[0];
+ province[1] += values[1];
+ }
+ out.put("婀栧寳鐪�", province);
+ return out;
+ }
+
+ /** 鐙珛瀵煎嚭鏃朵粠瀵瑰簲姹囨�绘瘝鐗堣鍙栭潤鎬佸熀鏁帮紱姹囨�诲鍑烘椂鐩存帴澶嶇敤鍐呭瓨涓殑宸ヤ綔绨匡紝閬垮厤閲嶅 I/O銆� */
+ private Map<String, double[]> loadMidRankStaticBase(String period, String mode, XSSFWorkbook summaryWb,
+ int currentYear, int maxMonth) {
+ if (summaryWb != null) {
+ Map<String, double[]> base = readMidDetailLastYearCumulative(summaryWb, currentYear, maxMonth);
+ if (!base.isEmpty()) return base;
+ log.warn("涓彛寰勬帓鍚嶏細褰撳墠姹囨�诲伐浣滅翱鐨勪腑鍙e緞鏄庣粏鏈鍒板畬鏁� 2025 闈欐�佺疮璁″熀鏁帮紝鍥為��搴撳唴鐜扮畻");
+ return null;
+ }
+ double savedZipRatio = ZipSecureFile.getMinInflateRatio();
+ ZipSecureFile.setMinInflateRatio(0.0001);
+ try {
+ File mother = resolveSummaryMother(period, mode);
+ if (mother == null || !mother.isFile()) {
+ log.warn("涓彛寰勬帓鍚嶏細鏈壘鍒� {} 鐨勬眹鎬绘瘝鐗堬紝2025 绱鍩烘暟鍥為��搴撳唴鐜扮畻", period);
+ return null;
+ }
+ try (InputStream in = new FileInputStream(mother); XSSFWorkbook wb = new XSSFWorkbook(in)) {
+ Map<String, double[]> base = readMidDetailLastYearCumulative(wb, currentYear, maxMonth);
+ if (base.isEmpty()) {
+ log.warn("涓彛寰勬帓鍚嶏細姣嶇増 {} 鐨勪腑鍙e緞鏄庣粏鏈鍒板畬鏁� 2025 闈欐�佺疮璁″熀鏁帮紝鍥為��搴撳唴鐜扮畻", mother.getName());
+ return null;
+ }
+ log.info("涓彛寰勬帓鍚嶏細2025 绱鍩烘暟鏀圭敤姣嶇増闈欐�佸垪锛坽}锛�", mother.getName());
+ return base;
+ }
+ } catch (Exception e) {
+ log.warn("涓彛寰勬帓鍚嶏細璇诲彇姣嶇増 2025 闈欐�佺疮璁″熀鏁板け璐ワ紝鍥為��搴撳唴鐜扮畻锛歿}", e.getMessage());
+ return null;
+ } finally {
+ ZipSecureFile.setMinInflateRatio(savedZipRatio);
+ }
+ }
+
+ /** 涓彛寰勬帓鍚嶇疮璁″悓姣旓細鏂规 A 鏈夐潤鎬佸熀鏁版椂涓庡叾瀵归綈锛屽惁鍒欎繚鐣欏師搴撳唴鐜扮畻閫昏緫銆� */
+ private Double midRankCumYoy(Map<Integer, Map<String, double[][]>> mid, int year, String area,
+ int monthCount, boolean volume, Map<String, double[]> staticBase) {
+ if (staticBase == null) return midCumYoy(mid, year, area, monthCount, 0, volume);
+ double[] values = staticBase.get(area);
+ if (values == null) return null;
+ double current = midCumClass(mid, year, area, monthCount, 0, volume);
+ if (current == 0.0) return null;
+ double base = volume ? values[0] : values[1];
+ return base == 0.0 ? null : yoyOf(current, base);
}
/** 鍐欐暟鍊兼垨娓呯┖锛堝叕寮忓崟鍏冩牸淇濈暀涓嶅姩锛� */
@@ -3507,7 +3619,7 @@
* 淇濈暀鍏ㄩ儴椤电/鐗堝紡/鍏紡/2025 骞寸紦瀛樺�硷紝浠呮妸琛ㄥご骞翠唤鍔ㄦ�佸寲涓虹洰鏍囧勾锛涙暟鎹~鍏呮寜 P2 璧烽�愭鎺ュ叆銆�
*/
public byte[] exportSummaryWorkbook(String period, String mode) throws Exception {
- return exportSummaryWorkbook(period, mode, null);
+ return exportSummaryWorkbook(period, mode, null, null);
}
/** 鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛圥0-P1 楠ㄦ灦锛屾敮鎸� toMonth 瑁佸壀锛夛細
@@ -3515,6 +3627,13 @@
* toMonth 闈炵┖涓斿皬浜� M 鏃讹紝鎶� 8 涓彴璐�/鍚堟垚椤佃鍒� toMonth 鏈堬紙toMonth+1..M 鐨勫綋鏈堝��/鍚屾瘮鍒楁竻绌恒�佷繚鐣欒〃澶达紝
* 绱涓庣疮璁″悓姣斿叕寮忔敼涓烘寜 toMonth 鏈堥噸绠楋級锛岀敤浜庘�滃厛鐢熸垚 1-6 鏈堛��1-7 鏈堟墿鍒椻�濋獙璇併�� */
public byte[] exportSummaryWorkbook(String period, String mode, Integer toMonth) throws Exception {
+ return exportSummaryWorkbook(period, mode, toMonth, null);
+ }
+
+ /** 鍚屼笂锛岄澶栭�氳繃 progress 鍥炶皟鎶ュ憡杩涘害锛堝紓姝ュ鍑轰换鍔$敤锛氭楠ょ櫨鍒嗘瘮 + 姝ラ鏂囨锛� */
+ public byte[] exportSummaryWorkbook(String period, String mode, Integer toMonth,
+ java.util.function.BiConsumer<Integer, String> progress) throws Exception {
+ reportProgress(progress, 2, "鏍¢獙鎶ヨ〃鏈熶笌姣嶇増");
int year = Integer.parseInt(period.contains("-") ? period.split("-")[0] : period);
int month = period.contains("-") ? monthOf(period, mode) : 12;
File template = resolveSummaryMother(period, mode);
@@ -3522,9 +3641,11 @@
throw new RuntimeException("鏈壘鍒版眹鎬昏〃姣嶇増銆�" + year + "骞�" + month + "鏈堥亾璺繍杈撻噺姹囨�昏〃.xlsx銆嬶紙docs/鐢熸垚姹囨�诲ぇ琛級");
}
double savedZipRatio = ZipSecureFile.getMinInflateRatio();
+ reportProgress(progress, 6, "璇诲彇姣嶇増宸ヤ綔绨�");
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)) {
+ reportProgress(progress, 12, "杩樺師鍚屾湡鏈堝害鍧�");
File donor = resolveSummaryDonor(year, month);
if (donor != null) {
try (InputStream din = new FileInputStream(donor); XSSFWorkbook dwb = new XSSFWorkbook(din)) {
@@ -3543,26 +3664,42 @@
int effMonth = (toMonth != null && toMonth > 0 && toMonth < month) ? toMonth : month;
List<String> dbProblems = new ArrayList<>();
String effPeriod = String.format("%04d-%02d", year, effMonth);
+ reportProgress(progress, 22, "鍥炲~褰撴湀鏁版嵁");
int dbFilled = summaryWorkbookFiller.fillMonthlyLedger(wb, effPeriod, year, effMonth, dbProblems);
int yoyFilled = summaryWorkbookFiller.ensureMonthYoyFormulas(wb, year, effMonth);
int monthFormulaFilled = summaryWorkbookFiller.ensureMonthFormulas(wb, year, effMonth);
+ // 绱鍚屾瘮鍒嗘瘝淇锛氭瘝鐗堟妸銆屽幓骞村悇鏈堛�嶄笌銆屽幓骞�1-M鏈堢疮璁°�嶈緟鍔╁垪鍚屾椂璁″叆锛堥噸澶嶈/婕忓綋鏈堬級
+ int cumYoyFixed = summaryWorkbookFiller.repairCumulativeYoyFormulas(wb, year, effMonth);
+ reportProgress(progress, 45, "鍒锋柊鎺掑悕椤典笌璺ㄩ〉鍚堟垚椤�");
int rankRefreshed = refreshSummaryRankSheets(wb, effPeriod, "month");
int cumulativeRefreshed = refreshSummaryCumulativeSheets(wb, year, effMonth); // 鍥涘紶璺ㄩ〉鍚堟垚椤甸殢鎶ヨ〃鏈熷埛鏂�
int widthApplied = applySummaryReferenceLayout(wb);
log.info("姹囨�诲伐浣滅翱鏁版嵁椹卞姩鍥炲~瀹屾垚锛氬啓鍏ュ綋鏈堝�兼牸鏁�={}锛岃ˉ鍐欏綋鏈堝悓姣斿叕寮忔牸鏁�={}锛岃ˉ鍐欏綋鏈堝悎璁″叕寮忔牸鏁�={}锛�"
- + "鍒锋柊鎺掑悕椤垫暟鍊兼牸鏁�={}锛屽埛鏂板洓椤碉紙鍩庡競姹囨��/鍩庡競鍒嗗競宸�/涓彛寰勫垎鏋�/閬撹矾杩愯緭鍛ㄨ浆閲忥級鏁板�兼牸鏁�={}锛�"
+ + "淇绱鍚屾瘮鍒嗘瘝鏍兼暟={}锛屽埛鏂版帓鍚嶉〉鏁板�兼牸鏁�={}锛屽埛鏂板洓椤碉紙鍩庡競姹囨��/鍩庡競鍒嗗競宸�/涓彛寰勫垎鏋�/閬撹矾杩愯緭鍛ㄨ浆閲忥級鏁板�兼牸鏁�={}锛�"
+ "鎸夈�婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃銆嬪榻愬垪瀹藉垪鏁�={}锛岀己鏁版彁绀�={}",
- dbFilled, yoyFilled, monthFormulaFilled, rankRefreshed, cumulativeRefreshed, widthApplied, dbProblems);
+ dbFilled, yoyFilled, monthFormulaFilled, cumYoyFixed, rankRefreshed, cumulativeRefreshed, widthApplied, dbProblems);
applyTwoDecimalFormat(wb); // 鏁板�兼樉绀轰袱浣嶅皬鏁帮紙淇濈暀鍏ㄧ簿搴︼級锛�% / 鏃ユ湡绛夋棦鏈夋牱寮忎笉鏀瑰彉
int inlineFixed = normalizeStaleInlineStrings(wb); // 娓呮帀銆岄潪 inlineStr 鍗存畫鐣� <is>銆嶇殑闈炴硶鏍硷紝鍚﹀垯 Excel 鎷掔粷鎵撳紑
if (inlineFixed > 0) log.warn("姹囨�诲伐浣滅翱娓呮帀娈嬬暀 inlineStr 闈炴硶鏍� {} 涓紙姣嶇増鐢� openpyxl 鍐欏嚭鏃剁殑鍏稿瀷闂锛�", inlineFixed);
+ reportProgress(progress, 70, "閲嶇畻鏁存湰鍏紡锛堢害 4 涓囨牸锛�");
int[] recalc = recalcWorkbookFormulas(wb); // 鍏紡閲嶇畻骞跺啓鍥炵紦瀛樺�硷細鍏ㄧ渷琛�(危甯傚窞)/绱鍒�/鎺掑悕 SUM 鎵嶈兘琚瑙堜笌浜屾澶勭悊璇诲埌
log.info("姹囨�诲伐浣滅翱鍏紡閲嶇畻瀹屾垚锛氭垚鍔� {} 鏍硷紝澶辫触 {} 鏍硷紙澶辫触鏍间繚鐣欐瘝鐗堟棫缂撳瓨鍊硷紝鎵撳紑鏃舵寜 fullCalcOnLoad 閲嶇畻锛�", recalc[0], recalc[1]);
if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr();
wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // 杩樺師/鏀瑰啓鍏紡鍚庢墦寮�鍗抽噸绠�
+ reportProgress(progress, 95, "鍐欏嚭鏂囦欢");
return toBytes(wb);
} finally {
ZipSecureFile.setMinInflateRatio(savedZipRatio);
+ }
+ }
+
+ /** 瀵煎嚭杩涘害鍥炶皟锛堝紓姝ュ鍑轰换鍔$敤锛夛細鍥炶皟鑷韩寮傚父涓嶅奖鍝嶅鍑� */
+ private static void reportProgress(java.util.function.BiConsumer<Integer, String> progress, int percent, String step) {
+ if (progress == null) return;
+ try {
+ progress.accept(percent, step);
+ } catch (Exception ignore) {
+ // 杩涘害鍥炶皟澶辫触涓嶅奖鍝嶅鍑�
}
}
@@ -3729,7 +3866,7 @@
int n = 0;
n += copyRankValues(wb, "璐ц繍閲忔帓鍚�", exportFreightRank(period, mode), new int[][]{{0, 0}, {22, 0}});
n += copyRankValues(wb, "璐ц繍鍛ㄨ浆閲忔帓鍚�", exportTurnoverRank(period, mode), new int[][]{{0, 0}, {22, 0}});
- n += copyRankValues(wb, "涓彛寰勬帓鍚�", exportPassengerMidRank(period, mode), new int[][]{{0, 0}, {0, 10}});
+ n += copyRankValues(wb, "涓彛寰勬帓鍚�", exportPassengerMidRank(period, mode, wb), new int[][]{{0, 0}, {0, 10}});
return n;
}
@@ -5428,6 +5565,7 @@
hssfText(row, 0, "鍚堣");
InvestAgg prov = new InvestAgg();
for (InvestmentMonthly m : monthlies) prov.add(m, pmap.get(m.getProjectId()));
+ addProjectsWithoutMonthly(prov, projects, mByProject, null, null);
writeInvestAggHssf(row, prov);
continue;
}
@@ -5440,6 +5578,7 @@
InvestmentProject p = pmap.get(m.getProjectId());
if (p != null && blockCategory.equals(p.getCategory())) agg.add(m, p);
}
+ addProjectsWithoutMonthly(agg, projects, mByProject, blockCategory, null);
writeInvestAggHssf(row, agg);
seq = 0;
continue;
@@ -5468,6 +5607,7 @@
if (p != null && city.equals(p.getCity())
&& (blockCategory == null || blockCategory.equals(p.getCategory()))) agg.add(m, p);
}
+ addProjectsWithoutMonthly(agg, projects, mByProject, blockCategory, city);
writeInvestAggHssf(row, agg);
continue;
}
@@ -5513,6 +5653,24 @@
hssfNum(row, 9, agg.yearPlan == 0 ? null : agg.yearPlan);
hssfNum(row, 22, agg.yearCum == 0 ? null : agg.yearCum);
if (agg.yearPlan > 0) hssfNum(row, 23, agg.yearCum / agg.yearPlan);
+ }
+
+ /**
+ * 琛ヨ銆屾湰鏈熸病鏈夋湀搴﹁褰曘�佷絾椤圭洰涓绘。鏈夋�绘姇璧勩�嶇殑椤圭洰锛堝彧璁℃�绘姇璧勮繖涓�鍒楋級銆�
+ * 鍦烘櫙锛氭煇甯傚窞鏈湀鏈姤閫佸崟琛紙濡傛姹� 8 鏈堢墿娴侊級锛屼富妗i噷椤圭洰浠嶅湪锛岃嫢涓嶈ˉ璁★紝璇ュ競宸炲皬璁�/鍚堣浼氭暣鍧椾负绌恒��
+ *
+ * @param category 鍧楃被鍒紙瀹㈣繍绔欏満/鐗╂祦鍥尯锛夛紱null 琛ㄧず涓嶉檺
+ * @param city 甯傚窞鍏ㄧО锛堝 姝︽眽甯傦級锛沶ull 琛ㄧず涓嶉檺
+ */
+ private void addProjectsWithoutMonthly(InvestAgg agg, List<InvestmentProject> projects,
+ Map<Long, InvestmentMonthly> mByProject,
+ String category, String city) {
+ for (InvestmentProject p : projects) {
+ if (p == null || mByProject.containsKey(p.getId())) continue;
+ if (category != null && !category.equals(p.getCategory())) continue;
+ if (city != null && !city.equals(p.getCity())) continue;
+ if (p.getTotalInvestment() != null) agg.total += p.getTotalInvestment();
+ }
}
@@ -5623,6 +5781,8 @@
String curCity = null;
Set<Long> used = new HashSet<>();
int filled = 0, kept = 0, dup = 0;
+ List<String> skipped = new ArrayList<>();
+ Map<String, Integer> skippedByCity = new LinkedHashMap<>();
for (int r = 8; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
@@ -5634,11 +5794,16 @@
if (!a.matches("\\d+(\\.\\d+)?")) continue;
if (b.isEmpty() || curCity == null) continue;
InvestmentProject p = matchInvest(projects, b, curCity, "瀹㈣繍绔欏満", used);
+ p = preferMonthlyCandidate(projects, p, mByProject, "瀹㈣繍绔欏満", used);
InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
if (p == null || m == null) {
kept++;
// 鏈夊簱鍐呴」鐩絾褰撴湀鏃犳暟鎹� 鈫� 璁颁负寰呮牳瀵癸紱鏃犲尮閰嶉」鐩� 鈫� 缁撹浆涓婃湀
if (p != null) dup++;
+ // 2026-09-22锛氫笉鍐嶉潤榛樼粨杞笂鏈堬紝鏈鐩栫殑琛屽繀椤荤暀鐥曪紙琛屽彿+甯傚窞+椤圭洰鍚�+鍘熷洜锛�
+ skipped.add("绗�" + (r + 1) + "琛� " + curCity + " " + b
+ + (p == null ? " 鈫� 搴撳唴鏃犲尮閰嶉」鐩�" : " 鈫� 搴撳唴鏃� " + period + " 鏈堝害鏁版嵁"));
+ skippedByCity.merge(curCity, 1, Integer::sum);
continue;
}
used.add(p.getId());
@@ -5668,6 +5833,7 @@
}
log.info("瀹㈣繍绔欐姇璧勬槑缁嗚〃 {}锛氬啓鍏� {} 琛岋紝缁撹浆姣嶇増鍘熷�� {} 琛岋紙鍏朵腑搴撳唴鏈夐」鐩絾褰撴湀鏃犳暟鎹� {} 琛岋級",
period, filled, kept, dup);
+ logSkippedRows("瀹㈣繍绔欐姇璧勬槑缁嗚〃", period, skipped, skippedByCity);
return toBytes(wb);
}
}
@@ -5708,6 +5874,8 @@
Map<String, double[]> cityStats = new LinkedHashMap<>(); // 鍏ㄥ悕 -> {鎬绘暟, 绱, 瑙勫垝鍐呮暟, 瑙勫垝鍐呯疮璁�, 瑙勫垝澶栨暟, 瑙勫垝澶栫疮璁
Set<Long> used = new HashSet<>();
int filled = 0, kept = 0;
+ List<String> skipped = new ArrayList<>();
+ Map<String, Integer> skippedByCity = new LinkedHashMap<>();
for (int i = 0; i < cityRows.size(); i++) {
int cr = cityRows.get(i);
String cityLabel = nvl(cellText(sheet.getRow(cr).getCell(0)));
@@ -5730,8 +5898,16 @@
if (!a.matches("\\d+(\\.\\d+)?")) continue;
if (b.isEmpty()) continue;
InvestmentProject p = matchInvest(projects, b, city, "鐗╂祦鍥尯", used);
+ p = preferMonthlyCandidate(projects, p, mByProject, "鐗╂祦鍥尯", used);
InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
- if (p == null || m == null) { kept++; continue; }
+ if (p == null || m == null) {
+ kept++;
+ // 2026-09-22锛氫笉鍐嶉潤榛樼粨杞笂鏈堬紝鏈鐩栫殑琛屽繀椤荤暀鐥�
+ skipped.add("绗�" + (r + 1) + "琛� " + city + " " + b
+ + (p == null ? " 鈫� 搴撳唴鏃犲尮閰嶉」鐩�" : " 鈫� 搴撳唴鏃� " + period + " 鏈堝害鏁版嵁"));
+ skippedByCity.merge(city, 1, Integer::sum);
+ continue;
+ }
used.add(p.getId());
// 鍙e緞鍚屽杩愮珯鏄庣粏琛細鍙鐩� H 鑷紑濮嬪缓璁剧疮璁� / J 鑷勾鍒濈疮璁� / K 鏈湀瀹屾垚 / M 褰㈣薄杩涘害
if (m.getStartCum() != null) putNum(row, 7, m.getStartCum());
@@ -5862,6 +6038,7 @@
log.warn("鐗╂祦绔欏満鎶曡祫鏄庣粏鍏紡姹傚�煎け璐�: {}", e.getMessage());
}
log.info("鐗╂祦绔欏満鎶曡祫鏄庣粏 {}锛氬啓鍏� {} 琛岋紝缁撹浆姣嶇増鍘熷�� {} 琛�", period, filled, kept);
+ logSkippedRows("鐗╂祦绔欏満鎶曡祫鏄庣粏", period, skipped, skippedByCity);
return toBytesHssf(wb);
}
@@ -6156,6 +6333,40 @@
return full != null && RegionUtil.cityList().contains(full);
}
+ /**
+ * 鍚屽悕锛堝綊涓�鍖栧悗涓�鑷达級澶氭。鏃讹紝浼樺厛閫夈�屽綋鏈堟湁鏁版嵁銆嶇殑閭d竴妗c��
+ * 鑳屾櫙锛�2026-09-22锛夛細鍗佸牥銆屽揩閫掔墿娴佷骇涓氬洯浜屾湡銆嶅湪搴撳唴鏈変袱妗� id=154锛堢宸� 2026锛屾湀搴﹀彧鍒� 7 鏈堬級涓�
+ * id=181锛堢宸� 2029锛屾湁 8 鏈堬級锛屾ā鏉胯鍚嶅墠缂�涓� 154 涓�鑷� 鈫� 瀵煎嚭鍙栧埌 154 鐨� 7 鏈堝�笺��
+ */
+ private InvestmentProject preferMonthlyCandidate(List<InvestmentProject> pool, InvestmentProject p,
+ Map<Long, InvestmentMonthly> mByProject,
+ String category, Set<Long> excludeIds) {
+ if (p == null || mByProject.containsKey(p.getId())) return p;
+ String norm = normInvestName(p.getProjectName());
+ if (norm.isEmpty()) return p;
+ InvestmentProject better = null;
+ for (InvestmentProject c : pool) {
+ if (c == null || c.getId().equals(p.getId())) continue;
+ if (excludeIds != null && excludeIds.contains(c.getId())) continue;
+ if (category != null && !category.equals(c.getCategory())) continue;
+ if (!mByProject.containsKey(c.getId())) continue;
+ if (!norm.equals(normInvestName(c.getProjectName()))) continue;
+ if (better == null || c.getId() > better.getId()) better = c;
+ }
+ return better != null ? better : p;
+ }
+
+ /** 鏈寜褰撴湀鏁版嵁瑕嗙洊鐨勮蹇呴』鐣欑棔锛氶�愯鍒楀嚭琛屽彿+甯傚窞+椤圭洰鍚嶏紝閬垮厤銆岄潤榛樼粨杞笂鏈熷�笺�嶅啀娆″彂鐢� */
+ private void logSkippedRows(String reportName, String period, List<String> skipped, Map<String, Integer> byCity) {
+ if (skipped == null || skipped.isEmpty()) return;
+ log.warn("{} {}锛歿} 琛屾湭鎸夊綋鏈堟暟鎹鐩栵紙缁撹浆姣嶇増鍘熷�硷級锛岄渶浜哄伐鏍稿婧愭枃浠�/搴撳唴鏁版嵁锛�",
+ reportName, period, skipped.size());
+ int limit = Math.min(skipped.size(), 50);
+ for (int i = 0; i < limit; i++) log.warn(" [鏈鐩朷 {}", skipped.get(i));
+ if (skipped.size() > limit) log.warn(" 鈥﹀彟鏈� {} 琛屾湭閫愯鍒楀嚭", skipped.size() - limit);
+ if (byCity != null && !byCity.isEmpty()) log.warn(" 鏈鐩栬鎸夊競宸炵粺璁★細{}", byCity);
+ }
+
/** 鍦ㄥ�欓�夐」鐩睜涓寜鍚嶇О鍖归厤 DB 椤圭洰锛堝彲閫� 甯傚窞/绫诲埆 杩囨护 + 鍘垮尯鍓嶇紑娑堟锛夛紝鏈懡涓繑鍥� null */
private InvestmentProject matchInvest(List<InvestmentProject> pool, String name, String cityFull, String category) {
return matchInvest(pool, name, cityFull, category, null);
@@ -6204,7 +6415,8 @@
if (name == null) return "";
String s = name.trim();
s = s.replaceAll("[锛�(]", "").replaceAll("[锛�)]", "");
- s = s.replaceAll("[路鈥⑩�旓紞_\\-]", "");
+ // 2026-09-22锛氳ˉ鍏呭崐/鍏ㄨ鍙ョ偣銆侀】鍙枫�佸垎鍙风瓑锛堝簱鍐呫�屼腑鍥戒緵閿�.鍏畨鍟嗚锤鐗╂祦鍥�嶅洜鍗婅鍙ョ偣鍖归厤涓嶄笂妯℃澘琛岋級
+ s = s.replaceAll("[路鈥⑩�旓紞_\\-锛�.銆傘�侊紝,;锛�:锛歖", "");
s = s.replaceAll("[鈪犫叀鈪⑩叄鈪も叆鈪︹収鈪ㄢ叐鈪扳叡鈪测叧鈪粹叺鈪垛叿鈪糕吂]", "I");
s = s.replaceAll("^(婀栧寳鐪亅姝︽眽甯倈榛勭煶甯倈鍗佸牥甯倈瀹滄槍甯倈瑗勯槼甯倈閯傚窞甯倈鑽嗛棬甯倈瀛濇劅甯倈鑽嗗窞甯倈榛勫唸甯倈鍜稿畞甯倈闅忓窞甯倈鎭╂柦宸瀨浠欐甯倈娼滄睙甯倈澶╅棬甯倈绁炲啘鏋舵灄鍖�)", "");
s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:鍘縷甯倈鍖�))", "");
--
Gitblit v1.9.1