让成交卡等报价文件真正发出后再出现,并收紧 PDF 顶部留白,避免抬头被裁或空一截。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+154
-8
@@ -191,6 +191,11 @@ public final class QuoteTemplateExcelSupport {
|
||||
writeFeeRows(wb, mapping, facts, yellow);
|
||||
// 后台常未配 fee_rows:按「空运费」等名称写入金额列,SUM 才能加算
|
||||
writeFeeAmountsByName(wb, facts, yellow);
|
||||
// 模板「预估总价」是 SUM。POI 写完数字不会重算,缓存仍是 0。
|
||||
// 企微/WPS 预览常不重算,销售会看到汇总为 0。这里把公式缓存刷成当前值。
|
||||
refreshFormulas(wb);
|
||||
// 转 PDF 前把纸张改成 A4 居中,避免顶部裁字、整页偏左。
|
||||
applyQuotePrintLayout(wb);
|
||||
Files.createDirectories(targetXlsx.getParent());
|
||||
try (OutputStream out = Files.newOutputStream(targetXlsx)) {
|
||||
wb.write(out);
|
||||
@@ -252,7 +257,62 @@ public final class QuoteTemplateExcelSupport {
|
||||
if (cell == null) {
|
||||
cell = row.createCell(col);
|
||||
}
|
||||
cell.setCellValue(value == null ? "" : value);
|
||||
writeNumericOrText(cell, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额/数量/单价写成数字,SUM 才能加算;名称等仍写文本。
|
||||
*/
|
||||
private static void writeNumericOrText(Cell cell, String value) {
|
||||
if (value == null) {
|
||||
cell.setCellValue("");
|
||||
return;
|
||||
}
|
||||
String norm = stripMoneyPrefix(value);
|
||||
if (StringUtils.isBlank(norm) || "-".equals(norm)) {
|
||||
cell.setCellValue(value);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
cell.setCellValue(Double.parseDouble(norm));
|
||||
} catch (NumberFormatException e) {
|
||||
cell.setCellValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填后重算公式,把 SUM 等缓存从 0 刷成真实合计。
|
||||
* 失败不抛:出单仍要发出,只记日志。
|
||||
*/
|
||||
static void refreshFormulas(Workbook wb) {
|
||||
if (wb == null) {
|
||||
return;
|
||||
}
|
||||
FormulaEvaluator ev = wb.getCreationHelper().createFormulaEvaluator();
|
||||
for (int s = 0; s < wb.getNumberOfSheets(); s++) {
|
||||
Sheet sheet = wb.getSheetAt(s);
|
||||
if (sheet == null) {
|
||||
continue;
|
||||
}
|
||||
for (Row row : sheet) {
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
for (Cell cell : row) {
|
||||
if (cell == null || cell.getCellType() != CellType.FORMULA) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ev.evaluateFormulaCell(cell);
|
||||
} catch (Exception e) {
|
||||
log.warn("报价公式未重算 {}!{}: {}",
|
||||
sheet.getSheetName(),
|
||||
new CellReference(cell).formatAsString(),
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearCell(Workbook wb, String cellRef) {
|
||||
@@ -493,6 +553,23 @@ public final class QuoteTemplateExcelSupport {
|
||||
return fuzzy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用明细表头原字,回填系统字段时不得覆盖。
|
||||
*/
|
||||
static boolean isFeeTableHeader(String text) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return false;
|
||||
}
|
||||
String t = text.trim();
|
||||
return "项目".equals(t) || "费用名称".equals(t) || "费用名".equals(t)
|
||||
|| "单位".equals(t) || "單位".equals(t)
|
||||
|| "币别".equals(t) || "币種".equals(t) || "币种".equals(t)
|
||||
|| "单价".equals(t) || "單價".equals(t)
|
||||
|| "数量".equals(t) || "數量".equals(t)
|
||||
|| "金额".equals(t) || "金額".equals(t)
|
||||
|| "备注".equals(t) || "備註".equals(t);
|
||||
}
|
||||
|
||||
private static String readCellText(Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
@@ -585,6 +662,10 @@ public final class QuoteTemplateExcelSupport {
|
||||
String text = value == null ? "" : value;
|
||||
// 标签格「报价编号:」只换冒号后的值,避免整格被工单号顶掉看起来像残缺
|
||||
String existing = readCellText(cell);
|
||||
// 「币别」右侧常被误映到「单价」表头,不能把 CNY 盖掉单价二字
|
||||
if (isFeeTableHeader(existing)) {
|
||||
return;
|
||||
}
|
||||
int colon = Math.max(existing.indexOf(':'), existing.indexOf(':'));
|
||||
if (colon >= 0 && colon == existing.length() - 1) {
|
||||
text = existing + text;
|
||||
@@ -599,13 +680,78 @@ public final class QuoteTemplateExcelSupport {
|
||||
copy.setDataFormat(wb.createDataFormat().getFormat("@"));
|
||||
cell.setCellStyle(copy);
|
||||
}
|
||||
// 工单号、日期略加宽,避免列太窄只露出半截
|
||||
if (dest[1] >= 0 && dest[1] < 20 && text.length() >= 8) {
|
||||
org.apache.poi.ss.usermodel.Sheet sh = sheet;
|
||||
int col = dest[1];
|
||||
double width = sh.getColumnWidth(col) / 256.0;
|
||||
if (width > 0 && width < text.length() + 2) {
|
||||
sh.setColumnWidth(col, (int) Math.min(40, text.length() + 4) * 256);
|
||||
// 不再临时加宽列:会把打印缩放打乱,转 PDF 后整页偏左。
|
||||
}
|
||||
|
||||
/**
|
||||
* 报价单打印版心:A4、水平居中、按页宽缩放。
|
||||
* <p>
|
||||
* 空运模板常是 62% 缩放且不居中。LibreOffice 按这个出 PDF,
|
||||
* 公司名会被裁掉上半截,正文整页靠左。出单时统一改过来。
|
||||
* 只改打印属性,不改单元格里的业务数字。
|
||||
*/
|
||||
static void applyQuotePrintLayout(Workbook wb) {
|
||||
if (wb == null) {
|
||||
return;
|
||||
}
|
||||
for (int s = 0; s < wb.getNumberOfSheets(); s++) {
|
||||
Sheet sheet = wb.getSheetAt(s);
|
||||
if (sheet == null) {
|
||||
continue;
|
||||
}
|
||||
sheet.setMargin(Sheet.LeftMargin, 0.5);
|
||||
sheet.setMargin(Sheet.RightMargin, 0.5);
|
||||
// 上边距适中:太小企微会裁公司名,太大纸顶空一截
|
||||
sheet.setMargin(Sheet.TopMargin, 0.75);
|
||||
sheet.setMargin(Sheet.BottomMargin, 0.6);
|
||||
sheet.setMargin(Sheet.HeaderMargin, 0.1);
|
||||
sheet.setMargin(Sheet.FooterMargin, 0.2);
|
||||
sheet.setHorizontallyCenter(true);
|
||||
sheet.setVerticallyCenter(false);
|
||||
sheet.setAutobreaks(true);
|
||||
sheet.setFitToPage(true);
|
||||
PrintSetup ps = sheet.getPrintSetup();
|
||||
ps.setPaperSize(PrintSetup.A4_PAPERSIZE);
|
||||
ps.setLandscape(false);
|
||||
ps.setFitWidth((short) 1);
|
||||
ps.setFitHeight((short) 0);
|
||||
ensureHeaderRowHeight(sheet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 抬头合并格(公司中英文)行高太矮时加高。
|
||||
* 现网空运模板 A1:H2 合计只有 42 磅,两行字会裁顶。
|
||||
*/
|
||||
static void ensureHeaderRowHeight(Sheet sheet) {
|
||||
if (sheet == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
|
||||
org.apache.poi.ss.util.CellRangeAddress range = sheet.getMergedRegion(i);
|
||||
if (range.getFirstRow() != 0 || range.getLastRow() < 1) {
|
||||
continue;
|
||||
}
|
||||
Row first = sheet.getRow(range.getFirstRow());
|
||||
Cell cell = first == null ? null : first.getCell(range.getFirstColumn());
|
||||
String text = readCellText(cell);
|
||||
if (!text.contains("公司") && text.toLowerCase().indexOf("company") < 0) {
|
||||
continue;
|
||||
}
|
||||
// 模板末尾常带空行,字会被顶到合并格上沿,转 PDF 再被裁一刀
|
||||
if (cell != null && StringUtils.isNotBlank(text)) {
|
||||
cell.setCellValue(text.replaceAll("[\\r\\n]+$", ""));
|
||||
}
|
||||
int rows = range.getLastRow() - range.getFirstRow() + 1;
|
||||
float each = Math.max(32f, 64f / rows);
|
||||
for (int r = range.getFirstRow(); r <= range.getLastRow(); r++) {
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null) {
|
||||
row = sheet.createRow(r);
|
||||
}
|
||||
if (row.getHeightInPoints() < each) {
|
||||
row.setHeightInPoints(each);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user