mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 05:30:43 +00:00
QQQ-42 initial implementation of qqq reports (pivots, WIP)
This commit is contained in:
@ -30,9 +30,9 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput;
|
||||
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.fields.QFieldMetaData;
|
||||
@ -50,7 +50,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
/*******************************************************************************
|
||||
** Unit test for the ReportAction
|
||||
*******************************************************************************/
|
||||
class ReportActionTest
|
||||
class ExportActionTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
@ -120,22 +120,22 @@ class ReportActionTest
|
||||
{
|
||||
try(FileOutputStream outputStream = new FileOutputStream(filename))
|
||||
{
|
||||
ReportInput reportInput = new ReportInput(TestUtils.defineInstance(), TestUtils.getMockSession());
|
||||
reportInput.setTableName("person");
|
||||
QTableMetaData table = reportInput.getTable();
|
||||
ExportInput exportInput = new ExportInput(TestUtils.defineInstance(), TestUtils.getMockSession());
|
||||
exportInput.setTableName("person");
|
||||
QTableMetaData table = exportInput.getTable();
|
||||
|
||||
reportInput.setReportFormat(reportFormat);
|
||||
reportInput.setReportOutputStream(outputStream);
|
||||
reportInput.setQueryFilter(new QQueryFilter());
|
||||
reportInput.setLimit(recordCount);
|
||||
exportInput.setReportFormat(reportFormat);
|
||||
exportInput.setReportOutputStream(outputStream);
|
||||
exportInput.setQueryFilter(new QQueryFilter());
|
||||
exportInput.setLimit(recordCount);
|
||||
|
||||
if(specifyFields)
|
||||
{
|
||||
reportInput.setFieldNames(table.getFields().values().stream().map(QFieldMetaData::getName).collect(Collectors.toList()));
|
||||
exportInput.setFieldNames(table.getFields().values().stream().map(QFieldMetaData::getName).collect(Collectors.toList()));
|
||||
}
|
||||
ReportOutput reportOutput = new ReportAction().execute(reportInput);
|
||||
assertNotNull(reportOutput);
|
||||
assertEquals(recordCount, reportOutput.getRecordCount());
|
||||
ExportOutput exportOutput = new ExportAction().execute(exportInput);
|
||||
assertNotNull(exportOutput);
|
||||
assertEquals(recordCount, exportOutput.getRecordCount());
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,12 +147,12 @@ class ReportActionTest
|
||||
@Test
|
||||
void testBadFieldNames()
|
||||
{
|
||||
ReportInput reportInput = new ReportInput(TestUtils.defineInstance(), TestUtils.getMockSession());
|
||||
reportInput.setTableName("person");
|
||||
reportInput.setFieldNames(List.of("Foo", "Bar", "Baz"));
|
||||
ExportInput exportInput = new ExportInput(TestUtils.defineInstance(), TestUtils.getMockSession());
|
||||
exportInput.setTableName("person");
|
||||
exportInput.setFieldNames(List.of("Foo", "Bar", "Baz"));
|
||||
assertThrows(QUserFacingException.class, () ->
|
||||
{
|
||||
new ReportAction().execute(reportInput);
|
||||
new ExportAction().execute(exportInput);
|
||||
});
|
||||
}
|
||||
|
||||
@ -164,15 +164,15 @@ class ReportActionTest
|
||||
@Test
|
||||
void testPreExecuteCount() throws QException
|
||||
{
|
||||
ReportInput reportInput = new ReportInput(TestUtils.defineInstance(), TestUtils.getMockSession());
|
||||
reportInput.setTableName("person");
|
||||
ExportInput exportInput = new ExportInput(TestUtils.defineInstance(), TestUtils.getMockSession());
|
||||
exportInput.setTableName("person");
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// use xlsx, which has a max-rows limit, to verify that code runs, but doesn't throw when there aren't too many rows //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
reportInput.setReportFormat(ReportFormat.XLSX);
|
||||
exportInput.setReportFormat(ReportFormat.XLSX);
|
||||
|
||||
new ReportAction().preExecute(reportInput);
|
||||
new ExportAction().preExecute(exportInput);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// nothing to assert - but if preExecute throws, then the test will fail. //
|
||||
@ -198,17 +198,17 @@ class ReportActionTest
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
qInstance.addTable(wideTable);
|
||||
|
||||
ReportInput reportInput = new ReportInput(qInstance, TestUtils.getMockSession());
|
||||
reportInput.setTableName("wide");
|
||||
ExportInput exportInput = new ExportInput(qInstance, TestUtils.getMockSession());
|
||||
exportInput.setTableName("wide");
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// use xlsx, which has a max-cols limit, to verify that code. //
|
||||
////////////////////////////////////////////////////////////////
|
||||
reportInput.setReportFormat(ReportFormat.XLSX);
|
||||
exportInput.setReportFormat(ReportFormat.XLSX);
|
||||
|
||||
assertThrows(QUserFacingException.class, () ->
|
||||
{
|
||||
new ReportAction().preExecute(reportInput);
|
||||
new ExportAction().preExecute(exportInput);
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QFormulaException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import org.assertj.core.data.Offset;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static com.kingsrook.qqq.backend.core.actions.reporting.FormulaInterpreter.interpretFormula;
|
||||
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.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for FormulaInterpreter
|
||||
*******************************************************************************/
|
||||
class FormulaInterpreterTest
|
||||
{
|
||||
public static final Offset<BigDecimal> ZERO_OFFSET = Offset.offset(BigDecimal.ZERO);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInterpretFormulaSimpleSuccess() throws QFormulaException
|
||||
{
|
||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||
|
||||
assertEquals(new BigDecimal("7"), interpretFormula(vi, "7"));
|
||||
assertEquals(new BigDecimal("8"), interpretFormula(vi, "ADD(3,5)"));
|
||||
assertEquals(new BigDecimal("9"), interpretFormula(vi, "ADD(2,ADD(3,4))"));
|
||||
assertEquals(new BigDecimal("10"), interpretFormula(vi, "ADD(ADD(1,5),4)"));
|
||||
assertEquals(new BigDecimal("11"), interpretFormula(vi, "ADD(ADD(1,5),ADD(2,3))"));
|
||||
assertEquals(new BigDecimal("15"), interpretFormula(vi, "ADD(1,ADD(2,ADD(3,ADD(4,5))))"));
|
||||
assertEquals(new BigDecimal("15"), interpretFormula(vi, "ADD(1,ADD(ADD(2,ADD(3,4)),5))"));
|
||||
assertEquals(new BigDecimal("15"), interpretFormula(vi, "ADD(ADD(ADD(ADD(1,2),3),4),5)"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInterpretFormulaWithVariables() throws QFormulaException
|
||||
{
|
||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||
vi.addValueMap("input", Map.of("i", 5, "j", 6, "f", new BigDecimal("0.1")));
|
||||
|
||||
assertEquals("5", interpretFormula(vi, "${input.i}"));
|
||||
assertEquals(new BigDecimal("8"), interpretFormula(vi, "ADD(3,${input.i})"));
|
||||
assertEquals(new BigDecimal("11"), interpretFormula(vi, "ADD(${input.i},${input.j})"));
|
||||
assertEquals(new BigDecimal("11.1"), interpretFormula(vi, "ADD(${input.f},ADD(${input.i},${input.j}))"));
|
||||
assertEquals(new BigDecimal("11.2"), interpretFormula(vi, "ADD(ADD(${input.f},ADD(${input.i},${input.j})),${input.f})"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInterpretFormulaRecursiveExceptions()
|
||||
{
|
||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||
vi.addValueMap("input", Map.of("i", 5, "c", 'c'));
|
||||
|
||||
assertThatThrownBy(() -> interpretFormula(vi, "")).hasMessageContaining("No results");
|
||||
assertThatThrownBy(() -> interpretFormula(vi, "NOT-A-FUN(1,2)")).hasMessageContaining("unrecognized expression");
|
||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1)")).hasMessageContaining("Wrong number of arguments");
|
||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,2,3)")).hasMessageContaining("Wrong number of arguments");
|
||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,A)")).hasMessageContaining("[A] as a number");
|
||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,${input.c})")).hasMessageContaining("[c] as a number");
|
||||
// todo - bad syntax (e.g., missing ')'
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testFunctions() throws QFormulaException
|
||||
{
|
||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||
|
||||
assertEquals(new BigDecimal("3"), interpretFormula(vi, "ADD(1,2)"));
|
||||
assertEquals(new BigDecimal("2"), interpretFormula(vi, "MINUS(4,2)"));
|
||||
assertEquals(new BigDecimal("34.500"), interpretFormula(vi, "MULTIPLY(100,0.345)"));
|
||||
|
||||
assertThat((BigDecimal) interpretFormula(vi, "DIVIDE(1,2)")).isCloseTo(new BigDecimal("0.5"), ZERO_OFFSET);
|
||||
assertNull(interpretFormula(vi, "DIVIDE(1,0)"));
|
||||
|
||||
assertEquals(new BigDecimal("0.5"), interpretFormula(vi, "ROUND(0.510,1)"));
|
||||
assertEquals(new BigDecimal("5.0"), interpretFormula(vi, "ROUND(5.010,2)"));
|
||||
assertEquals(new BigDecimal("5"), interpretFormula(vi, "ROUND(5.010,1)"));
|
||||
|
||||
assertEquals(new BigDecimal("0.5100"), interpretFormula(vi, "SCALE(0.510,4)"));
|
||||
assertEquals(new BigDecimal("5.01"), interpretFormula(vi, "SCALE(5.010,2)"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QFormulaException
|
||||
{
|
||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||
vi.addValueMap("pivot", Map.of("sum.noOfShoes", 5));
|
||||
vi.addValueMap("total", Map.of("sum.noOfShoes", 18));
|
||||
|
||||
assertEquals(new BigDecimal("27.78"), interpretFormula(vi, "SCALE(MULTIPLY(100,DIVIDE_SCALE(${pivot.sum.noOfShoes},${total.sum.noOfShoes},6)),2)"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,442 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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;
|
||||
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
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.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
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.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
||||
import com.kingsrook.qqq.backend.core.testutils.PersonQRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for GenerateReportAction
|
||||
*******************************************************************************/
|
||||
class GenerateReportActionTest
|
||||
{
|
||||
private static final String REPORT_NAME = "personReport1";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
void beforeAndAfterEach()
|
||||
{
|
||||
ListOfMapsExportStreamer.getList().clear();
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPivot1() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
qInstance.addReport(defineReport(true));
|
||||
insertPersonRecords(qInstance);
|
||||
runReport(qInstance, LocalDate.of(1980, Month.JANUARY, 1), LocalDate.of(1980, Month.DECEMBER, 31));
|
||||
|
||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
||||
Iterator<Map<String, String>> iterator = list.iterator();
|
||||
Map<String, String> row = iterator.next();
|
||||
assertEquals(3, list.size());
|
||||
assertThat(list.get(0)).containsOnlyKeys("Last Name", "Report Start Date", "Report End Date", "Person Count", "Quantity", "Revenue", "Cost", "Profit", "Cost Per", "% Total", "Margins", "Revenue Per", "Margin Per");
|
||||
|
||||
assertThat(row.get("Last Name")).isEqualTo("Keller");
|
||||
assertThat(row.get("Person Count")).isEqualTo("1");
|
||||
assertThat(row.get("Quantity")).isEqualTo("5");
|
||||
assertThat(row.get("Report Start Date")).isEqualTo("1980-01-01");
|
||||
assertThat(row.get("Report End Date")).isEqualTo("1980-12-31");
|
||||
assertThat(row.get("Cost")).isEqualTo("3.50");
|
||||
assertThat(row.get("Revenue")).isEqualTo("2.40");
|
||||
assertThat(row.get("Cost Per")).isEqualTo("0.70");
|
||||
assertThat(row.get("Revenue Per")).isEqualTo("0.48");
|
||||
assertThat(row.get("Margin Per")).isEqualTo("-0.22");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||
assertThat(row.get("Person Count")).isEqualTo("2");
|
||||
assertThat(row.get("Quantity")).isEqualTo("13");
|
||||
assertThat(row.get("Cost")).isEqualTo("7.00"); // sum of the 2 Kelkhoff rows' costs
|
||||
assertThat(row.get("Revenue")).isEqualTo("8.40"); // sum of the 2 Kelkhoff rows' price
|
||||
assertThat(row.get("Cost Per")).isEqualTo("0.54"); // sum cost / quantity
|
||||
assertThat(row.get("Revenue Per")).isEqualTo("0.65"); // sum price (Revenue) / quantity
|
||||
assertThat(row.get("Margin Per")).isEqualTo("0.11"); // Revenue Per - Cost Per
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Last Name")).isEqualTo("Totals");
|
||||
assertThat(row.get("Person Count")).isEqualTo("3");
|
||||
assertThat(row.get("Quantity")).isEqualTo("18");
|
||||
assertThat(row.get("Cost")).isEqualTo("10.50");
|
||||
assertThat(row.get("Cost Per")).startsWith("0.58");
|
||||
assertThat(row.get("Cost")).isEqualTo("10.50"); // sum of all 3 matching rows' costs
|
||||
assertThat(row.get("Revenue")).isEqualTo("10.80"); // sum of all 3 matching rows' price
|
||||
assertThat(row.get("Profit")).isEqualTo("0.30"); // Revenue - Cost
|
||||
assertThat(row.get("Margins")).isEqualTo("0.03"); // 100*Profit / Revenue
|
||||
assertThat(row.get("Cost Per")).isEqualTo("0.58"); // sum cost / quantity
|
||||
assertThat(row.get("Revenue Per")).isEqualTo("0.60"); // sum price (Revenue) / quantity
|
||||
assertThat(row.get("Margin Per")).isEqualTo("0.02"); // Revenue Per - Cost Per
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPivot2() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QReportMetaData report = defineReport(false);
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// change from the default to sort reversed //
|
||||
//////////////////////////////////////////////
|
||||
report.getViews().get(0).getOrderByFields().get(0).setIsAscending(false);
|
||||
qInstance.addReport(report);
|
||||
insertPersonRecords(qInstance);
|
||||
runReport(qInstance, LocalDate.of(1980, Month.JANUARY, 1), LocalDate.of(1980, Month.DECEMBER, 31));
|
||||
|
||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
||||
Iterator<Map<String, String>> iterator = list.iterator();
|
||||
Map<String, String> row = iterator.next();
|
||||
assertEquals(2, list.size());
|
||||
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||
assertThat(row.get("Quantity")).isEqualTo("13");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Last Name")).isEqualTo("Keller");
|
||||
assertThat(row.get("Quantity")).isEqualTo("5");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPivot3() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QReportMetaData report = defineReport(false);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// remove the filters, change to sort by personCount (to get some ties), then sumPrice desc //
|
||||
// this also shows the behavior of a null value in an order by //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
report.setQueryFilter(null);
|
||||
report.getViews().get(0).setOrderByFields(List.of(new QFilterOrderBy("personCount"), new QFilterOrderBy("sumPrice", false)));
|
||||
qInstance.addReport(report);
|
||||
insertPersonRecords(qInstance);
|
||||
runReport(qInstance, LocalDate.now(), LocalDate.now());
|
||||
|
||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
||||
Iterator<Map<String, String>> iterator = list.iterator();
|
||||
Map<String, String> row = iterator.next();
|
||||
|
||||
assertEquals(5, list.size());
|
||||
assertThat(row.get("Last Name")).isEqualTo("Keller");
|
||||
assertThat(row.get("Person Count")).isEqualTo("1");
|
||||
assertThat(row.get("Revenue")).isEqualTo("2.40");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelly");
|
||||
assertThat(row.get("Person Count")).isEqualTo("1");
|
||||
assertThat(row.get("Revenue")).isEqualTo("1.20");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Last Name")).isEqualTo("Jones");
|
||||
assertThat(row.get("Person Count")).isEqualTo("1");
|
||||
assertThat(row.get("Revenue")).isEqualTo("1.00");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Last Name")).isEqualTo("Jonson");
|
||||
assertThat(row.get("Person Count")).isEqualTo("1");
|
||||
assertThat(row.get("Revenue")).isNull();
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||
assertThat(row.get("Person Count")).isEqualTo("2");
|
||||
assertThat(row.get("Revenue")).isEqualTo("8.40");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPivot4() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QReportMetaData report = defineReport(false);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// remove the filter, change to have 2 pivot columns - homeStateId and lastName - we should get no roll-up like this. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
report.setQueryFilter(null);
|
||||
report.getViews().get(0).setPivotFields(List.of(
|
||||
"homeStateId",
|
||||
"lastName"
|
||||
));
|
||||
qInstance.addReport(report);
|
||||
insertPersonRecords(qInstance);
|
||||
runReport(qInstance, LocalDate.now(), LocalDate.now());
|
||||
|
||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
||||
Iterator<Map<String, String>> iterator = list.iterator();
|
||||
Map<String, String> row = iterator.next();
|
||||
assertEquals(6, list.size());
|
||||
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Jonson");
|
||||
assertThat(row.get("Quantity")).isNull();
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Jones");
|
||||
assertThat(row.get("Quantity")).isEqualTo("3");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelly");
|
||||
assertThat(row.get("Quantity")).isEqualTo("4");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Keller");
|
||||
assertThat(row.get("Quantity")).isEqualTo("5");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||
assertThat(row.get("Quantity")).isEqualTo("6");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("2");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||
assertThat(row.get("Quantity")).isEqualTo("7");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPivot5() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
QReportMetaData report = defineReport(false);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// remove the filter, and just pivot on homeStateId - should aggregate differently //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
report.setQueryFilter(null);
|
||||
report.getViews().get(0).setPivotFields(List.of("homeStateId"));
|
||||
qInstance.addReport(report);
|
||||
insertPersonRecords(qInstance);
|
||||
runReport(qInstance, LocalDate.now(), LocalDate.now());
|
||||
|
||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
||||
Iterator<Map<String, String>> iterator = list.iterator();
|
||||
Map<String, String> row = iterator.next();
|
||||
assertEquals(2, list.size());
|
||||
assertThat(row.get("Home State Id")).isEqualTo("2");
|
||||
assertThat(row.get("Last Name")).isNull();
|
||||
assertThat(row.get("Quantity")).isEqualTo("7");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Last Name")).isNull();
|
||||
assertThat(row.get("Quantity")).isEqualTo("18");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void runToCsv() throws Exception
|
||||
{
|
||||
String name = "/tmp/report.csv";
|
||||
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
qInstance.addReport(defineReport(true));
|
||||
insertPersonRecords(qInstance);
|
||||
|
||||
ReportInput reportInput = new ReportInput(qInstance);
|
||||
reportInput.setSession(new QSession());
|
||||
reportInput.setReportName(REPORT_NAME);
|
||||
reportInput.setReportFormat(ReportFormat.CSV);
|
||||
reportInput.setReportOutputStream(fileOutputStream);
|
||||
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
|
||||
new GenerateReportAction().execute(reportInput);
|
||||
System.out.println("Wrote File: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void runToXlsx() throws Exception
|
||||
{
|
||||
String name = "/tmp/report.xlsx";
|
||||
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
qInstance.addReport(defineReport(true));
|
||||
insertPersonRecords(qInstance);
|
||||
|
||||
ReportInput reportInput = new ReportInput(qInstance);
|
||||
reportInput.setSession(new QSession());
|
||||
reportInput.setReportName(REPORT_NAME);
|
||||
reportInput.setReportFormat(ReportFormat.XLSX);
|
||||
reportInput.setReportOutputStream(fileOutputStream);
|
||||
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
|
||||
new GenerateReportAction().execute(reportInput);
|
||||
System.out.println("Wrote File: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void runReport(QInstance qInstance, LocalDate startDate, LocalDate endDate) throws QException
|
||||
{
|
||||
ReportInput reportInput = new ReportInput(qInstance);
|
||||
reportInput.setSession(new QSession());
|
||||
reportInput.setReportName(REPORT_NAME);
|
||||
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
|
||||
reportInput.setReportOutputStream(new ByteArrayOutputStream());
|
||||
reportInput.setInputValues(Map.of("startDate", startDate, "endDate", endDate));
|
||||
new GenerateReportAction().execute(reportInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void insertPersonRecords(QInstance qInstance) throws QException
|
||||
{
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY), List.of(
|
||||
new PersonQRecord().withLastName("Jonson").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(null).withHomeStateId(1).withPrice(null).withCost(new BigDecimal("0.50")), // wrong last initial
|
||||
new PersonQRecord().withLastName("Jones").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(3).withHomeStateId(1).withPrice(new BigDecimal("1.00")).withCost(new BigDecimal("0.50")), // wrong last initial
|
||||
new PersonQRecord().withLastName("Kelly").withBirthDate(LocalDate.of(1979, Month.DECEMBER, 30)).withNoOfShoes(4).withHomeStateId(1).withPrice(new BigDecimal("1.20")).withCost(new BigDecimal("0.50")), // bad birthdate
|
||||
new PersonQRecord().withLastName("Keller").withBirthDate(LocalDate.of(1980, Month.JANUARY, 7)).withNoOfShoes(5).withHomeStateId(1).withPrice(new BigDecimal("2.40")).withCost(new BigDecimal("3.50")),
|
||||
new PersonQRecord().withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.FEBRUARY, 15)).withNoOfShoes(6).withHomeStateId(1).withPrice(new BigDecimal("3.60")).withCost(new BigDecimal("3.50")),
|
||||
new PersonQRecord().withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.MARCH, 20)).withNoOfShoes(7).withHomeStateId(2).withPrice(new BigDecimal("4.80")).withCost(new BigDecimal("3.50"))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QReportMetaData defineReport(boolean includeTotalRow)
|
||||
{
|
||||
return new QReportMetaData()
|
||||
.withName(REPORT_NAME)
|
||||
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||
.withInputFields(List.of(
|
||||
new QFieldMetaData("startDate", QFieldType.DATE_TIME),
|
||||
new QFieldMetaData("endDate", QFieldType.DATE_TIME)
|
||||
))
|
||||
.withQueryFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.STARTS_WITH, List.of("K")))
|
||||
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.BETWEEN, List.of("${input.startDate}", "${input.endDate}")))
|
||||
)
|
||||
.withViews(List.of(
|
||||
new QReportView()
|
||||
.withName("pivot")
|
||||
.withType(ReportType.PIVOT)
|
||||
.withPivotFields(List.of("lastName"))
|
||||
.withTotalRow(includeTotalRow)
|
||||
.withTitleFormat("Number of shoes - people born between %s and %s - pivot on LastName, sort by Quantity, Revenue DESC")
|
||||
.withTitleFields(List.of("${input.startDate}", "${input.endDate}"))
|
||||
.withOrderByFields(List.of(new QFilterOrderBy("shoeCount"), new QFilterOrderBy("sumPrice", false)))
|
||||
.withColumns(List.of(
|
||||
new QReportField().withName("reportStartDate").withLabel("Report Start Date").withFormula("${input.startDate}"),
|
||||
new QReportField().withName("reportEndDate").withLabel("Report End Date").withFormula("${input.endDate}"),
|
||||
new QReportField().withName("personCount").withLabel("Person Count").withFormula("${pivot.count.id}").withDisplayFormat(DisplayFormat.COMMAS),
|
||||
new QReportField().withName("shoeCount").withLabel("Quantity").withFormula("${pivot.sum.noOfShoes}").withDisplayFormat(DisplayFormat.COMMAS),
|
||||
// new QReportField().withName("percentOfTotal").withLabel("% Total").withFormula("=MULTIPLY(100,DIVIDE(${pivot.sum.noOfShoes},${total.sum.noOfShoes}))").withDisplayFormat(DisplayFormat.PERCENT_POINT2),
|
||||
new QReportField().withName("percentOfTotal").withLabel("% Total").withFormula("=DIVIDE(${pivot.sum.noOfShoes},${total.sum.noOfShoes})").withDisplayFormat(DisplayFormat.PERCENT_POINT2),
|
||||
new QReportField().withName("sumCost").withLabel("Cost").withFormula("${pivot.sum.cost}").withDisplayFormat(DisplayFormat.CURRENCY),
|
||||
new QReportField().withName("sumPrice").withLabel("Revenue").withFormula("${pivot.sum.price}").withDisplayFormat(DisplayFormat.CURRENCY),
|
||||
new QReportField().withName("profit").withLabel("Profit").withFormula("=MINUS(${pivot.sum.price},${pivot.sum.cost})").withDisplayFormat(DisplayFormat.CURRENCY),
|
||||
// new QReportField().withName("margin").withLabel("Margins").withFormula("=SCALE(MULTIPLY(100,DIVIDE(MINUS(${pivot.sum.price},${pivot.sum.cost}),${pivot.sum.price})),0)").withDisplayFormat(DisplayFormat.PERCENT),
|
||||
new QReportField().withName("margin").withLabel("Margins").withFormula("=SCALE(DIVIDE(MINUS(${pivot.sum.price},${pivot.sum.cost}),${pivot.sum.price}),2)").withDisplayFormat(DisplayFormat.PERCENT),
|
||||
new QReportField().withName("costPerShoe").withLabel("Cost Per").withFormula("=DIVIDE_SCALE(${pivot.sum.cost},${pivot.sum.noOfShoes},2)").withDisplayFormat(DisplayFormat.CURRENCY),
|
||||
new QReportField().withName("revenuePerShoe").withLabel("Revenue Per").withFormula("=DIVIDE_SCALE(${pivot.sum.price},${pivot.sum.noOfShoes},2)").withDisplayFormat(DisplayFormat.CURRENCY),
|
||||
new QReportField().withName("marginPer").withLabel("Margin Per").withFormula("=MINUS(DIVIDE_SCALE(${pivot.sum.price},${pivot.sum.noOfShoes},2),DIVIDE_SCALE(${pivot.sum.cost},${pivot.sum.noOfShoes},2))").withDisplayFormat(DisplayFormat.CURRENCY)
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
}
|
@ -64,6 +64,15 @@ class QValueFormatterTest
|
||||
assertEquals("1000", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), new BigDecimal("1000")));
|
||||
assertEquals("1000", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), 1000));
|
||||
|
||||
assertEquals("1%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT), 1));
|
||||
assertEquals("1%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT), new BigDecimal("1.0")));
|
||||
assertEquals("1.0%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT_POINT1), 1));
|
||||
assertEquals("1.1%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT_POINT1), new BigDecimal("1.1")));
|
||||
assertEquals("1.1%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT_POINT1), new BigDecimal("1.12")));
|
||||
assertEquals("1.00%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT_POINT2), 1));
|
||||
assertEquals("1.10%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT_POINT2), new BigDecimal("1.1")));
|
||||
assertEquals("1.12%", qValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.PERCENT_POINT2), new BigDecimal("1.12")));
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// this one flows through the exceptional cases //
|
||||
//////////////////////////////////////////////////
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@ -162,6 +163,43 @@ class QMetaDataVariableInterpreterTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testValueMaps()
|
||||
{
|
||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||
variableInterpreter.addValueMap("input", Map.of("foo", "bar", "amount", new BigDecimal("3.50")));
|
||||
|
||||
assertEquals("bar", variableInterpreter.interpretForObject("${input.foo}"));
|
||||
assertEquals(new BigDecimal("3.50"), variableInterpreter.interpretForObject("${input.amount}"));
|
||||
assertEquals("${input.x}", variableInterpreter.interpretForObject("${input.x}"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testMultipleValueMaps()
|
||||
{
|
||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||
variableInterpreter.addValueMap("input", Map.of("amount", new BigDecimal("3.50"), "x", "y"));
|
||||
variableInterpreter.addValueMap("others", Map.of("foo", "fu", "amount", new BigDecimal("1.75")));
|
||||
|
||||
assertEquals("${input.foo}", variableInterpreter.interpretForObject("${input.foo}"));
|
||||
assertEquals("fu", variableInterpreter.interpretForObject("${others.foo}"));
|
||||
assertEquals(new BigDecimal("3.50"), variableInterpreter.interpretForObject("${input.amount}"));
|
||||
assertEquals(new BigDecimal("1.75"), variableInterpreter.interpretForObject("${others.amount}"));
|
||||
assertEquals("y", variableInterpreter.interpretForObject("${input.x}"));
|
||||
assertEquals("${others.x}", variableInterpreter.interpretForObject("${others.x}"));
|
||||
assertEquals("${input.nil}", variableInterpreter.interpretForObject("${input.nil}"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.testutils;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class PersonQRecord extends QRecord
|
||||
{
|
||||
public PersonQRecord withLastName(String lastName)
|
||||
{
|
||||
setValue("lastName", lastName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public PersonQRecord withBirthDate(LocalDate birthDate)
|
||||
{
|
||||
setValue("birthDate", birthDate);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public PersonQRecord withNoOfShoes(Integer noOfShoes)
|
||||
{
|
||||
setValue("noOfShoes", noOfShoes);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public PersonQRecord withPrice(BigDecimal price)
|
||||
{
|
||||
setValue("price", price);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public PersonQRecord withCost(BigDecimal cost)
|
||||
{
|
||||
setValue("cost", cost);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public PersonQRecord withHomeStateId(int homeStateId)
|
||||
{
|
||||
setValue("homeStateId", homeStateId);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -58,6 +58,7 @@ 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.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
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.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
@ -91,7 +92,7 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for backend-core test classes
|
||||
**
|
||||
** TODO - move to testutils package.
|
||||
*******************************************************************************/
|
||||
public class TestUtils
|
||||
{
|
||||
@ -406,6 +407,9 @@ public class TestUtils
|
||||
.withField(new QFieldMetaData("homeStateId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_STATE))
|
||||
.withField(new QFieldMetaData("favoriteShapeId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_SHAPE))
|
||||
.withField(new QFieldMetaData("customValue", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_CUSTOM))
|
||||
.withField(new QFieldMetaData("noOfShoes", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS))
|
||||
.withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
||||
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.utils.aggregates;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import org.assertj.core.data.Offset;
|
||||
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.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for Aggregates
|
||||
*******************************************************************************/
|
||||
class AggregatesTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInteger()
|
||||
{
|
||||
IntegerAggregates aggregates = new IntegerAggregates();
|
||||
|
||||
assertEquals(0, aggregates.getCount());
|
||||
assertNull(aggregates.getMin());
|
||||
assertNull(aggregates.getMax());
|
||||
assertNull(aggregates.getSum());
|
||||
assertNull(aggregates.getAverage());
|
||||
|
||||
aggregates.add(5);
|
||||
assertEquals(1, aggregates.getCount());
|
||||
assertEquals(5, aggregates.getMin());
|
||||
assertEquals(5, aggregates.getMax());
|
||||
assertEquals(5, aggregates.getSum());
|
||||
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("5"), Offset.offset(BigDecimal.ZERO));
|
||||
|
||||
aggregates.add(10);
|
||||
assertEquals(2, aggregates.getCount());
|
||||
assertEquals(5, aggregates.getMin());
|
||||
assertEquals(10, aggregates.getMax());
|
||||
assertEquals(15, aggregates.getSum());
|
||||
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("7.5"), Offset.offset(BigDecimal.ZERO));
|
||||
|
||||
aggregates.add(15);
|
||||
assertEquals(3, aggregates.getCount());
|
||||
assertEquals(5, aggregates.getMin());
|
||||
assertEquals(15, aggregates.getMax());
|
||||
assertEquals(30, aggregates.getSum());
|
||||
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("10"), Offset.offset(BigDecimal.ZERO));
|
||||
|
||||
aggregates.add(null);
|
||||
assertEquals(3, aggregates.getCount());
|
||||
assertEquals(5, aggregates.getMin());
|
||||
assertEquals(15, aggregates.getMax());
|
||||
assertEquals(30, aggregates.getSum());
|
||||
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("10"), Offset.offset(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testBigDecimal()
|
||||
{
|
||||
BigDecimalAggregates aggregates = new BigDecimalAggregates();
|
||||
|
||||
assertEquals(0, aggregates.getCount());
|
||||
assertNull(aggregates.getMin());
|
||||
assertNull(aggregates.getMax());
|
||||
assertNull(aggregates.getSum());
|
||||
assertNull(aggregates.getAverage());
|
||||
|
||||
BigDecimal bd51 = new BigDecimal("5.1");
|
||||
aggregates.add(bd51);
|
||||
assertEquals(1, aggregates.getCount());
|
||||
assertEquals(bd51, aggregates.getMin());
|
||||
assertEquals(bd51, aggregates.getMax());
|
||||
assertEquals(bd51, aggregates.getSum());
|
||||
assertThat(aggregates.getAverage()).isCloseTo(bd51, Offset.offset(BigDecimal.ZERO));
|
||||
|
||||
BigDecimal bd101 = new BigDecimal("10.1");
|
||||
aggregates.add(new BigDecimal("10.1"));
|
||||
assertEquals(2, aggregates.getCount());
|
||||
assertEquals(bd51, aggregates.getMin());
|
||||
assertEquals(bd101, aggregates.getMax());
|
||||
assertEquals(new BigDecimal("15.2"), aggregates.getSum());
|
||||
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("7.6"), Offset.offset(BigDecimal.ZERO));
|
||||
|
||||
BigDecimal bd148 = new BigDecimal("14.8");
|
||||
aggregates.add(bd148);
|
||||
|
||||
aggregates.add(null);
|
||||
assertEquals(3, aggregates.getCount());
|
||||
assertEquals(bd51, aggregates.getMin());
|
||||
assertEquals(bd148, aggregates.getMax());
|
||||
assertEquals(new BigDecimal("30.0"), aggregates.getSum());
|
||||
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("10.0"), Offset.offset(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user