mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Merged feature/dk-misc-20250318 into dev
This commit is contained in:
@ -649,7 +649,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
|||||||
input.setRecordList(records);
|
input.setRecordList(records);
|
||||||
input.setAction(action);
|
input.setAction(action);
|
||||||
|
|
||||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getRecordAutomationHandler(action);
|
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandler.class, action.getCodeReference());
|
||||||
recordAutomationHandler.execute(input);
|
recordAutomationHandler.execute(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
|||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
@ -43,9 +37,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Utility to load code for running QQQ customizers.
|
** Utility to load code for running QQQ customizers.
|
||||||
**
|
**
|
||||||
** TODO - redo all to go through method that memoizes class & constructor
|
** That memoization causes 1,000,000 such calls to go from ~500ms to ~100ms.
|
||||||
** lookup. That memoziation causes 1,000,000 such calls to go from ~500ms
|
|
||||||
** to ~100ms.
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QCodeLoader
|
public class QCodeLoader
|
||||||
{
|
{
|
||||||
@ -70,84 +62,6 @@ public class QCodeLoader
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T, R> Function<T, R> getFunction(QCodeReference codeReference)
|
|
||||||
{
|
|
||||||
if(codeReference == null)
|
|
||||||
{
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
|
||||||
{
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
|
||||||
return ((Function<T, R>) customizerClass.getConstructor().newInstance());
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
|
||||||
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
|
||||||
// if it finds an invalid code reference //
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T extends BackendStep> T getBackendStep(Class<T> expectedType, QCodeReference codeReference)
|
|
||||||
{
|
|
||||||
if(codeReference == null)
|
|
||||||
{
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
|
||||||
{
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
throw (new IllegalArgumentException("Only JAVA BackendSteps are supported at this time."));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
|
||||||
return ((T) customizerClass.getConstructor().newInstance());
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
|
||||||
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
|
||||||
// if it finds an invalid code reference //
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -177,7 +91,17 @@ public class QCodeLoader
|
|||||||
|
|
||||||
if(constructor.isPresent())
|
if(constructor.isPresent())
|
||||||
{
|
{
|
||||||
return ((T) constructor.get().newInstance());
|
T t = (T) constructor.get().newInstance();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
// if the object is initializable, then, well, initialize it! //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
if(t instanceof InitializableViaCodeReference initializableViaCodeReference)
|
||||||
|
{
|
||||||
|
initializableViaCodeReference.initialize(codeReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -187,7 +111,7 @@ public class QCodeLoader
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
LOG.error("Error initializing codeReference", e, logPair("codeReference", codeReference));
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||||
@ -198,67 +122,4 @@ public class QCodeLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public static RecordAutomationHandler getRecordAutomationHandler(TableAutomationAction action) throws QException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QCodeReference codeReference = action.getCodeReference();
|
|
||||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
|
||||||
{
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<?> codeClass = Class.forName(codeReference.getName());
|
|
||||||
Object codeObject = codeClass.getConstructor().newInstance();
|
|
||||||
if(!(codeObject instanceof RecordAutomationHandler recordAutomationHandler))
|
|
||||||
{
|
|
||||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of RecordAutomationHandler"));
|
|
||||||
}
|
|
||||||
return (recordAutomationHandler);
|
|
||||||
}
|
|
||||||
catch(QException qe)
|
|
||||||
{
|
|
||||||
throw (qe);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
throw (new QException("Error getting record automation handler for action [" + action.getName() + "]", e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Class<?> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
|
|
||||||
Object codeObject = codeClass.getConstructor().newInstance();
|
|
||||||
if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
|
|
||||||
{
|
|
||||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
|
|
||||||
}
|
|
||||||
return (customPossibleValueProvider);
|
|
||||||
}
|
|
||||||
catch(QException qe)
|
|
||||||
{
|
|
||||||
throw (qe);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,18 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@Deprecated(since = "call one that doesn't take input param")
|
||||||
public static String linkRecordEdit(AbstractActionInput input, String tableName, Serializable recordId) throws QException
|
public static String linkRecordEdit(AbstractActionInput input, String tableName, Serializable recordId) throws QException
|
||||||
|
{
|
||||||
|
return linkRecordEdit(tableName, recordId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static String linkRecordEdit(String tableName, Serializable recordId) throws QException
|
||||||
{
|
{
|
||||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||||
return (tablePath + "/" + recordId + "/edit");
|
return (tablePath + "/" + recordId + "/edit");
|
||||||
@ -317,7 +328,17 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@Deprecated(since = "call one that doesn't take input param")
|
||||||
public static String linkProcessForFilter(AbstractActionInput input, String processName, QQueryFilter filter) throws QException
|
public static String linkProcessForFilter(AbstractActionInput input, String processName, QQueryFilter filter) throws QException
|
||||||
|
{
|
||||||
|
return linkProcessForFilter(processName, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static String linkProcessForFilter(String processName, QQueryFilter filter) throws QException
|
||||||
{
|
{
|
||||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||||
if(process == null)
|
if(process == null)
|
||||||
@ -337,10 +358,21 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@Deprecated(since = "call one that doesn't take input param")
|
||||||
public static String linkProcessForRecord(AbstractActionInput input, String processName, Serializable recordId) throws QException
|
public static String linkProcessForRecord(AbstractActionInput input, String processName, Serializable recordId) throws QException
|
||||||
|
{
|
||||||
|
return linkProcessForRecord(processName, recordId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static String linkProcessForRecord(String processName, Serializable recordId) throws QException
|
||||||
{
|
{
|
||||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||||
String tableName = process.getTableName();
|
String tableName = process.getTableName();
|
||||||
|
@ -54,6 +54,14 @@ public interface ExportStreamerInterface
|
|||||||
// noop in base class
|
// noop in base class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
default void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
|
||||||
|
{
|
||||||
|
// noop in base class
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Called once per sheet, before any rows are available. Meant to write a
|
** Called once per sheet, before any rows are available. Meant to write a
|
||||||
** header, for example.
|
** header, for example.
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** interface for classes that can be used to customize visual style aspects of
|
||||||
|
** exports/reports.
|
||||||
|
**
|
||||||
|
** Anticipates very different sub-interfaces based on the file type being generated,
|
||||||
|
** and the capabilities of each. e.g., excel (bolds, fonts, cell merging) vs
|
||||||
|
** json (different structure of objects).
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface ExportStyleCustomizerInterface
|
||||||
|
{
|
||||||
|
}
|
@ -163,6 +163,17 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
reportStreamer = reportFormat.newReportStreamer();
|
reportStreamer = reportFormat.newReportStreamer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(reportInput.getExportStyleCustomizer() != null)
|
||||||
|
{
|
||||||
|
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, reportInput.getExportStyleCustomizer());
|
||||||
|
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);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -211,7 +222,8 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(dataSourceTableView.getViewCustomizer() != null)
|
if(dataSourceTableView.getViewCustomizer() != null)
|
||||||
{
|
{
|
||||||
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getFunction(dataSourceTableView.getViewCustomizer());
|
@SuppressWarnings("unchecked")
|
||||||
|
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getAdHoc(Function.class, dataSourceTableView.getViewCustomizer());
|
||||||
if(viewCustomizerFunction instanceof ReportViewCustomizer reportViewCustomizer)
|
if(viewCustomizerFunction instanceof ReportViewCustomizer reportViewCustomizer)
|
||||||
{
|
{
|
||||||
reportViewCustomizer.setReportInput(reportInput);
|
reportViewCustomizer.setReportInput(reportInput);
|
||||||
@ -660,7 +672,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// if any fields are 'showPossibleValueLabel', then move display values for them into the record's values map //
|
// if any fields are 'showPossibleValueLabel', then move display values for them into the record's values map //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
for(QReportField column : tableView.getColumns())
|
for(QReportField column : CollectionUtils.nonNullList(tableView.getColumns()))
|
||||||
{
|
{
|
||||||
if(column.getShowPossibleValueLabel())
|
if(column.getShowPossibleValueLabel())
|
||||||
{
|
{
|
||||||
|
@ -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,7 +114,8 @@ 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<>();
|
||||||
|
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,7 +25,10 @@ package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import org.apache.poi.ss.util.CellReference;
|
import org.apache.poi.ss.util.CellReference;
|
||||||
|
|
||||||
|
|
||||||
@ -53,13 +56,33 @@ public class StreamedSheetWriter
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void beginSheet() throws IOException
|
public void beginSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
|
||||||
{
|
{
|
||||||
writer.write("""
|
writer.write("""
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">""");
|
||||||
<sheetData>""");
|
|
||||||
|
|
||||||
|
if(styleCustomizerInterface != null && view != null)
|
||||||
|
{
|
||||||
|
List<Integer> columnWidths = styleCustomizerInterface.getColumnWidthsForView(view);
|
||||||
|
if(CollectionUtils.nullSafeHasContents(columnWidths))
|
||||||
|
{
|
||||||
|
writer.write("<cols>");
|
||||||
|
for(int i = 0; i < columnWidths.size(); i++)
|
||||||
|
{
|
||||||
|
Integer width = columnWidths.get(i);
|
||||||
|
if(width != null)
|
||||||
|
{
|
||||||
|
writer.write("""
|
||||||
|
<col min="%d" max="%d" width="%d" customWidth="1"/>
|
||||||
|
""".formatted(i + 1, i + 1, width));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.write("</cols>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write("<sheetData>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -67,11 +90,25 @@ public class StreamedSheetWriter
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void endSheet() throws IOException
|
public void endSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
|
||||||
{
|
{
|
||||||
writer.write("""
|
writer.write("</sheetData>");
|
||||||
</sheetData>
|
|
||||||
</worksheet>""");
|
if(styleCustomizerInterface != null && view != null)
|
||||||
|
{
|
||||||
|
List<String> mergedRanges = styleCustomizerInterface.getMergedRangesForView(view);
|
||||||
|
if(CollectionUtils.nullSafeHasContents(mergedRanges))
|
||||||
|
{
|
||||||
|
writer.write(String.format("<mergeCells count=\"%d\">", mergedRanges.size()));
|
||||||
|
for(String range : mergedRanges)
|
||||||
|
{
|
||||||
|
writer.write(String.format("<mergeCell ref=\"%s\"/>", range));
|
||||||
|
}
|
||||||
|
writer.write("</mergeCells>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write("</worksheet>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +53,8 @@ public class QJavaExecutor implements QCodeExecutor
|
|||||||
Serializable output;
|
Serializable output;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Function<Map<String, Object>, Serializable> function = QCodeLoader.getFunction(codeReference);
|
@SuppressWarnings("unchecked")
|
||||||
|
Function<Map<String, Object>, Serializable> function = QCodeLoader.getAdHoc(Function.class, codeReference);
|
||||||
output = function.apply(context);
|
output = function.apply(context);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
|
@ -341,7 +341,7 @@ public class QPossibleValueTranslator
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||||
return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
|
return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
|
@ -424,7 +424,7 @@ public class SearchPossibleValueSourceAction
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||||
List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
|
List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
|
||||||
|
|
||||||
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
||||||
|
@ -1425,7 +1425,7 @@ public class QInstanceEnricher
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||||
|
|
||||||
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
|
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
|
||||||
Type returnType = getPossibleValueMethod.getGenericReturnType();
|
Type returnType = getPossibleValueMethod.getGenericReturnType();
|
||||||
|
@ -28,6 +28,7 @@ import java.util.Map;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ public class ReportInput extends AbstractTableActionInput
|
|||||||
private ReportDestination reportDestination;
|
private ReportDestination reportDestination;
|
||||||
|
|
||||||
private Supplier<? extends ExportStreamerInterface> overrideExportStreamerSupplier;
|
private Supplier<? extends ExportStreamerInterface> overrideExportStreamerSupplier;
|
||||||
|
private QCodeReference exportStyleCustomizer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -208,4 +210,35 @@ public class ReportInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for exportStyleCustomizer
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getExportStyleCustomizer()
|
||||||
|
{
|
||||||
|
return (this.exportStyleCustomizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for exportStyleCustomizer
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||||
|
{
|
||||||
|
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for exportStyleCustomizer
|
||||||
|
*******************************************************************************/
|
||||||
|
public ReportInput withExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||||
|
{
|
||||||
|
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -583,4 +583,31 @@ public abstract class QRecordEntity
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public static String getTableName(Class<? extends QRecordEntity> entityClass) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Field tableNameField = entityClass.getDeclaredField("TABLE_NAME");
|
||||||
|
String tableNameValue = (String) tableNameField.get(null);
|
||||||
|
return (tableNameValue);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QException("Could not get TABLE_NAME from entity class: " + entityClass.getSimpleName(), e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** named without the 'get' to avoid conflict w/ entity fields named that...
|
||||||
|
***************************************************************************/
|
||||||
|
public String tableName() throws QException
|
||||||
|
{
|
||||||
|
return (getTableName(this.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -417,7 +417,7 @@ public class MetaDataProducerHelper
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName);
|
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy());
|
||||||
producer.setSourceClass(entityClass);
|
producer.setSourceClass(entityClass);
|
||||||
return producer;
|
return producer;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.model.metadata.code;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** an object which is intended to be constructed via a CodeReference, and,
|
||||||
|
** moreso, after it is created, then the initialize method here gets called,
|
||||||
|
** passing the codeRefernce in - e.g., to do additional initalization of the
|
||||||
|
** object, e.g., properties in a QCodeReferenceWithProperties
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface InitializableViaCodeReference
|
||||||
|
{
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
void initialize(QCodeReference codeReference);
|
||||||
|
|
||||||
|
}
|
@ -29,7 +29,7 @@ import java.io.Serializable;
|
|||||||
** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
|
** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
|
||||||
** maybe process steps, maybe customization to a table, etc.
|
** maybe process steps, maybe customization to a table, etc.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QCodeReference implements Serializable
|
public class QCodeReference implements Serializable, Cloneable
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private QCodeType codeType;
|
private QCodeType codeType;
|
||||||
@ -58,6 +58,25 @@ public class QCodeReference implements Serializable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QCodeReference clone()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QCodeReference clone = (QCodeReference) super.clone();
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
catch(CloneNotSupportedException e)
|
||||||
|
{
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -179,5 +198,4 @@ public class QCodeReference implements Serializable
|
|||||||
this.inlineCode = inlineCode;
|
this.inlineCode = inlineCode;
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.model.metadata.code;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** a code reference that also has a map of properties. This object (with the
|
||||||
|
** properties) will be passed in to the referenced object, if it implements
|
||||||
|
** InitializableViaCodeReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QCodeReferenceWithProperties extends QCodeReference
|
||||||
|
{
|
||||||
|
private final Map<String, Serializable> properties;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public QCodeReferenceWithProperties(Class<?> javaClass, Map<String, Serializable> properties)
|
||||||
|
{
|
||||||
|
super(javaClass);
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for properties
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Serializable> getProperties()
|
||||||
|
{
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
@ -62,7 +62,8 @@ public class WidgetAdHocValue extends AbstractWidgetValueSource
|
|||||||
context.putAll(inputValues);
|
context.putAll(inputValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
Function<Object, Object> function = QCodeLoader.getFunction(codeReference);
|
@SuppressWarnings("unchecked")
|
||||||
|
Function<Object, Object> function = QCodeLoader.getAdHoc(Function.class, codeReference);
|
||||||
Object result = function.apply(context);
|
Object result = function.apply(context);
|
||||||
return (result);
|
return (result);
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ public class QFieldMetaData implements Cloneable
|
|||||||
|
|
||||||
if(StringUtils.hasContent(fieldAnnotation.defaultValue()))
|
if(StringUtils.hasContent(fieldAnnotation.defaultValue()))
|
||||||
{
|
{
|
||||||
ValueUtils.getValueAsFieldType(this.type, fieldAnnotation.defaultValue());
|
withDefaultValue(ValueUtils.getValueAsFieldType(this.type, fieldAnnotation.defaultValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ public class QFrontendFieldMetaData implements Serializable
|
|||||||
private String possibleValueSourceName;
|
private String possibleValueSourceName;
|
||||||
private String displayFormat;
|
private String displayFormat;
|
||||||
private Serializable defaultValue;
|
private Serializable defaultValue;
|
||||||
|
private Integer maxLength;
|
||||||
|
|
||||||
private List<FieldAdornment> adornments;
|
private List<FieldAdornment> adornments;
|
||||||
private List<QHelpContent> helpContents;
|
private List<QHelpContent> helpContents;
|
||||||
@ -85,6 +86,7 @@ public class QFrontendFieldMetaData implements Serializable
|
|||||||
this.defaultValue = fieldMetaData.getDefaultValue();
|
this.defaultValue = fieldMetaData.getDefaultValue();
|
||||||
this.helpContents = fieldMetaData.getHelpContents();
|
this.helpContents = fieldMetaData.getHelpContents();
|
||||||
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
|
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
|
||||||
|
this.maxLength = fieldMetaData.getMaxLength();
|
||||||
|
|
||||||
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
|
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
|
||||||
{
|
{
|
||||||
|
@ -48,6 +48,8 @@ public class QFrontendProcessMetaData
|
|||||||
private String label;
|
private String label;
|
||||||
private String tableName;
|
private String tableName;
|
||||||
private boolean isHidden;
|
private boolean isHidden;
|
||||||
|
private Integer minInputRecords;
|
||||||
|
private Integer maxInputRecords;
|
||||||
|
|
||||||
private QIcon icon;
|
private QIcon icon;
|
||||||
|
|
||||||
@ -72,6 +74,8 @@ public class QFrontendProcessMetaData
|
|||||||
this.tableName = processMetaData.getTableName();
|
this.tableName = processMetaData.getTableName();
|
||||||
this.isHidden = processMetaData.getIsHidden();
|
this.isHidden = processMetaData.getIsHidden();
|
||||||
this.stepFlow = processMetaData.getStepFlow().toString();
|
this.stepFlow = processMetaData.getStepFlow().toString();
|
||||||
|
this.minInputRecords = processMetaData.getMinInputRecords();
|
||||||
|
this.maxInputRecords = processMetaData.getMaxInputRecords();
|
||||||
|
|
||||||
if(includeSteps)
|
if(includeSteps)
|
||||||
{
|
{
|
||||||
@ -213,4 +217,27 @@ public class QFrontendProcessMetaData
|
|||||||
{
|
{
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for minInputRecords
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getMinInputRecords()
|
||||||
|
{
|
||||||
|
return minInputRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for maxInputRecords
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getMaxInputRecords()
|
||||||
|
{
|
||||||
|
return maxInputRecords;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
|||||||
** Future may allow something like a "namespace", and/or multiple icons for
|
** Future may allow something like a "namespace", and/or multiple icons for
|
||||||
** use in different frontends, etc.
|
** use in different frontends, etc.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QIcon
|
public class QIcon implements Cloneable
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private String path;
|
private String path;
|
||||||
@ -58,6 +58,25 @@ public class QIcon
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QIcon clone()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QIcon clone = (QIcon) super.clone();
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
catch(CloneNotSupportedException e)
|
||||||
|
{
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for name
|
** Getter for name
|
||||||
**
|
**
|
||||||
@ -154,6 +173,4 @@ public class QIcon
|
|||||||
this.color = color;
|
this.color = color;
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.producers;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||||
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.joins.JoinOn;
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
|
||||||
|
|
||||||
@ -39,8 +41,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|||||||
**
|
**
|
||||||
** e.g., Orders & LineItems - on the Order entity
|
** e.g., Orders & LineItems - on the Order entity
|
||||||
** <code>
|
** <code>
|
||||||
@QMetaDataProducingEntity(
|
@QMetaDataProducingEntity( childTables = { @ChildTable(
|
||||||
childTables = { @ChildTable(
|
|
||||||
childTableEntityClass = LineItem.class,
|
childTableEntityClass = LineItem.class,
|
||||||
childJoin = @ChildJoin(enabled = true),
|
childJoin = @ChildJoin(enabled = true),
|
||||||
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
||||||
@ -62,13 +63,16 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
|||||||
private String parentTableName; // e.g., order
|
private String parentTableName; // e.g., order
|
||||||
private String foreignKeyFieldName; // e.g., orderId
|
private String foreignKeyFieldName; // e.g., orderId
|
||||||
|
|
||||||
|
private ChildJoin.OrderBy[] orderBys;
|
||||||
|
|
||||||
private Class<?> sourceClass;
|
private Class<?> sourceClass;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName)
|
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName, ChildJoin.OrderBy[] orderBys)
|
||||||
{
|
{
|
||||||
Objects.requireNonNull(childTableName, "childTableName cannot be null");
|
Objects.requireNonNull(childTableName, "childTableName cannot be null");
|
||||||
Objects.requireNonNull(parentTableName, "parentTableName cannot be null");
|
Objects.requireNonNull(parentTableName, "parentTableName cannot be null");
|
||||||
@ -77,6 +81,7 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
|||||||
this.childTableName = childTableName;
|
this.childTableName = childTableName;
|
||||||
this.parentTableName = parentTableName;
|
this.parentTableName = parentTableName;
|
||||||
this.foreignKeyFieldName = foreignKeyFieldName;
|
this.foreignKeyFieldName = foreignKeyFieldName;
|
||||||
|
this.orderBys = orderBys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -87,18 +92,39 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
|||||||
@Override
|
@Override
|
||||||
public QJoinMetaData produce(QInstance qInstance) throws QException
|
public QJoinMetaData produce(QInstance qInstance) throws QException
|
||||||
{
|
{
|
||||||
QTableMetaData possibleValueTable = qInstance.getTable(parentTableName);
|
QTableMetaData parentTable = qInstance.getTable(parentTableName);
|
||||||
if(possibleValueTable == null)
|
if(parentTable == null)
|
||||||
{
|
{
|
||||||
throw (new QException("Could not find tableMetaData " + parentTableName));
|
throw (new QException("Could not find tableMetaData " + parentTableName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTableMetaData childTable = qInstance.getTable(childTableName);
|
||||||
|
if(childTable == null)
|
||||||
|
{
|
||||||
|
throw (new QException("Could not find tableMetaData " + childTable));
|
||||||
|
}
|
||||||
|
|
||||||
QJoinMetaData join = new QJoinMetaData()
|
QJoinMetaData join = new QJoinMetaData()
|
||||||
.withLeftTable(parentTableName)
|
.withLeftTable(parentTableName)
|
||||||
.withRightTable(childTableName)
|
.withRightTable(childTableName)
|
||||||
.withInferredName()
|
.withInferredName()
|
||||||
.withType(JoinType.ONE_TO_MANY)
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
.withJoinOn(new JoinOn(possibleValueTable.getPrimaryKeyField(), foreignKeyFieldName));
|
.withJoinOn(new JoinOn(parentTable.getPrimaryKeyField(), foreignKeyFieldName));
|
||||||
|
|
||||||
|
if(orderBys != null && orderBys.length > 0)
|
||||||
|
{
|
||||||
|
for(ChildJoin.OrderBy orderBy : orderBys)
|
||||||
|
{
|
||||||
|
join.withOrderBy(new QFilterOrderBy(orderBy.fieldName(), orderBy.isAscending()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// by default, sort by the id of the child table... mmm //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
join.withOrderBy(new QFilterOrderBy(childTable.getPrimaryKeyField()));
|
||||||
|
}
|
||||||
|
|
||||||
return (join);
|
return (join);
|
||||||
}
|
}
|
||||||
@ -126,6 +152,7 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter for sourceClass
|
** Fluent setter for sourceClass
|
||||||
**
|
**
|
||||||
|
@ -35,4 +35,16 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
public @interface ChildJoin
|
public @interface ChildJoin
|
||||||
{
|
{
|
||||||
boolean enabled();
|
boolean enabled();
|
||||||
|
|
||||||
|
OrderBy[] orderBy() default { };
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@interface OrderBy
|
||||||
|
{
|
||||||
|
String fieldName();
|
||||||
|
|
||||||
|
boolean isAscending() default true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|||||||
** (optionally along with queryJoins and queryInputCustomizer) is used.
|
** (optionally along with queryJoins and queryInputCustomizer) is used.
|
||||||
** - else a staticDataSupplier is used.
|
** - else a staticDataSupplier is used.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QReportDataSource
|
public class QReportDataSource implements Cloneable
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ -55,6 +55,39 @@ public class QReportDataSource
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QReportDataSource clone()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QReportDataSource clone = (QReportDataSource) super.clone();
|
||||||
|
if(queryFilter != null)
|
||||||
|
{
|
||||||
|
clone.queryFilter = queryFilter.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(queryJoins != null)
|
||||||
|
{
|
||||||
|
clone.queryJoins = new ArrayList<>();
|
||||||
|
for(QueryJoin join : queryJoins)
|
||||||
|
{
|
||||||
|
queryJoins.add(join.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
catch(CloneNotSupportedException e)
|
||||||
|
{
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for name
|
** Getter for name
|
||||||
**
|
**
|
||||||
@ -274,6 +307,7 @@ public class QReportDataSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for customRecordSource
|
** Getter for customRecordSource
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -303,5 +337,4 @@ public class QReportDataSource
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
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.TopLevelMetaDataInterface;
|
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
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.layout.QAppChildMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||||
@ -37,7 +38,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Meta-data definition of a report generated by QQQ
|
** Meta-data definition of a report generated by QQQ
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface
|
public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface, Cloneable
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private String label;
|
private String label;
|
||||||
@ -52,6 +53,72 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
|
|||||||
|
|
||||||
private QIcon icon;
|
private QIcon icon;
|
||||||
|
|
||||||
|
private QCodeReference exportStyleCustomizer;
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QReportMetaData clone()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QReportMetaData clone = (QReportMetaData) super.clone();
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Deep copy mutable fields //
|
||||||
|
//////////////////////////////
|
||||||
|
if(this.inputFields != null)
|
||||||
|
{
|
||||||
|
clone.inputFields = new ArrayList<>();
|
||||||
|
for(QFieldMetaData inputField : this.inputFields)
|
||||||
|
{
|
||||||
|
clone.inputFields.add(inputField.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.dataSources != null)
|
||||||
|
{
|
||||||
|
clone.dataSources = new ArrayList<>();
|
||||||
|
for(QReportDataSource dataSource : this.dataSources)
|
||||||
|
{
|
||||||
|
clone.dataSources.add(dataSource.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.views != null)
|
||||||
|
{
|
||||||
|
clone.views = new ArrayList<>();
|
||||||
|
for(QReportView view : this.views)
|
||||||
|
{
|
||||||
|
clone.views.add(view.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.permissionRules != null)
|
||||||
|
{
|
||||||
|
clone.permissionRules = this.permissionRules.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.icon != null)
|
||||||
|
{
|
||||||
|
clone.icon = this.icon.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.exportStyleCustomizer != null)
|
||||||
|
{
|
||||||
|
clone.exportStyleCustomizer = this.exportStyleCustomizer.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
catch(CloneNotSupportedException e)
|
||||||
|
{
|
||||||
|
throw new AssertionError("Cloning not supported", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -397,4 +464,35 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
|
|||||||
qInstance.addReport(this);
|
qInstance.addReport(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for exportStyleCustomizer
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getExportStyleCustomizer()
|
||||||
|
{
|
||||||
|
return (this.exportStyleCustomizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for exportStyleCustomizer
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||||
|
{
|
||||||
|
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for exportStyleCustomizer
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportMetaData withExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||||
|
{
|
||||||
|
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,12 @@ package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
@ -37,9 +41,14 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mapp
|
|||||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mapping.BulkLoadTableStructureBuilder;
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mapping.BulkLoadTableStructureBuilder;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadFileRow;
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadFileRow;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfile;
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfile;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfileField;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadTableStructure;
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadTableStructure;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -72,7 +81,7 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
|||||||
{
|
{
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<String> headerValues = (List<String>) runBackendStepOutput.getValue("headerValues");
|
List<String> headerValues = (List<String>) runBackendStepOutput.getValue("headerValues");
|
||||||
buildSuggestedMapping(headerValues, tableStructure, runBackendStepOutput);
|
buildSuggestedMapping(headerValues, getPrepopulatedValues(runBackendStepInput), tableStructure, runBackendStepOutput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +90,62 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
|||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
private void buildSuggestedMapping(List<String> headerValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
|
private Map<String, Serializable> getPrepopulatedValues(RunBackendStepInput runBackendStepInput)
|
||||||
|
{
|
||||||
|
String prepopulatedValuesJson = runBackendStepInput.getValueString("prepopulatedValues");
|
||||||
|
if(StringUtils.hasContent(prepopulatedValuesJson))
|
||||||
|
{
|
||||||
|
Map<String, Serializable> rs = new LinkedHashMap<>();
|
||||||
|
JSONObject jsonObject = JsonUtils.toJSONObject(prepopulatedValuesJson);
|
||||||
|
for(String key : jsonObject.keySet())
|
||||||
|
{
|
||||||
|
rs.put(key, jsonObject.optString(key, null));
|
||||||
|
}
|
||||||
|
return (rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private void buildSuggestedMapping(List<String> headerValues, Map<String, Serializable> prepopulatedValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
|
||||||
{
|
{
|
||||||
BulkLoadMappingSuggester bulkLoadMappingSuggester = new BulkLoadMappingSuggester();
|
BulkLoadMappingSuggester bulkLoadMappingSuggester = new BulkLoadMappingSuggester();
|
||||||
BulkLoadProfile bulkLoadProfile = bulkLoadMappingSuggester.suggestBulkLoadMappingProfile(tableStructure, headerValues);
|
BulkLoadProfile bulkLoadProfile = bulkLoadMappingSuggester.suggestBulkLoadMappingProfile(tableStructure, headerValues);
|
||||||
|
|
||||||
|
if(CollectionUtils.nullSafeHasContents(prepopulatedValues))
|
||||||
|
{
|
||||||
|
for(Map.Entry<String, Serializable> entry : prepopulatedValues.entrySet())
|
||||||
|
{
|
||||||
|
String fieldName = entry.getKey();
|
||||||
|
boolean foundFieldInProfile = false;
|
||||||
|
|
||||||
|
for(BulkLoadProfileField bulkLoadProfileField : bulkLoadProfile.getFieldList())
|
||||||
|
{
|
||||||
|
if(bulkLoadProfileField.getFieldName().equals(fieldName))
|
||||||
|
{
|
||||||
|
foundFieldInProfile = true;
|
||||||
|
bulkLoadProfileField.setColumnIndex(null);
|
||||||
|
bulkLoadProfileField.setHeaderName(null);
|
||||||
|
bulkLoadProfileField.setDefaultValue(entry.getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!foundFieldInProfile)
|
||||||
|
{
|
||||||
|
BulkLoadProfileField bulkLoadProfileField = new BulkLoadProfileField();
|
||||||
|
bulkLoadProfileField.setFieldName(fieldName);
|
||||||
|
bulkLoadProfileField.setDefaultValue(entry.getValue());
|
||||||
|
bulkLoadProfile.getFieldList().add(bulkLoadProfileField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runBackendStepOutput.addValue("bulkLoadProfile", bulkLoadProfile);
|
runBackendStepOutput.addValue("bulkLoadProfile", bulkLoadProfile);
|
||||||
runBackendStepOutput.addValue("suggestedBulkLoadProfile", bulkLoadProfile);
|
runBackendStepOutput.addValue("suggestedBulkLoadProfile", bulkLoadProfile);
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,23 @@ public class XlsxFileToRows extends AbstractIteratorBasedFileToRows<org.dhatim.f
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** open/go-to a specific sheet (by 0-based index). resets rows & iterator.
|
||||||
|
***************************************************************************/
|
||||||
|
public void openSheet(int index) throws IOException
|
||||||
|
{
|
||||||
|
Optional<Sheet> sheet = workbook.getSheet(index);
|
||||||
|
|
||||||
|
if(sheet.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new IOException("No sheet found for index: " + index));
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = sheet.get().openStream();
|
||||||
|
setIterator(rows.iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
|
@ -53,7 +53,7 @@ public class BaseStreamedETLStep
|
|||||||
protected AbstractExtractStep getExtractStep(RunBackendStepInput runBackendStepInput)
|
protected AbstractExtractStep getExtractStep(RunBackendStepInput runBackendStepInput)
|
||||||
{
|
{
|
||||||
QCodeReference codeReference = (QCodeReference) runBackendStepInput.getValue(StreamedETLWithFrontendProcess.FIELD_EXTRACT_CODE);
|
QCodeReference codeReference = (QCodeReference) runBackendStepInput.getValue(StreamedETLWithFrontendProcess.FIELD_EXTRACT_CODE);
|
||||||
return (QCodeLoader.getBackendStep(AbstractExtractStep.class, codeReference));
|
return (QCodeLoader.getAdHoc(AbstractExtractStep.class, codeReference));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,13 +22,13 @@
|
|||||||
package com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend;
|
package com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend;
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class CouldNotFindQueryFilterForExtractStepException extends QException
|
public class CouldNotFindQueryFilterForExtractStepException extends QUserFacingException
|
||||||
{
|
{
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
|
@ -279,7 +279,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
|
|||||||
return (new QQueryFilter().withCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, idStrings)));
|
return (new QQueryFilter().withCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, idStrings)));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw (new CouldNotFindQueryFilterForExtractStepException("Could not find query filter for Extract step."));
|
throw (new CouldNotFindQueryFilterForExtractStepException("No records were selected for running this process."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -318,7 +318,7 @@ public class SavedReportToReportMetaDataAdapter
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable)
|
public static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable)
|
||||||
{
|
{
|
||||||
QReportField reportField = new QReportField();
|
QReportField reportField = new QReportField();
|
||||||
|
|
||||||
@ -404,5 +404,5 @@ public class SavedReportToReportMetaDataAdapter
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
|
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,19 @@
|
|||||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.Timer;
|
import com.kingsrook.qqq.backend.core.utils.Timer;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -80,6 +87,7 @@ class QCodeLoaderTest extends BaseTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -91,4 +99,50 @@ class QCodeLoaderTest extends BaseTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testCodeReferenceWithProperties()
|
||||||
|
{
|
||||||
|
assertNull(QCodeLoader.getAdHoc(SomeClass.class, new QCodeReference(SomeClass.class)));
|
||||||
|
|
||||||
|
SomeClass someObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someValue")));
|
||||||
|
assertEquals("someValue", someObject.someProperty);
|
||||||
|
|
||||||
|
SomeClass someOtherObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someOtherValue")));
|
||||||
|
assertEquals("someOtherValue", someOtherObject.someProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public static class SomeClass implements InitializableViaCodeReference
|
||||||
|
{
|
||||||
|
private String someProperty;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void initialize(QCodeReference codeReference)
|
||||||
|
{
|
||||||
|
if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties)
|
||||||
|
{
|
||||||
|
someProperty = ValueUtils.getValueAsString(codeReferenceWithProperties.getProperties().get("property"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!StringUtils.hasContent(someProperty))
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Missing property");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingStyleCustomizerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
|
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||||
|
import org.apache.poi.ss.usermodel.Font;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class TestExcelStyler implements ExcelPoiBasedStreamingStyleCustomizerInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<Integer> getColumnWidthsForView(QReportView view)
|
||||||
|
{
|
||||||
|
return List.of(60, 50, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<String> getMergedRangesForView(QReportView view)
|
||||||
|
{
|
||||||
|
return List.of("A1:B1");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void customizeStyles(Map<String, XSSFCellStyle> styles, XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
Font font = workbook.createFont();
|
||||||
|
font.setFontHeightInPoints((short) 16);
|
||||||
|
font.setBold(true);
|
||||||
|
XSSFCellStyle cellStyle = workbook.createCellStyle();
|
||||||
|
cellStyle.setFont(font);
|
||||||
|
styles.put("header", cellStyle);
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ import org.junit.jupiter.api.AfterEach;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
@ -566,4 +567,22 @@ class QRecordEntityTest extends BaseTest
|
|||||||
assertEquals(0, order.getLineItems().size());
|
assertEquals(0, order.getLineItems().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testTableName() throws QException
|
||||||
|
{
|
||||||
|
assertEquals(Item.TABLE_NAME, QRecordEntity.getTableName(Item.class));
|
||||||
|
assertEquals(Item.TABLE_NAME, Item.getTableName(Item.class));
|
||||||
|
assertEquals(Item.TABLE_NAME, new Item().tableName());
|
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
// no TABLE_NAME in Order class //
|
||||||
|
//////////////////////////////////
|
||||||
|
assertThatThrownBy(() -> Order.getTableName(Order.class));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -22,8 +22,22 @@
|
|||||||
package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
|
package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfile;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfileField;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
@ -66,4 +80,39 @@ class BulkInsertPrepareFileMappingStepTest extends BaseTest
|
|||||||
assertEquals("BAA", BulkInsertPrepareFileMappingStep.toHeaderLetter(2 * 26 * 26 + 26 + 0));
|
assertEquals("BAA", BulkInsertPrepareFileMappingStep.toHeaderLetter(2 * 26 * 26 + 26 + 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void test() throws Exception
|
||||||
|
{
|
||||||
|
String fileName = "personFile.csv";
|
||||||
|
|
||||||
|
StorageInput storageInput = new StorageInput(TestUtils.TABLE_NAME_MEMORY_STORAGE).withReference(fileName);
|
||||||
|
OutputStream outputStream = new StorageAction().createOutputStream(storageInput);
|
||||||
|
outputStream.write("""
|
||||||
|
name,noOfShoes
|
||||||
|
John,2
|
||||||
|
Jane,4
|
||||||
|
""".getBytes(StandardCharsets.UTF_8));
|
||||||
|
outputStream.close();
|
||||||
|
|
||||||
|
RunProcessInput runProcessInput = new RunProcessInput();
|
||||||
|
BulkInsertStepUtils.setStorageInputForTheFile(runProcessInput, storageInput);
|
||||||
|
runProcessInput.addValue("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||||
|
runProcessInput.addValue("prepopulatedValues", JsonUtils.toJson(Map.of("homeStateId", 1)));
|
||||||
|
|
||||||
|
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(runProcessInput.getProcessState());
|
||||||
|
RunBackendStepOutput runBackendStepOutput = new RunBackendStepOutput();
|
||||||
|
|
||||||
|
new BulkInsertPrepareFileMappingStep().run(runBackendStepInput, runBackendStepOutput);
|
||||||
|
|
||||||
|
BulkLoadProfile bulkLoadProfile = (BulkLoadProfile) runBackendStepOutput.getValue("suggestedBulkLoadProfile");
|
||||||
|
Optional<BulkLoadProfileField> homeStateId = bulkLoadProfile.getFieldList().stream().filter(f -> f.getFieldName().equals("homeStateId")).findFirst();
|
||||||
|
assertThat(homeStateId).isPresent();
|
||||||
|
assertEquals("1", homeStateId.get().getDefaultValue());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user