From 48c5b2a856d926a1c35ca036b8323a33d2de67a3 Mon Sep 17 00:00:00 2001 From: cst61 Date: Wed, 27 May 2026 12:41:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B1=87=E6=80=BB=E7=BB=93=E6=9E=9C=E8=A1=A8?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E8=A1=A8=E7=9A=84=E9=87=91=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LbDailyUserTradeReportExportSupport.java | 272 +++++++++++++++++- 1 file changed, 258 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/rj/service/support/LbDailyUserTradeReportExportSupport.java b/src/main/java/com/rj/service/support/LbDailyUserTradeReportExportSupport.java index 11655d6..6215317 100644 --- a/src/main/java/com/rj/service/support/LbDailyUserTradeReportExportSupport.java +++ b/src/main/java/com/rj/service/support/LbDailyUserTradeReportExportSupport.java @@ -34,12 +34,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * 当日用户交易报表 Excel 导出(POI 样式与写入逻辑)。 @@ -60,7 +55,9 @@ public final class LbDailyUserTradeReportExportSupport { private static final int RESULT_SHEET_INDEX = 6; private static final int RESULT_CHECK_SHEET_INDEX = 7; - /** 结果表检查表:M~O 留白;P 用户ID、Q 昵称、R 最终收付款;其后为买卖明细/汇总。 */ + /** 结果表检查表:M/N 先按 K 列、再按 I 列补写 L 合计(M 已有值不覆盖);O 留白;P 起为买卖检查列。 */ + private static final int RESULT_CHECK_COL_IK_NICKNAME_L_SUM = 12; + private static final int RESULT_CHECK_COL_CURRENT_NICKNAME = 13; private static final int RESULT_CHECK_COL_USER_ID = 15; private static final int RESULT_CHECK_COL_NICKNAME = 16; private static final int RESULT_CHECK_COL_FINAL_SETTLEMENT = 17; @@ -411,7 +408,7 @@ public final class LbDailyUserTradeReportExportSupport { autoSizeColumns(resultSheet, RESULT_LAST_COL); } - /** 结果表 / 结果表检查表:L 列(匹配金额)求和,写入最后一行。 */ + /** 结果表:L 列(匹配金额)求和,写入最后一行。 */ private static void writeResultMatchColumnLSum(Sheet sheet, SheetStyles styles) { int lastRow = sheet.getLastRowNum(); if (lastRow < 1) { @@ -463,7 +460,7 @@ public final class LbDailyUserTradeReportExportSupport { Map sellTotalBySellerId, Map sellDetailBySellerId, SheetStyles styles) { - copyResultSheetContent(resultSource, target); + copyResultSheetContent(resultSource, target, true); writeResultCheckExtraHeaders(target, styles); Map nicknameToUserId = userIdByNickname == null ? Map.of() : userIdByNickname; Map buyTotalMap = buyTotalByBuyerId == null ? Map.of() : buyTotalByBuyerId; @@ -477,7 +474,7 @@ public final class LbDailyUserTradeReportExportSupport { for (int rowIdx = 1; rowIdx <= lastRow; rowIdx++) { Row row = target.getRow(rowIdx); - if (row == null) { + if (row == null || isResultMatchSumLabelRow(row)) { continue; } String iName = normalizeResultCheckNickname( @@ -519,7 +516,7 @@ public final class LbDailyUserTradeReportExportSupport { for (int rowIdx = 1; rowIdx <= lastRow; rowIdx++) { Row row = target.getRow(rowIdx); - if (row == null) { + if (row == null || isResultMatchSumLabelRow(row)) { continue; } String kName = normalizeResultCheckNickname( @@ -542,10 +539,247 @@ public final class LbDailyUserTradeReportExportSupport { lastRow = Math.max(lastRow, summaryRow); } - writeResultMatchColumnLSum(target, styles); + fillResultCheckColumnsMandNByIandKNickname(target, styles); + writeResultCheckColumnLAndMSum(target, styles); autoSizeColumns(target, RESULT_CHECK_LAST_COL); } + /** 结果表检查表:末行追加 L、M 列数据合计。 */ + private static void writeResultCheckColumnLAndMSum(Sheet sheet, SheetStyles styles) { + int lastRow = sheet.getLastRowNum(); + if (lastRow < 1) { + return; + } + + int dataEndRow = lastRow; + Row trailingRow = sheet.getRow(lastRow); + if (trailingRow != null && isResultMatchSumLabelRow(trailingRow)) { + dataEndRow = lastRow - 1; + } + if (dataEndRow < 1) { + return; + } + + BigDecimal sumL = BigDecimal.ZERO; + BigDecimal sumM = BigDecimal.ZERO; + for (int rowIdx = 1; rowIdx <= dataEndRow; rowIdx++) { + Row row = sheet.getRow(rowIdx); + if (row == null || isResultMatchSumLabelRow(row)) { + continue; + } + sumL = sumL.add(readNumericCellValue(row.getCell(RESULT_MATCH_AMT))); + if (!isResultCheckColumnMEmpty(row)) { + sumM = sumM.add(readNumericCellValue(row.getCell(RESULT_CHECK_COL_IK_NICKNAME_L_SUM))); + } + } + + int sumRowIdx = dataEndRow + 1; + Row sumRow = sheet.getRow(sumRowIdx); + if (sumRow == null) { + sumRow = sheet.createRow(sumRowIdx); + applyRowHeight(sumRow, styles); + } + createTextCell(sumRow, RESULT_MATCH_NEG_NAME, "求和", styles.summaryTextStyle); + createPaymentActualCell(sumRow, RESULT_MATCH_AMT, sumL, styles.summaryRoundedAmountStyle); + createPaymentActualCell(sumRow, RESULT_CHECK_COL_IK_NICKNAME_L_SUM, sumM, styles.summaryRoundedAmountStyle); + } + + /** + * 结果表检查表:先按 K 列昵称汇总 L 写入 M、N;再按 I 列昵称汇总 L 补写 M、N(M 已有值不覆盖)。 + */ + private static void fillResultCheckColumnsMandNByIandKNickname(Sheet sheet, SheetStyles styles) { + int dataEndRow = resolveResultCheckDataEndRow(sheet); + if (dataEndRow < 1) { + return; + } + fillResultCheckColumnsMandNByKNickname(sheet, dataEndRow, styles); + fillResultCheckColumnsMandNByINicknameIfMEmpty(sheet, dataEndRow, styles); + } + + private static int resolveResultCheckDataEndRow(Sheet sheet) { + int lastRow = sheet.getLastRowNum(); + if (lastRow < 1) { + return -1; + } + Row trailingRow = sheet.getRow(lastRow); + if (trailingRow != null && isResultMatchSumLabelRow(trailingRow)) { + return lastRow - 1; + } + return lastRow; + } + + /** K 列昵称汇总:M、N 仅在连续相同 K 昵称块的最后一行展示。 */ + private static void fillResultCheckColumnsMandNByKNickname(Sheet sheet, int dataEndRow, SheetStyles styles) { + Map sumByKNickname = new HashMap<>(); + for (int rowIdx = 1; rowIdx <= dataEndRow; rowIdx++) { + Row row = sheet.getRow(rowIdx); + if (row == null || isResultMatchSumLabelRow(row)) { + continue; + } + String iName = normalizeResultCheckNickname( + readPaymentCellText(row.getCell(RESULT_MATCH_NEG_NAME))); + String kName = normalizeResultCheckNickname( + readPaymentCellText(row.getCell(RESULT_MATCH_POS_NAME))); + if (!includeRowForColumnKSum(iName, kName)) { + continue; + } + BigDecimal lValue = readNumericCellValue(row.getCell(RESULT_MATCH_AMT)); + sumByKNickname.merge(kName, lValue, BigDecimal::add); + } + + Set displayRows = resolveConsecutiveLastRowsByNickname( + sheet, dataEndRow, LbDailyUserTradeReportExportSupport::includeRowForColumnKSum, + (iName, kName) -> kName); + + writeResultCheckSummaryColumns( + sheet, dataEndRow, displayRows, sumByKNickname, + (iName, kName) -> kName, styles, false); + } + + /** I 列昵称汇总:仅在 M 列为空时写入,M、N 仅在连续相同 I 昵称块的最后一行展示。 */ + private static void fillResultCheckColumnsMandNByINicknameIfMEmpty( + Sheet sheet, int dataEndRow, SheetStyles styles) { + Map sumByINickname = new HashMap<>(); + for (int rowIdx = 1; rowIdx <= dataEndRow; rowIdx++) { + Row row = sheet.getRow(rowIdx); + if (row == null || isResultMatchSumLabelRow(row)) { + continue; + } + String iName = normalizeResultCheckNickname( + readPaymentCellText(row.getCell(RESULT_MATCH_NEG_NAME))); + if (iName.isEmpty()) { + continue; + } + BigDecimal lValue = readNumericCellValue(row.getCell(RESULT_MATCH_AMT)); + sumByINickname.merge(iName, lValue, BigDecimal::add); + } + + Set displayRows = resolveConsecutiveLastRowsByNickname( + sheet, dataEndRow, + (iName, kName) -> !iName.isEmpty(), + (iName, kName) -> iName); + + writeResultCheckSummaryColumns( + sheet, dataEndRow, displayRows, sumByINickname, + (iName, kName) -> iName, styles, true); + } + + private static void writeResultCheckSummaryColumns( + Sheet sheet, + int dataEndRow, + Set displayRows, + Map sumByNickname, + java.util.function.BiFunction sumNicknameFn, + SheetStyles styles, + boolean skipWhenMHasValue) { + for (int rowIdx = 1; rowIdx <= dataEndRow; rowIdx++) { + Row row = sheet.getRow(rowIdx); + if (row == null || isResultMatchSumLabelRow(row)) { + continue; + } + String iName = normalizeResultCheckNickname( + readPaymentCellText(row.getCell(RESULT_MATCH_NEG_NAME))); + String kName = normalizeResultCheckNickname( + readPaymentCellText(row.getCell(RESULT_MATCH_POS_NAME))); + String sumNickname = sumNicknameFn.apply(iName, kName); + if (sumNickname == null || sumNickname.isEmpty()) { + if (!skipWhenMHasValue) { + createTextCell(row, RESULT_CHECK_COL_IK_NICKNAME_L_SUM, "", styles.textStyle); + createTextCell(row, RESULT_CHECK_COL_CURRENT_NICKNAME, "", styles.textStyle); + } + continue; + } + if (!displayRows.contains(rowIdx)) { + if (!skipWhenMHasValue) { + createTextCell(row, RESULT_CHECK_COL_IK_NICKNAME_L_SUM, "", styles.textStyle); + createTextCell(row, RESULT_CHECK_COL_CURRENT_NICKNAME, "", styles.textStyle); + } + continue; + } + if (skipWhenMHasValue && !isResultCheckColumnMEmpty(row)) { + continue; + } + BigDecimal sum = sumByNickname.getOrDefault(sumNickname, BigDecimal.ZERO); + createPaymentActualCell( + row, RESULT_CHECK_COL_IK_NICKNAME_L_SUM, sum, paymentActualStyle(rowIdx, styles)); + createTextCell(row, RESULT_CHECK_COL_CURRENT_NICKNAME, sumNickname, styles.textStyle); + } + } + + private static boolean isResultCheckColumnMEmpty(Row row) { + if (row == null) { + return true; + } + Cell cell = row.getCell(RESULT_CHECK_COL_IK_NICKNAME_L_SUM); + if (cell == null || cell.getCellType() == CellType.BLANK) { + return true; + } + if (cell.getCellType() == CellType.STRING) { + String text = cell.getStringCellValue(); + return text == null || text.trim().isEmpty(); + } + return false; + } + + /** 参与 K 列汇总:I、K 列昵称均非空。 */ + private static boolean includeRowForColumnKSum(String iName, String kName) { + return !iName.isEmpty() && !kName.isEmpty(); + } + + /** + * 连续相同汇总昵称块:返回每块最后一行的行号。 + */ + private static Set resolveConsecutiveLastRowsByNickname( + Sheet sheet, + int dataEndRow, + java.util.function.BiPredicate includeRow, + java.util.function.BiFunction sumNicknameFn) { + Set displayRows = new HashSet<>(); + String groupNickname = null; + int groupLastRow = -1; + + for (int rowIdx = 1; rowIdx <= dataEndRow; rowIdx++) { + Row row = sheet.getRow(rowIdx); + if (row == null || isResultMatchSumLabelRow(row)) { + flushResultCheckNicknameGroup(displayRows, groupNickname, groupLastRow); + groupNickname = null; + groupLastRow = -1; + continue; + } + String iName = normalizeResultCheckNickname( + readPaymentCellText(row.getCell(RESULT_MATCH_NEG_NAME))); + String kName = normalizeResultCheckNickname( + readPaymentCellText(row.getCell(RESULT_MATCH_POS_NAME))); + if (!includeRow.test(iName, kName)) { + flushResultCheckNicknameGroup(displayRows, groupNickname, groupLastRow); + groupNickname = null; + groupLastRow = -1; + continue; + } + String sumNickname = sumNicknameFn.apply(iName, kName); + if (sumNickname == null || sumNickname.isEmpty()) { + flushResultCheckNicknameGroup(displayRows, groupNickname, groupLastRow); + groupNickname = null; + groupLastRow = -1; + continue; + } + if (!sumNickname.equals(groupNickname)) { + flushResultCheckNicknameGroup(displayRows, groupNickname, groupLastRow); + groupNickname = sumNickname; + } + groupLastRow = rowIdx; + } + flushResultCheckNicknameGroup(displayRows, groupNickname, groupLastRow); + return displayRows; + } + + private static void flushResultCheckNicknameGroup( + Set displayRows, String groupNickname, int groupLastRow) { + if (groupNickname != null && groupLastRow >= 0) { + displayRows.add(groupLastRow); + } + } + /** 优先使用 1..lastRow 内未占用的行,否则在表尾追加。 */ private static int allocateResultCheckSummaryRow( Sheet sheet, Set usedSummaryRows, int lastRow, SheetStyles styles) { @@ -614,13 +848,15 @@ public final class LbDailyUserTradeReportExportSupport { return nickname == null ? "" : nickname.trim(); } - /** 结果表检查表 N 列表头(第 1 行)。 */ + /** 结果表检查表 M~V 列表头(第 1 行)。 */ private static void writeResultCheckExtraHeaders(Sheet sheet, SheetStyles styles) { Row header = sheet.getRow(0); if (header == null) { header = sheet.createRow(0); header.setHeightInPoints(20 * styles.rowHeightFactor); } + createHeaderCell(header, RESULT_CHECK_COL_IK_NICKNAME_L_SUM, "IK昵称L列合计", styles.headerStyle); + createHeaderCell(header, RESULT_CHECK_COL_CURRENT_NICKNAME, "当前人昵称", styles.headerStyle); createHeaderCell(header, RESULT_CHECK_COL_USER_ID, "用户ID", styles.headerStyle); createHeaderCell(header, RESULT_CHECK_COL_NICKNAME, "昵称", styles.headerStyle); createHeaderCell(header, RESULT_CHECK_COL_FINAL_SETTLEMENT, "最终收付款", styles.headerStyle); @@ -632,17 +868,25 @@ public final class LbDailyUserTradeReportExportSupport { } private static void copyResultSheetContent(Sheet source, Sheet target) { + copyResultSheetContent(source, target, false); + } + + private static void copyResultSheetContent(Sheet source, Sheet target, boolean excludeSumLabelRows) { target.setDefaultRowHeight(source.getDefaultRowHeight()); for (int col = 0; col <= RESULT_LAST_COL; col++) { target.setColumnWidth(col, source.getColumnWidth(col)); } + int destRowIdx = 0; for (int rowIdx = 0; rowIdx <= source.getLastRowNum(); rowIdx++) { Row srcRow = source.getRow(rowIdx); if (srcRow == null) { continue; } - Row destRow = target.createRow(rowIdx); + if (excludeSumLabelRows && rowIdx >= 1 && isResultMatchSumLabelRow(srcRow)) { + continue; + } + Row destRow = target.createRow(destRowIdx++); destRow.setHeight(srcRow.getHeight()); for (int col = 0; col <= RESULT_LAST_COL; col++) { Cell srcCell = srcRow.getCell(col);