From 503ca8eb627aa0d08185f0016856d8391edbbc90 Mon Sep 17 00:00:00 2001 From: Deepak Pansheriya Date: Sat, 4 Jan 2020 19:49:48 +0530 Subject: [PATCH] IDEMPIERE-2924: Adding XLSX Export support (#6) --- .../201804121850_Extend_Export_Feature.sql | 11 + .../201804121850_Extend_Export_Feature.sql | 8 + org.adempiere.base-feature/feature.xml | 7 + org.adempiere.base/plugin.xml | 9 + .../impexp/AbstractXLSXExporter.java | 570 ++++++++++++++++++ .../adempiere/impexp/GridTabXLSXExporter.java | 202 +++++++ .../print/export/PrintDataXLSXExporter.java | 216 +++++++ .../src/org/compiere/print/ReportEngine.java | 15 + .../setup/configuration/config.ini | 3 +- .../webui/apps/form/WReportCustomization.java | 6 + .../webui/panel/action/ReportAction.java | 6 + .../webui/window/ZkReportViewer.java | 68 ++- 12 files changed, 1119 insertions(+), 2 deletions(-) create mode 100644 migration/i7.1z/oracle/201804121850_Extend_Export_Feature.sql create mode 100644 migration/i7.1z/postgresql/201804121850_Extend_Export_Feature.sql create mode 100644 org.adempiere.base/src/org/adempiere/impexp/AbstractXLSXExporter.java create mode 100644 org.adempiere.base/src/org/adempiere/impexp/GridTabXLSXExporter.java create mode 100644 org.adempiere.base/src/org/adempiere/print/export/PrintDataXLSXExporter.java diff --git a/migration/i7.1z/oracle/201804121850_Extend_Export_Feature.sql b/migration/i7.1z/oracle/201804121850_Extend_Export_Feature.sql new file mode 100644 index 0000000000..c55a7184d2 --- /dev/null +++ b/migration/i7.1z/oracle/201804121850_Extend_Export_Feature.sql @@ -0,0 +1,11 @@ +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Excel 2007 support in idempiere +-- Apr 12, 2018 3:12:17 PM IST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','xlsx - Excel file',0,0,'Y',TO_DATE('2018-04-12 15:12:16','YYYY-MM-DD HH24:MI:SS'),100,TO_DATE('2018-04-12 15:12:16','YYYY-MM-DD HH24:MI:SS'),100,200451,'FileXLSX','D','48cc5032-f2c7-4b27-99bc-05c6269d032b') +; + +SELECT register_migration_script('201804121850_Extend_Export_Feature.sql') FROM dual +; + diff --git a/migration/i7.1z/postgresql/201804121850_Extend_Export_Feature.sql b/migration/i7.1z/postgresql/201804121850_Extend_Export_Feature.sql new file mode 100644 index 0000000000..5cd6a41fa5 --- /dev/null +++ b/migration/i7.1z/postgresql/201804121850_Extend_Export_Feature.sql @@ -0,0 +1,8 @@ +-- Excel 2007 support in idempiere +-- Apr 12, 2018 3:12:17 PM IST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','xlsx - Excel file',0,0,'Y',TO_TIMESTAMP('2018-04-12 15:12:16','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2018-04-12 15:12:16','YYYY-MM-DD HH24:MI:SS'),100,200451,'FileXLSX','D','48cc5032-f2c7-4b27-99bc-05c6269d032b') +; + +SELECT register_migration_script('201804121850_Extend_Export_Feature.sql') FROM dual +; + diff --git a/org.adempiere.base-feature/feature.xml b/org.adempiere.base-feature/feature.xml index 2d13a1c841..e2e6a1a678 100644 --- a/org.adempiere.base-feature/feature.xml +++ b/org.adempiere.base-feature/feature.xml @@ -517,4 +517,11 @@ version="0.0.0" unpack="false"/> + + diff --git a/org.adempiere.base/plugin.xml b/org.adempiere.base/plugin.xml index 6aa93bd88f..91943b4fc5 100644 --- a/org.adempiere.base/plugin.xml +++ b/org.adempiere.base/plugin.xml @@ -140,4 +140,13 @@ + + + + diff --git a/org.adempiere.base/src/org/adempiere/impexp/AbstractXLSXExporter.java b/org.adempiere.base/src/org/adempiere/impexp/AbstractXLSXExporter.java new file mode 100644 index 0000000000..0c1919dbd6 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/impexp/AbstractXLSXExporter.java @@ -0,0 +1,570 @@ +/****************************************************************************** + * Copyright (C) 2018 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.impexp; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.sql.Timestamp; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.Properties; +import java.util.logging.Level; + +import org.apache.poi.hssf.usermodel.HSSFDataFormat; +import org.apache.poi.hssf.usermodel.HSSFHeader; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Footer; +import org.apache.poi.ss.usermodel.Header; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFDataFormat; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFPrintSetup; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.compiere.util.CLogger; +import org.compiere.util.DisplayType; +import org.compiere.util.Env; +import org.compiere.util.Ini; +import org.compiere.util.Language; +import org.compiere.util.Msg; +import org.compiere.util.Util; + +/** + * Abstract MS Excel Format (xlsx) Exporter + * + * @author Deepak Pansheriya + */ +public abstract class AbstractXLSXExporter +{ + /** + * Is the current Row a Function Row + * + * @return true if function row + */ + public abstract boolean isFunctionRow(); + + /** + * Get Columns Count + * + * @return number of columns + */ + public abstract int getColumnCount(); + + /** + * Get Rows Count + * + * @return number of rows + */ + public abstract int getRowCount(); + + /** + * Set current row + * + * @param row row index + */ + protected abstract void setCurrentRow(int row); + + /** + * @return current row index + */ + protected abstract int getCurrentRow(); + + /** + * Check if column is printed (displayed) + * + * @param col column index + * @return true if is visible + */ + public abstract boolean isColumnPrinted(int col); + + /** + * Get column header name + * + * @param col column index + * @return header name + */ + public abstract String getHeaderName(int col); + + /** + * Get cell display type (see {@link DisplayType}) + * + * @param row row index + * @param col column index + * @return display type + */ + public abstract int getDisplayType(int row, int col); + + /** + * Get cell value + * + * @param row row index + * @param col column index + * @return cell value + */ + public abstract Object getValueAt(int row, int col); + + /** + * Check if there is a page break on given cell + * + * @param row row index + * @param col column index + * @return true if there is a page break + */ + public abstract boolean isPageBreak(int row, int col); + + /** Logger */ + protected final CLogger log = CLogger.getCLogger(getClass()); + // + private XSSFWorkbook m_workbook; + private XSSFDataFormat m_dataFormat; + private XSSFFont m_fontHeader = null; + private XSSFFont m_fontDefault = null; + private Language m_lang = null; + private int m_sheetCount = 0; + // + private int m_colSplit = 1; + private int m_rowSplit = 1; + private boolean currentRowOnly = false; + /** Styles cache */ + private HashMap m_styles = new HashMap(); + + protected Boolean[] colSuppressRepeats; + + public AbstractXLSXExporter() + { + m_workbook = new XSSFWorkbook(); + m_dataFormat = m_workbook.createDataFormat(); + } + + protected Properties getCtx() + { + return Env.getCtx(); + } + + protected void setFreezePane(int colSplit, int rowSplit) + { + m_colSplit = colSplit; + m_rowSplit = rowSplit; + } + + private String fixString(String str) + { + // ms excel doesn't support UTF8 charset + return Util.stripDiacritics(str); + } + + protected Language getLanguage() + { + if (m_lang == null) + m_lang = Env.getLanguage(getCtx()); + return m_lang; + } + + private XSSFFont getFont(boolean isHeader) + { + XSSFFont font = null; + if (isHeader) + { + if (m_fontHeader == null) + { + m_fontHeader = m_workbook.createFont(); + m_fontHeader.setBold(true); + } + font = m_fontHeader; + } + else if (isFunctionRow()) + { + font = m_workbook.createFont(); + font.setBold(true); + font.setItalic(true); + } + else + { + if (m_fontDefault == null) + { + m_fontDefault = m_workbook.createFont(); + } + font = m_fontDefault; + } + return font; + } + + /** + * Get Excel number format string by given {@link NumberFormat} + * + * @param df number format + * @param isHighlightNegativeNumbers highlight negative numbers using RED + * color + * @return number excel format string + */ + private String getFormatString(NumberFormat df, boolean isHighlightNegativeNumbers) + { + StringBuffer format = new StringBuffer(); + int integerDigitsMin = df.getMinimumIntegerDigits(); + int integerDigitsMax = df.getMaximumIntegerDigits(); + for (int i = 0; i < integerDigitsMax; i++) + { + if (i < integerDigitsMin) + format.insert(0, "0"); + else + format.insert(0, "#"); + if (i == 2) + { + format.insert(0, ","); + } + } + int fractionDigitsMin = df.getMinimumFractionDigits(); + int fractionDigitsMax = df.getMaximumFractionDigits(); + for (int i = 0; i < fractionDigitsMax; i++) + { + if (i == 0) + format.append("."); + if (i < fractionDigitsMin) + format.append("0"); + else + format.append("#"); + } + if (isHighlightNegativeNumbers) + { + String f = format.toString(); + format = new StringBuffer(f).append(";[RED]-").append(f); + } + // + if (log.isLoggable(Level.FINEST)) + log.finest("NumberFormat: " + format); + return format.toString(); + + } + + private XSSFCellStyle getStyle(int row, int col) + { + int displayType = getDisplayType(row, col); + String key = "cell-" + col + "-" + displayType; + XSSFCellStyle cs = m_styles.get(key); + if (cs == null) + { + boolean isHighlightNegativeNumbers = true; + cs = m_workbook.createCellStyle(); + XSSFFont font = getFont(false); + cs.setFont(font); + // Border + cs.setBorderLeft(BorderStyle.THIN); + cs.setBorderTop(BorderStyle.THIN); + cs.setBorderRight(BorderStyle.THIN); + cs.setBorderBottom(BorderStyle.THIN); + // + if (DisplayType.isDate(displayType)) + { + cs.setDataFormat(m_dataFormat.getFormat(DisplayType.getDateFormat(getLanguage()).toPattern())); + } + else if (DisplayType.isNumeric(displayType)) + { + DecimalFormat df = DisplayType.getNumberFormat(displayType, getLanguage()); + String format = getFormatString(df, isHighlightNegativeNumbers); + cs.setDataFormat(m_dataFormat.getFormat(format)); + } + m_styles.put(key, cs); + } + return cs; + } + + private XSSFCellStyle getHeaderStyle(int col) + { + String key = "header-" + col; + XSSFCellStyle cs_header = m_styles.get(key); + if (cs_header == null) + { + XSSFFont font_header = getFont(true); + cs_header = m_workbook.createCellStyle(); + cs_header.setFont(font_header); + cs_header.setBorderLeft(BorderStyle.MEDIUM); + cs_header.setBorderTop(BorderStyle.MEDIUM); + cs_header.setBorderRight(BorderStyle.MEDIUM); + cs_header.setBorderBottom(BorderStyle.MEDIUM); + cs_header.setDataFormat(HSSFDataFormat.getBuiltinFormat("text")); + cs_header.setWrapText(true); + m_styles.put(key, cs_header); + } + return cs_header; + } + + private void fixColumnWidth(XSSFSheet sheet, int lastColumnIndex) + { + for (short colnum = 0; colnum < lastColumnIndex; colnum++) + { + sheet.autoSizeColumn(colnum); + } + } + + private void closeTableSheet(XSSFSheet prevSheet, String prevSheetName, int colCount) + { + if (prevSheet == null) + return; + // + fixColumnWidth(prevSheet, colCount); + if (m_colSplit >= 0 || m_rowSplit >= 0) + prevSheet.createFreezePane(m_colSplit >= 0 ? m_colSplit : 0, m_rowSplit >= 0 ? m_rowSplit : 0); + if (!Util.isEmpty(prevSheetName, true) && m_sheetCount > 0) + { + int prevSheetIndex = m_sheetCount - 1; + try + { + m_workbook.setSheetName(prevSheetIndex, prevSheetName); + } + catch (Exception e) + { + log.log(Level.WARNING, "Error setting sheet " + prevSheetIndex + " name to " + prevSheetName, e); + } + } + } + + private XSSFSheet createTableSheet() + { + XSSFSheet sheet = m_workbook.createSheet(); + formatPage(sheet); + createHeaderFooter(sheet); + createTableHeader(sheet); + m_sheetCount++; + // + return sheet; + } + + private void createTableHeader(XSSFSheet sheet) + { + int colnumMax = 0; + + XSSFRow row = sheet.createRow(0); + // for all columns + int colnum = 0; + for (int col = 0; col < getColumnCount(); col++) + { + if (colnum > colnumMax) + colnumMax = colnum; + // + if (isColumnPrinted(col)) + { + XSSFCell cell = row.createCell(colnum); + // header row + XSSFCellStyle style = getHeaderStyle(col); + cell.setCellStyle(style); + String str = fixString(getHeaderName(col)); + cell.setCellValue(new XSSFRichTextString(str)); + colnum++; + } // printed + } // for all columns + } + + protected void createHeaderFooter(XSSFSheet sheet) + { + // Sheet Header + Header header = sheet.getHeader(); + header.setRight(HSSFHeader.page() + " / " + HSSFHeader.numPages()); + // Sheet Footer + Footer footer = sheet.getFooter(); + footer.setLeft(Env.getStandardReportFooterTrademarkText()); + footer.setCenter(Env.getHeader(getCtx(), 0)); + Timestamp now = new Timestamp(System.currentTimeMillis()); + footer.setRight(DisplayType.getDateFormat(DisplayType.DateTime, getLanguage()).format(now)); + } + + protected void formatPage(XSSFSheet sheet) + { + sheet.setFitToPage(true); + // Print Setup + XSSFPrintSetup ps = sheet.getPrintSetup(); + ps.setFitWidth((short) 1); + ps.setNoColor(true); + ps.setPaperSize(XSSFPrintSetup.A4_PAPERSIZE); + ps.setLandscape(false); + } + + protected boolean isCurrentRowOnly() + { + return currentRowOnly; + } + + protected void setCurrentRowOnly(boolean b) + { + currentRowOnly = b; + } + + /** + * Export to given stream + * + * @param out + * @throws Exception + */ + private void export(OutputStream out) throws Exception + { + XSSFSheet sheet = createTableSheet(); + String sheetName = null; + // + int colnumMax = 0; + int rownum = isCurrentRowOnly() ? getCurrentRow() : 0; + int lastRowNum = isCurrentRowOnly() ? getCurrentRow() + 1 : getRowCount(); + Object[] preValues = null; + int printColIndex = -1; + if (colSuppressRepeats != null) + { + preValues = new Object[colSuppressRepeats.length]; + } + + for (int xls_rownum = 1; rownum < lastRowNum; rownum++, xls_rownum++) + { + if (!isCurrentRowOnly()) + setCurrentRow(rownum); + + boolean isPageBreak = false; + XSSFRow row = sheet.createRow(xls_rownum); + printColIndex = -1; + // for all columns + int colnum = 0; + for (int col = 0; col < getColumnCount(); col++) + { + if (colnum > colnumMax) + colnumMax = colnum; + // + if (isColumnPrinted(col)) + { + printColIndex++; + XSSFCell cell = row.createCell(colnum); + + // line row + Object obj = getValueAt(rownum, col); + int displayType = getDisplayType(rownum, col); + if (obj == null) + { + if (colSuppressRepeats != null && colSuppressRepeats[printColIndex]) + { + preValues[printColIndex] = null; + } + } + else if (colSuppressRepeats != null && colSuppressRepeats[printColIndex] + && obj.equals(preValues[printColIndex])) + { + // suppress + } + else if (DisplayType.isDate(displayType)) + { + Timestamp value = (Timestamp) obj; + cell.setCellValue(value); + } + else if (DisplayType.isNumeric(displayType)) + { + double value = 0; + if (obj instanceof Number) + { + value = ((Number) obj).doubleValue(); + } + cell.setCellValue(value); + } + else if (DisplayType.YesNo == displayType) + { + boolean value = false; + if (obj instanceof Boolean) + value = (Boolean) obj; + else + value = "Y".equals(obj); + cell.setCellValue(new XSSFRichTextString(Msg.getMsg(getLanguage(), value == true ? "Y" : "N"))); + } + else + { + String value = fixString(obj.toString()); // formatted + cell.setCellValue(new XSSFRichTextString(value)); + } + // + XSSFCellStyle style = getStyle(rownum, col); + cell.setCellStyle(style); + // Page break + if (isPageBreak(rownum, col)) + { + isPageBreak = true; + sheetName = fixString(cell.getRichStringCellValue().getString()); + } + // + colnum++; + if (colSuppressRepeats != null) + preValues[printColIndex] = obj; + } // printed + } // for all columns + // + // Page Break + if (isPageBreak) + { + closeTableSheet(sheet, sheetName, colnumMax); + sheet = createTableSheet(); + xls_rownum = 0; + isPageBreak = false; + } + } // for all rows + closeTableSheet(sheet, sheetName, colnumMax); + // + + if (out != null) + { + m_workbook.write(out); + out.close(); + } + + // + // Workbook Info + if (log.isLoggable(Level.FINE)) + { + log.fine("Sheets #" + m_sheetCount); + log.fine("Styles used #" + m_styles.size()); + } + } + + /** + * Export to file + * + * @param file + * @param language reporting language + * @throws Exception + */ + public void export(File file, Language language) throws Exception + { + export(file, language, true); + } + + /** + * Export to file + * + * @param file + * @param language reporting language + * @param autoOpen auto open file after generated + * @throws Exception + */ + public void export(File file, Language language, boolean autoOpen) throws Exception + { + m_lang = language; + if (file == null) + file = File.createTempFile("Report_", ".xlsx"); + FileOutputStream out = new FileOutputStream(file); + export(out); + if (autoOpen && Ini.isClient()) + Env.startBrowser(file.toURI().toString()); + } + + public void exportToWorkbook(XSSFWorkbook workbook, Language language) throws Exception + { + m_lang = language; + m_workbook = workbook; + export(null); + } +} diff --git a/org.adempiere.base/src/org/adempiere/impexp/GridTabXLSXExporter.java b/org.adempiere.base/src/org/adempiere/impexp/GridTabXLSXExporter.java new file mode 100644 index 0000000000..b77891c4b6 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/impexp/GridTabXLSXExporter.java @@ -0,0 +1,202 @@ +/****************************************************************************** + * Copyright (C) 2018 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.impexp; + +import java.io.File; +import java.util.HashMap; +import java.util.List; + +import org.adempiere.base.IGridTabExporter; +import org.adempiere.exceptions.AdempiereException; +import org.compiere.model.GridField; +import org.compiere.model.GridTab; +import org.compiere.model.Lookup; +import org.compiere.model.MLookup; +import org.compiere.model.MLookupFactory; +import org.compiere.util.DisplayType; +import org.compiere.util.Env; +import org.compiere.util.Msg; +import org.compiere.util.Util; + +/** + * Excel Exporter Adapter for GridTab + * + * @author Deepak Pansheriya + */ +public class GridTabXLSXExporter extends AbstractXLSXExporter implements IGridTabExporter +{ + private GridTab m_tab = null; + + public GridTabXLSXExporter() + { + setFreezePane(0, 1); + } + + @Override + public int getColumnCount() + { + return m_tab.getFieldCount(); + } + + @Override + public int getDisplayType(int row, int col) + { + return m_tab.getField(col).getDisplayType(); + } + + @Override + public String getHeaderName(int col) + { + return m_tab.getField(col).getHeader(); + } + + @Override + public int getRowCount() + { + return m_tab.getRowCount(); + } + + @Override + public Object getValueAt(int row, int col) + { + GridField f = m_tab.getField(col); + Object key = m_tab.getValue(row, f.getColumnName()); + Object value = key; + Lookup lookup = f.getLookup(); + if (lookup != null) + { + ; + } + else if (f.getDisplayType() == DisplayType.Button) + { + lookup = getButtonLookup(f); + } + + if (lookup != null) + { + value = lookup.getDisplay(key); + } + return value; + } + + @Override + public boolean isColumnPrinted(int col) + { + GridField f = m_tab.getField(col); + // Hide not displayed fields + if (!f.isDisplayed()) + return false; + // Hide encrypted fields + if (f.isEncrypted()) + return false; + // Hide simple button fields without a value + if (f.getDisplayType() == DisplayType.Button && f.getAD_Reference_Value_ID() == 0) + return false; + return true; + } + + @Override + public boolean isFunctionRow() + { + return false; + } + + @Override + public boolean isPageBreak(int row, int col) + { + return false; + } + + @Override + protected void setCurrentRow(int row) + { + ; // nothing + } + + protected int getCurrentRow() + { + return m_tab.getCurrentRow(); + } + + private HashMap m_buttonLookups = new HashMap(); + + private MLookup getButtonLookup(GridField mField) + { + MLookup lookup = m_buttonLookups.get(mField.getColumnName()); + if (lookup != null) + return lookup; + // TODO: refactor with org.compiere.grid.ed.VButton.setField(GridField) + if (mField.getColumnName().endsWith("_ID") && !mField.getColumnName().equals("Record_ID")) + { + lookup = MLookupFactory.get(Env.getCtx(), mField.getWindowNo(), 0, mField.getAD_Column_ID(), + DisplayType.Search); + } + else if (mField.getAD_Reference_Value_ID() != 0) + { + // Assuming List + lookup = MLookupFactory.get(Env.getCtx(), mField.getWindowNo(), 0, mField.getAD_Column_ID(), + DisplayType.List); + } + // + m_buttonLookups.put(mField.getColumnName(), lookup); + return lookup; + } + + @Override + public void export(GridTab gridTab, List childs, boolean currentRowOnly, File file, int indxDetailSelected) + { + m_tab = gridTab; + setCurrentRowOnly(currentRowOnly); + try + { + export(file, null); + } + catch (Exception e) + { + throw new AdempiereException(e); + } + } + + @Override + public String getFileExtension() + { + return "xlsx"; + } + + @Override + public String getFileExtensionLabel() + { + return Msg.getMsg(Env.getCtx(), "FileXLSX"); + } + + @Override + public String getContentType() + { + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + } + + @Override + public String getSuggestedFileName(GridTab gridTab) + { + return gridTab.getName() + "." + getFileExtension(); + } + + /** + * {@inheritDoc} no detail tab is support to export with excel + */ + @Override + public boolean isExportableTab(GridTab gridTab) + { + return false; + } +} diff --git a/org.adempiere.base/src/org/adempiere/print/export/PrintDataXLSXExporter.java b/org.adempiere.base/src/org/adempiere/print/export/PrintDataXLSXExporter.java new file mode 100644 index 0000000000..93fed0da02 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/print/export/PrintDataXLSXExporter.java @@ -0,0 +1,216 @@ +/****************************************************************************** + * Copyright (C) 2018 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.print.export; + +import java.sql.Timestamp; +import java.util.Date; + +import javax.print.attribute.standard.MediaSizeName; + +import org.adempiere.impexp.AbstractXLSXExporter; +import org.apache.poi.hssf.usermodel.HSSFPrintSetup; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.compiere.print.MPrintFormat; +import org.compiere.print.MPrintFormatItem; +import org.compiere.print.MPrintPaper; +import org.compiere.print.PrintData; +import org.compiere.print.PrintDataElement; + +/** + * Export PrintData to Excel (XLSX) file + * + * @author Deepak Pansheriya + */ +public class PrintDataXLSXExporter extends AbstractXLSXExporter +{ + private PrintData m_printData; + private MPrintFormat m_printFormat; + + public PrintDataXLSXExporter(PrintData printData, MPrintFormat printFormat, Boolean[] colSuppressRepeats) + { + super(); + this.m_printData = printData; + this.m_printFormat = printFormat; + this.colSuppressRepeats = colSuppressRepeats; + } + + @Override + public int getColumnCount() + { + return m_printFormat.getItemCount(); + } + + private PrintDataElement getPDE(int row, int col) + { + if (m_printData.getRowIndex() != row) + m_printData.setRowIndex(row); + // + MPrintFormatItem item = m_printFormat.getItem(col); + int AD_Column_ID = item.getAD_Column_ID(); + Object obj = null; + if (AD_Column_ID > 0) + obj = m_printData.getNode(Integer.valueOf(AD_Column_ID)); + if (obj != null && obj instanceof PrintDataElement) + { + return (PrintDataElement) obj; + } + return null; + } + + @Override + public int getDisplayType(int row, int col) + { + PrintDataElement pde = getPDE(row, col); + if (pde != null) + { + return pde.getDisplayType(); + } + return -1; + // + } + + @Override + public Object getValueAt(int row, int col) + { + PrintDataElement pde = getPDE(row, col); + Object value = null; + if (pde == null) + ; + else if (pde.isDate()) + { + Object o = pde.getValue(); + if (o instanceof Date) + value = new Timestamp(((Date) o).getTime()); + else + value = (Timestamp) pde.getValue(); + } + else if (pde.isNumeric()) + { + Object o = pde.getValue(); + if (o instanceof Number) + { + value = ((Number) o).doubleValue(); + } + } + else if (pde.isYesNo()) + { + value = pde.getValue(); + } + else if (pde.isPKey()) + { + value = pde.getValueAsString(); + } + else + { + value = pde.getValueDisplay(getLanguage()); + } + // + return value; + } + + @Override + public String getHeaderName(int col) + { + return m_printFormat.getItem(col).getPrintName(getLanguage()); + } + + @Override + public int getRowCount() + { + return m_printData.getRowCount(); + } + + @Override + public boolean isColumnPrinted(int col) + { + MPrintFormatItem item = m_printFormat.getItem(col); + return item.isPrinted(); + } + + @Override + public boolean isPageBreak(int row, int col) + { + PrintDataElement pde = getPDE(row, col); + return pde != null ? pde.isPageBreak() : false; + } + + @Override + protected void setCurrentRow(int row) + { + m_printData.setRowIndex(row); + } + + protected int getCurrentRow() + { + return m_printData.getRowIndex(); + } + + @Override + public boolean isFunctionRow() + { + return m_printData.isFunctionRow(); + } + + @Override + protected void formatPage(XSSFSheet sheet) + { + super.formatPage(sheet); + MPrintPaper paper = MPrintPaper.get(this.m_printFormat.getAD_PrintPaper_ID()); + + // Set paper size: + short paperSize = -1; + MediaSizeName mediaSizeName = paper.getMediaSize().getMediaSizeName(); + if (MediaSizeName.NA_LETTER.equals(mediaSizeName)) + { + paperSize = HSSFPrintSetup.LETTER_PAPERSIZE; + } + else if (MediaSizeName.NA_LEGAL.equals(mediaSizeName)) + { + paperSize = HSSFPrintSetup.LEGAL_PAPERSIZE; + } + else if (MediaSizeName.EXECUTIVE.equals(mediaSizeName)) + { + paperSize = HSSFPrintSetup.EXECUTIVE_PAPERSIZE; + } + else if (MediaSizeName.ISO_A4.equals(mediaSizeName)) + { + paperSize = HSSFPrintSetup.A4_PAPERSIZE; + } + else if (MediaSizeName.ISO_A5.equals(mediaSizeName)) + { + paperSize = HSSFPrintSetup.A5_PAPERSIZE; + } + else if (MediaSizeName.NA_NUMBER_10_ENVELOPE.equals(mediaSizeName)) + { + paperSize = HSSFPrintSetup.ENVELOPE_10_PAPERSIZE; + } + else if (MediaSizeName.MONARCH_ENVELOPE.equals(mediaSizeName)) + { + paperSize = HSSFPrintSetup.ENVELOPE_MONARCH_PAPERSIZE; + } + if (paperSize != -1) + { + sheet.getPrintSetup().setPaperSize(paperSize); + } + + // Set Landscape/Portrait: + sheet.getPrintSetup().setLandscape(paper.isLandscape()); + + // Set Paper Margin: + sheet.setMargin(HSSFSheet.TopMargin, ((double) paper.getMarginTop()) / 72); + sheet.setMargin(HSSFSheet.RightMargin, ((double) paper.getMarginRight()) / 72); + sheet.setMargin(HSSFSheet.LeftMargin, ((double) paper.getMarginLeft()) / 72); + sheet.setMargin(HSSFSheet.BottomMargin, ((double) paper.getMarginBottom()) / 72); + } +} diff --git a/org.adempiere.base/src/org/compiere/print/ReportEngine.java b/org.adempiere.base/src/org/compiere/print/ReportEngine.java index ebec1e8a73..de3c2098a1 100644 --- a/org.adempiere.base/src/org/compiere/print/ReportEngine.java +++ b/org.adempiere.base/src/org/compiere/print/ReportEngine.java @@ -63,6 +63,7 @@ import javax.xml.transform.stream.StreamResult; import org.adempiere.exceptions.AdempiereException; import org.adempiere.pdf.Document; import org.adempiere.print.export.PrintDataExcelExporter; +import org.adempiere.print.export.PrintDataXLSXExporter; import org.apache.ecs.XhtmlDocument; import org.apache.ecs.xhtml.a; import org.apache.ecs.xhtml.script; @@ -1483,6 +1484,20 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) exp.export(outFile, language); } + /** + * Create ExcelX file + * @param outFile output file + * @param language + * @throws Exception if error + */ + public void createXLSX(File outFile, Language language) + throws Exception + { + Boolean [] colSuppressRepeats = m_layout == null || m_layout.colSuppressRepeats == null? LayoutEngine.getColSuppressRepeats(m_printFormat):m_layout.colSuppressRepeats; + PrintDataXLSXExporter exp = new PrintDataXLSXExporter(getPrintData(), getPrintFormat(), colSuppressRepeats); + exp.export(outFile, language); + } + /************************************************************************** * Get Report Engine for process info * @param ctx context diff --git a/org.adempiere.server-feature/setup/configuration/config.ini b/org.adempiere.server-feature/setup/configuration/config.ini index 695a4073eb..a945a57992 100644 --- a/org.adempiere.server-feature/setup/configuration/config.ini +++ b/org.adempiere.server-feature/setup/configuration/config.ini @@ -67,7 +67,8 @@ osgi.bundles=org.eclipse.equinox.ds@1:start,\ com.google.zxing.core,\ org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec,\ jakarta.xml.bind-api,\ - org.eclipse.osgi@start + org.eclipse.osgi@start,\ + org.dom4j osgi.framework.extensions= osgi.bundles.defaultStartLevel=4 osgi.compatibility.bootdelegation=true diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WReportCustomization.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WReportCustomization.java index 05eb5e57ae..7a020531c7 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WReportCustomization.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WReportCustomization.java @@ -470,6 +470,7 @@ public class WReportCustomization implements IFormController,EventListener cboExportType.appendItem("txt" + " - " + Msg.getMsg(Env.getCtx(), "FileTXT"), "txt"); cboExportType.appendItem("ssv" + " - " + Msg.getMsg(Env.getCtx(), "FileSSV"), "ssv"); cboExportType.appendItem("csv" + " - " + Msg.getMsg(Env.getCtx(), "FileCSV"), "csv"); + cboExportType.appendItem("xlsx" + " - " + Msg.getMsg(Env.getCtx(), "FileXLSX"), "xlsx"); ListItem li = cboExportType.appendItem("xls" + " - " + Msg.getMsg(Env.getCtx(), "FileXLS"), "xls"); cboExportType.setSelectedItem(li); cboExportType.setVisible(false); @@ -440,6 +441,11 @@ public class ReportAction implements EventListener inputFile = File.createTempFile("Export", ".xls"); re.createXLS(inputFile, re.getPrintFormat().getLanguage()); } + else if (ext.equals("xlsx")) + { + inputFile = File.createTempFile("Export", ".xlsx"); + re.createXLSX(inputFile, re.getPrintFormat().getLanguage()); + } else { FDialog.error(0, winReport, "FileInvalidExtension"); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkReportViewer.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkReportViewer.java index 0b81c48813..abc3dc3f08 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkReportViewer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkReportViewer.java @@ -280,6 +280,7 @@ public class ZkReportViewer extends Window implements EventListener, ITab { previewType.appendItem("Excel", "XLS"); previewType.appendItem("CSV", "CSV"); + previewType.appendItem("Excel X", "XLSX"); } toolBar.appendChild(previewType); @@ -658,7 +659,9 @@ public class ZkReportViewer extends Window implements EventListener, ITab future = Adempiere.getThreadPoolExecutor().submit(new DesktopRunnable(new XLSRendererRunnable(this),getDesktop())); } else if ("CSV".equals(previewType.getSelectedItem().getValue())) { future = Adempiere.getThreadPoolExecutor().submit(new DesktopRunnable(new CSVRendererRunnable(this),getDesktop())); - } + } else if ("XLSX".equals(previewType.getSelectedItem().getValue())) { + future = Adempiere.getThreadPoolExecutor().submit(new DesktopRunnable(new XLSXRendererRunnable(this),getDesktop())); + } } private void onPreviewReport() { @@ -1135,6 +1138,7 @@ public class ZkReportViewer extends Window implements EventListener, ITab cboType.appendItem("ssv" + " - " + Msg.getMsg(Env.getCtx(), "FileSSV"), "ssv"); cboType.appendItem("csv" + " - " + Msg.getMsg(Env.getCtx(), "FileCSV"), "csv"); cboType.appendItem("xls" + " - " + Msg.getMsg(Env.getCtx(), "FileXLS"), "xls"); + cboType.appendItem("xlsx" + " - " + Msg.getMsg(Env.getCtx(), "FileXLSX"), "xlsx"); cboType.setSelectedItem(li); Hbox hb = new Hbox(); @@ -1218,6 +1222,11 @@ public class ZkReportViewer extends Window implements EventListener, ITab m_reportEngine.createHTML(sw, false, m_reportEngine.getPrintFormat().getLanguage(), new HTMLExtension(contextPath, "rp", this.getUuid()), true); data = sw.getBuffer().toString().getBytes(); } + else if (ext.equals("xlsx")) + { + inputFile = File.createTempFile("Export", ".xlsx"); + m_reportEngine.createXLSX(inputFile, m_reportEngine.getPrintFormat().getLanguage()); + } else if (ext.equals("xls")) { inputFile = File.createTempFile("Export", ".xls"); @@ -1724,4 +1733,61 @@ public class ZkReportViewer extends Window implements EventListener, ITab } } + + protected static class XLSXRendererRunnable extends ContextRunnable implements IServerPushCallback + { + + private ZkReportViewer viewer; + + public XLSXRendererRunnable(ZkReportViewer viewer) + { + super(); + this.viewer = viewer; + } + + @Override + protected void doRun() + { + try + { + if (!ArchiveEngine.isValid(viewer.m_reportEngine.getLayout())) + log.warning("Cannot archive Document"); + String path = System.getProperty("java.io.tmpdir"); + String prefix = viewer.makePrefix(viewer.m_reportEngine.getName()); + if (log.isLoggable(Level.FINE)) + { + log.log(Level.FINE, "Path=" + path + " Prefix=" + prefix); + } + File file = File.createTempFile(prefix, ".xlsx", new File(path)); + viewer.m_reportEngine.createXLSX(file, viewer.m_reportEngine.getPrintFormat().getLanguage()); + viewer.media = new AMedia(file.getName(), "xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file, true); + } + catch (Exception e) + { + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException(e); + } + finally + { + Desktop desktop = AEnv.getDesktop(); + if (desktop != null && desktop.isAlive()) + { + new ServerPushTemplate(desktop).executeAsync(this); + } + } + } + + @Override + public void updateUI() + { + viewer.labelDrill.setVisible(false); + viewer.comboDrill.setVisible(false); + viewer.onPreviewReport(); + } + + } + }