mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Add style customizer to report action, with excel poi implementation for columnWidths, more cell styles, merged ranges
This commit is contained in:
@ -168,6 +168,11 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, reportInput.getExportStyleCustomizer());
|
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, reportInput.getExportStyleCustomizer());
|
||||||
reportStreamer.setExportStyleCustomizer(styleCustomizer);
|
reportStreamer.setExportStyleCustomizer(styleCustomizer);
|
||||||
}
|
}
|
||||||
|
else if(report.getExportStyleCustomizer() != null)
|
||||||
|
{
|
||||||
|
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, report.getExportStyleCustomizer());
|
||||||
|
reportStreamer.setExportStyleCustomizer(styleCustomizer);
|
||||||
|
}
|
||||||
|
|
||||||
reportStreamer.preRun(reportInput.getReportDestination(), views);
|
reportStreamer.preRun(reportInput.getReportDestination(), views);
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ import java.util.zip.ZipEntry;
|
|||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.ReportUtils;
|
import com.kingsrook.qqq.backend.core.actions.reporting.ReportUtils;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||||
@ -77,6 +78,7 @@ import org.apache.poi.xssf.usermodel.XSSFPivotTable;
|
|||||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -112,9 +114,10 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
public static final String EXCEL_DATE_FORMAT = "yyyy-MM-dd";
|
public static final String EXCEL_DATE_FORMAT = "yyyy-MM-dd";
|
||||||
public static final String EXCEL_DATE_TIME_FORMAT = "yyyy-MM-dd H:mm:ss";
|
public static final String EXCEL_DATE_TIME_FORMAT = "yyyy-MM-dd H:mm:ss";
|
||||||
|
|
||||||
private PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
|
private ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface;
|
||||||
|
|
||||||
private Map<String, String> excelCellFormats;
|
private Map<String, String> excelCellFormats;
|
||||||
private Map<String, XSSFCellStyle> styles = new HashMap<>();
|
private Map<String, XSSFCellStyle> styles = new HashMap<>();
|
||||||
|
|
||||||
private int rowNo = 0;
|
private int rowNo = 0;
|
||||||
private int sheetIndex = 1;
|
private int sheetIndex = 1;
|
||||||
@ -402,6 +405,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
dateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
dateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
||||||
styles.put("datetime", dateTimeStyle);
|
styles.put("datetime", dateTimeStyle);
|
||||||
|
|
||||||
|
PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
|
||||||
styles.put("title", poiExcelStylerInterface.createStyleForTitle(workbook, createHelper));
|
styles.put("title", poiExcelStylerInterface.createStyleForTitle(workbook, createHelper));
|
||||||
styles.put("header", poiExcelStylerInterface.createStyleForHeader(workbook, createHelper));
|
styles.put("header", poiExcelStylerInterface.createStyleForHeader(workbook, createHelper));
|
||||||
styles.put("footer", poiExcelStylerInterface.createStyleForFooter(workbook, createHelper));
|
styles.put("footer", poiExcelStylerInterface.createStyleForFooter(workbook, createHelper));
|
||||||
@ -413,6 +417,11 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
XSSFCellStyle footerDateTimeStyle = poiExcelStylerInterface.createStyleForFooter(workbook, createHelper);
|
XSSFCellStyle footerDateTimeStyle = poiExcelStylerInterface.createStyleForFooter(workbook, createHelper);
|
||||||
footerDateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
footerDateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
||||||
styles.put("footer-datetime", footerDateTimeStyle);
|
styles.put("footer-datetime", footerDateTimeStyle);
|
||||||
|
|
||||||
|
if(styleCustomizerInterface != null)
|
||||||
|
{
|
||||||
|
styleCustomizerInterface.customizeStyles(styles, workbook, createHelper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -458,7 +467,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sheetWriter.beginSheet();
|
sheetWriter.beginSheet(view, styleCustomizerInterface);
|
||||||
|
|
||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
// put the title and header rows in the sheet //
|
// put the title and header rows in the sheet //
|
||||||
@ -560,6 +569,16 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public static void setStyleForField(QRecord record, String fieldName, String styleName)
|
||||||
|
{
|
||||||
|
record.setDisplayValue(fieldName + ":excelStyle", styleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -567,12 +586,12 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
{
|
{
|
||||||
sheetWriter.insertRow(rowNo++);
|
sheetWriter.insertRow(rowNo++);
|
||||||
|
|
||||||
int styleIndex = -1;
|
int baseStyleIndex = -1;
|
||||||
int dateStyleIndex = styles.get("date").getIndex();
|
int dateStyleIndex = styles.get("date").getIndex();
|
||||||
int dateTimeStyleIndex = styles.get("datetime").getIndex();
|
int dateTimeStyleIndex = styles.get("datetime").getIndex();
|
||||||
if(isFooter)
|
if(isFooter)
|
||||||
{
|
{
|
||||||
styleIndex = styles.get("footer").getIndex();
|
baseStyleIndex = styles.get("footer").getIndex();
|
||||||
dateStyleIndex = styles.get("footer-date").getIndex();
|
dateStyleIndex = styles.get("footer-date").getIndex();
|
||||||
dateTimeStyleIndex = styles.get("footer-datetime").getIndex();
|
dateTimeStyleIndex = styles.get("footer-datetime").getIndex();
|
||||||
}
|
}
|
||||||
@ -582,6 +601,13 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
{
|
{
|
||||||
Serializable value = qRecord.getValue(field.getName());
|
Serializable value = qRecord.getValue(field.getName());
|
||||||
|
|
||||||
|
String overrideStyleName = qRecord.getDisplayValue(field.getName() + ":excelStyle");
|
||||||
|
int styleIndex = baseStyleIndex;
|
||||||
|
if(overrideStyleName != null)
|
||||||
|
{
|
||||||
|
styleIndex = styles.get(overrideStyleName).getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
if(value != null)
|
if(value != null)
|
||||||
{
|
{
|
||||||
if(value instanceof String s)
|
if(value instanceof String s)
|
||||||
@ -706,7 +732,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
{
|
{
|
||||||
if(!ReportType.PIVOT.equals(currentView.getType()))
|
if(!ReportType.PIVOT.equals(currentView.getType()))
|
||||||
{
|
{
|
||||||
sheetWriter.endSheet();
|
sheetWriter.endSheet(currentView, styleCustomizerInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
activeSheetWriter.flush();
|
activeSheetWriter.flush();
|
||||||
@ -815,7 +841,29 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
protected PoiExcelStylerInterface getStylerInterface()
|
protected PoiExcelStylerInterface getStylerInterface()
|
||||||
{
|
{
|
||||||
|
if(styleCustomizerInterface != null)
|
||||||
|
{
|
||||||
|
return styleCustomizerInterface.getExcelStyler();
|
||||||
|
}
|
||||||
|
|
||||||
return (new PlainPoiExcelStyler());
|
return (new PlainPoiExcelStyler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
|
||||||
|
{
|
||||||
|
if(exportStyleCustomizer instanceof ExcelPoiBasedStreamingStyleCustomizerInterface poiExcelStylerInterface)
|
||||||
|
{
|
||||||
|
this.styleCustomizerInterface = poiExcelStylerInterface;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG.debug("Supplied export style customizer is not an instance of ExcelPoiStyleCustomizerInterface, so will not be used for an excel export", logPair("exportStyleCustomizerClass", exportStyleCustomizer.getClass().getSimpleName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* 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 Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
|
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** style customization points for Excel files generated via our streaming POI.
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface ExcelPoiBasedStreamingStyleCustomizerInterface extends ExportStyleCustomizerInterface
|
||||||
|
{
|
||||||
|
/***************************************************************************
|
||||||
|
** slightly legacy way we did excel styles - but get an instance of object
|
||||||
|
** that defaults "default" styles (header, footer, etc).
|
||||||
|
***************************************************************************/
|
||||||
|
default PoiExcelStylerInterface getExcelStyler()
|
||||||
|
{
|
||||||
|
return (new PlainPoiExcelStyler());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** either change "default" styles put in the styles map, or create new ones
|
||||||
|
** which can then be applied to row/field values (cells) via:
|
||||||
|
** ExcelPoiBasedStreamingExportStreamer.setStyleForField(row, fieldName, styleName);
|
||||||
|
***************************************************************************/
|
||||||
|
default void customizeStyles(Map<String, XSSFCellStyle> styles, XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
//////////////////
|
||||||
|
// noop default //
|
||||||
|
//////////////////
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** for a given view (sheet), return a list of custom column widths.
|
||||||
|
** any nulls in the list are ignored (so default width is used).
|
||||||
|
***************************************************************************/
|
||||||
|
default List<Integer> getColumnWidthsForView(QReportView view)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** for a given view (sheet), return a list of any ranges which should be
|
||||||
|
** merged, as in "A1:C1" (first three cells in first row).
|
||||||
|
***************************************************************************/
|
||||||
|
default List<String> getMergedRangesForView(QReportView view)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -38,6 +38,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.excel.TestExcelStyler;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel.ExcelFastexcelExportStreamer;
|
import com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel.ExcelFastexcelExportStreamer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.BoldHeaderAndFooterPoiExcelStyler;
|
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.BoldHeaderAndFooterPoiExcelStyler;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingExportStreamer;
|
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingExportStreamer;
|
||||||
@ -56,6 +57,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
@ -490,6 +492,34 @@ public class GenerateReportActionTest extends BaseTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void runXlsxWithStyleCustomizer() throws Exception
|
||||||
|
{
|
||||||
|
ReportFormat format = ReportFormat.XLSX;
|
||||||
|
String name = "/tmp/report-customized.xlsx";
|
||||||
|
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
||||||
|
{
|
||||||
|
QInstance qInstance = QContext.getQInstance();
|
||||||
|
qInstance.addReport(defineTableOnlyReport());
|
||||||
|
insertPersonRecords(qInstance);
|
||||||
|
|
||||||
|
ReportInput reportInput = new ReportInput();
|
||||||
|
reportInput.setReportName(REPORT_NAME);
|
||||||
|
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
|
||||||
|
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
|
||||||
|
reportInput.setExportStyleCustomizer(new QCodeReference(TestExcelStyler.class));
|
||||||
|
new GenerateReportAction().execute(reportInput);
|
||||||
|
System.out.println("Wrote File: " + name);
|
||||||
|
|
||||||
|
LocalMacDevUtils.openFile(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
Reference in New Issue
Block a user