应付账单初始化

This commit is contained in:
caojia
2026-11-04 16:04:38 +08:00
committed by liuminghui
parent 04be15deab
commit 51a89b18e1
90 changed files with 15661 additions and 6 deletions
@@ -0,0 +1,290 @@
package com.bms.common.utils.poi;
import java.math.BigDecimal;
import java.util.Date;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Hyperlink;
import org.apache.poi.xssf.usermodel.XSSFHyperlink;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import com.bms.common.utils.DateUtils;
/**
* 把一个工作表读成文本网格。
* 合并单元格只保留左上角的原文,不把国家、处理费、超重费再填到每一段重量上。
*/
public class ExcelGrid
{
/** 单表最多读取的行数,避免样式撑满的空行把内存打满 */
public static final int MAX_ROWS = 2500;
/** 单表最多读取的列数。价卡分区列不会超过这个宽度 */
public static final int MAX_COLS = 60;
private final String[][] text;
private final Double[][] number;
private final String[][] linkSheet;
/** 合并区域左上角横向占几列,其余单元格为 0 */
private final int[][] colSpan;
private final int rows;
private final int cols;
private ExcelGrid(String[][] text, Double[][] number, String[][] linkSheet, int[][] colSpan, int rows, int cols)
{
this.text = text;
this.number = number;
this.linkSheet = linkSheet;
this.colSpan = colSpan;
this.rows = rows;
this.cols = cols;
}
/**
* 读取工作表。日期写成 yyyy-MM-dd,不把日期序列号当成金额。
*/
public static ExcelGrid read(Sheet sheet)
{
return read(sheet, MAX_ROWS);
}
/**
* 按指定行数上限读取。邮编分区表会超过价卡默认的 2500 行。
*/
public static ExcelGrid read(Sheet sheet, int maxRows)
{
int limit = maxRows < 1 ? MAX_ROWS : maxRows;
int lastRow = sheet == null || sheet.getLastRowNum() < 0 ? -1 : Math.min(sheet.getLastRowNum(), limit - 1);
int cols = 0;
if (sheet != null)
{
for (int r = 0; r <= lastRow; r++)
{
Row row = sheet.getRow(r);
if (row == null || row.getLastCellNum() <= 0)
{
continue;
}
int lastCell = Math.min(row.getLastCellNum(), MAX_COLS);
for (int c = 0; c < lastCell; c++)
{
Cell cell = row.getCell(c);
if (hasValue(cell) && c + 1 > cols)
{
cols = c + 1;
}
}
}
}
int rows = lastRow + 1;
String[][] text = new String[Math.max(rows, 0)][Math.max(cols, 0)];
Double[][] number = new Double[Math.max(rows, 0)][Math.max(cols, 0)];
String[][] linkSheet = new String[Math.max(rows, 0)][Math.max(cols, 0)];
int[][] colSpan = new int[Math.max(rows, 0)][Math.max(cols, 0)];
if (sheet != null && rows > 0 && cols > 0)
{
for (int r = 0; r < rows; r++)
{
Row row = sheet.getRow(r);
if (row == null)
{
continue;
}
for (int c = 0; c < cols; c++)
{
fill(row.getCell(c), text, number, linkSheet, r, c);
}
}
for (CellRangeAddress region : sheet.getMergedRegions())
{
int r0 = Math.max(region.getFirstRow(), 0);
int c0 = Math.max(region.getFirstColumn(), 0);
if (r0 >= rows || c0 >= cols)
{
continue;
}
int width = region.getLastColumn() - region.getFirstColumn() + 1;
if (width > 1)
{
colSpan[r0][c0] = width;
}
}
}
return new ExcelGrid(text, number, linkSheet, colSpan, rows, cols);
}
public int rows()
{
return rows;
}
public int cols()
{
return cols;
}
public String text(int row, int col)
{
if (row < 0 || col < 0 || row >= rows || col >= cols)
{
return "";
}
return text[row][col] == null ? "" : text[row][col];
}
public Double number(int row, int col)
{
if (row < 0 || col < 0 || row >= rows || col >= cols)
{
return null;
}
return number[row][col];
}
/** 单元格内部超链接指向的工作表名,没有链接时返回空串 */
public String linkSheet(int row, int col)
{
if (row < 0 || col < 0 || row >= rows || col >= cols || linkSheet[row][col] == null)
{
return "";
}
return linkSheet[row][col];
}
/** 该格是横向合并的左上角时,返回合并了几列;普通格返回 1 */
public int colSpan(int row, int col)
{
if (row < 0 || col < 0 || row >= rows || col >= cols)
{
return 1;
}
return colSpan[row][col] > 1 ? colSpan[row][col] : 1;
}
public boolean blankRow(int row)
{
if (row < 0 || row >= rows)
{
return true;
}
for (int c = 0; c < cols; c++)
{
if (!isBlank(text[row][c]))
{
return false;
}
}
return true;
}
private static void fill(Cell cell, String[][] text, Double[][] number, String[][] linkSheet, int r, int c)
{
if (cell == null)
{
return;
}
linkSheet[r][c] = sheetName(cell.getHyperlink());
CellType type = cell.getCellType();
if (type == CellType.FORMULA)
{
type = cell.getCachedFormulaResultType();
}
if (type == CellType.NUMERIC)
{
if (DateUtil.isCellDateFormatted(cell))
{
Date date = cell.getDateCellValue();
text[r][c] = date == null ? "" : DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, date);
return;
}
double raw = cell.getNumericCellValue();
number[r][c] = raw;
text[r][c] = BigDecimal.valueOf(raw).stripTrailingZeros().toPlainString();
return;
}
if (type == CellType.STRING)
{
text[r][c] = clean(cell.getStringCellValue());
number[r][c] = plainNumber(text[r][c]);
return;
}
if (type == CellType.BOOLEAN)
{
text[r][c] = Boolean.toString(cell.getBooleanCellValue());
}
}
private static boolean hasValue(Cell cell)
{
if (cell == null || cell.getCellType() == CellType.BLANK)
{
return false;
}
if (cell.getCellType() == CellType.STRING)
{
return !isBlank(cell.getStringCellValue());
}
return true;
}
private static Double plainNumber(String text)
{
if (text == null || !text.matches("-?\\d+(\\.\\d+)?"))
{
return null;
}
try
{
return Double.valueOf(text);
}
catch (NumberFormatException ex)
{
return null;
}
}
/** 内部链接形如 目录!A1 或 'evri 偏远'!A1,这里只留工作表名 */
static String sheetName(Hyperlink link)
{
if (link == null)
{
return "";
}
String location = link instanceof XSSFHyperlink ? ((XSSFHyperlink) link).getLocation() : "";
if (isBlank(location))
{
location = link.getAddress();
}
if (isBlank(location) || !location.contains("!"))
{
return "";
}
String name = location.substring(0, location.lastIndexOf('!')).trim();
if (name.startsWith("'") && name.endsWith("'") && name.length() >= 2)
{
name = name.substring(1, name.length() - 1).replace("''", "'");
}
return name.trim();
}
private static String clean(String value)
{
if (value == null)
{
return "";
}
return value.replace('\u00A0', ' ').replace('\r', ' ').trim();
}
private static boolean isBlank(String value)
{
return value == null || value.trim().isEmpty();
}
}