修改账单模板

This commit is contained in:
caojia
2026-10-08 16:51:53 +08:00
parent 111678a07c
commit fea34d1d10
@@ -102,6 +102,15 @@ public class ArBillServiceImpl implements IArBillService {
private static final int TEMPLATE_DATA_START_ROW = 5;
private static final int TEMPLATE_FOOTER_START_ROW = 5;
private static final int TEMPLATE_COLUMN_COUNT = 20;
/** 客户订单号、运单号位于 Excel 的 C、D 列,按给定样例长度并额外保留两字符边距。 */
private static final int CUSTOMER_ORDER_NO_COLUMN_INDEX = 3;
private static final int TRACKING_NO_COLUMN_INDEX = 4;
private static final int FORMULA_COLUMN_INDEX = 16;
private static final int FEE_TYPE_COLUMN_INDEX = 17;
private static final double MIN_CUSTOMER_ORDER_NO_COLUMN_WIDTH = 20.0;
private static final double MIN_TRACKING_NO_COLUMN_WIDTH = 35.0;
private static final double MIN_FORMULA_COLUMN_WIDTH = 25.0;
private static final double MIN_FEE_TYPE_COLUMN_WIDTH = 25.0;
/**
* 单次查询订单数;费用仅查询当前订单批次,避免大账单占满堆内存。
*/
@@ -122,6 +131,10 @@ public class ArBillServiceImpl implements IArBillService {
*/
private static final int TEMPLATE_DETAIL_STYLE_INDEX = 31;
private static final String TEMPLATE_SHEET_XML = "xl/worksheets/sheet1.xml";
private static final Pattern TEMPLATE_COLUMN_PATTERN = Pattern.compile("<col\\b([^>]*)/\\s*>", Pattern.DOTALL);
private static final Pattern TEMPLATE_COLUMN_MIN_PATTERN = Pattern.compile("\\bmin\\s*=\\s*[\"'](\\d+)[\"']");
private static final Pattern TEMPLATE_COLUMN_MAX_PATTERN = Pattern.compile("\\bmax\\s*=\\s*[\"'](\\d+)[\"']");
private static final Pattern TEMPLATE_COLUMN_WIDTH_PATTERN = Pattern.compile("\\bwidth\\s*=\\s*[\"']([\\d.]+)[\"']");
private static final Pattern TEMPLATE_ROW_PATTERN = Pattern.compile(
"<row\\b[^>]*\\br=\"(\\d+)\"[^>]*>.*?</row>", Pattern.DOTALL);
private static final Pattern CELL_PATTERN = Pattern.compile(
@@ -1286,7 +1299,8 @@ public class ArBillServiceImpl implements IArBillService {
}
Map<Integer, String> templateRows = extractTemplateRows(templateXml.substring(sheetDataOpenEnd + 1, sheetDataEnd));
String prefix = removeSheetDimension(templateXml.substring(0, sheetDataOpenEnd + 1));
String prefix = adjustExportColumnWidths(
removeSheetDimension(templateXml.substring(0, sheetDataOpenEnd + 1)));
writeUtf8(output, prefix);
String headerRow = replaceCellXml(templateRows.get(1), "A1",
@@ -1319,6 +1333,101 @@ public class ArBillServiceImpl implements IArBillService {
writeUtf8(output, suffix);
}
/**
* 仅加宽客户订单号、运单号、计算公式和费用类型列;不缩小模板原有列宽。
*/
private String adjustExportColumnWidths(String sheetPrefix) throws IOException {
Matcher columnMatcher = TEMPLATE_COLUMN_PATTERN.matcher(sheetPrefix);
StringBuffer result = new StringBuffer();
int[] targetColumns = {
CUSTOMER_ORDER_NO_COLUMN_INDEX,
TRACKING_NO_COLUMN_INDEX,
FORMULA_COLUMN_INDEX,
FEE_TYPE_COLUMN_INDEX
};
double[] minimumWidths = {
MIN_CUSTOMER_ORDER_NO_COLUMN_WIDTH,
MIN_TRACKING_NO_COLUMN_WIDTH,
MIN_FORMULA_COLUMN_WIDTH,
MIN_FEE_TYPE_COLUMN_WIDTH
};
boolean[] targetColumnsFound = new boolean[targetColumns.length];
while (columnMatcher.find()) {
String attributes = columnMatcher.group(1);
Matcher minMatcher = TEMPLATE_COLUMN_MIN_PATTERN.matcher(attributes);
Matcher maxMatcher = TEMPLATE_COLUMN_MAX_PATTERN.matcher(attributes);
Matcher widthMatcher = TEMPLATE_COLUMN_WIDTH_PATTERN.matcher(attributes);
if (!minMatcher.find() || !maxMatcher.find() || !widthMatcher.find()) {
columnMatcher.appendReplacement(result, Matcher.quoteReplacement(columnMatcher.group()));
continue;
}
int min = Integer.parseInt(minMatcher.group(1));
int max = Integer.parseInt(maxMatcher.group(1));
double currentWidth = Double.parseDouble(widthMatcher.group(1));
boolean needsExpansion = false;
for (int index = 0; index < targetColumns.length; index++) {
if (min <= targetColumns[index] && max >= targetColumns[index]) {
targetColumnsFound[index] = true;
needsExpansion |= currentWidth < minimumWidths[index];
}
}
if (!needsExpansion) {
columnMatcher.appendReplacement(result, Matcher.quoteReplacement(columnMatcher.group()));
continue;
}
StringBuilder replacement = new StringBuilder();
int segmentStart = min;
for (int index = 0; index < targetColumns.length; index++) {
int column = targetColumns[index];
if (column < min || column > max || currentWidth >= minimumWidths[index]) {
continue;
}
if (segmentStart < column) {
appendColumnSegment(replacement, attributes, segmentStart, column - 1, currentWidth, false);
}
appendColumnSegment(replacement, attributes, column, column, minimumWidths[index], true);
segmentStart = column + 1;
}
if (segmentStart <= max) {
appendColumnSegment(replacement, attributes, segmentStart, max, currentWidth, false);
}
columnMatcher.appendReplacement(result, Matcher.quoteReplacement(replacement.toString()));
}
columnMatcher.appendTail(result);
for (boolean found : targetColumnsFound) {
if (!found) {
throw new IOException("应收账单导出模板缺少客户订单号、运单号、计算公式或费用类型列宽定义");
}
}
return result.toString();
}
private void appendColumnSegment(StringBuilder xml, String attributes, int min, int max, double width,
boolean customWidth) {
String segmentAttributes = replaceColumnAttribute(attributes, "min", String.valueOf(min));
segmentAttributes = replaceColumnAttribute(segmentAttributes, "max", String.valueOf(max));
segmentAttributes = replaceColumnAttribute(segmentAttributes, "width", String.valueOf(width));
if (customWidth) {
segmentAttributes = replaceColumnAttribute(segmentAttributes, "customWidth", "1");
}
xml.append("<col").append(segmentAttributes).append("/>");
}
private String replaceColumnAttribute(String attributes, String name, String value) {
Pattern attributePattern = Pattern.compile("(\\b" + name + "\\s*=\\s*[\"'])([^\"']*)([\"'])",
Pattern.CASE_INSENSITIVE);
Matcher matcher = attributePattern.matcher(attributes);
if (matcher.find()) {
String replacement = matcher.group(1) + value + matcher.group(3);
return matcher.replaceFirst(Matcher.quoteReplacement(replacement));
}
return attributes + " " + name + "=\"" + value + "\"";
}
private Map<Integer, String> extractTemplateRows(String sheetData) {
Map<Integer, String> rows = new HashMap<>();
Matcher matcher = TEMPLATE_ROW_PATTERN.matcher(sheetData);