import { Injectable } from "@angular/core" ; import { EntityMapperService } from "../../core/entity/entity-mapper.service" ; import moment from "moment" ; import { RecurringActivity } from "./model/recurring-activity" ; import { ActivityAttendance } from "./model/activity-attendance" ; import { groupBy } from "../../utils/utils" ; import { DatabaseIndexingService } from "../../core/entity/database-indexing/database-indexing.service" ; import { ConfigService } from "../../core/config/config.service" ; import { INTERACTION_TYPE_CONFIG_ID , InteractionType , } from "../notes/model/interaction-type.interface" ; import { EventNote } from "./model/event-note" ; import { ChildrenService } from "../children/children.service" ; import { CONFIGURABLE_ENUM_CONFIG_PREFIX } from "../../core/configurable-enum/configurable-enum.interface" ; @ Injectable ( { providedIn : "root" , } ) export class AttendanceService { constructor ( private entityMapper , private dbIndexing , private configService , private childrenService ) { this . createIndices ( ) ; } private createIndices ( ) { const meetingInteractionTypes = this . configService . getConfig < InteractionType [ ] > ( CONFIGURABLE_ENUM_CONFIG_PREFIX + INTERACTION_TYPE_CONFIG_ID ) . filter ( ( t ) => t . isMeeting ) . map ( ( t ) => t . id ) ; this . createEventsIndex ( meetingInteractionTypes ) ; this . createRecurringActivitiesIndex ( ) ; } private createEventsIndex ( meetingInteractionTypes ) { const designDoc = { _id : "_design/events_index" , views : { by_date : { map : `(doc) => {
            if (doc._id.startsWith("${ EventNote . ENTITY_TYPE }") &&
                ${ JSON . stringify ( meetingInteractionTypes ) }.includes(doc.category)
            ) {
              var d = new Date(doc.date || null);
              var dString = d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0")
              emit(dString);
            }
          }` , } , by_activity : { map : `(doc) => {
            if (doc._id.startsWith("${ EventNote . ENTITY_TYPE }") && doc.relatesTo) {
              var d = new Date(doc.date || null);
              var dString = d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0")
              emit(doc.relatesTo + "_" + dString);
            }
          }` , } , } , } ; return this . dbIndexing . createIndex ( designDoc ) ; } private createRecurringActivitiesIndex ( ) { const designDoc = { _id : "_design/activities_index" , views : { by_participant : { map : `(doc) => {
            if (doc._id.startsWith("${ RecurringActivity . ENTITY_TYPE }")) {
              for (var p of (doc.participants || [])) {
                emit(p);
              }
            }
          }` , } , by_school : { map : `(doc) => {
            if (doc._id.startsWith("${ RecurringActivity . ENTITY_TYPE }")) {
              for (var g of (doc.linkedGroups || [])) {
                emit(g);
              }
            }
          }` , } , } , } ; return this . dbIndexing . createIndex ( designDoc ) ; } param startDate param endDate async getEventsOnDate ( startDate , endDate = startDate ) { const start = moment ( startDate ) ; const end = moment ( endDate ) ; const eventNotes = this . dbIndexing . queryIndexDocsRange ( EventNote , "events_index/by_date" , start . format ( "YYYY-MM-DD" ) , end . format ( "YYYY-MM-DD" ) ) ; const relevantNormalNotes = this . childrenService . getNotesInTimespan ( start , end ) . then ( ( notes ) => notes . filter ( ( n ) => n . category . isMeeting ) ) ; const allResults = await Promise . all ( [ eventNotes , relevantNormalNotes ] ) ; const existingEvents = allResults [ 0 ] . concat ( allResults [ 1 ] ) ; for ( const event of existingEvents ) { const participants = await this . loadParticipantsOfGroups ( event . schools ) ; for ( const newParticipant of participants ) { event . addChild ( newParticipant ) ; } } return existingEvents ; } param activityId param sinceDate async getEventsForActivity ( activityId , sinceDate ? ) { if ( ! activityId . startsWith ( RecurringActivity . ENTITY_TYPE ) ) { activityId = RecurringActivity . ENTITY_TYPE + ":" + activityId ; } let dateLimit = "" ; if ( sinceDate ) { dateLimit = "_" + sinceDate . getFullYear ( ) + "-" + String ( sinceDate . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) + "-" + String ( sinceDate . getDate ( ) ) . padStart ( 2 , "0" ) ; } return await this . dbIndexing . queryIndexDocsRange ( EventNote , "events_index/by_activity" , activityId + dateLimit , activityId ) ; } param activity param sinceDate async getActivityAttendances ( activity , sinceDate ? ) { const periods = new Map < number , ActivityAttendance > ( ) ; function getOrCreateAttendancePeriod ( event ) { const month = new Date ( event . date . getFullYear ( ) , event . date . getMonth ( ) ) ; let attMonth = periods . get ( month . getTime ( ) ) ; if ( ! attMonth ) { attMonth = ActivityAttendance . create ( month ) ; attMonth . periodTo = moment ( month ) . endOf ( "month" ) . toDate ( ) ; attMonth . activity = activity ; periods . set ( month . getTime ( ) , attMonth ) ; } return attMonth ; } const events = await this . getEventsForActivity ( activity . _id , sinceDate ) ; for ( const event of events ) { const record = getOrCreateAttendancePeriod ( event ) ; record . events . push ( event ) ; } return Array . from ( periods . values ( ) ) . sort ( ( a , b ) => a . periodFrom . getTime ( ) - b . periodFrom . getTime ( ) ) ; } async getAllActivityAttendancesForPeriod ( from , until ) { const matchingEvents = await this . getEventsOnDate ( from , until ) ; const groupedEvents = groupBy ( matchingEvents , "relatesTo" ) ; const records = [ ] ; for ( const [ activityId , activityEvents ] of groupedEvents ) { const activityRecord = ActivityAttendance . create ( from , activityEvents ) ; activityRecord . periodTo = until ; if ( activityId ) { activityRecord . activity = await this . entityMapper . load < RecurringActivity > ( RecurringActivity , activityId ) . catch ( ( ) => undefined ) ; } records . push ( activityRecord ) ; } return records ; } async getActivitiesForChild ( childId ) { const activities = await this . dbIndexing . queryIndexDocs ( RecurringActivity , "activities_index/by_participant" , childId ) ; const visitedSchools = ( await this . childrenService . queryRelationsOf ( "child" , childId ) ) . filter ( ( relation ) => relation . isActive ) ; for ( const currentRelation of visitedSchools ) { const activitiesThroughRelation = await this . dbIndexing . queryIndexDocs ( RecurringActivity , "activities_index/by_school" , currentRelation . schoolId ) ; for ( const activityThroughRelation of activitiesThroughRelation ) { if ( ! activities . some ( ( a ) => a . getId ( ) === activityThroughRelation . getId ( ) ) ) { activities . push ( activityThroughRelation ) ; } } } return activities ; } async createEventForActivity ( activity , date ) { const instance = new EventNote ( ) ; const schoolParticipants = await this . loadParticipantsOfGroups ( activity . linkedGroups ) ; instance . date = date ; instance . subject = activity . title ; instance . children = [ ... new Set ( activity . participants . concat ( ... schoolParticipants ) ) , ] ; instance . schools = activity . linkedGroups ; instance . relatesTo = activity . _id ; instance . category = activity . type ; return instance ; } param linkedGroups private async loadParticipantsOfGroups ( linkedGroups ) { const childIdPromises = linkedGroups . map ( ( groupId ) => this . childrenService . queryRelationsOf ( "school" , groupId ) . then ( ( relations ) => relations . map ( ( r ) => r . childId ) ) ) ; const allParticipants = await Promise . all ( childIdPromises ) ; return Array . from ( new Set ( [ ] . concat . apply ( [ ] , allParticipants ) ) ) ; } }