生成抵扣表支付表

This commit is contained in:
2026-05-24 08:48:39 +08:00
parent 27de5decc3
commit 62722a9ce7

View File

@@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.ComparisonOperator;
import org.apache.poi.ss.usermodel.ConditionalFormattingRule;
import org.apache.poi.ss.usermodel.DataFormat;
@@ -34,7 +35,6 @@ 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.List;
import java.util.Map;
@@ -43,14 +43,24 @@ import java.util.Map;
*/
public final class LbDailyUserTradeReportExportSupport {
/** 导出工作簿固定 7 个 sheet顺序与业务模板一致。 */
/** 导出工作簿固定 8 个 sheet顺序与业务模板一致。 */
private static final String[] EXPORT_SHEET_NAMES = {
"总表", "抵扣表", "支付", "11点表", "断货表", "临时表2", "结果表"
"总表", "抵扣表", "断货", "11点表", "支付表", "支付表检查", "临时表2", "结果表"
};
private static final int SUMMARY_SHEET_INDEX = 0;
private static final int DEDUCTION_SHEET_INDEX = 1;
private static final int OUT_OF_STOCK_SHEET_INDEX = 2;
private static final int ELEVEN_CLOCK_SHEET_INDEX = 3;
private static final int PAYMENT_SHEET_INDEX = 4;
private static final int PAYMENT_CHECK_SHEET_INDEX = 5;
private static final int PAYMENT_COL_NAME = 0;
private static final int PAYMENT_COL_ACTUAL = 1;
/** 支付表:实际收付 < 0 的记录移至 E、F 列 */
private static final int PAYMENT_NEGATIVE_COL_NAME = 4;
private static final int PAYMENT_NEGATIVE_COL_ACTUAL = 5;
private static final int PAYMENT_LAST_COL = PAYMENT_NEGATIVE_COL_ACTUAL;
private static final int COL_NICKNAME = 0;
private static final int COL_YESTODAY_BUY = 1;
@@ -67,6 +77,8 @@ public final class LbDailyUserTradeReportExportSupport {
private static final int COL_SEQUENCE_LEFT = 0;
private static final String DATA_TYPE_REPORT_SUM = "report_sum";
/** 支付表 A/B 列固定追加行姓名 */
private static final String PAYMENT_AB_SPECIAL_NAME = "刘瑞华";
private LbDailyUserTradeReportExportSupport() {
}
@@ -84,11 +96,18 @@ public final class LbDailyUserTradeReportExportSupport {
}
DateHeaderLabels labels = DateHeaderLabels.of(reportDate);
fillSummarySheet(workbook.getSheetAt(SUMMARY_SHEET_INDEX), labels, rows, styles);
List<LbDailyUserTradeReport> exportRows = prepareExportRows(rows);
fillSummarySheet(workbook.getSheetAt(SUMMARY_SHEET_INDEX), labels, exportRows, styles);
Map<String, BigDecimal> deductionMap = deductionByUserName == null ? Map.of() : deductionByUserName;
fillDeductionSheet(workbook.getSheetAt(DEDUCTION_SHEET_INDEX), labels, rows, styles, deductionMap, false, true);
// 11点表与抵扣表相同的数据与计算逻辑最左侧序号列不含最后一行汇总
fillDeductionSheet(workbook.getSheetAt(ELEVEN_CLOCK_SHEET_INDEX), labels, rows, styles, deductionMap, true, false);
fillDeductionSheet(workbook.getSheetAt(DEDUCTION_SHEET_INDEX), labels, exportRows, styles, deductionMap, false, true, false);
// 断货表:总表中当日买入金额为 0 的明细行,最左侧序号列
fillOutOfStockSheet(workbook.getSheetAt(OUT_OF_STOCK_SHEET_INDEX), labels, exportRows, styles);
// 11点表与抵扣表相同的数据与计算逻辑最左侧序号列不含最后一行汇总数值四舍五入取整
fillDeductionSheet(workbook.getSheetAt(ELEVEN_CLOCK_SHEET_INDEX), labels, exportRows, styles, deductionMap, true, false, true);
// 支付表A/B 列含刘瑞华,按 B 列倒序;< 0 移至 E/F 列
Sheet paymentSheet = workbook.getSheetAt(PAYMENT_SHEET_INDEX);
fillPaymentSheet(paymentSheet, exportRows, styles, deductionMap);
fillPaymentCheckSheet(workbook.getSheetAt(PAYMENT_CHECK_SHEET_INDEX), paymentSheet, styles);
String filename = "当日汇总报表" + reportDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".xlsx";
String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
@@ -107,46 +126,444 @@ public final class LbDailyUserTradeReportExportSupport {
private static void fillSummarySheet(
Sheet sheet,
DateHeaderLabels labels,
List<LbDailyUserTradeReport> rows,
List<LbDailyUserTradeReport> exportRows,
SheetStyles styles) {
int r = writeHeaderRow(sheet, labels, styles, false);
sheet.createFreezePane(0, 1);
sheet.setAutoFilter(new CellRangeAddress(0, 0, 0, SUMMARY_LAST_COL));
for (LbDailyUserTradeReport item : rows) {
Row row = sheet.createRow(r++);
applyRowHeight(row, styles);
RowStylePair rowStyles = rowStyles(item, r, styles);
writeBaseDataCells(row, item, rowStyles, roundedInt(item.getDikouAmt()), false);
}
applyDiffNegativeFormatting(sheet, r, false);
autoSizeColumns(sheet, SUMMARY_LAST_COL);
fillSummaryLikeSheet(sheet, labels, exportRows, styles, false, false);
}
/**
* 抵扣表 / 11点表先按总表写入各行抵扣金额列改为 lb_deduction_amount 匹配值;
* 「实际收付」= 抵扣金额 + 原实际收付;明细按实际收付倒序。
* 抵扣表最后一行汇总行的抵扣金额、实际收付为本列明细之和11点表不输出汇总行最左侧为序号列。
*/
private static void fillDeductionSheet(
/** 断货表复制总表中当日买入金额dailyBuyAmt为 0 的明细行,不含汇总行,最左侧序号列,数值四舍五入取整。 */
private static void fillOutOfStockSheet(
Sheet sheet,
DateHeaderLabels labels,
List<LbDailyUserTradeReport> exportRows,
SheetStyles styles) {
fillSummaryLikeSheet(sheet, labels, filterZeroDailyBuyRows(exportRows), styles, true, true);
}
private static List<LbDailyUserTradeReport> filterZeroDailyBuyRows(List<LbDailyUserTradeReport> exportRows) {
List<LbDailyUserTradeReport> filtered = new ArrayList<>();
for (LbDailyUserTradeReport row : exportRows) {
if (DATA_TYPE_REPORT_SUM.equals(row.getDataType())) {
continue;
}
if (defaultZero(row.getDailyBuyAmt()).compareTo(BigDecimal.ZERO) == 0) {
filtered.add(row);
}
}
return filtered;
}
private static void fillSummaryLikeSheet(
Sheet sheet,
DateHeaderLabels labels,
List<LbDailyUserTradeReport> rows,
SheetStyles styles,
Map<String, BigDecimal> deductionByUserName,
boolean sequenceOnLeft,
boolean includeSummaryRow) {
boolean roundValues,
boolean sequenceOnLeft) {
int lastCol = sequenceOnLeft ? ELEVEN_CLOCK_LAST_COL : SUMMARY_LAST_COL;
int r = writeHeaderRow(sheet, labels, styles, sequenceOnLeft);
sheet.createFreezePane(0, 1);
sheet.setAutoFilter(new CellRangeAddress(0, 0, 0, lastCol));
long totalDikou = 0;
long totalActual = 0;
int sequence = 0;
for (LbDailyUserTradeReport item : rows) {
Row row = sheet.createRow(r++);
applyRowHeight(row, styles);
RowStylePair rowStyles = rowStyles(item, r, styles, roundValues);
if (sequenceOnLeft) {
sequence++;
createIntCell(row, COL_SEQUENCE_LEFT, sequence, rowStyles.sequenceStyle);
}
writeBaseDataCells(row, item, rowStyles, defaultZero(item.getDikouAmt()), sequenceOnLeft, roundValues);
}
applyDiffNegativeFormatting(sheet, r, sequenceOnLeft);
autoSizeColumns(sheet, lastCol);
}
/**
* 支付表A/B 列含刘瑞华汇总行,与 |F 列总和| 平衡后按 B 列倒序;< 0 写入 E/F 列(升序)。
*/
private static void fillPaymentSheet(
Sheet sheet,
List<LbDailyUserTradeReport> exportRows,
SheetStyles styles,
Map<String, BigDecimal> deductionByUserName) {
boolean roundValues = true;
int r = 0;
Row header = sheet.createRow(r++);
header.setHeightInPoints(20 * styles.rowHeightFactor);
createHeaderCell(header, PAYMENT_COL_NAME, "姓名", styles.headerStyle);
createHeaderCell(header, PAYMENT_COL_ACTUAL, "实际收付", styles.headerStyle);
createHeaderCell(header, PAYMENT_NEGATIVE_COL_NAME, "姓名", styles.headerStyle);
createHeaderCell(header, PAYMENT_NEGATIVE_COL_ACTUAL, "实际收付", styles.headerStyle);
sheet.createFreezePane(0, 1);
sheet.setAutoFilter(new CellRangeAddress(0, 0, 0, PAYMENT_LAST_COL));
List<PaymentAbEntry> abEntries = new ArrayList<>();
List<LbDailyUserTradeReport> negativeRows = new ArrayList<>();
for (LbDailyUserTradeReport item : elevenClockDetailRows(exportRows, deductionByUserName)) {
BigDecimal actual = computeActualReceiptsForDeduction(item, deductionByUserName, roundValues);
if (actual.compareTo(BigDecimal.ZERO) >= 0) {
abEntries.add(PaymentAbEntry.detail(
item.getNickname() == null ? "" : item.getNickname(),
actual,
item));
} else {
negativeRows.add(item);
}
}
abEntries.add(PaymentAbEntry.special(
computeDeductionSheetSummaryActual(exportRows, deductionByUserName)));
balancePaymentSheetColumnTotals(abEntries, negativeRows, deductionByUserName, roundValues);
abEntries.sort((a, b) -> b.sortValue().compareTo(a.sortValue()));
int positiveRow = 1;
for (PaymentAbEntry entry : abEntries) {
Row row = sheet.getRow(positiveRow);
if (row == null) {
row = sheet.createRow(positiveRow);
applyRowHeight(row, styles);
}
if (entry.isSpecialRow()) {
createTextCell(row, PAYMENT_COL_NAME, entry.name(), paymentTextStyle(positiveRow, styles));
createPaymentActualCell(
row, PAYMENT_COL_ACTUAL, entry.actualAbs(), paymentActualStyle(positiveRow, styles));
} else {
RowStylePair rowStyles = rowStyles(entry.detailRow(), positiveRow, styles, roundValues);
createTextCell(row, PAYMENT_COL_NAME, entry.name(), rowStyles.textStyle);
createPaymentActualCell(
row, PAYMENT_COL_ACTUAL, entry.actualAbs(), paymentActualStyle(positiveRow, styles));
}
positiveRow++;
}
negativeRows.sort((a, b) -> computeActualReceiptsForDeduction(a, deductionByUserName, roundValues)
.compareTo(computeActualReceiptsForDeduction(b, deductionByUserName, roundValues)));
int negativeRow = 1;
for (LbDailyUserTradeReport item : negativeRows) {
Row row = sheet.getRow(negativeRow);
if (row == null) {
row = sheet.createRow(negativeRow);
applyRowHeight(row, styles);
}
RowStylePair rowStyles = rowStyles(item, negativeRow, styles, roundValues);
createTextCell(
row,
PAYMENT_NEGATIVE_COL_NAME,
item.getNickname() == null ? "" : item.getNickname(),
rowStyles.textStyle);
createPaymentActualCell(
row,
PAYMENT_NEGATIVE_COL_ACTUAL,
computeActualReceiptsForDeduction(item, deductionByUserName, roundValues),
paymentActualStyle(negativeRow, styles));
negativeRow++;
}
int nextRow = Math.max(positiveRow, negativeRow);
applyPaymentNegativeActualFormatting(sheet, nextRow);
autoSizeColumns(sheet, PAYMENT_LAST_COL);
}
/**
* 支付表检查完整复制支付表末尾追加一行「求和」B、F 列为上方数据行之和。
*/
private static void fillPaymentCheckSheet(Sheet target, Sheet paymentSource, SheetStyles styles) {
copyPaymentSheetContent(paymentSource, target);
BigDecimal sumB = BigDecimal.ZERO;
BigDecimal sumF = BigDecimal.ZERO;
int lastDataRow = paymentSource.getLastRowNum();
for (int rowIdx = 1; rowIdx <= lastDataRow; rowIdx++) {
Row row = paymentSource.getRow(rowIdx);
if (row == null) {
continue;
}
sumB = sumB.add(readNumericCellValue(row.getCell(PAYMENT_COL_ACTUAL)));
sumF = sumF.add(readNumericCellValue(row.getCell(PAYMENT_NEGATIVE_COL_ACTUAL)));
}
int sumRowIdx = lastDataRow + 1;
Row sumRow = target.createRow(sumRowIdx);
applyRowHeight(sumRow, styles);
createTextCell(sumRow, PAYMENT_COL_NAME, "求和", styles.summaryTextStyle);
createPaymentActualCell(sumRow, PAYMENT_COL_ACTUAL, sumB, styles.summaryRoundedAmountStyle);
createPaymentActualCell(sumRow, PAYMENT_NEGATIVE_COL_ACTUAL, sumF, styles.summaryRoundedAmountStyle);
applyPaymentNegativeActualFormatting(target, sumRowIdx);
autoSizeColumns(target, PAYMENT_LAST_COL);
}
private static void copyPaymentSheetContent(Sheet source, Sheet target) {
target.setDefaultRowHeight(source.getDefaultRowHeight());
for (int col = 0; col <= PAYMENT_LAST_COL; col++) {
target.setColumnWidth(col, source.getColumnWidth(col));
}
for (int rowIdx = 0; rowIdx <= source.getLastRowNum(); rowIdx++) {
Row srcRow = source.getRow(rowIdx);
if (srcRow == null) {
continue;
}
Row destRow = target.createRow(rowIdx);
destRow.setHeight(srcRow.getHeight());
for (int col = 0; col <= PAYMENT_LAST_COL; col++) {
Cell srcCell = srcRow.getCell(col);
if (srcCell == null) {
continue;
}
copyCellValueAndStyle(srcCell, destRow.createCell(col));
}
}
target.createFreezePane(0, 1);
target.setAutoFilter(new CellRangeAddress(0, 0, 0, PAYMENT_LAST_COL));
}
private static void copyCellValueAndStyle(Cell source, Cell target) {
target.setCellStyle(source.getCellStyle());
switch (source.getCellType()) {
case STRING -> target.setCellValue(source.getStringCellValue());
case NUMERIC -> target.setCellValue(source.getNumericCellValue());
case BOOLEAN -> target.setCellValue(source.getBooleanCellValue());
case FORMULA -> target.setCellFormula(source.getCellFormula());
case BLANK -> target.setBlank();
default -> {
}
}
}
private static BigDecimal readNumericCellValue(Cell cell) {
if (cell == null || cell.getCellType() == CellType.BLANK) {
return BigDecimal.ZERO;
}
if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) {
return BigDecimal.valueOf(cell.getNumericCellValue());
}
if (cell.getCellType() == CellType.STRING) {
String text = cell.getStringCellValue();
if (text == null || text.isBlank()) {
return BigDecimal.ZERO;
}
try {
return new BigDecimal(text.trim());
} catch (NumberFormatException ignored) {
return BigDecimal.ZERO;
}
}
return BigDecimal.ZERO;
}
/** 抵扣表汇总行「实际收付」= 明细行(抵扣金额 + 原实际收付)之和,与 fillDeductionSheet 口径一致。 */
private static BigDecimal computeDeductionSheetSummaryActual(
List<LbDailyUserTradeReport> exportRows,
Map<String, BigDecimal> deductionByUserName) {
BigDecimal totalActual = BigDecimal.ZERO;
for (LbDailyUserTradeReport item : orderRowsForDeductionSheet(exportRows, deductionByUserName, false)) {
if (DATA_TYPE_REPORT_SUM.equals(item.getDataType())) {
continue;
}
totalActual = totalActual.add(
computeActualReceiptsForDeduction(item, deductionByUserName, false));
}
return totalActual;
}
/** 支付表 B 列展示/排序值:实际收付绝对值四舍五入取整。 */
private static BigDecimal paymentAbDisplayValue(BigDecimal actual) {
return roundHalfUp(defaultZero(actual).abs());
}
/** 支付表 F 列展示值:实际收付四舍五入取整(保留符号,与写入 F 列口径一致)。 */
private static BigDecimal paymentFColumnDisplayValue(BigDecimal actual) {
return roundHalfUp(defaultZero(actual));
}
/**
* 支付表 B/F 列平衡:|F 列总和| 与 B 列总和不一致时,加减刘瑞华 B 列数值使两列总和相等。
*/
private static void balancePaymentSheetColumnTotals(
List<PaymentAbEntry> abEntries,
List<LbDailyUserTradeReport> negativeRows,
Map<String, BigDecimal> deductionByUserName,
boolean roundValues) {
BigDecimal fSum = BigDecimal.ZERO;
for (LbDailyUserTradeReport item : negativeRows) {
BigDecimal actual = computeActualReceiptsForDeduction(item, deductionByUserName, roundValues);
fSum = fSum.add(paymentFColumnDisplayValue(actual));
}
BigDecimal fTotalAbs = fSum.abs();
BigDecimal bSum = BigDecimal.ZERO;
for (PaymentAbEntry entry : abEntries) {
bSum = bSum.add(entry.actualAbs());
}
if (bSum.compareTo(fTotalAbs) == 0) {
return;
}
BigDecimal diff = fTotalAbs.subtract(bSum);
for (int i = 0; i < abEntries.size(); i++) {
PaymentAbEntry entry = abEntries.get(i);
if (entry.isSpecialRow()) {
abEntries.set(i, entry.withActualAbs(entry.actualAbs().add(diff)));
return;
}
}
}
private record PaymentAbEntry(String name, BigDecimal actualAbs, LbDailyUserTradeReport detailRow) {
static PaymentAbEntry detail(String name, BigDecimal actual, LbDailyUserTradeReport detailRow) {
return new PaymentAbEntry(name, paymentAbDisplayValue(actual), detailRow);
}
static PaymentAbEntry special(BigDecimal deductionSummaryActual) {
return new PaymentAbEntry(
PAYMENT_AB_SPECIAL_NAME,
paymentAbDisplayValue(deductionSummaryActual),
null);
}
boolean isSpecialRow() {
return detailRow == null;
}
PaymentAbEntry withActualAbs(BigDecimal newActualAbs) {
return new PaymentAbEntry(name, newActualAbs, detailRow);
}
BigDecimal sortValue() {
return actualAbs;
}
}
private static CellStyle paymentActualDecimalStyle(int rowIndex, SheetStyles styles) {
return (rowIndex % 2 == 0) ? styles.zebraPaymentActualDecimalStyle : styles.paymentActualDecimalStyle;
}
private static CellStyle paymentTextStyle(int rowIndex, SheetStyles styles) {
return (rowIndex % 2 == 0) ? styles.zebraTextStyle : styles.textStyle;
}
/** 11点表 / 支付表共用的明细行:按实际收付倒序,不含汇总行。 */
private static List<LbDailyUserTradeReport> elevenClockDetailRows(
List<LbDailyUserTradeReport> exportRows,
Map<String, BigDecimal> deductionByUserName) {
List<LbDailyUserTradeReport> rows = new ArrayList<>();
for (LbDailyUserTradeReport row : orderRowsForDeductionSheet(exportRows, deductionByUserName, true)) {
if (!DATA_TYPE_REPORT_SUM.equals(row.getDataType())) {
rows.add(row);
}
}
return rows;
}
private static void applyPaymentNegativeActualFormatting(Sheet sheet, int nextRowIndex) {
if (nextRowIndex > 1) {
SheetConditionalFormatting scf = sheet.getSheetConditionalFormatting();
ConditionalFormattingRule negative = scf.createConditionalFormattingRule(ComparisonOperator.LT, "0");
FontFormatting ff = negative.createFontFormatting();
ff.setFontColorIndex(IndexedColors.DARK_RED.getIndex());
ff.setFontStyle(false, false);
scf.addConditionalFormatting(
new CellRangeAddress[]{
new CellRangeAddress(
1, nextRowIndex - 1, PAYMENT_NEGATIVE_COL_ACTUAL, PAYMENT_NEGATIVE_COL_ACTUAL)
},
negative);
}
}
/**
* 导出行序:明细按差额倒序,汇总行固定在最后。
* 若库中无 report_sum则按明细字段聚合生成一条汇总行与 calculateReportSum 口径一致)。
*/
private static List<LbDailyUserTradeReport> prepareExportRows(List<LbDailyUserTradeReport> rows) {
if (rows == null || rows.isEmpty()) {
return List.of();
}
List<LbDailyUserTradeReport> details = new ArrayList<>();
LbDailyUserTradeReport summaryFromDb = null;
for (LbDailyUserTradeReport row : rows) {
if (DATA_TYPE_REPORT_SUM.equals(row.getDataType())) {
summaryFromDb = row;
} else {
details.add(row);
}
}
details.sort((a, b) -> {
BigDecimal da = a.getDiffAmt() == null ? BigDecimal.ZERO : a.getDiffAmt();
BigDecimal db = b.getDiffAmt() == null ? BigDecimal.ZERO : b.getDiffAmt();
return db.compareTo(da);
});
LbDailyUserTradeReport summaryRow =
summaryFromDb != null ? summaryFromDb : (details.isEmpty() ? null : buildSummaryRowFromDetails(details));
List<LbDailyUserTradeReport> ordered = new ArrayList<>(details.size() + (summaryRow == null ? 0 : 1));
ordered.addAll(details);
if (summaryRow != null) {
ordered.add(summaryRow);
}
return ordered;
}
private static LbDailyUserTradeReport buildSummaryRowFromDetails(List<LbDailyUserTradeReport> details) {
BigDecimal yestodayBuyAmt = BigDecimal.ZERO;
BigDecimal dailySellAmt = BigDecimal.ZERO;
BigDecimal dailyBuyAmt = BigDecimal.ZERO;
BigDecimal serviceAmt = BigDecimal.ZERO;
BigDecimal diffAmt = BigDecimal.ZERO;
BigDecimal dikouAmt = BigDecimal.ZERO;
for (LbDailyUserTradeReport row : details) {
yestodayBuyAmt = yestodayBuyAmt.add(defaultZero(row.getYestodayBuyAmt()));
dailySellAmt = dailySellAmt.add(defaultZero(row.getDailySellAmt()));
dailyBuyAmt = dailyBuyAmt.add(defaultZero(row.getDailyBuyAmt()));
serviceAmt = serviceAmt.add(defaultZero(row.getServiceAmt()));
diffAmt = diffAmt.add(defaultZero(row.getDiffAmt()));
dikouAmt = dikouAmt.add(defaultZero(row.getDikouAmt()));
}
LbDailyUserTradeReport summary = new LbDailyUserTradeReport();
summary.setDataType(DATA_TYPE_REPORT_SUM);
summary.setYestodayBuyAmt(yestodayBuyAmt);
summary.setDailySellAmt(dailySellAmt);
summary.setDailyBuyAmt(dailyBuyAmt);
summary.setServiceAmt(serviceAmt);
summary.setDiffAmt(diffAmt);
summary.setDikouAmt(dikouAmt);
summary.setActualReceiptsPayments(diffAmt.subtract(serviceAmt));
return summary;
}
private static BigDecimal defaultZero(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
/**
* 抵扣表 / 11点表先按总表写入各行抵扣金额列改为 lb_deduction_amount 匹配值;
* 「实际收付」= 抵扣金额 + 原实际收付;明细按实际收付倒序。
* 抵扣表最后一行汇总行的抵扣金额、实际收付为本列明细之和11点表不输出汇总行最左侧为序号列数值四舍五入取整。
*/
private static void fillDeductionSheet(
Sheet sheet,
DateHeaderLabels labels,
List<LbDailyUserTradeReport> exportRows,
SheetStyles styles,
Map<String, BigDecimal> deductionByUserName,
boolean sequenceOnLeft,
boolean includeSummaryRow,
boolean roundValues) {
int lastCol = sequenceOnLeft ? ELEVEN_CLOCK_LAST_COL : SUMMARY_LAST_COL;
int r = writeHeaderRow(sheet, labels, styles, sequenceOnLeft);
sheet.createFreezePane(0, 1);
sheet.setAutoFilter(new CellRangeAddress(0, 0, 0, lastCol));
BigDecimal totalDikou = BigDecimal.ZERO;
BigDecimal totalActual = BigDecimal.ZERO;
int sequence = 0;
for (LbDailyUserTradeReport item : orderRowsForDeductionSheet(rows, deductionByUserName)) {
for (LbDailyUserTradeReport item : orderRowsForDeductionSheet(exportRows, deductionByUserName, roundValues)) {
boolean isSummaryRow = DATA_TYPE_REPORT_SUM.equals(item.getDataType());
if (!includeSummaryRow && isSummaryRow) {
continue;
@@ -154,33 +571,31 @@ public final class LbDailyUserTradeReportExportSupport {
Row row = sheet.createRow(r++);
applyRowHeight(row, styles);
RowStylePair rowStyles = rowStyles(item, r, styles);
RowStylePair rowStyles = rowStyles(item, r, styles, roundValues);
// 第1步与总表一致其它列含汇总行均沿用报表原值
long dikouFromReport = roundedInt(item.getDikouAmt());
long actualReceiptsPayments = roundedInt(item.getActualReceiptsPayments());
writeBaseDataCells(row, item, rowStyles, dikouFromReport, sequenceOnLeft);
writeBaseDataCells(row, item, rowStyles, defaultZero(item.getDikouAmt()), sequenceOnLeft, roundValues);
if (isSummaryRow) {
// 汇总行:抵扣金额、实际收付 = 上方明细行之和
createIntCell(row, dataCol(COL_DIKOU, sequenceOnLeft), totalDikou, rowStyles.numberStyle);
createIntCell(row, dataCol(COL_ACTUAL, sequenceOnLeft), totalActual, rowStyles.numberStyle);
createNumericCell(row, dataCol(COL_DIKOU, sequenceOnLeft), totalDikou, rowStyles, roundValues);
createNumericCell(row, dataCol(COL_ACTUAL, sequenceOnLeft), totalActual, rowStyles, roundValues);
} else {
if (sequenceOnLeft) {
sequence++;
createIntCell(row, COL_SEQUENCE_LEFT, sequence, rowStyles.numberStyle);
createIntCell(row, COL_SEQUENCE_LEFT, sequence, rowStyles.sequenceStyle);
}
// 第2步按昵称 + 报表日匹配 lb_deduction_amount覆盖抵扣金额列
long dikouFromDb = roundedInt(lookupDeductionAmt(item.getNickname(), deductionByUserName));
createIntCell(row, dataCol(COL_DIKOU, sequenceOnLeft), dikouFromDb, rowStyles.numberStyle);
BigDecimal dikouFromDb = lookupDeductionAmt(item.getNickname(), deductionByUserName);
createNumericCell(row, dataCol(COL_DIKOU, sequenceOnLeft), dikouFromDb, rowStyles, roundValues);
// 第3步抵扣金额 + 原实际收付,覆盖「实际收付」列
long dikouPlusActual = computeActualReceiptsForDeduction(item, deductionByUserName);
createIntCell(row, dataCol(COL_ACTUAL, sequenceOnLeft), dikouPlusActual, rowStyles.numberStyle);
BigDecimal dikouPlusActual = computeActualReceiptsForDeduction(item, deductionByUserName, roundValues);
createNumericCell(row, dataCol(COL_ACTUAL, sequenceOnLeft), dikouPlusActual, rowStyles, roundValues);
totalDikou += dikouFromDb;
totalActual += dikouPlusActual;
totalDikou = totalDikou.add(dikouFromDb);
totalActual = totalActual.add(dikouPlusActual);
}
}
@@ -188,34 +603,40 @@ public final class LbDailyUserTradeReportExportSupport {
autoSizeColumns(sheet, lastCol);
}
/** 抵扣表:明细按计算后的实际收付倒序,汇总行保持在最后。 */
/** 抵扣表:明细按计算后的实际收付倒序,汇总行保持在最后exportRows 已保证末尾有汇总行)。 */
private static List<LbDailyUserTradeReport> orderRowsForDeductionSheet(
List<LbDailyUserTradeReport> rows,
Map<String, BigDecimal> deductionByUserName) {
List<LbDailyUserTradeReport> exportRows,
Map<String, BigDecimal> deductionByUserName,
boolean roundValues) {
List<LbDailyUserTradeReport> details = new ArrayList<>();
List<LbDailyUserTradeReport> summaries = new ArrayList<>();
for (LbDailyUserTradeReport row : rows) {
LbDailyUserTradeReport summaryRow = null;
for (LbDailyUserTradeReport row : exportRows) {
if (DATA_TYPE_REPORT_SUM.equals(row.getDataType())) {
summaries.add(row);
summaryRow = row;
} else {
details.add(row);
}
}
details.sort((a, b) -> Long.compare(
computeActualReceiptsForDeduction(b, deductionByUserName),
computeActualReceiptsForDeduction(a, deductionByUserName)));
List<LbDailyUserTradeReport> ordered = new ArrayList<>(details.size() + summaries.size());
details.sort((a, b) -> computeActualReceiptsForDeduction(b, deductionByUserName, roundValues)
.compareTo(computeActualReceiptsForDeduction(a, deductionByUserName, roundValues)));
List<LbDailyUserTradeReport> ordered = new ArrayList<>(details.size() + (summaryRow == null ? 0 : 1));
ordered.addAll(details);
ordered.addAll(summaries);
if (summaryRow != null) {
ordered.add(summaryRow);
}
return ordered;
}
private static long computeActualReceiptsForDeduction(
private static BigDecimal computeActualReceiptsForDeduction(
LbDailyUserTradeReport item,
Map<String, BigDecimal> deductionByUserName) {
long dikouFromDb = roundedInt(lookupDeductionAmt(item.getNickname(), deductionByUserName));
long actualReceiptsPayments = roundedInt(item.getActualReceiptsPayments());
return dikouFromDb + actualReceiptsPayments;
Map<String, BigDecimal> deductionByUserName,
boolean roundValues) {
BigDecimal dikou = lookupDeductionAmt(item.getNickname(), deductionByUserName);
BigDecimal actual = defaultZero(item.getActualReceiptsPayments());
if (roundValues) {
return roundHalfUp(dikou).add(roundHalfUp(actual));
}
return dikou.add(actual);
}
private static int dataCol(int col, boolean sequenceOnLeft) {
@@ -248,28 +669,39 @@ public final class LbDailyUserTradeReportExportSupport {
Row row,
LbDailyUserTradeReport item,
RowStylePair rowStyles,
long dikouAmt,
boolean sequenceOnLeft) {
BigDecimal dikouAmt,
boolean sequenceOnLeft,
boolean roundValues) {
createTextCell(
row,
dataCol(COL_NICKNAME, sequenceOnLeft),
item.getNickname() == null ? "" : item.getNickname(),
rowStyles.textStyle);
createIntCell(row, dataCol(COL_YESTODAY_BUY, sequenceOnLeft), roundedInt(item.getYestodayBuyAmt()), rowStyles.numberStyle);
createIntCell(row, dataCol(COL_DAILY_SELL, sequenceOnLeft), roundedInt(item.getDailySellAmt()), rowStyles.numberStyle);
createIntCell(row, dataCol(COL_DAILY_BUY, sequenceOnLeft), roundedInt(item.getDailyBuyAmt()), rowStyles.numberStyle);
createIntCell(row, dataCol(COL_SERVICE, sequenceOnLeft), roundedInt(item.getServiceAmt()), rowStyles.numberStyle);
createIntCell(row, dataCol(COL_DIFF, sequenceOnLeft), roundedInt(item.getDiffAmt()), rowStyles.numberStyle);
createIntCell(row, dataCol(COL_ACTUAL, sequenceOnLeft), roundedInt(item.getActualReceiptsPayments()), rowStyles.numberStyle);
createIntCell(row, dataCol(COL_DIKOU, sequenceOnLeft), dikouAmt, rowStyles.numberStyle);
createNumericCell(row, dataCol(COL_YESTODAY_BUY, sequenceOnLeft), item.getYestodayBuyAmt(), rowStyles, roundValues);
createNumericCell(row, dataCol(COL_DAILY_SELL, sequenceOnLeft), item.getDailySellAmt(), rowStyles, roundValues);
createNumericCell(row, dataCol(COL_DAILY_BUY, sequenceOnLeft), item.getDailyBuyAmt(), rowStyles, roundValues);
createNumericCell(row, dataCol(COL_SERVICE, sequenceOnLeft), item.getServiceAmt(), rowStyles, roundValues);
createNumericCell(row, dataCol(COL_DIFF, sequenceOnLeft), item.getDiffAmt(), rowStyles, roundValues);
createNumericCell(row, dataCol(COL_ACTUAL, sequenceOnLeft), item.getActualReceiptsPayments(), rowStyles, roundValues);
createNumericCell(row, dataCol(COL_DIKOU, sequenceOnLeft), dikouAmt, rowStyles, roundValues);
}
private static RowStylePair rowStyles(LbDailyUserTradeReport item, int rowIndex, SheetStyles styles) {
private static RowStylePair rowStyles(LbDailyUserTradeReport item, int rowIndex, SheetStyles styles, boolean roundValues) {
boolean isSummary = DATA_TYPE_REPORT_SUM.equals(item.getDataType());
boolean zebra = (rowIndex % 2 == 0);
CellStyle textStyle = isSummary ? styles.summaryTextStyle : (zebra ? styles.zebraTextStyle : styles.textStyle);
CellStyle numberStyle = isSummary ? styles.summaryIntStyle : (zebra ? styles.zebraIntStyle : styles.intStyle);
return new RowStylePair(textStyle, numberStyle);
CellStyle numberStyle;
if (roundValues) {
numberStyle = isSummary
? styles.summaryRoundedAmountStyle
: (zebra ? styles.zebraRoundedAmountStyle : styles.roundedAmountStyle);
} else {
numberStyle = isSummary
? styles.summaryAmountStyle
: (zebra ? styles.zebraAmountStyle : styles.amountStyle);
}
CellStyle sequenceStyle = isSummary ? styles.summarySequenceStyle : (zebra ? styles.zebraSequenceStyle : styles.sequenceStyle);
return new RowStylePair(textStyle, numberStyle, sequenceStyle);
}
private static BigDecimal lookupDeductionAmt(String nickname, Map<String, BigDecimal> deductionByUserName) {
@@ -317,13 +749,6 @@ public final class LbDailyUserTradeReportExportSupport {
}
}
private static long roundedInt(BigDecimal value) {
if (value == null) {
return 0L;
}
return value.setScale(0, RoundingMode.HALF_UP).longValue();
}
private static void createHeaderCell(Row row, int col, String text, CellStyle style) {
Cell cell = row.createCell(col);
cell.setCellValue(text);
@@ -342,6 +767,37 @@ public final class LbDailyUserTradeReportExportSupport {
cell.setCellStyle(style);
}
private static void createDecimalCell(Row row, int col, BigDecimal value, CellStyle style) {
Cell cell = row.createCell(col);
cell.setCellValue(defaultZero(value).doubleValue());
cell.setCellStyle(style);
}
private static CellStyle paymentActualStyle(int rowIndex, SheetStyles styles) {
return (rowIndex % 2 == 0) ? styles.zebraPaymentActualStyle : styles.paymentActualStyle;
}
private static void createPaymentActualCell(Row row, int col, BigDecimal value, CellStyle style) {
createIntCell(row, col, roundHalfUp(value).longValue(), style);
}
private static void createNumericCell(
Row row,
int col,
BigDecimal value,
RowStylePair rowStyles,
boolean roundValues) {
if (roundValues) {
createIntCell(row, col, roundHalfUp(value).longValue(), rowStyles.numberStyle);
} else {
createDecimalCell(row, col, value, rowStyles.numberStyle);
}
}
private static BigDecimal roundHalfUp(BigDecimal value) {
return defaultZero(value).setScale(0, RoundingMode.HALF_UP);
}
private record DateHeaderLabels(String yestodayBuyLabel, String dailySellLabel, String dailyBuyLabel) {
static DateHeaderLabels of(LocalDate reportDate) {
DateTimeFormatter mmdd = DateTimeFormatter.ofPattern("MM-dd");
@@ -351,7 +807,7 @@ public final class LbDailyUserTradeReportExportSupport {
}
}
private record RowStylePair(CellStyle textStyle, CellStyle numberStyle) {
private record RowStylePair(CellStyle textStyle, CellStyle numberStyle, CellStyle sequenceStyle) {
}
private static final class SheetStyles {
@@ -359,26 +815,56 @@ public final class LbDailyUserTradeReportExportSupport {
final CellStyle headerStyle;
final CellStyle textStyle;
final CellStyle zebraTextStyle;
final CellStyle intStyle;
final CellStyle zebraIntStyle;
final CellStyle amountStyle;
final CellStyle zebraAmountStyle;
final CellStyle roundedAmountStyle;
final CellStyle zebraRoundedAmountStyle;
final CellStyle paymentActualStyle;
final CellStyle zebraPaymentActualStyle;
final CellStyle paymentActualDecimalStyle;
final CellStyle zebraPaymentActualDecimalStyle;
final CellStyle sequenceStyle;
final CellStyle zebraSequenceStyle;
final CellStyle summaryTextStyle;
final CellStyle summaryIntStyle;
final CellStyle summaryAmountStyle;
final CellStyle summaryRoundedAmountStyle;
final CellStyle summarySequenceStyle;
private SheetStyles(
CellStyle headerStyle,
CellStyle textStyle,
CellStyle zebraTextStyle,
CellStyle intStyle,
CellStyle zebraIntStyle,
CellStyle amountStyle,
CellStyle zebraAmountStyle,
CellStyle roundedAmountStyle,
CellStyle zebraRoundedAmountStyle,
CellStyle paymentActualStyle,
CellStyle zebraPaymentActualStyle,
CellStyle paymentActualDecimalStyle,
CellStyle zebraPaymentActualDecimalStyle,
CellStyle sequenceStyle,
CellStyle zebraSequenceStyle,
CellStyle summaryTextStyle,
CellStyle summaryIntStyle) {
CellStyle summaryAmountStyle,
CellStyle summaryRoundedAmountStyle,
CellStyle summarySequenceStyle) {
this.headerStyle = headerStyle;
this.textStyle = textStyle;
this.zebraTextStyle = zebraTextStyle;
this.intStyle = intStyle;
this.zebraIntStyle = zebraIntStyle;
this.amountStyle = amountStyle;
this.zebraAmountStyle = zebraAmountStyle;
this.roundedAmountStyle = roundedAmountStyle;
this.zebraRoundedAmountStyle = zebraRoundedAmountStyle;
this.paymentActualStyle = paymentActualStyle;
this.zebraPaymentActualStyle = zebraPaymentActualStyle;
this.paymentActualDecimalStyle = paymentActualDecimalStyle;
this.zebraPaymentActualDecimalStyle = zebraPaymentActualDecimalStyle;
this.sequenceStyle = sequenceStyle;
this.zebraSequenceStyle = zebraSequenceStyle;
this.summaryTextStyle = summaryTextStyle;
this.summaryIntStyle = summaryIntStyle;
this.summaryAmountStyle = summaryAmountStyle;
this.summaryRoundedAmountStyle = summaryRoundedAmountStyle;
this.summarySequenceStyle = summarySequenceStyle;
}
static SheetStyles create(Workbook workbook) {
@@ -410,22 +896,58 @@ public final class LbDailyUserTradeReportExportSupport {
zebraTextStyle.cloneStyleFrom(textStyle);
zebraTextStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
CellStyle intStyle = workbook.createCellStyle();
intStyle.setAlignment(HorizontalAlignment.RIGHT);
intStyle.setVerticalAlignment(VerticalAlignment.CENTER);
intStyle.setDataFormat(dataFormat.getFormat("#,##0"));
intStyle.setBorderTop(BorderStyle.THIN);
intStyle.setBorderBottom(BorderStyle.THIN);
intStyle.setBorderLeft(BorderStyle.THIN);
intStyle.setBorderRight(BorderStyle.THIN);
CellStyle amountStyle = workbook.createCellStyle();
amountStyle.setAlignment(HorizontalAlignment.RIGHT);
amountStyle.setVerticalAlignment(VerticalAlignment.CENTER);
amountStyle.setDataFormat(dataFormat.getFormat("#,##0.##########"));
amountStyle.setBorderTop(BorderStyle.THIN);
amountStyle.setBorderBottom(BorderStyle.THIN);
amountStyle.setBorderLeft(BorderStyle.THIN);
amountStyle.setBorderRight(BorderStyle.THIN);
CellStyle zebraIntStyle = workbook.createCellStyle();
zebraIntStyle.cloneStyleFrom(intStyle);
zebraIntStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
CellStyle zebraAmountStyle = workbook.createCellStyle();
zebraAmountStyle.cloneStyleFrom(amountStyle);
zebraAmountStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
CellStyle roundedAmountStyle = workbook.createCellStyle();
roundedAmountStyle.cloneStyleFrom(amountStyle);
roundedAmountStyle.setDataFormat(dataFormat.getFormat("#,##0"));
CellStyle zebraRoundedAmountStyle = workbook.createCellStyle();
zebraRoundedAmountStyle.cloneStyleFrom(roundedAmountStyle);
zebraRoundedAmountStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
CellStyle paymentActualStyle = workbook.createCellStyle();
paymentActualStyle.cloneStyleFrom(roundedAmountStyle);
paymentActualStyle.setDataFormat(dataFormat.getFormat("0"));
CellStyle zebraPaymentActualStyle = workbook.createCellStyle();
zebraPaymentActualStyle.cloneStyleFrom(paymentActualStyle);
zebraPaymentActualStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
CellStyle paymentActualDecimalStyle = workbook.createCellStyle();
paymentActualDecimalStyle.cloneStyleFrom(paymentActualStyle);
paymentActualDecimalStyle.setDataFormat(dataFormat.getFormat("0.##########"));
CellStyle zebraPaymentActualDecimalStyle = workbook.createCellStyle();
zebraPaymentActualDecimalStyle.cloneStyleFrom(paymentActualDecimalStyle);
zebraPaymentActualDecimalStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
CellStyle sequenceStyle = workbook.createCellStyle();
sequenceStyle.cloneStyleFrom(amountStyle);
sequenceStyle.setDataFormat(dataFormat.getFormat("#,##0"));
CellStyle zebraSequenceStyle = workbook.createCellStyle();
zebraSequenceStyle.cloneStyleFrom(sequenceStyle);
zebraSequenceStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
XSSFColor zebraFill = new XSSFColor(new Color(242, 244, 248), null);
((XSSFCellStyle) zebraTextStyle).setFillForegroundColor(zebraFill);
((XSSFCellStyle) zebraIntStyle).setFillForegroundColor(zebraFill);
((XSSFCellStyle) zebraAmountStyle).setFillForegroundColor(zebraFill);
((XSSFCellStyle) zebraRoundedAmountStyle).setFillForegroundColor(zebraFill);
((XSSFCellStyle) zebraPaymentActualStyle).setFillForegroundColor(zebraFill);
((XSSFCellStyle) zebraPaymentActualDecimalStyle).setFillForegroundColor(zebraFill);
((XSSFCellStyle) zebraSequenceStyle).setFillForegroundColor(zebraFill);
CellStyle summaryTextStyle = workbook.createCellStyle();
summaryTextStyle.cloneStyleFrom(textStyle);
@@ -436,15 +958,45 @@ public final class LbDailyUserTradeReportExportSupport {
summaryTextStyle.setFont(summaryFont);
summaryTextStyle.setBorderTop(BorderStyle.THIN);
CellStyle summaryIntStyle = workbook.createCellStyle();
summaryIntStyle.cloneStyleFrom(intStyle);
summaryIntStyle.setFillForegroundColor(IndexedColors.LEMON_CHIFFON.getIndex());
summaryIntStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
summaryIntStyle.setFont(summaryFont);
summaryIntStyle.setBorderTop(BorderStyle.THIN);
CellStyle summaryAmountStyle = workbook.createCellStyle();
summaryAmountStyle.cloneStyleFrom(amountStyle);
summaryAmountStyle.setFillForegroundColor(IndexedColors.LEMON_CHIFFON.getIndex());
summaryAmountStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
summaryAmountStyle.setFont(summaryFont);
summaryAmountStyle.setBorderTop(BorderStyle.THIN);
CellStyle summaryRoundedAmountStyle = workbook.createCellStyle();
summaryRoundedAmountStyle.cloneStyleFrom(roundedAmountStyle);
summaryRoundedAmountStyle.setFillForegroundColor(IndexedColors.LEMON_CHIFFON.getIndex());
summaryRoundedAmountStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
summaryRoundedAmountStyle.setFont(summaryFont);
summaryRoundedAmountStyle.setBorderTop(BorderStyle.THIN);
CellStyle summarySequenceStyle = workbook.createCellStyle();
summarySequenceStyle.cloneStyleFrom(sequenceStyle);
summarySequenceStyle.setFillForegroundColor(IndexedColors.LEMON_CHIFFON.getIndex());
summarySequenceStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
summarySequenceStyle.setFont(summaryFont);
summarySequenceStyle.setBorderTop(BorderStyle.THIN);
return new SheetStyles(
headerStyle, textStyle, zebraTextStyle, intStyle, zebraIntStyle, summaryTextStyle, summaryIntStyle);
headerStyle,
textStyle,
zebraTextStyle,
amountStyle,
zebraAmountStyle,
roundedAmountStyle,
zebraRoundedAmountStyle,
paymentActualStyle,
zebraPaymentActualStyle,
paymentActualDecimalStyle,
zebraPaymentActualDecimalStyle,
sequenceStyle,
zebraSequenceStyle,
summaryTextStyle,
summaryAmountStyle,
summaryRoundedAmountStyle,
summarySequenceStyle);
}
}
}