From b0e2a4dc445011b0a9848fddec0d84051c408003 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 6 Jul 2023 19:12:31 +0200 Subject: [PATCH] feat(reports): "sum" query for advanced reports (#1919) closes #1705 --- src/app/core/config/config-fix.ts | 71 +++++++++++++++-------- src/app/core/export/query.service.spec.ts | 14 +++++ src/app/core/export/query.service.ts | 14 +++++ 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index a168eef544..643459d5af 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -3,6 +3,7 @@ import { School } from "../../child-dev-project/schools/model/school"; import { ChildSchoolRelation } from "../../child-dev-project/children/model/childSchoolRelation"; import { EventNote } from "../../child-dev-project/attendance/model/event-note"; import { defaultDateFilters } from "../filter/date-range-filter/date-range-filter-panel/date-range-filter-panel.component"; +import { EducationalMaterial } from "../../child-dev-project/children/educational-material/model/educational-material"; // prettier-ignore export const defaultJsonConfig = { @@ -224,21 +225,21 @@ export const defaultJsonConfig = { } ], "exportConfig": [ - { "label": "event_id", "query": "_id" }, - { "label": "date", "query": "date" }, - { "label": "event title", "query": "subject" }, - { "label": "event type", "query": "category" }, - { "label": "event description", "query": "text" }, + {"label": "event_id", "query": "_id"}, + {"label": "date", "query": "date"}, + {"label": "event title", "query": "subject"}, + {"label": "event type", "query": "category"}, + {"label": "event description", "query": "text"}, { "query": ":getAttendanceArray(true)", "subQueries": [ { "query": ".participant:toEntities(Child)", "subQueries": [ - { "label": "participant_id", "query": "_id" }, - { "label": "participant", "query": "name" }, - { "label": "gender", "query": "gender" }, - { "label": "religion", "query": "religion" }, + {"label": "participant_id", "query": "_id"}, + {"label": "participant", "query": "name"}, + {"label": "gender", "query": "gender"}, + {"label": "religion", "query": "religion"}, ] }, { @@ -248,8 +249,8 @@ export const defaultJsonConfig = { { "query": ".school:toEntities(School)", "subQueries": [ - { "label": "school_name", "query": "name" }, - { "label": "school_id", "query": "entityId" } + {"label": "school_name", "query": "name"}, + {"label": "school_id", "query": "entityId"} ] } ], @@ -627,7 +628,7 @@ export const defaultJsonConfig = { "config": { "rightSide": { "entityType": School.ENTITY_TYPE, - "availableFilters": [{ "id": "language" }], + "availableFilters": [{"id": "language"}], }, } } @@ -691,11 +692,11 @@ export const defaultJsonConfig = { component: "HistoricalDataComponent", config: [ "date", - { id: "isMotivatedDuringClass", visibleFrom: "lg" }, - { id: "isParticipatingInClass", visibleFrom: "lg" }, - { id: "isInteractingWithOthers", visibleFrom: "lg" }, - { id: "doesHomework", visibleFrom: "lg" }, - { id: "asksQuestions", visibleFrom: "lg" }, + {id: "isMotivatedDuringClass", visibleFrom: "lg"}, + {id: "isParticipatingInClass", visibleFrom: "lg"}, + {id: "isInteractingWithOthers", visibleFrom: "lg"}, + {id: "doesHomework", visibleFrom: "lg"}, + {id: "asksQuestions", visibleFrom: "lg"}, ] } ] @@ -740,9 +741,9 @@ export const defaultJsonConfig = { }, ], "exportConfig": [ - { label: "Title", query: "title" }, - { label: "Type", query: "type" }, - { label: "Assigned users", query: "assignedTo" } + {label: "Title", query: "title"}, + {label: "Type", query: "type"}, + {label: "Assigned users", query: "assignedTo"} ] } }, @@ -857,7 +858,7 @@ export const defaultJsonConfig = { "aggregationDefinitions": [ { "query": `${EventNote.ENTITY_TYPE}:toArray[* date >= ? & date <= ?]`, - groupBy: { label: "Type", property: "category" }, + groupBy: {label: "Type", property: "category"}, "subQueries": [ { query: ":getAttendanceArray:getAttendanceReport", @@ -888,6 +889,26 @@ export const defaultJsonConfig = { }, ], }, + { + "title": $localize`:Name of a report:Materials Distributed`, + "mode": "exporting", + "aggregationDefinitions": [ + { + "query": `${EducationalMaterial.ENTITY_TYPE}:toArray[*date >= ? & date <= ?]`, + "groupBy": {label: "Type", property: "materialType"}, + "subQueries": [ + { + "label": "Number of events of handing out", + "query": `.materialAmount:count` + }, + { + "label": "Total Items", + "query": `.materialAmount:sum` + }, + ] + }, + ] + } ] } }, @@ -1059,10 +1080,10 @@ export const defaultJsonConfig = { config: { rightSide: { entityType: School.ENTITY_TYPE, - prefilter: { "privateSchool": true }, - availableFilters: [{ "id": "language" }], + prefilter: {"privateSchool": true}, + availableFilters: [{"id": "language"}], }, - leftSide: { entityType: Child.ENTITY_TYPE }, + leftSide: {entityType: Child.ENTITY_TYPE}, } }, "appConfig:matching-entities": { @@ -1089,7 +1110,7 @@ export const defaultJsonConfig = { "entity": "Todo", "columns": ["deadline", "subject", "assignedTo", "startDate", "relatedEntities"], "filters": [ - { "id": "assignedTo" }, + {"id": "assignedTo"}, { "id": "due-status", diff --git a/src/app/core/export/query.service.spec.ts b/src/app/core/export/query.service.spec.ts index 489ac0965a..1c7f3b3583 100644 --- a/src/app/core/export/query.service.spec.ts +++ b/src/app/core/export/query.service.spec.ts @@ -622,6 +622,20 @@ describe("QueryService", () => { expect(res).toBe(1); }); + it("should allow to sum values", () => { + const data = [ + { a: 1, b: 2 }, + { a: "4" }, // also allows strings + { b: 5 }, // not existing as 0 + { a: -2 }, // allows negative + { a: "three" }, // skips invalid + ]; + + const res = service.queryData("a:sum", undefined, undefined, data); + + expect(res).toBe(3); + }); + function queryData(query: string, from?: Date, to?: Date, data?: any) { return service .cacheRequiredData(query, from, to) diff --git a/src/app/core/export/query.service.ts b/src/app/core/export/query.service.ts index 1c6b289fcc..3b3fa4027d 100644 --- a/src/app/core/export/query.service.ts +++ b/src/app/core/export/query.service.ts @@ -97,6 +97,7 @@ export class QueryService { toArray: this.toArray, unique: this.unique, count: this.count, + sum: this.sum, addPrefix: this.addPrefix, toEntities: this.toEntities.bind(this), getRelated: this.getRelated.bind(this), @@ -233,6 +234,19 @@ export class QueryService { return data ? data.length : 0; } + /** + * Returns the (integer) sum of the provided array. + * It can also handle integers in strings, e.g. "3" + * @param data and integer array + * @private + */ + private sum(data: any[]): number { + return data.reduce((res, cur) => { + const parsed = Number.parseInt(cur); + return Number.isNaN(parsed) ? res : res + parsed; + }, 0); + } + /** * Turns a list of ids (with the entity prefix) into a list of entities * @param ids the array of ids with entity prefix