diff --git a/src/main/java/com/xujie/sys/common/utils/ExcelTemplateALPHA.java b/src/main/java/com/xujie/sys/common/utils/ExcelTemplateALPHA.java new file mode 100644 index 00000000..3b03b819 --- /dev/null +++ b/src/main/java/com/xujie/sys/common/utils/ExcelTemplateALPHA.java @@ -0,0 +1,750 @@ +package com.xujie.sys.common.utils; + +import lombok.Setter; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.*; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ExcelTemplateALPHA { + + final private static Pattern PATTERN_HDR_FIELD = Pattern.compile("\\$\\{(.*?)}", Pattern.MULTILINE); + final private static Pattern PATTERN_DTL_FIELD = Pattern.compile("#\\{(.*?)}", Pattern.MULTILINE); + + private XSSFWorkbook workbook; + private Map variables = new HashMap<>(); + private List> listVariables = new ArrayList<>(); + // 存储需要合并的单元格区域信息:[列表起始索引, 列表结束索引, 列索引] + private List mergeRegions = new ArrayList<>(); + // 存储需要设置行高的行信息:[起始行索引, 结束行索引, 行高] + private List rowHeights = new ArrayList<>(); + // 是否下移形状格式 + @Setter + private boolean moveShape = false; + // 是否下移印章(印章是图片) + @Setter + private boolean moveSeal = false; + // 是否设置单元格样式 + @Setter + private boolean cellStyle = false; + // 是否设置单元格样式 针对RFID不需要序号定制 + @Setter + private boolean cellStyle2 = false; + // 是否设置合并单元格样式 + @Setter + private boolean rangeStyle = false; + // 价格靠右 发票 + @Setter + private boolean priceRight = false; + // 价格靠右 发票 + @Setter + private boolean invoiceLie = false; + // 数字靠右 箱单 + @Setter + private boolean intRight = false; + // 报关单 + @Setter + private boolean delRight = false; + // 箱单 + @Setter + private boolean boxFlag = false; + + private ExcelTemplateALPHA(){} + + public static ExcelTemplateALPHA load(InputStream template) throws IOException { + ExcelTemplateALPHA excelTemplate = new ExcelTemplateALPHA(); + excelTemplate.workbook = new XSSFWorkbook(template); + return excelTemplate; + } + + public void addVar(String key, Object value){ + variables.put(key, value); + } + + public void addVarAll(Map values){ + variables.putAll(values); + } + + public void addListVar(Map row){ + listVariables.add(row); + } + + public void addListVarAll(Collection rows){ + listVariables.addAll(rows); + } + + /** + * 添加需要合并的单元格区域(基于列表索引) + * @param startListIndex 列表起始索引(相对于listVariables) + * @param endListIndex 列表结束索引(相对于listVariables) + * @param colIndex 列索引 + */ + public void addMergeRegion(int startListIndex, int endListIndex, int colIndex) { + if (startListIndex <= endListIndex) { + mergeRegions.add(new int[]{startListIndex, endListIndex, colIndex, colIndex, 0}); // 格式:[startRow, endRow, startCol, endCol, alignType] + } + } + + /** + * 添加合并区域,并指定对齐方式 + * @param startListIndex 起始行索引 + * @param endListIndex 结束行索引 + * @param colIndex 列索引 + * @param alignType 对齐方式:0=右对齐,1=左对齐,2=居中 + */ + public void addMergeRegion(int startListIndex, int endListIndex, int colIndex, int alignType) { + if (startListIndex <= endListIndex) { + mergeRegions.add(new int[]{startListIndex, endListIndex, colIndex, colIndex, alignType}); + } + } + + /** + * 添加跨列的合并区域(支持横向和纵向同时合并) + * @param startListIndex 起始行索引 + * @param endListIndex 结束行索引 + * @param startColIndex 起始列索引 + * @param endColIndex 结束列索引 + * @param alignType 对齐方式:0=右对齐,1=左对齐,2=居中 + */ + public void addMergeRegion(int startListIndex, int endListIndex, int startColIndex, int endColIndex, int alignType) { + if (startListIndex <= endListIndex && startColIndex <= endColIndex) { + mergeRegions.add(new int[]{startListIndex, endListIndex, startColIndex, endColIndex, alignType}); + } + } + + /** + * 设置行高 + * @param startListIndex 起始行索引(相对于listVariables) + * @param endListIndex 结束行索引(相对于listVariables) + * @param height 行高(单位:点) + */ + public void setRowHeight(int startListIndex, int endListIndex, int height) { + if (startListIndex <= endListIndex) { + rowHeights.add(new int[]{startListIndex, endListIndex, height}); + } + } + + public void clearAll(){ + variables.clear(); + listVariables.clear(); + mergeRegions.clear(); + rowHeights.clear(); + moveShape = false; + moveSeal = false; + cellStyle = false; + cellStyle2 = false; + rangeStyle = false; + priceRight = false; + intRight = false; + delRight = false; + } + + private boolean findAndRemoveMergedRegion(XSSFSheet sheet, int rowIndex) { + for (int mi = 0; mi < sheet.getMergedRegions().size(); mi++) { + if (sheet.getMergedRegions().get(mi).getFirstRow() == rowIndex) { + sheet.removeMergedRegion(mi); + return true; + } + } + return false; + } + + public XSSFWorkbook render(int index) throws IOException { + XSSFSheet sheet = workbook.getSheetAt(index); + CellCopyPolicy copyPolicy = new CellCopyPolicy(); + int dtlRowIndex = -1; + // find detail rows + for (int i = 0; i < sheet.getLastRowNum(); i++) { + XSSFRow row = sheet.getRow(i); + if (row == null) { + continue; + } + for (int j = 0; j < row.getLastCellNum(); j++) { + XSSFCell c = sheet.getRow(i).getCell(j); + if (c == null) { + continue; + } + String cellValue = c.toString(); + Matcher matcherDtl = PATTERN_DTL_FIELD.matcher(cellValue); + if (matcherDtl.find()) { + dtlRowIndex = i; + break; + } + } + if (dtlRowIndex >= 0) { + break; + } + } + if (dtlRowIndex < sheet.getLastRowNum() && listVariables.size() > 1) { + //循环列表以下区域整体下移 + int _rows = sheet.getLastRowNum(); + for(int i = _rows; i > dtlRowIndex; i--){ + sheet.copyRows(i, i, i + listVariables.size() -1 , copyPolicy); + } + for(int i = 0; i < listVariables.size() - 1; i++) { + int destRowIndex = dtlRowIndex + 1 + i; + sheet.createRow(destRowIndex); //clear destination row + while(findAndRemoveMergedRegion(sheet, destRowIndex)){ + continue; + } + sheet.copyRows(dtlRowIndex, dtlRowIndex, destRowIndex, copyPolicy); + } + } + // 遍历所有形状 将列表下方的形状坐标也下移 + if (moveShape) { + int listStartRow = 24; // Excel 第25行(0-based) + int moveOffset = listVariables.size() - 1; + + for (POIXMLDocumentPart part : sheet.getRelations()) { + if (!(part instanceof XSSFDrawing)) { + continue; + } + + XSSFDrawing drawing = (XSSFDrawing) part; + for (XSSFShape shape : drawing.getShapes()) { + if (!(shape instanceof XSSFSimpleShape)) { + continue; + } + + XSSFSimpleShape simpleShape = (XSSFSimpleShape) shape; + ClientAnchor anchor = (ClientAnchor) simpleShape.getAnchor(); + + if (anchor != null && anchor.getRow1() >= listStartRow) { + anchor.setRow1(anchor.getRow1() + moveOffset); + if (anchor instanceof XSSFClientAnchor) { + XSSFClientAnchor xAnchor = (XSSFClientAnchor) anchor; + try { + xAnchor.setRow2(xAnchor.getRow2() + moveOffset); + } catch (Exception ignore) { + // row2 不存在,忽略 + } + } + } + } + } + } + + + + // 遍历所有图片 将列表下方的形状坐标也下移 + if (moveSeal) { + List relations = sheet.getRelations(); + for (POIXMLDocumentPart part : relations) { + if (part instanceof XSSFDrawing) { + XSSFDrawing drawing = (XSSFDrawing) part; + List shapes = drawing.getShapes(); + for (XSSFShape shape : shapes) { + // 只处理图片类型的形状 + if (shape instanceof XSSFPicture) { + XSSFPicture picture = (XSSFPicture) shape; + // 调整行坐标实现下移 + ClientAnchor anchor = (ClientAnchor) picture.getAnchor(); + if (anchor.getRow1() > 10) { // 只下移位于10行之后的图片 + anchor.setRow1(anchor.getRow1() + listVariables.size() - 1); + anchor.setRow2(anchor.getRow2() + listVariables.size() - 1); + } + + } + } + } + } + } + + Set dtlRows = new LinkedHashSet<>(); + List boxRows = new ArrayList<>(); + Set poNoCols = new HashSet<>(); + //整体填值 + for (int i = 0; i <= sheet.getLastRowNum(); i++) { + XSSFRow row = sheet.getRow(i); + if (row == null) { + continue; + } + for (int j = 0; j < row.getLastCellNum(); j++) { + XSSFCell c = sheet.getRow(i).getCell(j); + if (c == null) { + continue; + } + + String cellValue = c.toString(); + if ("#{pn}".equals(cellValue) || "#{levy}".equals(cellValue) || "#{artNo}".equals(cellValue)) { + dtlRows.add(i); + } + if ("#{cmcInvoice}".equals(cellValue)) { + boxRows.add(i); + } + // 仅记录明细模板行上的po_no列,避免同名占位符误伤其它数字列 + if (i == dtlRowIndex && "#{po_no}".equalsIgnoreCase(cellValue)) { + poNoCols.add(j); + } + Matcher matcherHdr = PATTERN_HDR_FIELD.matcher(cellValue); + String result = c.getStringCellValue(); + while (matcherHdr.find()) { + for (int ri = 1; ri <= matcherHdr.groupCount(); ri++) { + String field = matcherHdr.group(ri); + Object value = variables.getOrDefault(field, ""); + result = result.replace(matcherHdr.group(0), String.valueOf(value)); + } + if ("${phone1}".equals(cellValue) || "${phone2}".equals(cellValue) || "${hs_code}".equals(cellValue)) { + c.setCellValue(result); // 字符串 + } else { + try { + // 更严格的数字检查 + String cleanResult = result.replace(",", "").trim(); + if (!cleanResult.isEmpty() && !cleanResult.equals("-") && !cleanResult.equals(".")) { + double num = Double.parseDouble(cleanResult); + if (Double.isFinite(num)) { + c.setCellValue(num); // 数值 + // goods_total_qty 根据实际小数位动态设置格式 + if ("${goods_total_qty}".equals(cellValue)) { + XSSFCellStyle fmtStyle = workbook.createCellStyle(); + fmtStyle.cloneStyleFrom(c.getCellStyle()); + DataFormat dataFormat = workbook.createDataFormat(); + fmtStyle.setDataFormat(dataFormat.getFormat(buildDecimalFormat(num))); + c.setCellStyle(fmtStyle); + } + } else { + c.setCellValue(result); // 字符串 + } + } else { + c.setCellValue(result); // 字符串 + } + } catch (NumberFormatException e) { + c.setCellValue(result); // 字符串 + } + } + } + + Matcher matcherDtl = PATTERN_DTL_FIELD.matcher(cellValue); + while (matcherDtl.find()) { + for (int ri = 1; ri <= matcherDtl.groupCount(); ri++) { + String field = matcherDtl.group(ri); + // 检查数组边界,避免越界异常 + int listIndex = i - dtlRowIndex; + if (listIndex >= 0 && listIndex < listVariables.size()) { + c.setCellValue(cellValue.replace(matcherDtl.group(0), String.valueOf(listVariables.get(listIndex).getOrDefault(field, "")))); + } + } + } + + // 设置样式 + if (cellStyle && dtlRowIndex >= 0 && i >= dtlRowIndex && i < dtlRowIndex + listVariables.size()) { + XSSFCellStyle style = c.getCellStyle(); + style.setBorderBottom(BorderStyle.NONE); + style.setBorderTop(BorderStyle.NONE); + style.setWrapText(true); + style.setAlignment(HorizontalAlignment.LEFT); + c.setCellStyle(style); + if (rangeStyle && j < 4) { + for (int mi = 0; mi < 3; mi++) { + XSSFCell nextc = sheet.getRow(i).getCell(c.getColumnIndex()+mi+1); + if (nextc == null) { + continue; + } + nextc.setCellStyle(style); + } + } + } + } + } + // 设置明细样式,因为明细是动态添加的,会有样式和模版偏差,此处主要是第2、3列的样式需要设置 + if (cellStyle) { + for (Integer dtlRow : dtlRows) { + XSSFCell c1 = sheet.getRow(dtlRow).getCell(cellStyle2?0:1); + if (c1 == null) { + continue; + } + XSSFCellStyle style = workbook.createCellStyle(); + style.setBorderRight(BorderStyle.NONE); + style.setBorderLeft(BorderStyle.MEDIUM); // 实线 + style.setBorderBottom(BorderStyle.NONE); + style.setBorderTop(BorderStyle.NONE); + Font font = workbook.createFont(); + font.setFontName("Arial"); // 设置字体 + font.setFontHeightInPoints((short) 10); // 设置字号 + style.setFont(font); + style.setVerticalAlignment(VerticalAlignment.TOP); + style.setWrapText(true); + c1.setCellStyle(style); + + XSSFCell c2 = sheet.getRow(dtlRow).getCell(cellStyle2?1:2); + if (c2 == null) { + continue; + } + XSSFCellStyle style2 = workbook.createCellStyle(); + style2.setBorderRight(BorderStyle.MEDIUM); + style2.setBorderLeft(BorderStyle.NONE); + style2.setBorderBottom(BorderStyle.NONE); + style2.setBorderTop(BorderStyle.NONE); + Font font2 = workbook.createFont(); + font2.setFontName("Arial"); // 设置字体 + font2.setFontHeightInPoints((short) 10); // 设置字号 + style2.setFont(font2); + style2.setVerticalAlignment(VerticalAlignment.TOP); + style2.setAlignment(HorizontalAlignment.LEFT); + style2.setWrapText(true); + c2.setCellStyle(style2); + if (priceRight) { //仅供发票excel使用,第6、7、8是价格列居右 + for (int i = 6; i < (invoiceLie?11:9); i++) { + XSSFRow row = sheet.getRow(dtlRow); + if (row == null) { + continue; + } + XSSFCell c7 = row.getCell(i); + if (c7 == null) { + continue; + } + + // 尝试把字符串转成数值 + if (c7.getCellType() == CellType.STRING) { + String strVal = c7.getStringCellValue(); + if (strVal != null && !strVal.trim().isEmpty()) { + try { + double num = Double.parseDouble(strVal.replace(",", "")); + c7.setCellValue(num); // 转换为数值写回 + } catch (NumberFormatException e) { + // 如果不是数字就保留原字符串 + System.out.println("非数字,保持原值: " + strVal); + } + } + } + + // 创建样式 + XSSFCellStyle style7 = workbook.createCellStyle(); + style7.setBorderRight(BorderStyle.MEDIUM); + style7.setBorderLeft(BorderStyle.NONE); + style7.setBorderBottom(BorderStyle.NONE); + style7.setBorderTop(BorderStyle.NONE); + + Font font7 = workbook.createFont(); + font7.setFontName("Arial"); + font7.setFontHeightInPoints((short) 10); + style7.setFont(font7); + + style7.setVerticalAlignment(VerticalAlignment.TOP); + style7.setAlignment(HorizontalAlignment.RIGHT); + + // 设置千分位格式(根据单元格实际小数位动态决定格式) + DataFormat dataFormat = workbook.createDataFormat(); + String numFmt; + if (c7.getCellType() == CellType.NUMERIC) { + numFmt = buildDecimalFormat(c7.getNumericCellValue()); + } else { + numFmt = "#,##0.00"; + } + style7.setDataFormat(dataFormat.getFormat(numFmt)); + + c7.setCellStyle(style7); + } + } + if (intRight) { //仅供箱单excel使用 + // 覆盖到第10列(索引9),保证9、10列按数字处理 + for (int i = 4; i < 11; i++) { + XSSFRow row = sheet.getRow(dtlRow); + if (row == null) { + continue; + } + XSSFCell c7 = row.getCell(i); + if (c7 == null) { + continue; + } + boolean isPoNoColumn = poNoCols.contains(i); + BigDecimal numericValue = null; + // 尝试把字符串转成数值 + if (!isPoNoColumn && c7.getCellType() == CellType.STRING) { + String strVal = c7.getStringCellValue(); + BigDecimal parsed = parseNumericString(strVal); + if (parsed != null) { + numericValue = parsed; + c7.setCellValue(parsed.doubleValue()); // 转换为数值写回 + } + } else if (!isPoNoColumn && c7.getCellType() == CellType.NUMERIC) { + numericValue = BigDecimal.valueOf(c7.getNumericCellValue()); + } + + // 创建样式 + XSSFCellStyle style7 = workbook.createCellStyle(); + style7.setBorderRight(BorderStyle.MEDIUM); + style7.setBorderLeft(i==4?BorderStyle.MEDIUM:BorderStyle.NONE); + style7.setBorderBottom(BorderStyle.NONE); + style7.setBorderTop(BorderStyle.NONE); + + // 判断单元格的值是否小于等于0,如果小于等于0则加粗 + boolean isBold = false; + if (numericValue != null && numericValue.compareTo(BigDecimal.ZERO) <= 0) { + isBold = true; + } + + Font font7 = workbook.createFont(); + font7.setFontName("Arial"); + font7.setFontHeightInPoints((short) 10); + if (isBold) { + font7.setBold(true); + } + style7.setFont(font7); + + style7.setVerticalAlignment(VerticalAlignment.TOP); + style7.setAlignment(HorizontalAlignment.CENTER); + DataFormat dataFormat = workbook.createDataFormat(); + if (isPoNoColumn) { + // po_no 是编号列,必须按文本处理,不能套用千分位 + style7.setDataFormat(dataFormat.getFormat("@")); + } else { + // 设置千分位格式(根据单元格实际小数位动态决定格式) + String numFmt2; + if (numericValue != null) { + // 去掉末尾0后按实际小数位显示:20.000000 -> 20,0.150000 -> 0.15 + numFmt2 = buildDecimalFormatByDecimal(numericValue); + } else if (c7.getCellType() == CellType.NUMERIC) { + numFmt2 = buildDecimalFormat(c7.getNumericCellValue()); + } else { + numFmt2 = "#,##0"; + } + style7.setDataFormat(dataFormat.getFormat(numFmt2)); + } + c7.setCellStyle(style7); + } + + // net_weight列(J列,索引9)保持顶部右对齐 + /*XSSFRow row = sheet.getRow(dtlRow); + if (row != null) { + XSSFCell netWeightCell = row.getCell(9); + if (netWeightCell != null) { + XSSFCellStyle netWeightStyle = workbook.createCellStyle(); + netWeightStyle.cloneStyleFrom(netWeightCell.getCellStyle()); + netWeightStyle.setVerticalAlignment(VerticalAlignment.TOP); + netWeightStyle.setAlignment(HorizontalAlignment.RIGHT); + netWeightCell.setCellStyle(netWeightStyle); + } + }*/ + } + } + } + if (delRight) { + for (Integer dtlRow : dtlRows) { + for (int i = 0; i < 17; i++) { + XSSFRow row = sheet.getRow(dtlRow); + if (row == null) { + continue; + } + XSSFCell c7 = row.getCell(i); + if (c7 == null) { + continue; + } + // 创建样式 + XSSFCellStyle style = workbook.createCellStyle(); + style.setBorderRight(i==16?BorderStyle.THIN:BorderStyle.NONE); + style.setBorderLeft(i==0?BorderStyle.THIN:BorderStyle.NONE); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderTop(BorderStyle.NONE); + Font font = workbook.createFont(); + font.setFontName("Arial"); + font.setFontHeightInPoints((short) 10); + style.setFont(font); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setAlignment(HorizontalAlignment.LEFT); + c7.setCellStyle(style); + if (i==6 || i==11) { + // 尝试把字符串转成数值 + if (c7.getCellType() == CellType.STRING) { + String strVal = c7.getStringCellValue(); + if (strVal != null && !strVal.trim().isEmpty()) { + try { + double num = Double.parseDouble(strVal.replace(",", "")); + c7.setCellValue(num); // 转换为数值写回 + } catch (NumberFormatException e) { + // 如果不是数字就保留原字符串 + System.out.println("非数字,保持原值: " + strVal); + } + } + } + // 创建样式 + XSSFCellStyle style7 = workbook.createCellStyle(); + style7.setBorderRight(BorderStyle.NONE); + style7.setBorderLeft(BorderStyle.NONE); + style7.setBorderBottom(BorderStyle.THIN); + style7.setBorderTop(BorderStyle.NONE); + Font font7 = workbook.createFont(); + font7.setFontName("Arial"); + font7.setFontHeightInPoints((short) 10); + style7.setFont(font7); + style7.setVerticalAlignment(VerticalAlignment.CENTER); + style7.setAlignment(HorizontalAlignment.RIGHT); + // 设置千分位格式(根据单元格实际小数位动态决定格式) + DataFormat dataFormat = workbook.createDataFormat(); + String numFmt3 = (c7.getCellType() == CellType.NUMERIC) + ? buildDecimalFormat(c7.getNumericCellValue()) + : "#,##0.00"; + style7.setDataFormat(dataFormat.getFormat(numFmt3)); + c7.setCellStyle(style7); + } + } + } + } + if (boxFlag) { + for (Integer dtlRow : boxRows) { + for (int i = 1; i < 6; i++) { + if (i==1 || i==4 || i==5) { + XSSFRow row = sheet.getRow(dtlRow); + if (row == null) { + continue; + } + XSSFCell c7 = row.getCell(i); + if (c7 == null) { + continue; + } + // 尝试把字符串转成数值 + if (c7.getCellType() == CellType.STRING) { + String strVal = c7.getStringCellValue(); + if (strVal != null && !strVal.trim().isEmpty()) { + try { + double num = Double.parseDouble(strVal.replace(",", "")); + c7.setCellValue(num); // 转换为数值写回 + } catch (NumberFormatException e) { + // 如果不是数字就保留原字符串 + System.out.println("非数字,保持原值: " + strVal); + } + } + } + // 创建样式 + XSSFCellStyle style7 = workbook.createCellStyle(); + style7.setBorderRight(BorderStyle.THIN); + style7.setBorderLeft(BorderStyle.THIN); + style7.setBorderBottom(BorderStyle.THIN); + style7.setBorderTop(BorderStyle.THIN); + + Font font7 = workbook.createFont(); + font7.setFontName("DengXian"); // 等线 + font7.setFontHeightInPoints((short) 11); // 11号 + style7.setFont(font7); + + style7.setVerticalAlignment(VerticalAlignment.CENTER); + style7.setAlignment(HorizontalAlignment.RIGHT); + + // 设置千分位格式(根据单元格实际小数位动态决定格式) + DataFormat dataFormat = workbook.createDataFormat(); + String numFmt4 = (c7.getCellType() == CellType.NUMERIC) + ? buildDecimalFormat(c7.getNumericCellValue()) + : "#,##0"; + style7.setDataFormat(dataFormat.getFormat(numFmt4)); + c7.setCellStyle(style7); + } + } + } + } + + // 处理合并单元格 - 将列表索引转换为实际行号并合并 + if (dtlRowIndex >= 0 && !mergeRegions.isEmpty()) { + for (int[] region : mergeRegions) { + int startRow = dtlRowIndex + region[0]; + int endRow = dtlRowIndex + region[1]; + int startCol = region[2]; + int endCol = region.length >= 4 ? region[3] : region[2]; // 支持跨列,如果没有指定endCol则等于startCol + boolean needMerge = startRow < endRow || startCol < endCol; + if (needMerge) { + // 创建新的合并区域(支持横向和纵向合并) + CellRangeAddress mergeRange = new CellRangeAddress(startRow, endRow, startCol, endCol); + + // 检查并删除与新合并区域冲突的已存在合并区域 + List toRemove = new ArrayList<>(); + for (int i = 0; i < sheet.getNumMergedRegions(); i++) { + CellRangeAddress existingRegion = sheet.getMergedRegion(i); + if (existingRegion != null && mergeRange.intersects(existingRegion)) { + toRemove.add(i); + } + } + // 从后往前删除,避免索引变化 + for (int i = toRemove.size() - 1; i >= 0; i--) { + sheet.removeMergedRegion(toRemove.get(i)); + } + + // 添加新的合并区域 + sheet.addMergedRegion(mergeRange); + } + + // 根据是否实际合并设置对齐方式: + // 1) 已合并:垂直居中 + 水平居中 + // 2) 未合并(单格):垂直顶对齐 + 水平居中 + XSSFRow row = sheet.getRow(startRow); + if (row != null) { + XSSFCell cell = row.getCell(startCol); + if (cell != null) { + XSSFCellStyle mergeStyle = workbook.createCellStyle(); + mergeStyle.cloneStyleFrom(cell.getCellStyle()); + mergeStyle.setVerticalAlignment(needMerge ? VerticalAlignment.CENTER : VerticalAlignment.TOP); + mergeStyle.setAlignment(HorizontalAlignment.CENTER); + cell.setCellStyle(mergeStyle); + } + } + } + } + + // 处理行高设置 + if (dtlRowIndex >= 0 && !rowHeights.isEmpty()) { + for (int[] heightInfo : rowHeights) { + int startRow = dtlRowIndex + heightInfo[0]; + int endRow = dtlRowIndex + heightInfo[1]; + int height = heightInfo[2]; + + for (int rowIdx = startRow; rowIdx <= endRow; rowIdx++) { + XSSFRow row = sheet.getRow(rowIdx); + if (row != null) { + // 设置行高(Excel行高单位是1/20点,所以需要乘以20) + row.setHeight((short) (height * 20)); + } + } + } + } + + return workbook; + } + + private BigDecimal parseNumericString(String value) { + if (value == null) { + return null; + } + String clean = value.replace(",", "").trim(); + if (clean.isEmpty() || "-".equals(clean) || ".".equals(clean)) { + return null; + } + try { + return new BigDecimal(clean); + } catch (NumberFormatException e) { + return null; + } + } + + private String buildDecimalFormatByDecimal(BigDecimal value) { + if (value == null) { + return "#,##0"; + } + int scale = Math.max(0, value.stripTrailingZeros().scale()); + if (scale <= 0) { + return "#,##0"; + } + return "#,##0." + "0".repeat(scale); + } + + /** + * 根据数值的实际小数位数动态生成 Excel 数字格式串(带千分位)。 + * 例:1.0 → "#,##0",12.34 → "#,##0.00",0.00123 → "#,##0.00000" + * 使用 Double.toString() 转 BigDecimal 再去尾零,避免浮点误差导致位数虚高。 + */ + private String buildDecimalFormat(double value) { + if (value == 0) { + return "#,##0"; + } + BigDecimal bd = new BigDecimal(Double.toString(Math.abs(value))).stripTrailingZeros(); + int scale = Math.max(0, bd.scale()); + if (scale == 0) { + return "#,##0"; + } + return "#,##0." + "0".repeat(scale); + } +} diff --git a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelTXServiceImpl.java b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelTXServiceImpl.java index c7381b28..f6e706d1 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelTXServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelTXServiceImpl.java @@ -6,7 +6,7 @@ import com.aspose.cells.PaperSizeType; import com.aspose.cells.SaveFormat; import com.xujie.sys.common.utils.DateUtils; import com.xujie.sys.common.utils.ExcelTemplateTX; -import com.xujie.sys.common.utils.ExcelTemplateYB; +import com.xujie.sys.common.utils.ExcelTemplateALPHA; import com.xujie.sys.modules.ecss.data.*; import com.xujie.sys.modules.ecss.dto.SheetErrorInfo; import com.fasterxml.jackson.core.type.TypeReference; @@ -233,7 +233,9 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { throw new RuntimeException(e); } }); - successList.add(cmcInvoice); + if (cmcInvoice != null) { + successList.add(cmcInvoice); + } } catch (Exception e) { Throwable cause = e; while (cause.getCause() != null) { @@ -977,9 +979,9 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { } private static class YbExcelTemplateAdapter implements ExcelTemplateAdapter { - private final ExcelTemplateYB delegate; + private final ExcelTemplateALPHA delegate; - private YbExcelTemplateAdapter(ExcelTemplateYB delegate) { + private YbExcelTemplateAdapter(ExcelTemplateALPHA delegate) { this.delegate = delegate; } @@ -1077,9 +1079,9 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { workbook.write(response.getOutputStream()); } } else { - String xlsx = "templates/YB/declaration-packingList-template.xlsx"; - ExcelTemplateYB template = ExcelTemplateYB.load(new ClassPathResource(xlsx).getInputStream()); - exportPackingListYB(data, template, notifyHeader, 0); + String xlsx = "templates/ALPHA/declaration-packingList-template.xlsx"; + ExcelTemplateALPHA template = ExcelTemplateALPHA.load(new ClassPathResource(xlsx).getInputStream()); + exportPackingListAlpha(data, template, notifyHeader, 0); try (XSSFWorkbook workbook = template.render(0)) { workbook.write(response.getOutputStream()); } @@ -1238,7 +1240,7 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { extractedContract(data, templateAdapter); template.render(5); } else { - ExcelTemplateYB template = ExcelTemplateYB.load(new ClassPathResource("templates/YB/declaration-all-template.xlsx").getInputStream()); + ExcelTemplateALPHA template = ExcelTemplateALPHA.load(new ClassPathResource("templates/ALPHA/declaration-all-template.xlsx").getInputStream()); ExcelTemplateAdapter templateAdapter = new YbExcelTemplateAdapter(template); // 第一个sheet - 出口货物委托书 @@ -1252,7 +1254,7 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { // 第三个sheet - 箱单 templateAdapter.clearAll(); - exportPackingListYB(data, template, notifyHeader, 0); + exportPackingListAlpha(data, template, notifyHeader, 0); template.render(2); // 第四个sheet - 报关单 @@ -1328,8 +1330,8 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { extractedContract(data, templateAdapter); template.render(4); } else { - String xlsx = "templates/YB/declaration-all-template-pdf.xlsx"; - ExcelTemplateYB template = ExcelTemplateYB.load(new ClassPathResource(xlsx).getInputStream()); + String xlsx = "templates/ALPHA/declaration-all-template-pdf.xlsx"; + ExcelTemplateALPHA template = ExcelTemplateALPHA.load(new ClassPathResource(xlsx).getInputStream()); ExcelTemplateAdapter templateAdapter = new YbExcelTemplateAdapter(template); // 第一个sheet - 发票(PDF导出时跳过出口货物委托书) @@ -1338,7 +1340,7 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { // 第二个sheet - 箱单 templateAdapter.clearAll(); - exportPackingListYB(data, template, notifyHeader, 0); + exportPackingListAlpha(data, template, notifyHeader, 0); template.render(1); // 第三个sheet - 报关单 @@ -1994,14 +1996,14 @@ public class CoDelExcelTXServiceImpl implements CoDelExcelTXService { } /** - * YB装箱单导出,逻辑与TX基本一致,区别在于YB的装箱单模板与TX不同,部分字段需要特殊处理 + * Alpha装箱单导出 * 第一列展示托号(pallet_no) * @param data * @param template * @param notifyHeader * @param type */ - private void exportPackingListYB(EcssDeclarationHeaderData data, ExcelTemplateYB template, + private void exportPackingListAlpha(EcssDeclarationHeaderData data, ExcelTemplateALPHA template, EcssCoDelNotifyHeaderData notifyHeader,int type) { List notifyDetailList; if (type==0) { diff --git a/src/main/resources/templates/ALPHA/declaration-all-template-pdf.xlsx b/src/main/resources/templates/ALPHA/declaration-all-template-pdf.xlsx new file mode 100644 index 00000000..1a344b6f Binary files /dev/null and b/src/main/resources/templates/ALPHA/declaration-all-template-pdf.xlsx differ diff --git a/src/main/resources/templates/ALPHA/declaration-all-template.xlsx b/src/main/resources/templates/ALPHA/declaration-all-template.xlsx new file mode 100644 index 00000000..f1a4d263 Binary files /dev/null and b/src/main/resources/templates/ALPHA/declaration-all-template.xlsx differ diff --git a/src/main/resources/templates/ALPHA/declaration-contract-template.xlsx b/src/main/resources/templates/ALPHA/declaration-contract-template.xlsx new file mode 100644 index 00000000..42c8eb16 Binary files /dev/null and b/src/main/resources/templates/ALPHA/declaration-contract-template.xlsx differ diff --git a/src/main/resources/templates/ALPHA/declaration-elements-template.xlsx b/src/main/resources/templates/ALPHA/declaration-elements-template.xlsx new file mode 100644 index 00000000..fdba7637 Binary files /dev/null and b/src/main/resources/templates/ALPHA/declaration-elements-template.xlsx differ diff --git a/src/main/resources/templates/ALPHA/declaration-invoice2-template.xlsx b/src/main/resources/templates/ALPHA/declaration-invoice2-template.xlsx new file mode 100644 index 00000000..e6dd76b9 Binary files /dev/null and b/src/main/resources/templates/ALPHA/declaration-invoice2-template.xlsx differ diff --git a/src/main/resources/templates/ALPHA/declaration-packingList-template.xlsx b/src/main/resources/templates/ALPHA/declaration-packingList-template.xlsx new file mode 100644 index 00000000..23a4ed38 Binary files /dev/null and b/src/main/resources/templates/ALPHA/declaration-packingList-template.xlsx differ diff --git a/src/main/resources/templates/ALPHA/declaration-template.xlsx b/src/main/resources/templates/ALPHA/declaration-template.xlsx new file mode 100644 index 00000000..46be807e Binary files /dev/null and b/src/main/resources/templates/ALPHA/declaration-template.xlsx differ diff --git a/src/main/resources/templates/ALPHA/export-goods-template.xlsx b/src/main/resources/templates/ALPHA/export-goods-template.xlsx new file mode 100644 index 00000000..4377e387 Binary files /dev/null and b/src/main/resources/templates/ALPHA/export-goods-template.xlsx differ