diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java index 764813ba..0fd11194 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java @@ -67,7 +67,7 @@ public class RenderSavedReportExecuteStep implements BackendStep runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report"); - QReportMetaData reportMetaData = new SavedReportToReportMetaDataAdapter().adapt(savedReport); + QReportMetaData reportMetaData = new SavedReportToReportMetaDataAdapter().adapt(savedReport, reportFormat); try(FileOutputStream reportOutputStream = new FileOutputStream(tmpFile)) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportToReportMetaDataAdapter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportToReportMetaDataAdapter.java index 751f20d7..1f6e6f43 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportToReportMetaDataAdapter.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportToReportMetaDataAdapter.java @@ -29,8 +29,13 @@ import java.util.Set; import com.fasterxml.jackson.databind.DeserializationFeature; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition; +import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy; +import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue; +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.QueryJoin; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; @@ -48,7 +53,6 @@ import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport; 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.collections.ListBuilder; import org.apache.commons.lang.BooleanUtils; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; @@ -66,7 +70,7 @@ public class SavedReportToReportMetaDataAdapter /******************************************************************************* ** *******************************************************************************/ - public QReportMetaData adapt(SavedReport savedReport) throws QException + public QReportMetaData adapt(SavedReport savedReport, ReportFormat reportFormat) throws QException { try { @@ -97,11 +101,11 @@ public class SavedReportToReportMetaDataAdapter view.setLabel(savedReport.getLabel()); // todo eh? view.setIncludeHeaderRow(true); - //////////////////////////////////////////////////////////////////////////////////////////////// - // columns in the saved-report should look like a serialized version of ReportColumns object // - // map them to a list of QReportField objects // - // also keep track of what joinTables we find that we need to select // - //////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + // columns in the saved-report should look like a serialized version of ReportColumns object // + // map them to a list of QReportField objects // + // also keep track of what joinTables we find that we need to select // + /////////////////////////////////////////////////////////////////////////////////////////////// ReportColumns columnsObject = JsonUtils.toObject(savedReport.getColumnsJson(), ReportColumns.class, om -> om.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); List reportColumns = new ArrayList<>(); @@ -122,9 +126,9 @@ public class SavedReportToReportMetaDataAdapter //////////////////////////////////////////////////// // figure out the field being named by the column // //////////////////////////////////////////////////// - String fieldName = ValueUtils.getValueAsString(column.getName()); - QFieldMetaData field = getField(savedReport, fieldName, qInstance, neededJoinTables, table); - if(field == null) + String fieldName = column.getName(); + FieldAndJoinTable fieldAndJoinTable = getField(savedReport, fieldName, qInstance, neededJoinTables, table); + if(fieldAndJoinTable == null) { continue; } @@ -132,16 +136,7 @@ public class SavedReportToReportMetaDataAdapter ////////////////////////////////////////////////// // make a QReportField based on the table field // ////////////////////////////////////////////////// - QReportField reportField = new QReportField(); - reportColumns.add(reportField); - - reportField.setName(fieldName); - reportField.setLabel(field.getLabel()); - - if(StringUtils.hasContent(field.getPossibleValueSourceName())) - { - reportField.setShowPossibleValueLabel(true); - } + reportColumns.add(makeQReportField(fieldName, fieldAndJoinTable)); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -178,17 +173,93 @@ public class SavedReportToReportMetaDataAdapter } } - /////////////////////////////////////////////// - // if it's a pivot report, add that view too // - /////////////////////////////////////////////// + ///////////////////////////////////////// + // if it's a pivot report, handle that // + ///////////////////////////////////////// if(StringUtils.hasContent(savedReport.getPivotTableJson())) { + PivotTableDefinition pivotTableDefinition = JsonUtils.toObject(savedReport.getPivotTableJson(), PivotTableDefinition.class); + QReportView pivotView = new QReportView(); reportMetaData.getViews().add(pivotView); pivotView.setName("pivot"); - pivotView.setType(ReportType.PIVOT); - pivotView.setPivotTableSourceViewName(view.getName()); - pivotView.setPivotTableDefinition(JsonUtils.toObject(savedReport.getPivotTableJson(), PivotTableDefinition.class)); + pivotView.setLabel("Pivot Table"); + + if(reportFormat != null && reportFormat.getSupportsNativePivotTables()) + { + pivotView.setType(ReportType.PIVOT); + pivotView.setPivotTableSourceViewName(view.getName()); + pivotView.setPivotTableDefinition(pivotTableDefinition); + } + else + { + if(!CollectionUtils.nullSafeHasContents(pivotTableDefinition.getRows())) + { + throw (new QUserFacingException("To generate a pivot report in " + reportFormat + " format, it must have 1 or more Pivot Rows")); + } + + if(CollectionUtils.nullSafeHasContents(pivotTableDefinition.getColumns())) + { + throw (new QUserFacingException("To generate a pivot report in " + reportFormat + " format, it may not have any Pivot Columns")); + } + + /////////////////////// + // handle pivot rows // + /////////////////////// + List summaryFields = new ArrayList<>(); + List summaryOrderByFields = new ArrayList<>(); + for(PivotTableGroupBy row : pivotTableDefinition.getRows()) + { + String fieldName = row.getFieldName(); + FieldAndJoinTable fieldAndJoinTable = getField(savedReport, fieldName, qInstance, neededJoinTables, table); + if(fieldAndJoinTable == null) + { + LOG.warn("The field for a Pivot Row wasn't found, when converting to a summary...", logPair("savedReportId", savedReport.getId()), logPair("fieldName", fieldName)); + continue; + } + summaryFields.add(fieldName); + summaryOrderByFields.add(new QFilterOrderBy(fieldName)); + } + + ///////////////////////// + // handle pivot values // + ///////////////////////// + List summaryViewColumns = new ArrayList<>(); + for(PivotTableValue value : pivotTableDefinition.getValues()) + { + String fieldName = value.getFieldName(); + FieldAndJoinTable fieldAndJoinTable = getField(savedReport, fieldName, qInstance, neededJoinTables, table); + if(fieldAndJoinTable == null) + { + LOG.warn("The field for a Pivot Value wasn't found, when converting to a summary...", logPair("savedReportId", savedReport.getId()), logPair("fieldName", fieldName)); + continue; + } + + QReportField reportField = makeQReportField(fieldName, fieldAndJoinTable); + reportField.setName(fieldName + "_" + value.getFunction().name()); + reportField.setLabel(StringUtils.ucFirst(value.getFunction().name().toLowerCase()) + " Of " + reportField.getLabel()); + reportField.setFormula("${pivot." + value.getFunction().name().toLowerCase() + "." + fieldName + "}"); + summaryViewColumns.add(reportField); + summaryOrderByFields.add(new QFilterOrderBy(reportField.getName())); + } + + pivotView.setType(ReportType.SUMMARY); + pivotView.setDataSourceName(dataSource.getName()); + pivotView.setIncludeHeaderRow(true); + pivotView.setIncludeTotalRow(true); + pivotView.setColumns(summaryViewColumns); + pivotView.setSummaryFields(summaryFields); + pivotView.withOrderByFields(summaryOrderByFields); + } + + //////////////////////////////////////////////////////////////////////////////////// + // in case the reportFormat doesn't support multiple views, and we have a pivot - // + // then remove the data view // + //////////////////////////////////////////////////////////////////////////////////// + if(reportFormat != null && !reportFormat.getSupportsMultipleViews()) + { + reportMetaData.getViews().remove(0); + } } ///////////////////////////////////////////////////// @@ -217,7 +288,44 @@ public class SavedReportToReportMetaDataAdapter /******************************************************************************* ** *******************************************************************************/ - private static QFieldMetaData getField(SavedReport savedReport, String fieldName, QInstance qInstance, Set neededJoinTables, QTableMetaData table) + private static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable) + { + QReportField reportField = new QReportField(); + + reportField.setName(fieldName); + + if(fieldAndJoinTable.joinTable() == null) + { + //////////////////////////////////////////////////////////// + // for fields from this table, just use the field's label // + //////////////////////////////////////////////////////////// + reportField.setLabel(fieldAndJoinTable.field().getLabel()); + } + else + { + /////////////////////////////////////////////////////////////// + // for fields from join tables, use table label: field label // + /////////////////////////////////////////////////////////////// + reportField.setLabel(fieldAndJoinTable.joinTable().getLabel() + ": " + fieldAndJoinTable.field().getLabel()); + } + + if(StringUtils.hasContent(fieldAndJoinTable.field().getPossibleValueSourceName())) + { + reportField.setShowPossibleValueLabel(true); + } + + reportField.setType(fieldAndJoinTable.field().getType()); + + return reportField; + } + + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static FieldAndJoinTable getField(SavedReport savedReport, String fieldName, QInstance qInstance, Set neededJoinTables, QTableMetaData table) { QFieldMetaData field; if(fieldName.contains(".")) @@ -240,6 +348,8 @@ public class SavedReportToReportMetaDataAdapter LOG.warn("Saved Report has an unrecognized join field name", logPair("savedReportId", savedReport.getId()), logPair("fieldName", fieldName)); return null; } + + return new FieldAndJoinTable(field, joinTable); } else { @@ -255,8 +365,15 @@ public class SavedReportToReportMetaDataAdapter } return null; } + + return new FieldAndJoinTable(field, null); } - return field; } + + + /******************************************************************************* + ** + *******************************************************************************/ + private record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {} }