Data Access/Service class for Rock.Model.Attendance entity objects
        protected void attendanceDelete_Click(object sender, RowEventArgs e)
        {
            RockContext editContext = new RockContext();
            AttendanceService editAttServe = new AttendanceService(editContext);

            var attendItem = editAttServe.Queryable().Where(x => x.Id == e.RowKeyId).FirstOrDefault();
            if (attendItem.IsAuthorized("Edit", CurrentPerson))
            {
                attendItem.DidAttend = !attendItem.DidAttend;
                attendItem.DidNotOccur = !attendItem.DidAttend;
                editContext.SaveChanges();
            }

            _rockContext = new RockContext();
            attendServ = new AttendanceService(_rockContext);
            doStuff();
        }
        /// <summary>
        /// Handles the Click event of the lbSave control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void lbSave_Click( object sender, EventArgs e )
        {
            if ( _group != null && _occurrence != null )
            {
                var rockContext = new RockContext();
                var attendanceService = new AttendanceService( rockContext );
                var personAliasService = new PersonAliasService( rockContext );
                var locationService = new LocationService( rockContext );

                bool dateAdjusted = false;

                DateTime startDate = _occurrence.Date;

                // If this is a manuall entered occurrence, check to see if date was changed
                if ( !_occurrence.ScheduleId.HasValue )
                {
                    DateTime? originalDate = PageParameter( "Date" ).AsDateTime();
                    if ( originalDate.HasValue && originalDate.Value.Date != startDate.Date )
                    {
                        startDate = originalDate.Value.Date;
                        dateAdjusted = true;
                    }
                }
                DateTime endDate = startDate.AddDays( 1 );

                var existingAttendees = attendanceService
                    .Queryable( "PersonAlias" )
                    .Where( a =>
                        a.GroupId == _group.Id &&
                        a.LocationId == _occurrence.LocationId &&
                        a.ScheduleId == _occurrence.ScheduleId &&
                        a.StartDateTime >= startDate &&
                        a.StartDateTime < endDate );

                if ( dateAdjusted )
                {
                    foreach ( var attendee in existingAttendees )
                    {
                        attendee.StartDateTime = _occurrence.Date;
                    }
                }

                // If did not meet was selected and this was a manually entered occurrence (not based on a schedule/location)
                // then just delete all the attendance records instead of tracking a 'did not meet' value
                if ( cbDidNotMeet.Checked && !_occurrence.ScheduleId.HasValue )
                {
                    foreach ( var attendance in existingAttendees )
                    {
                        attendanceService.Delete( attendance );
                    }
                }
                else
                {
                    int? campusId = locationService.GetCampusIdForLocation( _occurrence.LocationId );
                    if ( !campusId.HasValue )
                    {
                        campusId = _group.CampusId;
                    }
                    if ( !campusId.HasValue && _allowCampusFilter )
                    {
                        var campus = CampusCache.Read( bddlCampus.SelectedValueAsInt() ?? 0 );
                        if ( campus != null )
                        {
                            campusId = campus.Id;
                        }
                    }

                    if ( cbDidNotMeet.Checked )
                    {
                        // If the occurrence is based on a schedule, set the did not meet flags
                        foreach ( var attendance in existingAttendees )
                        {
                            attendance.DidAttend = null;
                            attendance.DidNotOccur = true;
                        }
                    }

                    foreach ( var attendee in _attendees )
                    {
                        var attendance = existingAttendees
                            .Where( a => a.PersonAlias.PersonId == attendee.PersonId )
                            .FirstOrDefault();

                        if ( attendance == null )
                        {
                            int? personAliasId = personAliasService.GetPrimaryAliasId( attendee.PersonId );
                            if ( personAliasId.HasValue )
                            {
                                attendance = new Attendance();
                                attendance.GroupId = _group.Id;
                                attendance.ScheduleId = _group.ScheduleId;
                                attendance.PersonAliasId = personAliasId;
                                attendance.StartDateTime = _occurrence.Date.Date.Add( _occurrence.StartTime );
                                attendance.LocationId = _occurrence.LocationId;
                                attendance.CampusId = campusId;
                                attendance.ScheduleId = _occurrence.ScheduleId;

                                // check that the attendance record is valid
                                cvAttendance.IsValid = attendance.IsValid;
                                if ( !cvAttendance.IsValid )
                                {
                                    cvAttendance.ErrorMessage = attendance.ValidationResults.Select( a => a.ErrorMessage ).ToList().AsDelimited( "<br />" );
                                    return;
                                }

                                attendanceService.Add( attendance );
                            }
                        }

                        if ( attendance != null )
                        {
                            if ( cbDidNotMeet.Checked )
                            {
                                attendance.DidAttend = null;
                                attendance.DidNotOccur = true;
                            }
                            else
                            {
                                attendance.DidAttend = attendee.Attended;
                                attendance.DidNotOccur = null;
                            }
                        }
                    }
                }

                if ( _occurrence.LocationId.HasValue )
                {
                    Rock.CheckIn.KioskLocationAttendance.Flush( _occurrence.LocationId.Value );
                }

                rockContext.SaveChanges();

                WorkflowType workflowType = null;
                Guid? workflowTypeGuid = GetAttributeValue( "Workflow" ).AsGuidOrNull();
                if ( workflowTypeGuid.HasValue )
                {
                    var workflowTypeService = new WorkflowTypeService( rockContext );
                    workflowType = workflowTypeService.Get( workflowTypeGuid.Value );
                    if ( workflowType != null )
                    {
                        try
                        {
                            var workflow = Workflow.Activate( workflowType, _group.Name );

                            workflow.SetAttributeValue( "StartDateTime", _occurrence.Date.ToString( "o" ) );
                            workflow.SetAttributeValue( "Schedule", _group.Schedule.Guid.ToString() );

                            List<string> workflowErrors;
                            new WorkflowService( rockContext ).Process( workflow, _group, out workflowErrors );
                        }
                        catch ( Exception ex )
                        {
                            ExceptionLogService.LogException( ex, this.Context );
                        }
                    }
                }

                var qryParams = new Dictionary<string, string> { { "GroupId", _group.Id.ToString() } };

                var groupTypeIds = PageParameter( "GroupTypeIds" );
                if ( !string.IsNullOrWhiteSpace( groupTypeIds ) )
                {
                    qryParams.Add( "GroupTypeIds", groupTypeIds );
                }

                NavigateToParentPage( qryParams );
            }
        }
        /// <summary>
        /// Gets the chart data.
        /// </summary>
        /// <returns></returns>
        private IEnumerable<Rock.Chart.IChartData> GetAttendanceChartData()
        {
            var dateRange = SlidingDateRangePicker.CalculateDateRangeFromDelimitedValues( drpSlidingDateRange.DelimitedValues );

            string groupIds = GetSelectedGroupIds().AsDelimited( "," );

            var selectedCampusIds = clbCampuses.SelectedValues;
            string campusIds = selectedCampusIds.AsDelimited( "," );

            var chartData = new AttendanceService( _rockContext ).GetChartData(
                hfGroupBy.Value.ConvertToEnumOrNull<ChartGroupBy>() ?? ChartGroupBy.Week,
                hfGraphBy.Value.ConvertToEnumOrNull<AttendanceGraphBy>() ?? AttendanceGraphBy.Total,
                dateRange.Start,
                dateRange.End,
                groupIds,
                campusIds,
                dvpDataView.SelectedValueAsInt() );
            return chartData;
        }
        /// <summary>
        /// Binds the grid.
        /// </summary>
        protected void BindGrid()
        {
            var rockContext = new RockContext();
            var attendanceService = new AttendanceService( rockContext );
            var qryAttendance = attendanceService.Queryable();

            var personField = gHistory.Columns.OfType<PersonField>().FirstOrDefault();
            if ( personField != null )
            {
                personField.Visible = _person == null;
            }

            var groupField = gHistory.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Group" );
            if ( groupField != null )
            {
                groupField.Visible = _group == null;
            }

            if ( _person != null )
            {
                qryAttendance = qryAttendance.Where( a => a.PersonAlias.PersonId == _person.Id );

                if ( _group != null )
                {
                    qryAttendance = qryAttendance.Where( a => a.GroupId == _group.Id );
                }
                else
                {
                    int? groupId = ddlAttendanceGroup.SelectedValueAsInt();
                    if ( groupId.HasValue )
                    {
                        qryAttendance = qryAttendance.Where( a => a.GroupId == groupId.Value );
                    }
                }
            }
            else
            {
                int? personId = ppPerson.SelectedValue;
                if ( personId.HasValue )
                {
                    qryAttendance = qryAttendance.Where( a => a.PersonAlias.PersonId == personId.Value );
                }
            }

            // Filter by Date Range
            if ( drpDates.LowerValue.HasValue )
            {
                qryAttendance = qryAttendance.Where( t => t.StartDateTime >= drpDates.LowerValue.Value );
            }
            if ( drpDates.UpperValue.HasValue )
            {
                DateTime upperDate = drpDates.UpperValue.Value.Date.AddDays( 1 );
                qryAttendance = qryAttendance.Where( t => t.StartDateTime < upperDate );
            }

            // Filter by Schedule
            int? scheduleId = spSchedule.SelectedValue.AsIntegerOrNull();
            if ( scheduleId.HasValue && scheduleId.Value > 0 )
            {
                qryAttendance = qryAttendance.Where( h => h.ScheduleId == scheduleId.Value );
            }

            // Filter by DidAttend
            int? didAttend = ddlDidAttend.SelectedValueAsInt( false );
            if ( didAttend.HasValue )
            {
                if ( didAttend.Value == 1 )
                {
                    qryAttendance = qryAttendance.Where( a => a.DidAttend == true );
                }
                else
                {
                    qryAttendance = qryAttendance.Where( a => a.DidAttend == false );
                }
            }

            var qry = qryAttendance
                .Select( a => new
                {
                    LocationId = a.LocationId,
                    LocationName = a.Location.Name,
                    CampusId = a.CampusId,
                    CampusName = a.Campus.Name,
                    ScheduleName = a.Schedule.Name,
                    Person = a.PersonAlias.Person,
                    GroupName = a.Group.Name,
                    GroupTypeId = a.Group.GroupTypeId,
                    StartDateTime = a.StartDateTime,
                    EndDateTime = a.EndDateTime,
                    DidAttend = a.DidAttend
                } );

            SortProperty sortProperty = gHistory.SortProperty;
            if ( sortProperty != null )
            {
                qry = qry.Sort( sortProperty );
            }
            else
            {
                qry = qry.OrderByDescending( p => p.StartDateTime );
            }

            // build a lookup for _groupTypePaths for OnRowDatabound
            _groupTypePaths = new GroupTypeService( rockContext ).GetAllCheckinGroupTypePaths().ToList();

            // build a lookup for _locationpaths for OnRowDatabound
            _locationPaths = new Dictionary<int, string>();
            var qryLocations = new LocationService( rockContext ).Queryable().Where( a => qry.Any( b => b.LocationId == a.Id ) );
            foreach (var location in qryLocations)
            {
                var parentLocation = location.ParentLocation;
                var locationNames = new List<string>();
                while (parentLocation != null)
                {
                    locationNames.Add( parentLocation.Name );
                    parentLocation = parentLocation.ParentLocation;
                }

                string locationPath = string.Empty;
                if ( locationNames.Any() )
                {
                    locationNames.Reverse();
                    locationPath = locationNames.AsDelimited( " > " );
                }

                _locationPaths.AddOrIgnore( location.Id, locationPath );
            }

            gHistory.EntityTypeId = EntityTypeCache.Read<Attendance>().Id;
            gHistory.DataSource = qry.ToList();
            gHistory.DataBind();
        }
        /// <summary>
        /// Executes the specified workflow.
        /// </summary>
        /// <param name="action">The workflow action.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="errorMessages">The error messages.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public override bool Execute( Model.WorkflowAction action, Object entity, out List<string> errorMessages )
        {
            var checkInState = GetCheckInState( entity, out errorMessages );
            if ( checkInState != null )
            {
                DateTime sixMonthsAgo = DateTime.Today.AddMonths( -6 );
                var attendanceService = new AttendanceService();

                foreach ( var family in checkInState.CheckIn.Families.Where( f => f.Selected ) )
                {
                    foreach ( var person in family.People )
                    {
                        foreach ( var groupType in person.GroupTypes )
                        {
                            var groupTypeCheckIns = attendanceService.Queryable()
                                .Where( a =>
                                    a.PersonId == person.Person.Id &&
                                    a.Group.GroupTypeId == groupType.GroupType.Id &&
                                    a.StartDateTime >= sixMonthsAgo )
                                .ToList();

                            if ( groupTypeCheckIns.Any() )
                            {
                                groupType.LastCheckIn = groupTypeCheckIns.Select( a => a.StartDateTime ).Max();

                                foreach ( var group in groupType.Groups )
                                {
                                    var groupCheckIns = groupTypeCheckIns.Where( a => a.GroupId == group.Group.Id ).ToList();
                                    if ( groupCheckIns.Any() )
                                    {
                                        group.LastCheckIn = groupCheckIns.Select( a => a.StartDateTime ).Max();
                                    }

                                    foreach ( var location in group.Locations )
                                    {
                                        var locationCheckIns = groupCheckIns.Where( a => a.LocationId == location.Location.Id ).ToList();
                                        if ( locationCheckIns.Any() )
                                        {
                                            location.LastCheckIn = locationCheckIns.Select( a => a.StartDateTime ).Max();
                                        }

                                        foreach ( var schedule in location.Schedules )
                                        {
                                            var scheduleCheckIns = locationCheckIns.Where( a => a.ScheduleId == schedule.Schedule.Id ).ToList();
                                            if ( scheduleCheckIns.Any() )
                                            {
                                                schedule.LastCheckIn = scheduleCheckIns.Select( a => a.StartDateTime ).Max();
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        if ( person.GroupTypes.Any() )
                        {
                            person.LastCheckIn = person.GroupTypes.Select( g => g.LastCheckIn ).Max();
                        }
                    }
                }

                return true;

            }

            return false;
        }
        protected void bindGrid()
        {
            if ( String.IsNullOrWhiteSpace( CheckinCode ) )
            {
                ShowPanel( 0 );
            }
            else
            {
                rlCheckinCode.Text = CheckinCode + " <a href='?'><i class='fa fa-times'></i></a>";

                if ( !SelectedAttendanceGuid.IsEmpty() )
                {
                    ShowPanel( 2 );
                    var SelectedAttendance = new AttendanceService( rockContext ).Get( SelectedAttendanceGuid );
                    SelectedAttendance.LoadAttributes();
                    var attSearch = SelectedAttendance.GetAttributeValue( "SearchValue" );
                    var hasSearch = !String.IsNullOrWhiteSpace( attSearch );
                    if ( hasSearch )
                        attSearch = attSearch.AsNumeric();

                    var SelectedPerson = SelectedAttendance.PersonAlias.Person;
                    rlSelectedPerson.Text = SelectedPerson.FullName + " <a href='?CheckinCode=" + CheckinCode + "'><i class='fa fa-times'></i></a>";

                    var groupRoleServ = new GroupTypeRoleService( rockContext );

                    var knownRelationship_GroupMemberships = new GroupMemberService( rockContext ).Queryable()
                        .Where( gm => gm.Group.GroupTypeId == 11 && gm.PersonId == SelectedPerson.Id );

                    var shownRelationships = GetAttributeValue( "IncludedRelationships" ).Split( ',' ).Select( g => g.AsGuid() );

                    gReleventPeople.DataSource = SelectedPerson.GetFamilyMembers( false, rockContext )
                        .Select( m => new
                        {
                            Person = m.Person,
                            Role = m.GroupRole,
                            Priority = 100
                        } )
                        .ToList()
                        .Union(
                            knownRelationship_GroupMemberships
                            .Where( gm => gm.GroupRole.Guid == GUID_OwnerRole )
                            .SelectMany( gm => gm.Group.Members.Where( gm2 => gm2.PersonId != SelectedPerson.Id ) )
                            .ToList()
                            .Select( gm => new
                            {
                                Person = gm.Person,
                                Role = GetInverseRole( gm.GroupRole, groupRoleServ ),
                                Priority = 50
                            } )
                        )
                        .Union(
                             knownRelationship_GroupMemberships
                            .Where( gm => gm.GroupRole.Guid != GUID_OwnerRole )
                            .Select( gm => new
                            {
                                Person = gm.Group.Members.Where( gm2 => gm2.GroupRole.Guid == GUID_OwnerRole ).FirstOrDefault().Person,
                                Role = gm.GroupRole,
                                Priority = 40
                            } ).ToList()
                        )
                        .Where( r => shownRelationships.Contains( r.Role.Guid ) )
                        .GroupBy( x => x.Person )
                        .Select( x => new PersonRelationship
                        {
                            PersonAliasGuid = x.Key.PrimaryAlias.Guid,
                            FullName = x.Key.FullName,
                            Roles = String.Join( " ,", x.Select( y => y.Role.Name ) ),
                            Priority = x.Select( y => y.Priority ).Max(),
                            HomePhone = GetFormatedNumber( x.Key, GUID_HomePhone ),
                            MobilePhone = GetFormatedNumber( x.Key, GUID_MobilePhone ),
                            Highlight = hasSearch && x.Key.PhoneNumbers.Any( y => y.Number.Contains( attSearch ) )
                        } )
                        .OrderByDescending( r => r.Priority + ( r.Highlight ? 100 : 0) );

                    gReleventPeople.DataKeyNames = new string[] { "PersonAliasGuid" };
                    gReleventPeople.DataBind();
                }
                else
                {
                    ShowPanel( 1 );

                    int daysBacktoSearch = GetAttributeValue( "DaysBacktoSearch" ).AsInteger();
                    var searchDate = DateTime.Now.Date.AddDays( -daysBacktoSearch );

                    gSearchResults.SetLinqDataSource( new AttendanceCodeService( rockContext )
                        .Queryable()
                        .Where( c => c.Code == CheckinCode && c.IssueDateTime > searchDate )
                        .SelectMany( c => c.Attendances )
                        .OrderByDescending( "StartDateTime" ) );

                    gSearchResults.DataKeyNames = new string[] { "Guid" };
                    gSearchResults.DataBind();
                }
            }
        }
        /// <summary>
        /// Reads the specified id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public static KioskLocationAttendance Read( int id )
        {
            string cacheKey = KioskLocationAttendance.CacheKey( id );

            ObjectCache cache = MemoryCache.Default;
            KioskLocationAttendance locationAttendance = cache[cacheKey] as KioskLocationAttendance;

            if ( locationAttendance != null )
            {
                return locationAttendance;
            }
            else
            {
                var rockContext = new Rock.Data.RockContext();
                var location = new LocationService(rockContext).Get( id );
                if ( location != null )
                {
                    locationAttendance = new KioskLocationAttendance();
                    locationAttendance.LocationId = location.Id;
                    locationAttendance.LocationName = location.Name;
                    locationAttendance.Groups = new List<KioskGroupAttendance>();

                    var attendanceService = new AttendanceService(rockContext);
                    foreach ( var attendance in attendanceService.GetByDateAndLocation( RockDateTime.Today, location.Id ) )
                    {
                        AddAttendanceRecord( locationAttendance, attendance );
                    }

                    var cachePolicy = new CacheItemPolicy();
                    cachePolicy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds( 60 );
                    cache.Set( cacheKey, locationAttendance, cachePolicy );

                    return locationAttendance;
                }
            }

            return null;
        }
        /// <summary>
        /// Binds the grid.
        /// </summary>
        protected void BindGrid()
        {
            var attendanceService = new AttendanceService( new RockContext() );
            var attendanceQuery = attendanceService.Queryable();
            if ( _person != null )
            {
                attendanceQuery = attendanceQuery.Where( a => a.PersonId == _person.Id );
            }
            else if ( _group != null )
            {
                attendanceQuery = attendanceQuery.Where( a => a.GroupId == _group.Id );
            }

            var attendanceList = attendanceQuery.ToList();
            var qry = attendanceList.AsQueryable()
                .Select( a => new
                {
                    Location = a.Location,
                    Schedule = a.Schedule,
                    FullName = a.Person.FullName,
                    Group = a.Group,
                    StartDateTime = a.StartDateTime,
                    EndDateTime = a.EndDateTime
                } );

            // Filter by Date Range
            var drp = new DateRangePicker();
            drp.DelimitedValues = rFilter.GetUserPreference( "Date Range" );
            if ( drp.LowerValue.HasValue )
            {
                qry = qry.Where( t => t.StartDateTime >= drp.LowerValue.Value );
            }
            if ( drp.UpperValue.HasValue )
            {
                DateTime upperDate = drp.UpperValue.Value.Date.AddDays( 1 );
                qry = qry.Where( t => t.EndDateTime < upperDate );
            }

            // Filter by Person
            if ( ddlPeople.SelectedIndex > 0 )
            {
                if ( ddlPeople.SelectedValue.ToLower() != "all" )
                {
                    qry = qry.Where( h => h.FullName == ddlPeople.SelectedValue );
                }
            }

            // Filter by Group
            if ( ddlGroups.SelectedIndex > 0 )
            {
                if ( ddlGroups.SelectedValue.ToLower() != "all" )
                {
                    qry = qry.Where( h => h.Group.Name == ddlGroups.SelectedValue );
                }
            }

            // Filter by Schedule
            if ( ddlSchedules.SelectedIndex > 0 )
            {
                if ( ddlSchedules.SelectedValue.ToLower() != "all" )
                {
                    qry = qry.Where( h => h.Schedule.Name == ddlSchedules.SelectedValue );
                }
            }

            SortProperty sortProperty = gHistory.SortProperty;
            if ( sortProperty != null )
            {
                qry = qry.Sort( sortProperty );
            }
            else
            {
                qry = qry.OrderByDescending( p => p.StartDateTime );
            }

            gHistory.DataSource = qry.ToList();
            gHistory.DataBind();
        }
        /// <summary>
        /// Gets occurrence data for the selected group
        /// </summary>
        /// <param name="group">The group.</param>
        /// <param name="fromDateTime">From date time.</param>
        /// <param name="toDateTime">To date time.</param>
        /// <param name="locationIds">The location ids.</param>
        /// <param name="scheduleIds">The schedule ids.</param>
        /// <param name="loadSummaryData">if set to <c>true</c> [load summary data].</param>
        /// <returns></returns>
        public List<ScheduleOccurrence> GetGroupOccurrences( Group group, DateTime? fromDateTime, DateTime? toDateTime, 
            List<int> locationIds, List<int> scheduleIds, bool loadSummaryData )
        {
            var occurrences = new List<ScheduleOccurrence>();

            if ( group != null )
            {
                var rockContext = (RockContext)this.Context;
                var attendanceService = new AttendanceService( rockContext );
                var scheduleService = new ScheduleService( rockContext );
                var locationService = new LocationService( rockContext );

                // Set up an 'occurrences' query for the group
                var qry = attendanceService
                    .Queryable().AsNoTracking()
                    .Where( a => a.GroupId == group.Id );

                // Filter by date range
                if ( fromDateTime.HasValue )
                {
                    var fromDate = fromDateTime.Value.Date;
                    qry = qry.Where( a => DbFunctions.TruncateTime( a.StartDateTime ) >= ( fromDate ) );
                }
                if ( toDateTime.HasValue )
                {
                    var toDate = toDateTime.Value.Date;
                    qry = qry.Where( a => DbFunctions.TruncateTime( a.StartDateTime ) < ( toDate ) );
                }

                // Location Filter
                if ( locationIds.Any()  )
                {
                    qry = qry.Where( a =>
                        a.LocationId.HasValue &&
                        locationIds.Contains( a.LocationId.Value ) );
                }

                // Schedule Filter
                if ( scheduleIds.Any() )
                {
                    qry = qry.Where( a =>
                        a.ScheduleId.HasValue &&
                        scheduleIds.Contains( a.ScheduleId.Value ) );
                }

                // Get the unique combination of location/schedule/date for the selected group
                var occurrenceDates = qry
                    .Select( a => new
                    {
                        a.LocationId,
                        a.ScheduleId,
                        Date = DbFunctions.TruncateTime( a.StartDateTime )
                    } )
                    .Distinct()
                    .ToList();

                // Get the locations for each unique location id
                var selectedlocationIds = occurrenceDates.Select( o => o.LocationId ).Distinct().ToList();
                var locations = locationService
                    .Queryable().AsNoTracking()
                    .Where( l => selectedlocationIds.Contains( l.Id ) )
                    .Select( l => new { l.Id, l.ParentLocationId, l.Name } )
                    .ToList();
                var locationNames = new Dictionary<int, string>();
                locations.ForEach( l => locationNames.Add( l.Id, l.Name ) );

                // Get the parent location path for each unique location
                var parentlocationPaths = new Dictionary<int, string>();
                locations
                    .Where( l => l.ParentLocationId.HasValue )
                    .Select( l => l.ParentLocationId.Value )
                    .Distinct()
                    .ToList()
                    .ForEach( l => parentlocationPaths.Add( l, locationService.GetPath( l ) ) );
                var locationPaths = new Dictionary<int, string>();
                locations
                    .Where( l => l.ParentLocationId.HasValue )
                    .ToList()
                    .ForEach( l => locationPaths.Add( l.Id, parentlocationPaths[l.ParentLocationId.Value] ) );

                // Get the schedules for each unique schedule id
                var selectedScheduleIds = occurrenceDates.Select( o => o.ScheduleId ).Distinct().ToList();
                var schedules = scheduleService
                    .Queryable().AsNoTracking()
                    .Where( s => selectedScheduleIds.Contains( s.Id ) )
                    .ToList();
                var scheduleNames = new Dictionary<int, string>();
                var scheduleStartTimes = new Dictionary<int, TimeSpan>();
                schedules
                    .ForEach( s => {
                        scheduleNames.Add( s.Id, s.Name );
                        scheduleStartTimes.Add( s.Id, s.StartTimeOfDay );
                    });

                foreach ( var occurrence in occurrenceDates.Where( o => o.Date.HasValue ) )
                {
                    occurrences.Add(
                        new ScheduleOccurrence(
                            occurrence.Date.Value,
                            occurrence.ScheduleId.HasValue && scheduleStartTimes.ContainsKey( occurrence.ScheduleId.Value ) ?
                                scheduleStartTimes[occurrence.ScheduleId.Value] : new TimeSpan(),
                            occurrence.ScheduleId,
                            occurrence.ScheduleId.HasValue && scheduleNames.ContainsKey( occurrence.ScheduleId.Value ) ?
                                scheduleNames[occurrence.ScheduleId.Value] : string.Empty,
                            occurrence.LocationId,
                            occurrence.LocationId.HasValue && locationNames.ContainsKey( occurrence.LocationId.Value ) ?
                                locationNames[occurrence.LocationId.Value] : string.Empty,
                            occurrence.LocationId.HasValue && locationPaths.ContainsKey( occurrence.LocationId.Value ) ?
                                locationPaths[occurrence.LocationId.Value] : string.Empty
                        ) );
                }

                // Load the attendance data for each occurrence
                if ( loadSummaryData && occurrences.Any())
                {
                    var minDate = occurrences.Min( o => o.Date );
                    var maxDate = occurrences.Max( o => o.Date ).AddDays( 1 );

                    foreach( var summary in attendanceService
                        .Queryable().AsNoTracking()
                        .Where( a =>
                            a.PersonAliasId.HasValue &&
                            a.GroupId.HasValue &&
                            a.GroupId == group.Id &&
                            a.StartDateTime >= minDate &&
                            a.StartDateTime < maxDate )
                        .GroupBy( a => new
                        {
                            a.LocationId,
                            a.ScheduleId,
                            Date = DbFunctions.TruncateTime( a.StartDateTime )
                        } )
                        .Select( a => new
                        {
                            a.Key.LocationId,
                            a.Key.ScheduleId,
                            a.Key.Date,
                            DidAttendCount = a
                                .Where( t => t.DidAttend.HasValue && t.DidAttend.Value )
                                .Select( t => t.PersonAliasId.Value )
                                .Distinct()
                                .Count(),
                            DidNotOccurCount = a
                                .Where( t => t.DidNotOccur.HasValue && t.DidNotOccur.Value )
                                .Select( t => t.PersonAliasId.Value )
                                .Distinct()
                                .Count(),
                            TotalCount = a
                                .Select( t => t.PersonAliasId )
                                .Distinct()
                                .Count()
                        } ) )
                    {
                        var occurrence = occurrences
                            .Where( o =>
                                o.ScheduleId.Equals( summary.ScheduleId ) &&
                                o.LocationId.Equals( summary.LocationId ) &&
                                o.Date.Equals( summary.Date ) )
                            .FirstOrDefault();
                        if ( occurrence != null )
                        {
                            occurrence.DidAttendCount = summary.DidAttendCount;
                            occurrence.DidNotOccurCount = summary.DidNotOccurCount;
                            occurrence.TotalCount = summary.TotalCount;
                        }
                    }
                }

                // Create any missing occurrences from the group's schedule (not location schedules)
                Schedule groupSchedule = null;
                if ( group.ScheduleId.HasValue )
                {
                    groupSchedule = group.Schedule;
                    if ( groupSchedule == null )
                    {
                        groupSchedule = new ScheduleService( rockContext ).Get( group.ScheduleId.Value );
                    }
                }

                if ( groupSchedule != null )
                {
                    var newOccurrences = new List<ScheduleOccurrence>();

                    var existingDates = occurrences
                        .Where( o => o.ScheduleId.Equals( groupSchedule.Id ) )
                        .Select( o => o.Date )
                        .Distinct()
                        .ToList();

                    var startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths( -2 );
                    var endDate = toDateTime.HasValue ? toDateTime.Value : RockDateTime.Today.AddDays( 1 );

                    DDay.iCal.Event calEvent = groupSchedule.GetCalenderEvent();
                    if ( calEvent != null )
                    {
                        // If schedule has an iCal schedule, get all the past occurrences
                        foreach ( var occurrence in calEvent.GetOccurrences( startDate, endDate ) )
                        {
                            var scheduleOccurrence = new ScheduleOccurrence(
                                occurrence.Period.StartTime.Date, occurrence.Period.StartTime.TimeOfDay, groupSchedule.Id, groupSchedule.Name );
                            if ( !existingDates.Contains( scheduleOccurrence.Date ) )
                            {
                                newOccurrences.Add( scheduleOccurrence );
                            }
                        }
                    }
                    else
                    {
                        // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week
                        if ( groupSchedule.WeeklyDayOfWeek.HasValue )
                        {

                            // default to start with date 2 months earlier
                            startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths( -2 );
                            if ( existingDates.Any( d => d < startDate ) )
                            {
                                startDate = existingDates.Min();
                            }

                            // Back up start time to the correct day of week
                            while ( startDate.DayOfWeek != groupSchedule.WeeklyDayOfWeek.Value )
                            {
                                startDate = startDate.AddDays( -1 );
                            }

                            // Add the start time
                            if ( groupSchedule.WeeklyTimeOfDay.HasValue )
                            {
                                startDate = startDate.Add( groupSchedule.WeeklyTimeOfDay.Value );
                            }

                            // Create occurrences up to current time
                            while ( startDate < endDate )
                            {
                                if ( !existingDates.Contains( startDate.Date ) )
                                {
                                    var scheduleOccurrence = new ScheduleOccurrence( startDate.Date, startDate.TimeOfDay, groupSchedule.Id, groupSchedule.Name );
                                    newOccurrences.Add( scheduleOccurrence );
                                }

                                startDate = startDate.AddDays( 7 );
                            }
                        }
                    }

                    if ( newOccurrences.Any() )
                    {
                        // Filter Exclusions
                        var groupType = GroupTypeCache.Read( group.GroupTypeId );
                        foreach ( var exclusion in groupType.GroupScheduleExclusions )
                        {
                            if ( exclusion.Start.HasValue && exclusion.End.HasValue )
                            {
                                foreach ( var occurrence in newOccurrences.ToList() )
                                {
                                    if ( occurrence.Date >= exclusion.Start.Value &&
                                        occurrence.Date < exclusion.End.Value.AddDays( 1 ) )
                                    {
                                        newOccurrences.Remove( occurrence );
                                    }
                                }
                            }
                        }
                    }

                    foreach( var occurrence in newOccurrences )
                    {
                        occurrences.Add( occurrence );
                    }

                }
            }

            return occurrences;
        }
        /// <summary>
        /// Loads the attendance data.
        /// </summary>
        /// <param name="group">The group.</param>
        /// <param name="occurrence">The occurrence.</param>
        public void LoadSummaryData( Group group, ScheduleOccurrence occurrence )
        {
            if ( group != null && occurrence != null )
            {
                var attendances = new AttendanceService( (RockContext)this.Context )
                .Queryable().AsNoTracking()
                .Where( a =>
                    a.PersonAliasId.HasValue &&
                    a.GroupId.HasValue &&
                    a.GroupId == group.Id &&
                    a.LocationId.Equals( occurrence.LocationId ) &&
                    a.ScheduleId.Equals( occurrence.ScheduleId ) &&
                    DbFunctions.TruncateTime( a.StartDateTime ).Equals( occurrence.Date ) )
                .ToList();

                if ( attendances.Any() )
                {
                    occurrence.DidAttendCount = attendances
                        .Where( t => t.DidAttend.HasValue && t.DidAttend.Value )
                        .Select( t => t.PersonAliasId.Value )
                        .Distinct()
                        .Count();

                    occurrence.DidNotOccurCount = attendances
                        .Where( t => t.DidNotOccur.HasValue && t.DidNotOccur.Value )
                        .Select( t => t.PersonAliasId.Value )
                        .Distinct()
                        .Count();

                    occurrence.TotalCount = attendances
                        .Select( t => t.PersonAliasId )
                        .Distinct()
                        .Count();
                }
            }
        }
        protected void btnSave_Click(object sender, EventArgs e)
        {
            btnDone.Visible = true;
            lblPeople.Visible = true;

            //Create List to Save Registered People

            var peopleList = new List<string>();

            if (Session["peopleList"] != null)
            {
            peopleList = (List<string>)Session["peopleList"];
            }

            AttendanceCodeService attendanceCodeService = new AttendanceCodeService(rockContext);
            AttendanceService attendanceService = new AttendanceService(rockContext);
            GroupMemberService groupMemberService = new GroupMemberService(rockContext);
            PersonAliasService personAliasService = new PersonAliasService(rockContext);

            Session["person"] = ppPerson.SelectedValue.ToString();

            // Only create one attendance record per day for each person/schedule/group/location
            DateTime theTime = Convert.ToDateTime(Session["startDateTime"]);
            var attendance = attendanceService.Get(theTime, int.Parse(Session["location"].ToString()), int.Parse(Session["schedule"].ToString()), int.Parse(Session["group"].ToString()), int.Parse(ppPerson.SelectedValue.ToString()));
            var primaryAlias = personAliasService.GetPrimaryAlias(int.Parse(ppPerson.SelectedValue.ToString()));

            if (attendance == null)
            {

                if (primaryAlias != null)
                {
                    attendance = rockContext.Attendances.Create();
                    attendance.LocationId = int.Parse(Session["location"].ToString());
                    attendance.CampusId = int.Parse(Session["campus"].ToString());
                    attendance.ScheduleId = int.Parse(Session["schedule"].ToString());
                    attendance.GroupId = int.Parse(Session["group"].ToString());
                    attendance.PersonAlias = primaryAlias;
                    attendance.PersonAliasId = primaryAlias.Id;
                    attendance.DeviceId = null;
                    attendance.SearchTypeValueId = 1;
                    attendanceService.Add(attendance);
                }
            }
            attendance.AttendanceCodeId = null;
            attendance.StartDateTime = Convert.ToDateTime(Session["startDateTime"]);
            attendance.EndDateTime = null;
            attendance.DidAttend = true;

            //KioskLocationAttendance.AddAttendance(attendance);
            rockContext.SaveChanges();

            //Add Person to Dictionary
            peopleList.Add( ppPerson.PersonName );
            repLinks.DataSource = peopleList;
            repLinks.DataBind();
            Session["peopleList"] = peopleList;

            //Clear Person field
            ppPerson.PersonId = null;
            ppPerson.PersonName = null;

            //Update Current Participants List
        }
        /// <summary>
        /// Executes the specified workflow.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="action">The workflow action.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="errorMessages">The error messages.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public override bool Execute(RockContext rockContext, Rock.Model.WorkflowAction action, Object entity, out List<string> errorMessages)
        {
            var checkInState = GetCheckInState(entity, out errorMessages);
            if (checkInState == null)
            {
                return false;
            }

            int peopleWithoutAssignments = 0;
            bool roomBalance = GetAttributeValue(action, "RoomBalance").AsBoolean();
            int balanceOverride = GetAttributeValue(action, "DifferentialOverride").AsIntegerOrNull() ?? 10;
            int previousMonthsNumber = GetAttributeValue(action, "PreviousMonthsAttendance").AsIntegerOrNull() ?? 3;
            int maxAssignments = GetAttributeValue(action, "MaxAssignments").AsIntegerOrNull() ?? 5;

            var cutoffDate = Rock.RockDateTime.Today.AddMonths(previousMonthsNumber * -1);
            var attendanceService = new AttendanceService(rockContext);

            var family = checkInState.CheckIn.Families.FirstOrDefault(f => f.Selected);
            if (family != null)
            {
                // get the number of people checking in, including visitors or first-timers
                peopleWithoutAssignments = family.People.Where(p => p.Selected).Count();

                foreach (var previousAttender in family.People.Where(p => p.Selected && !p.FirstTime))
                {
                    var personGroupTypeIds = previousAttender.GroupTypes.Select(gt => gt.GroupType.Id);

                    var lastDateAttendances = attendanceService.Queryable()
                        .Where(a =>
                           a.PersonAlias.PersonId == previousAttender.Person.Id &&
                           personGroupTypeIds.Contains(a.Group.GroupTypeId) &&
                           a.StartDateTime >= cutoffDate && a.DidAttend == true)
                        .OrderByDescending(a => a.StartDateTime).Take(maxAssignments)
                        .ToList();

                    if (lastDateAttendances.Any())
                    {
                        bool createdMatchingAssignment = false;
                        var isSpecialNeeds = previousAttender.Person.GetAttributeValue("IsSpecialNeeds").AsBoolean();

                        var lastAttended = lastDateAttendances.Max(a => a.StartDateTime).Date;
                        foreach (var groupAttendance in lastDateAttendances.Where(a => a.StartDateTime >= lastAttended))
                        {
                            // Start with filtered groups unless they have abnormal age and grade parameters (1%)
                            var groupType = previousAttender.GroupTypes.FirstOrDefault(gt => gt.GroupType.Id == groupAttendance.Group.GroupTypeId && (!gt.ExcludedByFilter || isSpecialNeeds));
                            if (groupType != null)
                            {
                                CheckInGroup group = null;
                                if (groupType.Groups.Count == 1)
                                {
                                    // Only a single group is open
                                    group = groupType.Groups.FirstOrDefault(g => !g.ExcludedByFilter || isSpecialNeeds);
                                }
                                else
                                {
                                    // Pick the group they last attended
                                    group = groupType.Groups.FirstOrDefault(g => g.Group.Id == groupAttendance.GroupId && (!g.ExcludedByFilter || isSpecialNeeds));

                                    if (group != null && roomBalance && !isSpecialNeeds)
                                    {
                                        var currentAttendance = group.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum();
                                        var lowestAttendedGroup = groupType.Groups.Where(g => !g.ExcludedByFilter)
                                            .Select(g => new { Group = g, Attendance = g.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum() })
                                            .OrderBy(g => g.Attendance)
                                            .FirstOrDefault();

                                        if (lowestAttendedGroup != null && lowestAttendedGroup.Attendance < (currentAttendance - balanceOverride))
                                        {
                                            group = lowestAttendedGroup.Group;
                                        }
                                    }
                                }

                                if (group != null)
                                {
                                    CheckInLocation location = null;
                                    if (group.Locations.Count == 1)
                                    {
                                        // Only a single location is open
                                        location = group.Locations.FirstOrDefault(l => !l.ExcludedByFilter || isSpecialNeeds);
                                    }
                                    else
                                    {
                                        // Pick the location they last attended
                                        location = group.Locations.FirstOrDefault(l => l.Location.Id == groupAttendance.LocationId && (!l.ExcludedByFilter || isSpecialNeeds));

                                        if (location != null && roomBalance && !isSpecialNeeds)
                                        {
                                            var currentAttendance = KioskLocationAttendance.Read(location.Location.Id).CurrentCount;
                                            var lowestAttendedLocation = group.Locations.Where(l => !l.ExcludedByFilter)
                                                .Select(l => new { Location = l, Attendance = KioskLocationAttendance.Read(location.Location.Id).CurrentCount })
                                                .OrderBy(l => l.Attendance)
                                                .FirstOrDefault();

                                            if (lowestAttendedLocation != null && lowestAttendedLocation.Attendance < (currentAttendance - balanceOverride))
                                            {
                                                location = lowestAttendedLocation.Location;
                                            }
                                        }
                                    }

                                    if (location != null)
                                    {
                                        CheckInSchedule schedule = null;
                                        if (location.Schedules.Count == 1)
                                        {
                                            schedule = location.Schedules.FirstOrDefault(s => !s.ExcludedByFilter || isSpecialNeeds);
                                        }
                                        else if (groupAttendance.ScheduleId != null)
                                        {
                                            schedule = location.Schedules.FirstOrDefault(s => s.Schedule.Id == groupAttendance.ScheduleId && (!s.ExcludedByFilter || isSpecialNeeds));
                                        }
                                        else
                                        {
                                            // if the schedule doesn't exactly match but everything else does, select it
                                            schedule = location.Schedules.FirstOrDefault(s => (!s.ExcludedByFilter && !isSpecialNeeds));
                                        }

                                        if (schedule != null)
                                        {
                                            schedule.Selected = true;
                                            schedule.PreSelected = true;
                                            schedule.LastCheckIn = groupAttendance.StartDateTime;
                                            location.Selected = true;
                                            location.PreSelected = true;
                                            location.LastCheckIn = groupAttendance.StartDateTime;
                                            group.Selected = true;
                                            group.PreSelected = true;
                                            group.LastCheckIn = groupAttendance.StartDateTime;
                                            groupType.Selected = true;
                                            groupType.PreSelected = true;
                                            group.LastCheckIn = groupAttendance.StartDateTime;
                                            groupType.LastCheckIn = groupAttendance.StartDateTime;
                                            groupType.Selected = true;
                                            groupType.PreSelected = true;
                                            previousAttender.PreSelected = true;
                                            previousAttender.LastCheckIn = groupAttendance.StartDateTime;
                                            createdMatchingAssignment = true;
                                        }
                                    }
                                }
                            }
                        }

                        if (createdMatchingAssignment)
                        {
                            peopleWithoutAssignments--;
                        }
                    }
                }
            }

            // true condition will continue to the next auto-assignment
            // false condition will stop processing auto-assignments
            if (action.Activity.AttributeValues.Any() && action.Activity.AttributeValues.ContainsKey("ContinueAssignments"))
            {
                var continueAssignments = peopleWithoutAssignments > 0;
                action.Activity.AttributeValues["ContinueAssignments"].Value = continueAssignments.ToString();
            }

            return true;
        }
Exemple #13
0
        /// <summary>
        /// Binds the filter.
        /// </summary>
        private void BindFilter()
        {
            drpDates.DelimitedValues = rFilter.GetUserPreference( "Date Range" );

            ppPerson.Visible = _person == null;
            ddlAttendanceGroup.Visible = _group == null && _person != null;
            ddlAttendanceGroup.Items.Clear();
            ddlAttendanceGroup.Items.Add( new ListItem() );

            if ( _person != null )
            {
                var rockContext = new RockContext();
                var qryGroup = new GroupService( rockContext ).Queryable();

                var qryPersonAttendance = new AttendanceService( rockContext ).Queryable().Where( a => a.PersonAlias.PersonId == _person.Id );

                // only list groups that this person has attended before
                var groupList = qryGroup.Where( g => qryPersonAttendance.Any( a => a.GroupId == g.Id ) )
                    .OrderBy( a => a.Name )
                    .Select( a => new { a.Name, a.Id } ).ToList();

                foreach ( var group in groupList )
                {
                    ddlAttendanceGroup.Items.Add( new ListItem( group.Name, group.Id.ToString() ) );
                }

                ddlAttendanceGroup.SetValue( rFilter.GetUserPreference( "Group" ).AsIntegerOrNull() );
            }

            spSchedule.SetValue( rFilter.GetUserPreference( "Schedule" ).AsIntegerOrNull() );
        }
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            var dateRange = SlidingDateRangePicker.CalculateDateRangeFromDelimitedValues( drpSlidingDateRange.DelimitedValues );

            string groupTypeIds = null;
            string campusIds = cpCampuses.SelectedCampusIds.AsDelimited( "," );

            SortProperty sortProperty = gAttendance.SortProperty;

            var chartData = new AttendanceService( new RockContext() ).GetChartData(
                hfGroupBy.Value.ConvertToEnumOrNull<AttendanceGroupBy>() ?? AttendanceGroupBy.Week,
                hfGraphBy.Value.ConvertToEnumOrNull<AttendanceGraphBy>() ?? AttendanceGraphBy.Total,
                dateRange.Start,
                dateRange.End,
                groupTypeIds,
                campusIds );

            if ( sortProperty != null )
            {
                gAttendance.DataSource = chartData.AsQueryable().Sort( sortProperty ).ToList();
            }
            else
            {
                gAttendance.DataSource = chartData.OrderBy( a => a.DateTimeStamp ).ToList();
            }

            gAttendance.DataBind();
        }
Exemple #15
0
        /// <summary>
        /// Handles printing labels for the given parameters using the
        /// label data stored on the AttendanceData model.
        /// </summary>
        /// <param name="fileGuids">The file guids of the label types to print.</param>
        /// <param name="personId">The person whose labels to print.</param>
        /// <param name="selectedAttendanceIds">The attendance Ids that have the labels to be reprinted.</param>
        /// <param name="control">The control to register/inject the client side printing into. This should be
        /// a control that is inside an UpdatePanel control so that the ScriptManager can register the needed client script block.</param>
        /// <param name="request">The HTTP Request so that the request's URL can be used when needed for client printing.</param>
        /// <param name="printerAddress">The IP Address of a printer to send the print job to, overriding what is in the label.</param>
        /// <returns>
        /// A list of any messages that occur during printing.
        /// </returns>
        public static List <string> ReprintZebraLabels(List <Guid> fileGuids, int personId, List <int> selectedAttendanceIds, Control control, System.Web.HttpRequest request, string printerAddress = null)
        {
            // Fetch the actual labels and print them
            var rockContext       = new RockContext();
            var attendanceService = new Rock.Model.AttendanceService(rockContext);

            // Get the selected attendance records
            var attendanceRecords = attendanceService.GetByIds(selectedAttendanceIds);

            var printFromClient = new List <CheckInLabel>();
            var printFromServer = new List <CheckInLabel>();

            // Now grab only the selected label types (matching fileGuids) from those record's AttendanceData
            // for the selected  person
            foreach (var attendance in attendanceRecords)
            {
                var attendanceData = attendance.AttendanceData;
                var json           = attendanceData.LabelData.Trim();

                // skip if the return type is not an array
                if (json.Substring(0, 1) != "[")
                {
                    continue;
                }

                // De-serialize the JSON into a list of objects
                var checkinLabels = JsonConvert.DeserializeObject <List <CheckInLabel> >(json);

                // skip if no labels were found
                if (checkinLabels == null)
                {
                    continue;
                }

                // Take only the labels that match the selected person (or if they are Family type labels) and file guids).
                checkinLabels = checkinLabels.Where(l => (l.PersonId == personId || l.LabelType == KioskLabelType.Family) && fileGuids.Contains(l.FileGuid)).ToList();

                // Override the printer by printing to the given printerAddress?
                if (!string.IsNullOrEmpty(printerAddress))
                {
                    checkinLabels.ToList().ForEach(l => l.PrinterAddress = printerAddress);
                    printFromServer.AddRange(checkinLabels);
                }
                else
                {
                    printFromClient.AddRange(checkinLabels.Where(l => l.PrintFrom == Rock.Model.PrintFrom.Client));
                    printFromServer.AddRange(checkinLabels.Where(l => l.PrintFrom == Rock.Model.PrintFrom.Server));
                }
            }

            // Print client labels
            if (printFromClient.Any())
            {
                var urlRoot = string.Format("{0}://{1}", request.Url.Scheme, request.Url.Authority);
                printFromClient
                .OrderBy(l => l.PersonId)
                .ThenBy(l => l.Order)
                .ToList()
                .ForEach(l => l.LabelFile = urlRoot + l.LabelFile);

                AddLabelScript(printFromClient.ToJson(), control);
            }

            var messages = new List <string>();

            // Print server labels
            if (printFromServer.Any())
            {
                messages = ZebraPrint.PrintLabels(printFromServer);
            }

            // No messages is "good news".
            if (messages.Count == 0)
            {
                messages.Add("The labels have been printed.");
            }

            return(messages);
        }
Exemple #16
0
        /// <summary>
        /// Handles printing labels for the given parameters using the
        /// label data stored on the AttendanceData model.
        /// </summary>
        /// <param name="fileGuids">The file guids.</param>
        /// <param name="personId">The person identifier.</param>
        /// <param name="selectedAttendanceIds">The selected attendance ids.</param>
        /// <param name="control">The control.</param>
        /// <param name="request">The request.</param>
        /// <param name="reprintLabelOptions">The reprint label options.</param>
        /// <returns></returns>
        public static List <string> ReprintZebraLabels(List <Guid> fileGuids, int personId, List <int> selectedAttendanceIds, Control control, System.Web.HttpRequest request, ReprintLabelOptions reprintLabelOptions)
        {
            // Fetch the actual labels and print them
            var rockContext       = new RockContext();
            var attendanceService = new Rock.Model.AttendanceService(rockContext);

            reprintLabelOptions = reprintLabelOptions ?? new ReprintLabelOptions();

            // Get the selected attendance records (but only the ones that have label data)
            var labelDataList = attendanceService
                                .GetByIds(selectedAttendanceIds)
                                .Where(a => a.AttendanceData.LabelData != null)
                                .Select(a => a.AttendanceData.LabelData);

            var printFromClient = new List <CheckInLabel>();
            var printFromServer = new List <CheckInLabel>();

            // Now grab only the selected label types (matching fileGuids) from those record's AttendanceData
            // for the selected  person
            foreach (var labelData in labelDataList)
            {
                var json = labelData.Trim();

                // skip if the return type is not an array
                if (json.Substring(0, 1) != "[")
                {
                    continue;
                }

                // De-serialize the JSON into a list of objects
                var checkinLabels = JsonConvert.DeserializeObject <List <CheckInLabel> >(json);

                // skip if no labels were found
                if (checkinLabels == null)
                {
                    continue;
                }

                // Take only the labels that match the selected person (or if they are Family type labels) and file guids).
                checkinLabels = checkinLabels.Where(l => (l.PersonId == personId || l.LabelType == KioskLabelType.Family) && fileGuids.Contains(l.FileGuid)).ToList();

                if (reprintLabelOptions.PrintFrom == PrintFrom.Server && reprintLabelOptions.ServerPrinterIPAddress.IsNotNullOrWhiteSpace())
                {
                    // Override the printer by printing to the given printerAddress?
                    checkinLabels.ToList().ForEach(l => l.PrinterAddress = reprintLabelOptions.ServerPrinterIPAddress);
                    printFromServer.AddRange(checkinLabels);
                }
                else if (reprintLabelOptions.PrintFrom == PrintFrom.Client)
                {
                    // Override the printer by printing to the client
                    // send the checkin labels to ZebraPrint.js, which the ClientApp will use to print it
                    // IP Address of the printer will stay the same IP Address as where the original label was,
                    printFromClient.AddRange(checkinLabels);
                }
                else
                {
                    // Print to label's printer
                    printFromClient.AddRange(checkinLabels.Where(l => l.PrintFrom == Rock.Model.PrintFrom.Client));
                    printFromServer.AddRange(checkinLabels.Where(l => l.PrintFrom == Rock.Model.PrintFrom.Server));
                }
            }

            // Print client labels
            if (printFromClient.Any())
            {
                var urlRoot = string.Format("{0}://{1}", request.UrlProxySafe().Scheme, request.UrlProxySafe().Authority);

                /*
                 *              // This is extremely useful when debugging with ngrok and an iPad on the local network.
                 *              // X-Original-Host will contain the name of your ngrok hostname, therefore the labels will
                 *              // get a LabelFile url that will actually work with that iPad.
                 *              if ( request.Headers["X-Original-Host"] != null )
                 *              {
                 *                  var scheme = request.Headers["X-Forwarded-Proto"] ?? "http";
                 *                  urlRoot = string.Format( "{0}://{1}", scheme, request.Headers.GetValues( "X-Original-Host" ).First() );
                 *              }
                 */

                printFromClient
                .OrderBy(l => l.PersonId)
                .ThenBy(l => l.Order)
                .ToList()
                .ForEach(l => l.LabelFile = urlRoot + l.LabelFile);

                AddLabelScript(printFromClient.ToJson(), control);
            }

            var messages = new List <string>();

            // Print server labels
            if (printFromServer.Any())
            {
                messages = ZebraPrint.PrintLabels(printFromServer);
            }

            // No messages is "good news".
            if (messages.Count == 0)
            {
                messages.Add("The labels have been printed.");
            }

            return(messages);
        }
Exemple #17
0
        private void ShowDetail(Guid personGuid)
        {
            using ( var rockContext = new RockContext() )
            {
                var personService = new PersonService( rockContext );

                var person = personService.Queryable( "PhoneNumbers.NumberTypeValue,RecordTypeValue", true, true )
                    .FirstOrDefault( a => a.Guid == personGuid );

                if ( person != null )
                {
                    lName.Text = person.FullName;

                    string photoTag = Rock.Model.Person.GetPersonPhotoImageTag( person, 120, 120 );
                    if ( person.PhotoId.HasValue )
                    {
                        lPhoto.Text = string.Format( "<div class='photoframe'><a href='{0}'>{1}</a></div>", person.PhotoUrl, photoTag );
                    }
                    else
                    {
                        lPhoto.Text = photoTag;
                    }

                    lGender.Text = person.Gender != Gender.Unknown ? person.Gender.ConvertToString() : "";

                    if ( person.BirthDate.HasValue )
                    {
                        string ageText = ( person.BirthYear.HasValue && person.BirthYear != DateTime.MinValue.Year ) ?
                            string.Format( "{0} yrs old ", person.BirthDate.Value.Age() ) : string.Empty;
                        lAge.Text = string.Format( "{0} <small>({1})</small><br/>", ageText, person.BirthDate.Value.ToShortDateString() );
                    }
                    else
                    {
                        lAge.Text = string.Empty;
                    }

                    lGrade.Text = person.GradeFormatted;

                    lEmail.Visible = !string.IsNullOrWhiteSpace( person.Email );
                    lEmail.Text = person.GetEmailTag( ResolveRockUrl( "/" ), "btn btn-default", "<i class='fa fa-envelope'></i>" );

                    // Get all family member from all families ( including self )
                    var allFamilyMembers = personService.GetFamilyMembers( person.Id, true ).ToList();

                    // Add flag for this person in each family indicating if they are a child in family.
                    var childGuid = Rock.SystemGuid.GroupRole.GROUPROLE_FAMILY_MEMBER_CHILD.AsGuid();
                    var isFamilyChild = new Dictionary<int, bool>();
                    foreach ( var thisPerson in allFamilyMembers.Where( m => m.PersonId == person.Id ) )
                    {
                        isFamilyChild.Add( thisPerson.GroupId, thisPerson.GroupRole.Guid.Equals( childGuid ) );
                    }

                    // Get the current url's root (without the person's guid)
                    string urlRoot = Request.Url.ToString().ReplaceCaseInsensitive( personGuid.ToString(), "" );

                    // Get the other family members and the info needed for rendering
                    var familyMembers = allFamilyMembers.Where( m => m.PersonId != person.Id )
                        .OrderBy( m => m.GroupId )
                        .ThenBy( m => m.Person.BirthDate )
                        .Select( m => new
                        {
                            Url = urlRoot + m.Person.Guid.ToString(),
                            FullName = m.Person.FullName,
                            Gender = m.Person.Gender,
                            FamilyRole = m.GroupRole,
                            Note = isFamilyChild[m.GroupId] ?
                                ( m.GroupRole.Guid.Equals( childGuid ) ? " (Sibling)" : "(Parent)" ) :
                                ( m.GroupRole.Guid.Equals( childGuid ) ? " (Child)" : "" )
                        } )
                        .ToList();

                    rcwFamily.Visible = familyMembers.Any();
                    rptrFamily.DataSource = familyMembers;
                    rptrFamily.DataBind();

                    rptrPhones.DataSource = person.PhoneNumbers.Where( p => !p.IsUnlisted ).ToList();
                    rptrPhones.DataBind();

                    var schedules = new ScheduleService( rockContext )
                        .Queryable().AsNoTracking()
                        .Where( s => s.CheckInStartOffsetMinutes.HasValue )
                        .ToList();

                    var scheduleIds = schedules.Select( s => s.Id ).ToList();

                    var activeScheduleIds = new List<int>();
                    foreach ( var schedule in schedules )
                    {
                        if ( schedule.IsScheduleOrCheckInActive )
                        {
                            activeScheduleIds.Add( schedule.Id );
                        }
                    }

                    int? personAliasId = person.PrimaryAliasId;
                    if ( !personAliasId.HasValue )
                    {
                        personAliasId = new PersonAliasService( rockContext ).GetPrimaryAliasId( person.Id );
                    }

                    var attendances = new AttendanceService( rockContext )
                        .Queryable( "Schedule,Group,Location,AttendanceCode" )
                        .Where( a =>
                            a.PersonAliasId.HasValue &&
                            a.PersonAliasId == personAliasId &&
                            a.ScheduleId.HasValue &&
                            a.GroupId.HasValue &&
                            a.LocationId.HasValue &&
                            a.DidAttend.HasValue &&
                            a.DidAttend.Value &&
                            scheduleIds.Contains( a.ScheduleId.Value ) )
                        .OrderByDescending( a => a.StartDateTime )
                        .Take( 20 )
                        .ToList()                                                   // Run query to get recent most 20 checkins
                        .OrderByDescending( a => a.StartDateTime )                  // Then sort again by startdatetime and schedule start (which is not avail to sql query )
                        .ThenByDescending( a => a.Schedule.StartTimeOfDay )
                        .Select( a => new AttendanceInfo
                        {
                            Date = a.StartDateTime,
                            GroupId = a.Group.Id,
                            Group = a.Group.Name,
                            LocationId = a.LocationId.Value,
                            Location = a.Location.Name,
                            Schedule = a.Schedule.Name,
                            IsActive =
                                a.StartDateTime > DateTime.Today &&
                                activeScheduleIds.Contains( a.ScheduleId.Value ),
                            Code = a.AttendanceCode != null ? a.AttendanceCode.Code : ""
                        } ).ToList();

                    // Set active locations to be a link to the room in manager page
                    var qryParam = new Dictionary<string, string>();
                    qryParam.Add( "Group", "" );
                    qryParam.Add( "Location", "" );
                    foreach ( var attendance in attendances.Where( a => a.IsActive ) )
                    {
                        qryParam["Group"] = attendance.GroupId.ToString();
                        qryParam["Location"] = attendance.LocationId.ToString();
                        attendance.Location = string.Format( "<a href='{0}'>{1}</a>",
                            LinkedPageUrl( "ManagerPage", qryParam ), attendance.Location );
                    }

                    rcwCheckinHistory.Visible = attendances.Any();
                    gHistory.DataSource = attendances;
                    gHistory.DataBind();
                }
            }
        }
Exemple #18
0
        /// <summary>
        /// Handles adding families from the given XML element snippet
        /// </summary>
        /// <param name="elemFamilies">The xml element containing all the families.</param>
        /// <param name="rockContext">The rock context.</param>
        private void AddFamilies( XElement elemFamilies, RockContext rockContext )
        {
            if ( elemFamilies == null )
            {
                return;
            }

            // Persist the storage type's settings specific to the photo binary file type
            var settings = new Dictionary<string, string>();
            if ( _personImageBinaryFileType.Attributes == null )
            {
                _personImageBinaryFileType.LoadAttributes();
            }
            foreach ( var attributeValue in _personImageBinaryFileType.AttributeValues )
            {
                settings.Add( attributeValue.Key, attributeValue.Value.Value );
            }
            _personImageBinaryFileTypeSettings = settings.ToJson();

            bool fabricateAttendance = GetAttributeValue( "FabricateAttendance" ).AsBoolean();
            GroupService groupService = new GroupService( rockContext );
            var allFamilies = rockContext.Groups;

            List<Group> allGroups = new List<Group>();
            var attendanceData = new Dictionary<Guid, List<Attendance>>();

            // Next create the family along with its members and related data
            foreach ( var elemFamily in elemFamilies.Elements( "family" ) )
            {
                Guid guid = elemFamily.Attribute( "guid" ).Value.Trim().AsGuid();
                var familyMembers = BuildFamilyMembersFromXml( elemFamily.Element( "members" ), rockContext );

                // Call replica of groupService's SaveNewFamily method in an attempt to speed things up
                Group family = CreateNewFamily( familyMembers, campusId: 1 );
                family.Guid = guid;

                // add the family to the context's list of groups
                allFamilies.Add( family );

                // add the families address(es)
                AddFamilyAddresses( groupService, family, elemFamily.Element( "addresses" ), rockContext );

                // add their attendance data
                if ( fabricateAttendance )
                {
                    AddFamilyAttendance( family, elemFamily, rockContext, attendanceData );
                }

                allGroups.Add( family );

                _stopwatch.Stop();
                AppendFormat( "{0:00}:{1:00}.{2:00} added {3}<br/>", _stopwatch.Elapsed.Minutes, _stopwatch.Elapsed.Seconds, _stopwatch.Elapsed.Milliseconds / 10, family.Name );
                _stopwatch.Start();
            }
            rockContext.ChangeTracker.DetectChanges();
            rockContext.SaveChanges( disablePrePostProcessing: true );

            // Now save each person's attributevalues (who had them defined in the XML)
            // and add each person's ID to a dictionary for use later.
            _stopwatch.Stop();
            AppendFormat( "{0:00}:{1:00}.{2:00} saving attributes for everyone...<br/>", _stopwatch.Elapsed.Minutes, _stopwatch.Elapsed.Seconds, _stopwatch.Elapsed.Milliseconds / 10 );
            _stopwatch.Start();
            AttributeValueService attributeValueService = new AttributeValueService( rockContext );
            foreach ( var gm in allGroups.SelectMany( g => g.Members ) )
            {
                // Put the person's id into the people dictionary for later use.
                if ( !_peopleDictionary.ContainsKey( gm.Person.Guid ) )
                {
                    _peopleDictionary.Add( gm.Person.Guid, gm.Person.Id );
                }

                // Only save if the person had attributes, otherwise it will error.
                if ( _personWithAttributes.ContainsKey( gm.Person.Guid ) )
                {
                    foreach ( var attributeCache in gm.Person.Attributes.Select( a => a.Value ) )
                    {
                        var newValue = gm.Person.AttributeValues[attributeCache.Key];
                        if ( newValue != null )
                        {
                            var attributeValue = new AttributeValue();
                            attributeValue.AttributeId = newValue.AttributeId;
                            attributeValue.EntityId = gm.Person.Id;
                            attributeValue.Value = newValue.Value;
                            rockContext.AttributeValues.Add( attributeValue );
                        }
                    }
                }
            }
            rockContext.ChangeTracker.DetectChanges();
            rockContext.SaveChanges( disablePrePostProcessing: true );

            _stopwatch.Stop();
            AppendFormat( "{0:00}:{1:00}.{2:00} attributes saved<br/>", _stopwatch.Elapsed.Minutes, _stopwatch.Elapsed.Seconds, _stopwatch.Elapsed.Milliseconds / 10 );
            _stopwatch.Start();

            // Create person alias records for each person manually since we set disablePrePostProcessing=true on save
            PersonService personService = new PersonService( rockContext );
            foreach ( var person in personService.Queryable( "Aliases", true )
                .Where( p =>
                    _peopleDictionary.Keys.Contains( p.Guid ) &&
                    !p.Aliases.Any() ) )
            {
                person.Aliases.Add( new PersonAlias { AliasPersonId = person.Id, AliasPersonGuid = person.Guid } );
            }
            rockContext.ChangeTracker.DetectChanges();
            rockContext.SaveChanges( disablePrePostProcessing: true );

            _stopwatch.Stop();
            AppendFormat( "{0:00}:{1:00}.{2:00} added person aliases<br/>", _stopwatch.Elapsed.Minutes, _stopwatch.Elapsed.Seconds, _stopwatch.Elapsed.Milliseconds / 10 );
            _stopwatch.Start();

            // Put the person alias ids into the people alias dictionary for later use.
            PersonAliasService personAliasService = new PersonAliasService( rockContext );
            foreach ( var personAlias in personAliasService.Queryable( "Person" )
                .Where( a =>
                    _peopleDictionary.Keys.Contains( a.Person.Guid ) &&
                    a.PersonId == a.AliasPersonId ) )
            {
                _peopleAliasDictionary.Add( personAlias.Person.Guid, personAlias.Id );
            }

            // Now that person aliases have been saved, save the attendance records
            var attendanceService = new AttendanceService( rockContext );
            var attendanceGuids = attendanceData.Select( a => a.Key ).ToList();
            foreach ( var aliasKeyValue in _peopleAliasDictionary
                .Where( a => attendanceGuids.Contains( a.Key )) )
            {
                foreach ( var attendance in attendanceData[aliasKeyValue.Key] )
                {
                    attendance.PersonAliasId = aliasKeyValue.Value;
                    attendanceService.Add( attendance );
                }
            }
            rockContext.ChangeTracker.DetectChanges();
            rockContext.SaveChanges( disablePrePostProcessing: true );

            _stopwatch.Stop();
            AppendFormat( "{0:00}:{1:00}.{2:00} added attendance records<br/>", _stopwatch.Elapsed.Minutes, _stopwatch.Elapsed.Seconds, _stopwatch.Elapsed.Milliseconds / 10 );
            _stopwatch.Start();

            // Now re-process the family section looking for any giving data.
            // We do this last because we need the personAliases that were just added.
            // Persist the storage type's settings specific to the contribution binary file type
            settings = new Dictionary<string, string>();
            if ( _checkImageBinaryFileType.Attributes == null )
            {
                _checkImageBinaryFileType.LoadAttributes();
            }
            foreach ( var attributeValue in _checkImageBinaryFileType.AttributeValues )
            {
                settings.Add( attributeValue.Key, attributeValue.Value.Value );
            }
            _checkImageBinaryFileTypeSettings = settings.ToJson();

            foreach ( var elemFamily in elemFamilies.Elements( "family" ) )
            {
                // add the families giving data
                if ( GetAttributeValue( "EnableGiving" ).AsBoolean() )
                {
                    // Support multiple giving elements per family
                    foreach ( var elementGiving in elemFamily.Elements( "giving" ) )
                    {
                        AddFamilyGiving( elementGiving, elemFamily.Attribute( "name" ).Value, rockContext );
                    }
                }
            }

            if ( GetAttributeValue( "EnableGiving" ).AsBoolean() )
            {
                // Now add the batches to the service to be persisted
                var financialBatchService = new FinancialBatchService( rockContext );
                foreach ( var financialBatch in _contributionBatches )
                {
                    financialBatchService.Add( financialBatch.Value );
                }
            }
            rockContext.ChangeTracker.DetectChanges();
            rockContext.SaveChanges( disablePrePostProcessing: true );
        }
Exemple #19
0
        protected void lbSearch_Click( object sender, EventArgs e )
        {
            using ( var rockContext = new RockContext() )
            {

                // Get all the schedules that allow checkin
                var schedules = new ScheduleService( rockContext )
                    .Queryable().AsNoTracking()
                    .Where( s => s.CheckInStartOffsetMinutes.HasValue )
                    .ToList();

                // Get a lit of the schedule ids
                var scheduleIds = schedules.Select( s => s.Id ).ToList();

                // Get a list of the schedule id that are currently active for checkin
                var activeScheduleIds = new List<int>();
                foreach ( var schedule in schedules )
                {
                    if ( schedule.IsScheduleOrCheckInActive )
                    {
                        activeScheduleIds.Add( schedule.Id );
                    }
                }

                // Get a list of all the groups that we're concerned about
                var groupIds = NavData.Groups.Select( g => g.Id ).ToList();

                // Get a list of all the people that are currently checked in
                var today = RockDateTime.Today;
                var attendanceService = new AttendanceService( rockContext );
                var currentAttendeeIds = attendanceService
                    .Queryable().AsNoTracking()
                    .Where( a =>
                        a.ScheduleId.HasValue &&
                        a.GroupId.HasValue &&
                        a.LocationId.HasValue &&
                        a.PersonAlias != null &&
                        a.DidAttend.HasValue &&
                        a.DidAttend.Value &&
                        a.StartDateTime > today &&
                        activeScheduleIds.Contains( a.ScheduleId.Value ) &&
                        groupIds.Contains( a.GroupId.Value ) )
                    .Select( a =>
                        a.PersonAlias.PersonId )
                    .Distinct();

                // Create a qry to get the last checkin date (used in next statement's join)
                var attendanceQry = attendanceService
                    .Queryable().AsNoTracking()
                    .Where( a =>
                        a.ScheduleId.HasValue &&
                        a.GroupId.HasValue &&
                        a.LocationId.HasValue &&
                        a.PersonAliasId.HasValue &&
                        a.DidAttend.Value &&
                        scheduleIds.Contains( a.ScheduleId.Value ) &&
                        groupIds.Contains( a.GroupId.Value ) )
                    .GroupBy( a => new
                    {
                        PersonId = a.PersonAlias.PersonId
                    } )
                    .Select( g => new PersonResult
                    {
                        Id = g.Key.PersonId,
                        Guid = Guid.Empty,
                        Name = "",
                        PhotoId = null,
                        LastCheckin = g.Max( a => a.StartDateTime ),
                        CheckedInNow = false,
                        ScheduleGroupNames = ""
                    } );

                // Do the person search
                bool reversed = false;
                var results = new PersonService( rockContext )
                    .GetByFullName( tbSearch.Text, false, false, false, out reversed )
                    .ToList()
                    .GroupJoin(
                        attendanceQry,
                        p => p.Id,
                        a => a.Id,
                        ( p, a ) => a
                            .Select( c =>
                                new PersonResult
                                {
                                    Id = p.Id,
                                    Guid = p.Guid,
                                    Name = ( reversed ?
                                        p.LastName + ", " + p.NickName :
                                        p.NickName + " " + p.LastName ),
                                    PhotoId = p.PhotoId,
                                    Age = p.Age.ToString() ?? "",
                                    LastCheckin = c.LastCheckin,
                                    CheckedInNow = currentAttendeeIds.Contains( p.Id ),
                                    ScheduleGroupNames = ""
                                } )
                            .DefaultIfEmpty(
                                new PersonResult
                                {
                                    Id = p.Id,
                                    Guid = p.Guid,
                                    Name = ( reversed ?
                                        p.LastName + ", " + p.NickName :
                                        p.NickName + " " + p.LastName ),
                                    PhotoId = p.PhotoId,
                                    Age = p.Age.ToString() ?? "",
                                    LastCheckin = null,
                                    CheckedInNow = false,
                                    ScheduleGroupNames = ""
                                } ) )
                    .SelectMany( a => a )
                    .Distinct()
                    .OrderByDescending( a => a.CheckedInNow )
                    .ThenByDescending( a => a.LastCheckin )
                    .ThenBy( a => a.Name )
                    .ToList();

                pnlNavHeading.Attributes["onClick"] = upnlContent.GetPostBackEventReference( CurrentNavPath );
                lNavHeading.Text = "Back";

                rptNavItems.Visible = false;

                rptPeople.Visible = true;
                rptPeople.DataSource = results;
                rptPeople.DataBind();
            }

            RegisterStartupScript();
        }
        protected void gReleventPeople_RowSelected( object sender, RowEventArgs e )
        {
            var attendance = new AttendanceService( rockContext ).Get( SelectedAttendanceGuid );
            var selectedPlegePersonAlias = new PersonAliasService( rockContext ).Get( ( Guid ) e.RowKeyValue );
            var workflowType = new WorkflowTypeService( rockContext ).Get( GetAttributeValue( "WorkflowType" ).AsGuid() );

            if ( attendance != null && selectedPlegePersonAlias != null )
                LaunchWorkflow( rockContext, attendance, selectedPlegePersonAlias, workflowType );
        }
Exemple #21
0
        /// <summary>
        /// Handles the ItemCommand event of the rptPeople control.
        /// </summary>
        /// <param name="source">The source of the event.</param>
        /// <param name="e">The <see cref="RepeaterCommandEventArgs"/> instance containing the event data.</param>
        protected void rptPeople_ItemCommand( object source, RepeaterCommandEventArgs e )
        {
            if ( e.CommandName == "Delete" )
            {
                int personId = e.CommandArgument.ToString().AsInteger();

                if ( string.IsNullOrWhiteSpace( CurrentNavPath ) || !CurrentNavPath.StartsWith( _configuredMode ) )
                {
                    CurrentNavPath = _configuredMode;
                }

                var pathParts = CurrentNavPath.Split( new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries );
                int partIndex = pathParts.Length - 1;

                if ( partIndex >= 0 )
                {
                    string itemKey = pathParts[partIndex];
                    if ( itemKey.Length > 0 )
                    {
                        string itemType = itemKey.Left( 1 );
                        int? itemId = itemKey.Length > 1 ? itemKey.Substring( 1 ).AsIntegerOrNull() : null;

                        if ( itemType == "L" && itemId.HasValue )
                        {
                            var dayStart = RockDateTime.Today;
                            var now = RockDateTime.Now;

                            using ( var rockContext = new RockContext() )
                            {
                                var activeSchedules = new List<int>();
                                foreach ( var schedule in new ScheduleService( rockContext )
                                    .Queryable().AsNoTracking()
                                    .Where( s => s.CheckInStartOffsetMinutes.HasValue ) )
                                {
                                    if ( schedule.IsScheduleOrCheckInActive )
                                    {
                                        activeSchedules.Add( schedule.Id );
                                    }
                                }

                                var attendanceService = new AttendanceService( rockContext );
                                foreach ( var attendance in attendanceService
                                    .Queryable()
                                    .Where( a =>
                                        a.StartDateTime > dayStart &&
                                        a.StartDateTime < now &&
                                        a.LocationId.HasValue &&
                                        a.LocationId.Value == itemId.Value &&
                                        a.PersonAlias != null &&
                                        a.PersonAlias.PersonId == personId &&
                                        a.DidAttend.HasValue &&
                                        a.DidAttend.Value &&
                                        a.ScheduleId.HasValue &&
                                        activeSchedules.Contains( a.ScheduleId.Value ) ) )
                                {
                                    attendanceService.Delete( attendance );
                                }

                                rockContext.SaveChanges();
                            }

                            int? campusId = CurrentCampusId.AsIntegerOrNull();
                            if ( campusId.HasValue )
                            {
                                NavData = GetNavigationData( CampusCache.Read( campusId.Value ) );
                            }
                            BuildNavigationControls();

                        }
                    }
                }
            }
        }
        /// <summary>
        /// Handles the Delete event of the gOccurrences control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RowEventArgs"/> instance containing the event data.</param>
        protected void gOccurrences_Delete( object sender, RowEventArgs e )
        {
            if ( _group != null )
            {
                var occurrenceDate = (DateTime)e.RowKeyValues["Date"];
                DateTime startDate = occurrenceDate.Date;
                DateTime endDate = occurrenceDate.Date.AddDays( 1 );

                using ( var rockContext = new RockContext() )
                {
                    var attendanceService = new AttendanceService( rockContext );
                    var qry = attendanceService.Queryable()
                        .Where( a =>
                            a.GroupId == _group.Id &&
                            a.StartDateTime >= startDate &&
                            a.StartDateTime < endDate );

                    int? scheduleId = e.RowKeyValues["ScheduleId"] as int?;
                    if ( scheduleId.HasValue )
                    {
                        qry = qry.Where( a =>
                            a.ScheduleId.HasValue &&
                            a.ScheduleId.Value == scheduleId.Value );
                    }
                    else
                    {
                        qry = qry.Where( a => !a.ScheduleId.HasValue );
                    }

                    int? locationId = e.RowKeyValues["LocationId"] as int?;
                    if ( locationId.HasValue )
                    {
                        qry = qry.Where( a =>
                            a.LocationId.HasValue &&
                            a.LocationId.Value == locationId.Value );
                    }
                    else
                    {
                        qry = qry.Where( a => !a.LocationId.HasValue );
                    }

                    foreach ( var attendance in qry )
                    {
                        attendanceService.Delete( attendance );
                    }

                    rockContext.SaveChanges();

                    if ( locationId.HasValue )
                    {
                        Rock.CheckIn.KioskLocationAttendance.Flush( locationId.Value );
                    }
                }
            }

            BindGrid();
        }
Exemple #23
0
        private void BuildNavigationControls()
        {
            if ( NavData != null )
            {
                if ( string.IsNullOrWhiteSpace( CurrentNavPath ) || !CurrentNavPath.StartsWith( _configuredMode ) )
                {
                    CurrentNavPath = _configuredMode;
                }

                var pathParts = CurrentNavPath.Split( new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries );
                int numParts = pathParts.Length;

                NavigationItem item = NavData.GetItem( pathParts[numParts-1] );
                while (item == null && numParts > 1)
                {
                    numParts--;
                    item = NavData.GetItem( pathParts[numParts-1] );
                }
                CurrentNavPath = pathParts.Take( numParts ).ToList().AsDelimited( "|" );

                string itemKey = pathParts[numParts - 1];
                string itemType = itemKey.Left( 1 );
                int? itemId = itemKey.Length > 1 ? itemKey.Substring( 1 ).AsIntegerOrNull() : null;

                SetUserPreference( "CurrentNavPath", CurrentNavPath );

                var navItems = new List<NavigationItem>();

                switch ( itemType )
                {
                    case "L":   // Location
                        {
                            // Add child locations
                            NavData.Locations
                                .Where( l => l.ParentId == itemId )
                                .ToList()
                                .ForEach( l => navItems.Add( l ) );

                            break;
                        }

                    case "T":   // Group Type
                        {
                            // Add child group types
                            NavData.GroupTypes
                                .Where( t => t.ParentId.Equals(itemId) )
                                .ToList().ForEach( t => navItems.Add( t ) );

                            // Add child groups
                            var groupIds = NavData.GroupTypes
                                .Where( t => t.Id == itemId )
                                .SelectMany( t => t.ChildGroupIds )
                                .Distinct()
                                .ToList();
                            NavData.Groups
                                .Where( g => groupIds.Contains( g.Id ) )
                                .ToList()
                                .ForEach( g => navItems.Add( g ) );

                            break;
                        }

                    case "G":   // Group
                        {
                            // Add child groups
                            var groupIds = NavData.Groups
                                .Where( g => g.Id == itemId )
                                .SelectMany( g => g.ChildGroupIds )
                                .Distinct()
                                .ToList();
                            NavData.Groups
                                .Where( g => groupIds.Contains( g.Id ) )
                                .ToList()
                                .ForEach( g => navItems.Add( g ) );

                            // Add child locations
                            var locations = NavData.Groups
                                .Where( g => g.Id == itemId )
                                .SelectMany( g => g.ChildLocationIds )
                                .Distinct()
                                .ToList();
                            NavData.Locations
                                .Where( l => locations.Contains( l.Id ) )
                                .ToList()
                                .ForEach( l => navItems.Add( l ) );

                            break;
                        }

                }

                // Get chart data
                Dictionary<DateTime, List<int>> chartCounts = null;
                if (item != null)
                {
                    chartCounts = item.RecentPersonIds;
                }
                else
                {
                    chartCounts = new Dictionary<DateTime, List<int>>();
                    foreach(var navItem in navItems)
                    {
                        foreach ( var kv in navItem.RecentPersonIds )
                        {
                            if ( !chartCounts.ContainsKey( kv.Key ) )
                            {
                                chartCounts.Add( kv.Key, new List<int>() );
                            }
                            chartCounts[kv.Key] = chartCounts[kv.Key].Union( kv.Value ).ToList();
                        }
                    }
                }

                var chartData = new List<string>();

                TimeSpan baseSpan = new TimeSpan( new DateTime(1970, 1, 1).Ticks );
                foreach ( var kv in chartCounts.OrderBy( c => c.Key ) )
                {
                    DateTime offsetTime = kv.Key.Subtract( baseSpan );
                    long ticks = (long)( offsetTime.Ticks / 10000 );
                    chartData.Add( string.Format( "[{0}, {1}]", ticks, kv.Value.Count() ) );
                }
                hfChartData.Value = string.Format( "[ [ {0} ] ]", chartData.AsDelimited( ", " ) );
                pnlChart.Attributes["onClick"] = upnlContent.GetPostBackEventReference("R");

                pnlNavHeading.Visible = item != null;
                if ( item != null )
                {
                    if ( numParts > 0 )
                    {
                        pnlNavHeading.Attributes["onClick"] = upnlContent.GetPostBackEventReference(
                            pathParts.ToList().Take( numParts - 1 ).ToList().AsDelimited( "|" ) );
                    }
                    lNavHeading.Text = item.Name;

                    var locationItem = item as NavigationLocation;
                    if (locationItem != null && locationItem.HasGroups)
                    {
                        tglHeadingRoom.Visible = true;
                        tglHeadingRoom.Checked = locationItem.IsActive;
                        tglHeadingRoom.Attributes["data-key"] = locationItem.Id.ToString();

                        var rockContext = new RockContext();
                        var activeSchedules = new List<int>();
                        foreach ( var schedule in new ScheduleService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( s => s.CheckInStartOffsetMinutes.HasValue ) )
                        {
                            if ( schedule.IsScheduleOrCheckInActive )
                            {
                                activeSchedules.Add( schedule.Id );
                            }
                        }

                        var dayStart = RockDateTime.Today;
                        var now = RockDateTime.Now;
                        var attendees = new AttendanceService( rockContext )
                            .Queryable( "Group,PersonAlias.Person,Schedule" )
                            .AsNoTracking()
                            .Where( a =>
                                a.StartDateTime > dayStart &&
                                a.StartDateTime < now &&
                                a.LocationId.HasValue &&
                                a.LocationId == locationItem.Id &&
                                a.DidAttend.HasValue &&
                                a.DidAttend.Value &&
                                a.ScheduleId.HasValue &&
                                activeSchedules.Contains( a.ScheduleId.Value ) )
                            .ToList();

                        var people = new List<PersonResult>();
                        foreach ( var personId in attendees
                            .OrderBy( a => a.PersonAlias.Person.NickName )
                            .ThenBy( a => a.PersonAlias.Person.LastName )
                            .Select( a => a.PersonAlias.PersonId )
                            .Distinct() )
                        {
                            var matchingAttendees = attendees
                                .Where( a => a.PersonAlias.PersonId == personId )
                                .ToList();

                            people.Add( new PersonResult( matchingAttendees ) );
                        }

                        rptPeople.Visible = true;
                        rptPeople.DataSource = people;
                        rptPeople.DataBind();
                    }
                    else
                    {
                        tglHeadingRoom.Visible = false;
                        rptPeople.Visible = false;
                    }
                }
                else
                {
                    rptPeople.Visible = false;
                }

                rptNavItems.Visible = navItems.Any();
                rptNavItems.DataSource = navItems
                    .OrderBy( i => i.TypeKey )
                    .ThenBy( i => i.Order )
                    .ThenBy( i => i.Name );
                rptNavItems.DataBind();

                RegisterStartupScript();

            }
        }
        /// <summary>
        /// Binds the filter.
        /// </summary>
        private void BindFilter()
        {
            drpDates.DelimitedValues = rFilter.GetUserPreference( "Date Range" );

            using ( var rockContext = new RockContext() )
            {
                if ( _person != null )
                {
                    ppPerson.Visible = false;

                    if ( _group == null )
                    {
                        ddlAttendanceGroup.Visible = true;
                        ddlAttendanceGroup.Items.Clear();
                        ddlAttendanceGroup.Items.Add( new ListItem() );

                        // only list groups that this person has attended before
                        var groupIdsAttended = new AttendanceService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( a =>
                                a.PersonAlias != null &&
                                a.PersonAlias.PersonId == _person.Id )
                            .Select( a => a.GroupId )
                            .Distinct()
                            .ToList();

                        foreach ( var group in new GroupService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( g => groupIdsAttended.Contains( g.Id ) )
                            .OrderBy( g => g.Name )
                            .Select( g => new { g.Name, g.Id } ).ToList() )
                        {
                            ddlAttendanceGroup.Items.Add( new ListItem( group.Name, group.Id.ToString() ) );
                        }

                        ddlAttendanceGroup.SetValue( rFilter.GetUserPreference( "Group" ).AsIntegerOrNull() );
                    }
                    else
                    {
                        ddlAttendanceGroup.Visible = false;
                    }
                }
                else
                {
                    ppPerson.Visible = true;
                    int? personId = rFilter.GetUserPreference( "Person" ).AsIntegerOrNull();
                    if ( personId.HasValue )
                    {
                        var person = new PersonService( rockContext ).Get( personId.Value );
                        ppPerson.SetValue( person );
                    }

                    ddlAttendanceGroup.Visible = false;
                }
            }

            spSchedule.SetValue( rFilter.GetUserPreference( "Schedule" ).AsIntegerOrNull() );

            string filterValue = rFilter.GetUserPreference( "Attended" );
            var filterAttendance = GetAttributeValue( "FilterAttendanceByDefault" ).AsBoolean();
            if ( string.IsNullOrEmpty( filterValue ) && filterAttendance )
            {
                filterValue = "1";
                rFilter.SaveUserPreference( "Attended", filterValue );
            }

            ddlDidAttend.SetValue( filterValue );
        }
Exemple #25
0
        private NavigationData GetNavigationData( CampusCache campus )
        {
            using ( var rockContext = new RockContext() )
            {
                var validLocationids = new List<int>();
                if ( campus.LocationId.HasValue )
                {
                    // Get all the child locations
                    validLocationids.Add( campus.LocationId.Value );
                    new LocationService( rockContext )
                        .GetAllDescendents( campus.LocationId.Value )
                        .Select( l => l.Id )
                        .ToList()
                        .ForEach( l => validLocationids.Add( l ) );
                }

                var groupTypeTemplateGuid = PageParameter( "Area" ).AsGuidOrNull();
                if ( !groupTypeTemplateGuid.HasValue )
                {
                    groupTypeTemplateGuid = this.GetAttributeValue( "GroupTypeTemplate" ).AsGuidOrNull();
                }

                if ( !groupTypeTemplateGuid.HasValue )
                {
                    // Check to see if a specific group was specified
                    Guid? guid = Rock.SystemGuid.DefinedValue.GROUPTYPE_PURPOSE_CHECKIN_TEMPLATE.AsGuid();
                    int? groupId = PageParameter( "Group" ).AsIntegerOrNull();
                    if ( groupId.HasValue && guid.HasValue )
                    {
                        var group = new GroupService( rockContext ).Get( groupId.Value );
                        if ( group != null && group.GroupType != null )
                        {
                            var groupType = GetParentPurposeGroupType( group.GroupType, guid.Value );
                            if ( groupType != null )
                            {
                                groupTypeTemplateGuid = groupType.Guid;
                            }
                        }
                    }
                }

                if ( groupTypeTemplateGuid.HasValue )
                {
                    pnlContent.Visible = true;

                    NavData = new NavigationData();

                    var chartTimes = GetChartTimes();

                    // Get the group types
                    var parentGroupType = GroupTypeCache.Read( groupTypeTemplateGuid.Value );
                    if ( parentGroupType != null )
                    {
                        foreach ( var childGroupType in parentGroupType.ChildGroupTypes )
                        {
                            AddGroupType( childGroupType, chartTimes );
                        }
                    }

                    // Get the groups
                    var groupTypeIds = NavData.GroupTypes.Select( t => t.Id ).ToList();

                    var groups = new GroupService( rockContext )
                        .Queryable( "GroupLocations" ).AsNoTracking()
                        .Where( g =>
                            groupTypeIds.Contains( g.GroupTypeId ) &&
                            g.IsActive )
                        .ToList();
                    var groupIds = groups.Select( g => g.Id ).ToList();

                    foreach( var group in groups )
                    {
                        var childGroupIds = groups
                            .Where( g =>
                                g.ParentGroupId.HasValue &&
                                g.ParentGroupId.Value == group.Id )
                            .Select( g => g.Id )
                            .ToList();

                        var childLocationIds = group.GroupLocations
                            .Where( l => validLocationids.Contains( l.LocationId ) )
                            .Select( l => l.LocationId )
                            .ToList();

                        if ( childLocationIds.Any() || childGroupIds.Any() )
                        {
                            var navGroup = new NavigationGroup( group, chartTimes );
                            navGroup.ChildLocationIds = childLocationIds;
                            navGroup.ChildGroupIds = childGroupIds;
                            NavData.Groups.Add( navGroup );

                            if ( !group.ParentGroupId.HasValue || groupIds.Contains( group.ParentGroupId.Value ) )
                            {
                                NavData.GroupTypes.Where( t => t.Id == group.GroupTypeId ).ToList()
                                    .ForEach( t => t.ChildGroupIds.Add( group.Id ) );
                            }
                        }
                    }

                    // Remove any groups without child locations
                    var emptyGroupIds = NavData.Groups
                        .Where( g => !g.ChildGroupIds.Any() && !g.ChildLocationIds.Any() )
                        .Select( g => g.Id )
                        .ToList();
                    while ( emptyGroupIds.Any() )
                    {
                        NavData.Groups = NavData.Groups.Where( g => !emptyGroupIds.Contains( g.Id ) ).ToList();
                        NavData.Groups.ForEach( g =>
                            g.ChildGroupIds = g.ChildGroupIds.Where( c => !emptyGroupIds.Contains( c ) ).ToList() );
                        emptyGroupIds = NavData.Groups
                            .Where( g => !g.ChildGroupIds.Any() && !g.ChildLocationIds.Any() )
                            .Select( g => g.Id )
                            .ToList();
                    }

                    // Remove any grouptype without groups
                    var emptyGroupTypeIds = NavData.GroupTypes
                        .Where( t => !t.ChildGroupIds.Any() && !t.ChildGroupTypeIds.Any() )
                        .Select( t => t.Id )
                        .ToList();
                    while ( emptyGroupTypeIds.Any() )
                    {
                        NavData.GroupTypes = NavData.GroupTypes.Where( t => !emptyGroupTypeIds.Contains( t.Id ) ).ToList();
                        NavData.GroupTypes.ForEach( t =>
                            t.ChildGroupTypeIds = t.ChildGroupTypeIds.Where( c => !emptyGroupTypeIds.Contains( c ) ).ToList() );
                        emptyGroupTypeIds = NavData.GroupTypes
                            .Where( t => !t.ChildGroupIds.Any() && !t.ChildGroupTypeIds.Any() )
                            .Select( t => t.Id )
                            .ToList();
                    }

                    // If not group types left, redirect to area select page
                    if ( NavData.GroupTypes.Count == 0 )
                    {
                        NavigateToLinkedPage( "AreaSelectPage" );
                    }

                    // Get the locations
                    var locationIds = NavData.Groups.SelectMany( g => g.ChildLocationIds ).Distinct().ToList();
                    foreach ( var location in new LocationService( rockContext )
                        .Queryable( "ParentLocation" ).AsNoTracking()
                        .Where( l => locationIds.Contains( l.Id ) ) )
                    {
                        var navLocation = AddLocation( location, chartTimes );
                        navLocation.HasGroups = true;
                    }

                    // Get the attendance counts
                    var dayStart = RockDateTime.Today;
                    var now = RockDateTime.Now;
                    groupIds = NavData.Groups.Select( g => g.Id ).ToList();

                    var attendances = new AttendanceService( rockContext ).Queryable()
                        .Where( a =>
                            a.ScheduleId.HasValue &&
                            a.GroupId.HasValue &&
                            a.LocationId.HasValue &&
                            a.StartDateTime > dayStart &&
                            a.StartDateTime < now &&
                            a.DidAttend.HasValue &&
                            a.DidAttend.Value &&
                            groupIds.Contains( a.GroupId.Value ) &&
                            locationIds.Contains( a.LocationId.Value ) )
                        .ToList();

                    var schedules = new ScheduleService( rockContext ).Queryable()
                            .Where( s => s.CheckInStartOffsetMinutes.HasValue )
                            .ToList();

                    foreach ( DateTime chartTime in chartTimes )
                    {
                        // Get the active schedules
                        var activeSchedules = new List<int>();
                        foreach ( var schedule in schedules )
                        {
                            if ( schedule.WasScheduleOrCheckInActive( chartTime ) )
                            {
                                activeSchedules.Add( schedule.Id );
                            }
                        }

                        bool current = chartTime.Equals( chartTimes.Max() );

                        foreach ( var groupLocSched in attendances
                            .Where( a =>
                                a.StartDateTime < chartTime &&
                                a.PersonAlias != null &&
                                activeSchedules.Contains( a.ScheduleId.Value ) )
                            .GroupBy( a => new
                            {
                                ScheduleId = a.ScheduleId.Value,
                                GroupId = a.GroupId.Value,
                                LocationId = a.LocationId.Value
                            } )
                            .Select( g => new
                            {
                                ScheduleId = g.Key.ScheduleId,
                                GroupId = g.Key.GroupId,
                                LocationId = g.Key.LocationId,
                                PersonIds = g.Select( a => a.PersonAlias.PersonId ).Distinct().ToList()
                            } ) )
                        {
                            AddGroupCount( chartTime, groupLocSched.GroupId, groupLocSched.PersonIds, current );
                            AddLocationCount( chartTime, groupLocSched.LocationId, groupLocSched.PersonIds, current );
                        }
                    }
                    return NavData;
                }
                else
                {
                    if ( string.IsNullOrWhiteSpace( PageParameter( "Area" ) ) )
                    {
                        // If could not determine area and did not come from are select, redirect to area select page
                        NavigateToLinkedPage( "AreaSelectPage" );
                    }

                    nbWarning.Text = "Please select a valid Check-in type in the block settings.";
                    nbWarning.Visible = true;
                    pnlContent.Visible = false;
                }
            }

            return null;
        }
        /// <summary>
        /// Executes the specified workflow.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="action">The workflow action.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="errorMessages">The error messages.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public override bool Execute( RockContext rockContext, Model.WorkflowAction action, Object entity, out List<string> errorMessages )
        {
            var checkInState = GetCheckInState( entity, out errorMessages );
            if ( checkInState != null && checkInState.CheckInType.TypeOfCheckin == TypeOfCheckin.Family )
            {
                bool configPrevents = checkInState.CheckInType.PreventDuplicateCheckin;

                var family = checkInState.CheckIn.CurrentFamily;

                if ( family != null )
                {
                    var remove = GetAttributeValue( action, "Remove" ).AsBoolean();

                    var personIds = family.People.Select( p => p.Person.Id ).ToList();
                    var today = RockDateTime.Today;
                    var tomorrow = RockDateTime.Today.AddDays( 1 );

                    var existingAttendance = new AttendanceService( rockContext )
                        .Queryable().AsNoTracking()
                        .Where( a =>
                            a.StartDateTime.CompareTo( today ) >= 0 &&
                            a.StartDateTime.CompareTo( tomorrow ) < 0 &&
                            a.DidAttend.HasValue &&
                            a.DidAttend.Value &&
                            a.PersonAlias != null &&
                            personIds.Contains( a.PersonAlias.PersonId ) &&
                            a.ScheduleId.HasValue )
                        .Select( a => new
                        {
                            PersonId = a.PersonAlias.PersonId,
                            ScheduleId = a.ScheduleId.Value
                        } )
                        .ToList();

                    if ( existingAttendance.Any() )
                    {
                        foreach ( var person in family.People.ToList() )
                        {
                            var attendedScheduleIds = existingAttendance.Where( a => a.PersonId == person.Person.Id ).Select( a => a.ScheduleId ).ToList();
                            if ( attendedScheduleIds.Any() )
                            {
                                foreach ( var groupType in person.GroupTypes.ToList() )
                                {
                                    if ( configPrevents || groupType.GroupType.GetAttributeValue( "PreventDuplicateCheckin" ).AsBoolean() )
                                    {
                                        attendedScheduleIds.ForEach( s => groupType.AvailableForSchedule.Remove( s ) );

                                        if ( !groupType.AvailableForSchedule.Any() )
                                        {
                                            person.GroupTypes.Remove( groupType );
                                        }
                                        else
                                        {
                                            foreach ( var group in groupType.Groups.ToList() )
                                            {
                                                attendedScheduleIds.ForEach( s => group.AvailableForSchedule.Remove( s ) );
                                                if ( !group.AvailableForSchedule.Any() )
                                                {
                                                    groupType.Groups.Remove( group );
                                                }
                                                else
                                                {
                                                    foreach ( var location in group.Locations.ToList() )
                                                    {
                                                        attendedScheduleIds.ForEach( s => location.AvailableForSchedule.Remove( s ) );
                                                        if ( !location.AvailableForSchedule.Any() )
                                                        {
                                                            group.Locations.Remove( location );
                                                        }
                                                    }
                                                    if ( group.Locations.Count == 0 )
                                                    {
                                                        groupType.Groups.Remove( group );
                                                    }
                                                }
                                            }
                                            if ( groupType.Groups.Count == 0 )
                                            {
                                                person.GroupTypes.Remove( groupType );
                                            }
                                        }
                                    }
                                }

                                if ( person.GroupTypes.Count == 0 )
                                {
                                    family.People.Remove( person );
                                }
                            }
                        }
                    }
                }

                return true;
            }

            return false;
        }
        /// <summary>
        /// Executes the specified workflow.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="action">The action.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="errorMessages">The error messages.</param>
        /// <returns></returns>
        public override bool Execute( RockContext rockContext, WorkflowAction action, Object entity, out List<string> errorMessages )
        {
            errorMessages = new List<string>();

            Guid? groupGuid = null;
            Person person = null;
            DateTime attendanceDateTime = DateTime.Now;
            bool addToGroup = true;

            // get the group attribute
            Guid groupAttributeGuid = GetAttributeValue(action, "Group").AsGuid();

            if ( !groupAttributeGuid.IsEmpty() )
            {
                groupGuid = action.GetWorklowAttributeValue(groupAttributeGuid).AsGuidOrNull();

                if ( !groupGuid.HasValue )
                {
                    errorMessages.Add("The group could not be found!");
                }
            }

            // get person alias guid
            Guid personAliasGuid = Guid.Empty;
            string personAttribute = GetAttributeValue( action, "Person" );

            Guid guid = personAttribute.AsGuid();
            if (!guid.IsEmpty())
            {
                var attribute = AttributeCache.Read( guid, rockContext );
                if ( attribute != null )
                {
                    string value = action.GetWorklowAttributeValue(guid);
                    personAliasGuid = value.AsGuid();
                }

                if ( personAliasGuid != Guid.Empty )
                {
                    person = new PersonAliasService(rockContext).Queryable().AsNoTracking()
                                    .Where(p => p.Guid.Equals(personAliasGuid))
                                    .Select(p => p.Person)
                                    .FirstOrDefault();
                }
                else {
                    errorMessages.Add("The person could not be found in the attribute!");
                }
            }

            // get attendance date
            Guid dateTimeAttributeGuid = GetAttributeValue(action, "AttendanceDatetime").AsGuid();
            if ( !dateTimeAttributeGuid.IsEmpty() )
            {
                string attributeDatetime = action.GetWorklowAttributeValue(dateTimeAttributeGuid);

                if ( !string.IsNullOrWhiteSpace(attributeDatetime) )
                {
                    if ( !DateTime.TryParse(attributeDatetime, out attendanceDateTime) )
                    {
                        errorMessages.Add(string.Format("Could not parse the date provided {0}.", attributeDatetime));
                    }
                }
            }

            // get add to group
            addToGroup = GetAttributeValue(action, "AddToGroup").AsBoolean();

            // get location
            Guid locationGuid = Guid.Empty;
            Guid locationAttributeGuid = GetAttributeValue(action, "Location").AsGuid();
            if ( !locationAttributeGuid.IsEmpty() )
            {
                var locationAttribute = AttributeCache.Read(locationAttributeGuid, rockContext);

                if ( locationAttribute != null )
                {
                    locationGuid = action.GetWorklowAttributeValue(locationAttributeGuid).AsGuid();
                }
            }

            //// get Schedule
            Guid scheduleGuid = Guid.Empty;
            Guid scheduleAttributeGuid = GetAttributeValue( action, "Schedule" ).AsGuid();
            if ( !scheduleAttributeGuid.IsEmpty() )
            {
                var scheduleAttribute = AttributeCache.Read( scheduleAttributeGuid, rockContext );
                if ( scheduleAttribute != null )
                {
                    scheduleGuid = action.GetWorklowAttributeValue( scheduleAttributeGuid ).AsGuid();
                }
            }

            // set attribute
            if ( groupGuid.HasValue && person != null && attendanceDateTime != DateTime.MinValue )
            {
                var group = new GroupService(rockContext).Queryable("GroupType.DefaultGroupRole")
                                            .Where(g => g.Guid == groupGuid)
                                            .FirstOrDefault();
                if ( group != null )
                {
                    GroupMemberService groupMemberService = new GroupMemberService(rockContext);

                    // get group member
                    var groupMember = groupMemberService.Queryable()
                                            .Where(m => m.Group.Guid == groupGuid
                                                && m.PersonId == person.Id)
                                            .FirstOrDefault();
                    if ( groupMember == null )
                    {
                        if ( addToGroup )
                        {

                            if ( group != null )
                            {
                                groupMember = new GroupMember();
                                groupMember.GroupId = group.Id;
                                groupMember.PersonId = person.Id;
                                groupMember.GroupMemberStatus = GroupMemberStatus.Active;
                                groupMember.GroupRole = group.GroupType.DefaultGroupRole;
                                groupMemberService.Add(groupMember);
                                rockContext.SaveChanges();
                            }
                        }
                        else
                        {
                            action.AddLogEntry(string.Format("{0} was not a member of the group {1} and the action was not configured to add them.", person.FullName, group.Name));
                        }
                    }

                    AttendanceService attendanceService = new AttendanceService(rockContext);

                    Attendance attendance = new Attendance();
                    attendance.GroupId = group.Id;
                    attendance.PersonAliasId = person.PrimaryAliasId;
                    attendance.StartDateTime = attendanceDateTime;
                    attendance.CampusId = group.CampusId;
                    attendance.DidAttend = true;

                    if ( locationGuid != Guid.Empty )
                    {
                        var location = new LocationService(rockContext).Queryable().AsNoTracking()
                                            .Where(l => l.Guid == locationGuid)
                                            .FirstOrDefault();

                        if ( location != null )
                        {
                            attendance.LocationId = location.Id;
                        }
                    }

                    attendanceService.Add(attendance);
                    rockContext.SaveChanges();

                    if ( attendance.LocationId.HasValue )
                    {
                        Rock.CheckIn.KioskLocationAttendance.Flush( attendance.LocationId.Value );
                    }
                }
                else
                {
                    errorMessages.Add(string.Format("Could not find group matching the guid '{0}'.", groupGuid));
                }
            }

            errorMessages.ForEach( m => action.AddLogEntry( m, true ) );

            return true;
        }
        /// <summary>
        /// Binds the attendees grid.
        /// </summary>
        private void BindAttendeesGrid()
        {
            var dateRange = SlidingDateRangePicker.CalculateDateRangeFromDelimitedValues( drpSlidingDateRange.DelimitedValues );
            if ( dateRange.End == null || dateRange.End > RockDateTime.Now )
            {
                dateRange.End = RockDateTime.Now;
            }

            var rockContext = new RockContext();

            // make a qryPersonAlias so that the generated SQL will be a "WHERE .. IN ()" instead of an OUTER JOIN (which is incredibly slow for this)
            var qryPersonAlias = new PersonAliasService( rockContext ).Queryable();

            var qryAttendance = new AttendanceService( rockContext ).Queryable();

            qryAttendance = qryAttendance.Where( a => a.DidAttend.HasValue && a.DidAttend.Value );
            var groupType = this.GetSelectedTemplateGroupType();
            var qryAllVisits = qryAttendance;
            if ( groupType != null )
            {
                var childGroupTypeIds = new GroupTypeService( rockContext ).GetChildGroupTypes( groupType.Id ).Select( a => a.Id );
                qryAllVisits = qryAttendance.Where( a => childGroupTypeIds.Any( b => b == a.Group.GroupTypeId ) );
            }
            else
            {
                return;
            }

            var groupIdList = new List<int>();
            string groupIds = GetSelectedGroupIds().AsDelimited( "," );
            if ( !string.IsNullOrWhiteSpace( groupIds ) )
            {
                groupIdList = groupIds.Split( ',' ).AsIntegerList();
                qryAttendance = qryAttendance.Where( a => a.GroupId.HasValue && groupIdList.Contains( a.GroupId.Value ) );
            }

            //// If campuses were included, filter attendances by those that have selected campuses
            //// if 'null' is one of the campuses, treat that as a 'CampusId is Null'
            var includeNullCampus = clbCampuses.SelectedValues.Any( a => a.Equals( "null", StringComparison.OrdinalIgnoreCase ) );
            var campusIdList = clbCampuses.SelectedValues.AsIntegerList();

            // remove 0 from the list, just in case it is there
            campusIdList.Remove( 0 );

            if ( campusIdList.Any() )
            {
                if ( includeNullCampus )
                {
                    // show records that have a campusId in the campusIdsList + records that have a null campusId
                    qryAttendance = qryAttendance.Where( a => ( a.CampusId.HasValue && campusIdList.Contains( a.CampusId.Value ) ) || !a.CampusId.HasValue );
                }
                else
                {
                    // only show records that have a campusId in the campusIdList
                    qryAttendance = qryAttendance.Where( a => a.CampusId.HasValue && campusIdList.Contains( a.CampusId.Value ) );
                }
            }
            else if ( includeNullCampus )
            {
                // 'null' was the only campusId in the campusIds parameter, so only show records that have a null CampusId
                qryAttendance = qryAttendance.Where( a => !a.CampusId.HasValue );
            }

            // have the "Missed" query be the same as the qry before the Main date range is applied since it'll have a different date range
            var qryMissed = qryAttendance;

            if ( dateRange.Start.HasValue )
            {
                qryAttendance = qryAttendance.Where( a => a.StartDateTime >= dateRange.Start.Value );
            }

            if ( dateRange.End.HasValue )
            {
                qryAttendance = qryAttendance.Where( a => a.StartDateTime < dateRange.End.Value );
            }

            // we want to get the first 2 visits at a minimum so we can show the date in the grid
            int nthVisitsTake = 2;
            int? byNthVisit = null;

            if ( radByVisit.Checked )
            {
                // If we are filtering by nth visit, we might want to get up to first 5
                byNthVisit = ddlNthVisit.SelectedValue.AsIntegerOrNull();
                if ( byNthVisit.HasValue && byNthVisit > 2 )
                {
                    nthVisitsTake = byNthVisit.Value;
                }
            }

            ChartGroupBy groupBy = hfGroupBy.Value.ConvertToEnumOrNull<ChartGroupBy>() ?? ChartGroupBy.Week;

            IQueryable<PersonWithSummary> qryByPersonWithSummary = null;

            if ( byNthVisit.HasValue && byNthVisit.Value == 0 )
            {
                // Show members of the selected groups that did not attend at all during selected date range

                // Get all the person ids that did attend
                var attendeePersonIds = qryAttendance.Select( a => a.PersonAlias.PersonId );

                // Get all the active members of the selected groups who have no attendance within selected date range and campus
                qryByPersonWithSummary = new GroupMemberService( rockContext )
                    .Queryable().AsNoTracking()
                    .Where( m =>
                        groupIdList.Contains( m.GroupId ) &&
                        !attendeePersonIds.Contains( m.PersonId ) &&
                        m.GroupMemberStatus == GroupMemberStatus.Active )
                    .Select( m => new PersonWithSummary
                    {
                        PersonId = m.PersonId,
                        FirstVisits = new DateTime[] { }.AsQueryable(),
                        LastVisit = new AttendancePersonAlias(),
                        AttendanceSummary = new DateTime[] { }.AsQueryable()
                    } );
            }
            else
            {
                var qryAttendanceWithSummaryDateTime = qryAttendance.GetAttendanceWithSummaryDateTime( groupBy );
                var qryGroup = new GroupService( rockContext ).Queryable();

                var qryJoinPerson = qryAttendance.Join(
                    qryPersonAlias,
                    k1 => k1.PersonAliasId,
                    k2 => k2.Id,
                    ( a, pa ) => new
                    {
                        CampusId = a.CampusId,
                        GroupId = a.GroupId,
                        ScheduleId = a.ScheduleId,
                        StartDateTime = a.StartDateTime,
                        PersonAliasId = pa.Id,
                        PersonAliasPersonId = pa.PersonId
                    } );

                var qryJoinFinal = qryJoinPerson.Join(
                    qryGroup,
                    k1 => k1.GroupId,
                    k2 => k2.Id,
                    ( a, g ) => new AttendancePersonAlias
                    {
                        CampusId = a.CampusId,
                        GroupId = a.GroupId,
                        GroupName = g.Name,
                        ScheduleId = a.ScheduleId,
                        StartDateTime = a.StartDateTime,
                        PersonAliasId = a.PersonAliasId,
                        PersonAliasPersonId = a.PersonAliasPersonId
                    } );

                var qryByPerson = qryJoinFinal.GroupBy( a => a.PersonAliasPersonId ).Select( a => new
                {
                    PersonId = a.Key,
                    Attendances = a
                } );

                int? attendedMinCount = null;
                int? attendedMissedCount = null;
                DateRange attendedMissedDateRange = new DateRange();
                if ( radByPattern.Checked )
                {
                    attendedMinCount = tbPatternXTimes.Text.AsIntegerOrNull();
                    if ( cbPatternAndMissed.Checked )
                    {
                        attendedMissedCount = tbPatternMissedXTimes.Text.AsIntegerOrNull();
                        attendedMissedDateRange = new DateRange( drpPatternDateRange.LowerValue, drpPatternDateRange.UpperValue );
                        if ( !attendedMissedDateRange.Start.HasValue || !attendedMissedDateRange.End.HasValue )
                        {
                            nbMissedDateRangeRequired.Visible = true;
                            return;
                        }
                    }
                }

                nbMissedDateRangeRequired.Visible = false;

                // get either the first 2 visits or the first 5 visits (using a const take of 2 or 5 vs a variable to help the SQL optimizer)
                qryByPersonWithSummary = qryByPerson.Select( a => new PersonWithSummary
                {
                    PersonId = a.PersonId,
                    FirstVisits = qryAllVisits.Where( b => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == b.PersonAliasId ) ).Select( s => s.StartDateTime ).OrderBy( x => x ).Take( 2 ),
                    LastVisit = a.Attendances.OrderByDescending( x => x.StartDateTime ).FirstOrDefault(),
                    AttendanceSummary = qryAttendanceWithSummaryDateTime.Where( x => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == x.Attendance.PersonAliasId ) ).GroupBy( g => g.SummaryDateTime ).Select( s => s.Key )
                } );

                if ( nthVisitsTake > 2 )
                {
                    qryByPersonWithSummary = qryByPerson.Select( a => new PersonWithSummary
                    {
                        PersonId = a.PersonId,
                        FirstVisits = qryAllVisits.Where( b => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == b.PersonAliasId ) ).Select( s => s.StartDateTime ).OrderBy( x => x ).Take( 5 ),
                        LastVisit = a.Attendances.OrderByDescending( x => x.StartDateTime ).FirstOrDefault(),
                        AttendanceSummary = qryAttendanceWithSummaryDateTime.Where( x => qryPersonAlias.Where( pa => pa.PersonId == a.PersonId ).Any( pa => pa.Id == x.Attendance.PersonAliasId ) ).GroupBy( g => g.SummaryDateTime ).Select( s => s.Key )
                    } );
                }

                if ( byNthVisit.HasValue )
                {
                    // only return attendees where their nth visit is within the selected daterange
                    int skipCount = byNthVisit.Value - 1;
                    qryByPersonWithSummary = qryByPersonWithSummary.Where( a => a.FirstVisits.OrderBy( x => x ).Skip( skipCount ).Take( 1 ).Any( d => d >= dateRange.Start && d < dateRange.End ) );
                }

                if ( attendedMinCount.HasValue )
                {
                    qryByPersonWithSummary = qryByPersonWithSummary.Where( a => a.AttendanceSummary.Count() >= attendedMinCount );
                }

                if ( attendedMissedCount.HasValue )
                {
                    if ( attendedMissedDateRange.Start.HasValue && attendedMissedDateRange.End.HasValue )
                    {
                        var attendedMissedPossible = GetPossibleAttendancesForDateRange( attendedMissedDateRange, groupBy );
                        int attendedMissedPossibleCount = attendedMissedPossible.Count();

                        qryMissed = qryMissed.Where( a => a.StartDateTime >= attendedMissedDateRange.Start.Value && a.StartDateTime < attendedMissedDateRange.End.Value );
                        var qryMissedAttendanceByPersonAndSummary = qryMissed.GetAttendanceWithSummaryDateTime( groupBy )
                            .GroupBy( g1 => new { g1.SummaryDateTime, g1.Attendance.PersonAlias.PersonId } )
                            .GroupBy( a => a.Key.PersonId )
                            .Select( a => new
                            {
                                PersonId = a.Key,
                                AttendanceCount = a.Count()
                            } );

                        var qryMissedByPerson = qryMissedAttendanceByPersonAndSummary
                            .Where( x => ( attendedMissedPossibleCount - x.AttendanceCount ) >= attendedMissedCount );

                        // filter to only people that missed at least X weeks/months/years between specified missed date range
                        qryByPersonWithSummary = qryByPersonWithSummary.Where( a => qryMissedByPerson.Any( b => b.PersonId == a.PersonId ) );
                    }
                }
            }

            var personService = new PersonService( rockContext );

            // Filter by dataview
            var dataViewId = dvpDataView.SelectedValueAsInt();
            if ( dataViewId.HasValue )
            {
                var dataView = new DataViewService( _rockContext ).Get( dataViewId.Value );
                if ( dataView != null )
                {
                    var errorMessages = new List<string>();
                    ParameterExpression paramExpression = personService.ParameterExpression;
                    Expression whereExpression = dataView.GetExpression( personService, paramExpression, out errorMessages );

                    SortProperty sort = null;
                    var dataViewPersonIdQry = personService
                        .Queryable().AsNoTracking()
                        .Where( paramExpression, whereExpression, sort )
                        .Select( p => p.Id );

                    qryByPersonWithSummary = qryByPersonWithSummary.Where( a => dataViewPersonIdQry.Contains( a.PersonId ) );
                }
            }

            // declare the qryResult that we'll use in case they didn't choose IncludeParents or IncludeChildren (and the Anonymous Type will also work if we do include parents or children)
            var qryPerson = personService.Queryable();

            var qryResult = qryByPersonWithSummary.Join(
                    qryPerson,
                    a => a.PersonId,
                    p => p.Id,
                    ( a, p ) => new
                        {
                            a.PersonId,
                            ParentId = (int?)null,
                            ChildId = (int?)null,
                            Person = p,
                            Parent = (Person)null,
                            Child = (Person)null,
                            a.FirstVisits,
                            a.LastVisit,
                            p.PhoneNumbers,
                            a.AttendanceSummary
                        } );

            var includeParents = hfViewBy.Value.ConvertToEnumOrNull<ViewBy>().GetValueOrDefault( ViewBy.Attendees ) == ViewBy.ParentsOfAttendees;
            var includeChildren = hfViewBy.Value.ConvertToEnumOrNull<ViewBy>().GetValueOrDefault( ViewBy.Attendees ) == ViewBy.ChildrenOfAttendees;

            // if Including Parents, join with qryChildWithParent instead of qryPerson
            if ( includeParents )
            {
                var qryChildWithParent = new PersonService( rockContext ).GetChildWithParent();
                qryResult = qryByPersonWithSummary.Join(
                    qryChildWithParent,
                    a => a.PersonId,
                    p => p.Child.Id,
                    ( a, p ) => new
                    {
                        a.PersonId,
                        ParentId = (int?)p.Parent.Id,
                        ChildId = (int?)null,
                        Person = p.Child,
                        Parent = p.Parent,
                        Child = (Person)null,
                        a.FirstVisits,
                        a.LastVisit,
                        p.Parent.PhoneNumbers,
                        a.AttendanceSummary
                    } );
            }

            if ( includeChildren )
            {
                var qryParentWithChildren = new PersonService( rockContext ).GetParentWithChild();
                qryResult = qryByPersonWithSummary.Join(
                    qryParentWithChildren,
                    a => a.PersonId,
                    p => p.Parent.Id,
                    ( a, p ) => new
                    {
                        a.PersonId,
                        ParentId = (int?)null,
                        ChildId = (int?)p.Child.Id,
                        Person = p.Parent,
                        Parent = (Person)null,
                        Child = p.Child,
                        a.FirstVisits,
                        a.LastVisit,
                        p.Child.PhoneNumbers,
                        a.AttendanceSummary
                    } );
            }

            var parentField = gAttendeesAttendance.Columns.OfType<PersonField>().FirstOrDefault( a => a.HeaderText == "Parent" );
            if ( parentField != null )
            {
                parentField.Visible = includeParents;
            }

            var parentEmailField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Parent Email" );
            if ( parentEmailField != null )
            {
                parentEmailField.ExcelExportBehavior = includeParents ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude;
            }

            var childField = gAttendeesAttendance.Columns.OfType<PersonField>().FirstOrDefault( a => a.HeaderText == "Child" );
            if ( childField != null )
            {
                childField.Visible = includeChildren;
            }

            var childEmailField = gAttendeesAttendance.Columns.OfType<RockBoundField>().FirstOrDefault( a => a.HeaderText == "Child Email" );
            if ( childEmailField != null )
            {
                childEmailField.ExcelExportBehavior = includeChildren ? ExcelExportBehavior.AlwaysInclude : ExcelExportBehavior.NeverInclude;
            }

            SortProperty sortProperty = gAttendeesAttendance.SortProperty;

            if ( sortProperty != null )
            {
                if ( sortProperty.Property == "AttendanceSummary.Count" )
                {
                    if ( sortProperty.Direction == SortDirection.Descending )
                    {
                        qryResult = qryResult.OrderByDescending( a => a.AttendanceSummary.Count() );
                    }
                    else
                    {
                        qryResult = qryResult.OrderBy( a => a.AttendanceSummary.Count() );
                    }
                }
                else if ( sortProperty.Property == "FirstVisit.StartDateTime" )
                {
                    if ( sortProperty.Direction == SortDirection.Descending )
                    {
                        qryResult = qryResult.OrderByDescending( a => a.FirstVisits.Min() );
                    }
                    else
                    {
                        qryResult = qryResult.OrderBy( a => a.FirstVisits.Min() );
                    }
                }
                else
                {
                    qryResult = qryResult.Sort( sortProperty );
                }
            }
            else
            {
                qryResult = qryResult.OrderBy( a => a.Person.LastName ).ThenBy( a => a.Person.NickName );
            }

            var attendancePercentField = gAttendeesAttendance.Columns.OfType<RockTemplateField>().First( a => a.HeaderText.EndsWith( "Attendance %" ) );
            attendancePercentField.HeaderText = string.Format( "{0}ly Attendance %", groupBy.ConvertToString() );

            // Calculate all the possible attendance summary dates
            UpdatePossibleAttendances( dateRange, groupBy );

            // pre-load the schedule names since FriendlyScheduleText requires building the ICal object, etc
            _scheduleNameLookup = new ScheduleService( rockContext ).Queryable()
                .ToList()
                .ToDictionary( k => k.Id, v => v.FriendlyScheduleText );

            if ( includeParents )
            {
                gAttendeesAttendance.PersonIdField = "ParentId";
                gAttendeesAttendance.DataKeyNames = new string[] { "ParentId", "PersonId" };
            }
            else if ( includeChildren )
            {
                gAttendeesAttendance.PersonIdField = "ChildId";
                gAttendeesAttendance.DataKeyNames = new string[] { "ChildId", "PersonId" };
            }
            else
            {
                gAttendeesAttendance.PersonIdField = "PersonId";
                gAttendeesAttendance.DataKeyNames = new string[] { "PersonId" };
            }

            // Create the dynamic attendance grid columns as needed
            CreateDynamicAttendanceGridColumns();

            try
            {
                nbAttendeesError.Visible = false;

                // increase the timeout from 30 to 90. The Query can be slow if SQL hasn't calculated the Query Plan for the query yet.
                // Sometimes, most of the time consumption is figuring out the Query Plan, but after it figures it out, it caches it so that the next time it'll be much faster
                rockContext.Database.CommandTimeout = 90;
                gAttendeesAttendance.SetLinqDataSource( qryResult.AsNoTracking() );

                gAttendeesAttendance.DataBind();
            }
            catch ( Exception exception )
            {
                LogAndShowException( exception );
            }
        }
        /// <summary>
        /// Gets the expression.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="entityIdProperty">The entity identifier property.</param>
        /// <param name="selection">The selection.</param>
        /// <returns></returns>
        public override Expression GetExpression( RockContext context, MemberExpression entityIdProperty, string selection )
        {
            Guid groupTypeGuid = selection.AsGuid();

            if ( groupTypeGuid != Guid.Empty )
            {
                AttendanceService attendanceService = new AttendanceService( context );
                var groupAttendanceQry = attendanceService.Queryable().Where( a => a.Group.GroupType.Guid == groupTypeGuid);

                var qry = new PersonService( context ).Queryable()
                    .Select( p => groupAttendanceQry.Where( xx => xx.PersonAlias.PersonId == p.Id && xx.DidAttend == true ).Max( xx => xx.StartDateTime ));

                Expression selectExpression = SelectExpressionExtractor.Extract( qry, entityIdProperty, "p" );

                return selectExpression;
            }

            return null;
        }
        private void ShowDetail(Guid personGuid)
        {
            using ( var rockContext = new RockContext() )
            {
                var personService = new PersonService( new RockContext() );

                var person = personService.Get( personGuid );

                if ( person != null )
                {
                    lName.Text = person.FullName;

                    string photoTag = Rock.Model.Person.GetPhotoImageTag( person, 120, 120 );
                    if ( person.PhotoId.HasValue )
                    {
                        lPhoto.Text = string.Format( "<a href='{0}'>{1}</a>", person.PhotoUrl, photoTag );
                    }
                    else
                    {
                        lPhoto.Text = photoTag;
                    }

                    lEmail.Visible = !string.IsNullOrWhiteSpace( person.Email );
                    lEmail.Text = person.GetEmailTag( ResolveRockUrl( "/" ), "btn btn-default", "<i class='fa fa-envelope'></i>" );

                    var childGuid = Rock.SystemGuid.GroupRole.GROUPROLE_FAMILY_MEMBER_CHILD.AsGuid();
                    var isFamilyChild = new Dictionary<int, bool>();

                    var allFamilyMembers = person.GetFamilyMembers( true ).ToList();
                    allFamilyMembers.Where( m => m.PersonId == person.Id ).ToList().ForEach(
                        m => isFamilyChild.Add( m.GroupId, m.GroupRole.Guid.Equals( childGuid ) ) );

                    string urlRoot = Request.Url.ToString().ReplaceCaseInsensitive( personGuid.ToString(), "" );

                    var familyMembers = allFamilyMembers.Where( m => m.PersonId != person.Id )
                        .OrderBy( m => m.GroupId )
                        .ThenBy( m => m.Person.BirthDate )
                        .Select( m => new
                        {
                            Url = urlRoot + m.Person.Guid.ToString(),
                            FullName = m.Person.FullName,
                            Gender = m.Person.Gender,
                            FamilyRole = m.GroupRole,
                            Note = isFamilyChild[m.GroupId] ?
                                ( m.GroupRole.Guid.Equals( childGuid ) ? " (Sibling)" : "(Parent)" ) :
                                ( m.GroupRole.Guid.Equals( childGuid ) ? " (Child)" : "" )
                        } )
                        .ToList();

                    rcwFamily.Visible = familyMembers.Any();
                    rptrFamily.DataSource = familyMembers;
                    rptrFamily.DataBind();

                    rptrPhones.DataSource = person.PhoneNumbers.Where( p => !p.IsUnlisted ).ToList();
                    rptrPhones.DataBind();

                    var schedules = new ScheduleService( rockContext ).Queryable()
                            .Where( s => s.CheckInStartOffsetMinutes.HasValue )
                            .ToList();

                    var scheduleIds = schedules.Select( s => s.Id ).ToList();

                    var activeScheduleIds = new List<int>();
                    foreach ( var schedule in schedules )
                    {
                        if ( schedule.IsScheduleOrCheckInActive )
                        {
                            activeScheduleIds.Add( schedule.Id );
                        }
                    }

                    int? personAliasId = person.PrimaryAliasId;
                    if ( !personAliasId.HasValue )
                    {
                        personAliasId = new PersonAliasService( rockContext ).GetPrimaryAliasId( person.Id );
                    }

                    var attendances = new AttendanceService( rockContext )
                        .Queryable( "Schedule,Group,Location" )
                        .Where( a =>
                            a.PersonAliasId.HasValue &&
                            a.PersonAliasId == personAliasId &&
                            a.ScheduleId.HasValue &&
                            a.GroupId.HasValue &&
                            a.LocationId.HasValue &&
                            a.DidAttend.HasValue &&
                            a.DidAttend.Value &&
                            scheduleIds.Contains( a.ScheduleId.Value ) )
                        .OrderByDescending( a => a.StartDateTime )
                        .Take( 20 )
                        .Select( a => new AttendanceInfo
                        {
                            Date = a.StartDateTime,
                            GroupId = a.Group.Id,
                            Group = a.Group.Name,
                            LocationId = a.LocationId.Value,
                            Location = a.Location.Name,
                            Schedule = a.Schedule.Name,
                            IsActive =
                                a.StartDateTime > DateTime.Today &&
                                activeScheduleIds.Contains( a.ScheduleId.Value )
                        } ).ToList();

                    // Set active locations to be a link to the room in manager page
                    var qryParam = new Dictionary<string, string>();
                    qryParam.Add( "Group", "" );
                    qryParam.Add( "Location", "" );
                    foreach ( var attendance in attendances.Where( a => a.IsActive ) )
                    {
                        qryParam["Group"] = attendance.GroupId.ToString();
                        qryParam["Location"] = attendance.LocationId.ToString();
                        attendance.Location = string.Format( "<a href='{0}'>{1}</a>",
                            LinkedPageUrl( "ManagerPage", qryParam ), attendance.Location );
                    }

                    rcwCheckinHistory.Visible = attendances.Any();
                    gHistory.DataSource = attendances;
                    gHistory.DataBind();
                }
            }
        }
        /// <summary>
        /// Binds the filter.
        /// </summary>
        private void BindFilter()
        {
            drpDates.DelimitedValues = rFilter.GetUserPreference( "Date Range" );

            var attendanceService = new AttendanceService( new RockContext() );
            var attendanceQuery = attendanceService.Queryable();
            if ( _person != null )
            {
                attendanceQuery = attendanceQuery.Where( a => a.PersonId == _person.Id );

                ddlGroups.DataSource = attendanceQuery.Select( a => a.Group ).Distinct().OrderBy( a => a.Name ).ToList();
                ddlGroups.DataBind();
                ddlGroups.Items.Insert( 0, Rock.Constants.All.ListItem );
                ddlGroups.Visible = attendanceQuery.Select( a => a.Group ).Distinct().ToList().Any();
                ddlGroups.SetValue( rFilter.GetUserPreference( "Group" ) );

                ddlPeople.Visible = false;
            }

            if ( _group != null )
            {
                attendanceQuery = attendanceQuery.Where( a => a.GroupId == _group.Id );
                var attendanceList = attendanceQuery.ToList();

                ddlPeople.DataSource = attendanceList.OrderBy( a => a.Person.FullName ).Select( a => a.Person.FullName ).Distinct();
                ddlPeople.DataBind();
                ddlPeople.Items.Insert( 0, Rock.Constants.All.ListItem );
                ddlPeople.Visible = attendanceList.Select( a => a.Person.FullName ).Distinct().Any();
                ddlPeople.SetValue( rFilter.GetUserPreference( "Person" ) );

                ddlGroups.Visible = false;
            }

            ddlSchedules.DataSource = attendanceQuery.OrderBy( a => a.Schedule.Name ).Select( a => a.Schedule.Name ).Distinct().ToList();
            ddlSchedules.DataBind();
            ddlSchedules.Items.Insert( 0, Rock.Constants.All.ListItem );
            ddlSchedules.Visible = attendanceQuery.Select( a => a.Schedule.Name ).Distinct().ToList().Any();
            ddlSchedules.SetValue( rFilter.GetUserPreference( "Schedule" ) );
        }
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public virtual void Execute( IJobExecutionContext context )
        {
            JobDataMap dataMap = context.JobDetail.JobDataMap;
            var groupType = GroupTypeCache.Read( dataMap.GetString( "GroupType" ).AsGuid() );
            int attendanceRemindersSent = 0;
            if ( groupType.TakesAttendance && groupType.SendAttendanceReminder )
            {

                // Get the occurrence dates that apply
                var dates = new List<DateTime>();
                dates.Add( RockDateTime.Today );
                try
                {
                    string[] reminderDays = dataMap.GetString( "SendReminders" ).Split( ',' );
                    foreach ( string reminderDay in reminderDays )
                    {
                        if ( reminderDay.Trim() != string.Empty )
                        {
                            var reminderDate = RockDateTime.Today.AddDays( 0 - Convert.ToInt32( reminderDay ) );
                            if ( !dates.Contains( reminderDate ) )
                            {
                                dates.Add( reminderDate );
                            }
                        }
                    }
                }
                catch { }

                var rockContext = new RockContext();
                var groupService = new GroupService( rockContext );
                var groupMemberService = new GroupMemberService( rockContext );
                var scheduleService = new ScheduleService( rockContext );
                var attendanceService = new AttendanceService( rockContext );

                var startDate = dates.Min();
                var endDate = dates.Max().AddDays( 1 );

                // Find all 'occurrences' for the groups that occur on the affected dates
                var occurrences = new Dictionary<int, List<DateTime>>();
                foreach ( var group in groupService
                    .Queryable( "Schedule" ).AsNoTracking()
                    .Where( g =>
                        g.GroupTypeId == groupType.Id &&
                        g.IsActive &&
                        g.Schedule != null &&
                        g.Members.Any( m =>
                            m.GroupMemberStatus == GroupMemberStatus.Active &&
                            m.GroupRole.IsLeader &&
                            m.Person.Email != null &&
                            m.Person.Email != "" ) ) )
                {
                    // Add the group
                    occurrences.Add( group.Id, new List<DateTime>() );

                    // Check for a iCal schedule
                    if ( !string.IsNullOrWhiteSpace( group.Schedule.iCalendarContent ) )
                    {
                        // If schedule has an iCal schedule, get occurrences between first and last dates
                        foreach ( var occurrence in group.Schedule.GetOccurrences( startDate, endDate ) )
                        {
                            var startTime = occurrence.Period.StartTime.Value;
                            if ( dates.Contains( startTime.Date ) )
                            {
                                occurrences[group.Id].Add( startTime );
                            }
                        }
                    }
                    else
                    {
                        // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week
                        if ( group.Schedule.WeeklyDayOfWeek.HasValue )
                        {
                            foreach ( var date in dates )
                            {
                                if ( date.DayOfWeek == group.Schedule.WeeklyDayOfWeek.Value )
                                {
                                    var startTime = date;
                                    if ( group.Schedule.WeeklyTimeOfDay.HasValue )
                                    {
                                        startTime = startTime.Add( group.Schedule.WeeklyTimeOfDay.Value );
                                    }
                                    occurrences[group.Id].Add( startTime );
                                }
                            }
                        }
                    }
                }

                // Remove any occurrences during group type exclusion date ranges
                foreach ( var exclusion in groupType.GroupScheduleExclusions )
                {
                    if ( exclusion.Start.HasValue && exclusion.End.HasValue )
                    {
                        foreach ( var keyVal in occurrences )
                        {
                            foreach ( var occurrenceDate in keyVal.Value.ToList() )
                            {
                                if ( occurrenceDate >= exclusion.Start.Value &&
                                    occurrenceDate < exclusion.End.Value.AddDays( 1 ) )
                                {
                                    keyVal.Value.Remove( occurrenceDate );
                                }
                            }
                        }
                    }
                }

                // Remove any 'occurrenes' that already have attendance data entered
                foreach ( var occurrence in attendanceService
                    .Queryable().AsNoTracking()
                    .Where( a =>
                        a.StartDateTime >= startDate &&
                        a.StartDateTime < endDate &&
                        occurrences.Keys.Contains( a.GroupId.Value ) &&
                        a.ScheduleId.HasValue )
                    .Select( a => new
                    {
                        GroupId = a.GroupId.Value,
                        a.StartDateTime
                    } )
                    .Distinct()
                    .ToList() )
                {
                    occurrences[occurrence.GroupId].RemoveAll( d => d.Date == occurrence.StartDateTime.Date );
                }

                // Get the groups that have occurrences
                var groupIds = occurrences.Where( o => o.Value.Any() ).Select( o => o.Key ).ToList();

                // Get the leaders of those groups
                var leaders = groupMemberService
                    .Queryable( "Group,Person" ).AsNoTracking()
                    .Where( m =>
                        groupIds.Contains( m.GroupId ) &&
                        m.GroupMemberStatus == GroupMemberStatus.Active &&
                        m.GroupRole.IsLeader &&
                        m.Person.Email != null &&
                        m.Person.Email != "" )
                    .ToList();

                // Loop through the leaders
                foreach ( var leader in leaders )
                {
                    foreach ( var group in occurrences.Where( o => o.Key == leader.GroupId ) )
                    {
                        var mergeObjects = Rock.Lava.LavaHelper.GetCommonMergeFields(  null, leader.Person );
                        mergeObjects.Add( "Person", leader.Person );
                        mergeObjects.Add( "Group", leader.Group );
                        mergeObjects.Add( "Occurrence", group.Value.Max() );

                        var recipients = new List<RecipientData>();
                        recipients.Add( new RecipientData( leader.Person.Email, mergeObjects ) );

                        Email.Send( dataMap.GetString( "SystemEmail" ).AsGuid(), recipients );
                        attendanceRemindersSent++;

                    }
                }
            }

            context.Result = string.Format( "{0} attendance reminders sent", attendanceRemindersSent );
        }
        public List <ScheduleOccurrence> GetGroupOccurrences(Group group, DateTime?fromDateTime, DateTime?toDateTime,
                                                             List <int> locationIds, List <int> scheduleIds, bool loadSummaryData, int?campusId)
        {
            var occurrences = new List <ScheduleOccurrence>();

            if (group != null)
            {
                var rockContext       = (RockContext)this.Context;
                var attendanceService = new AttendanceService(rockContext);
                var scheduleService   = new ScheduleService(rockContext);
                var locationService   = new LocationService(rockContext);

                using (new Rock.Data.QueryHintScope(rockContext, QueryHintType.RECOMPILE))
                {
                    // Set up an 'occurrences' query for the group
                    var qry = attendanceService
                              .Queryable().AsNoTracking()
                              .Where(a => a.GroupId == group.Id);

                    // Filter by date range
                    if (fromDateTime.HasValue)
                    {
                        var fromDate = fromDateTime.Value.Date;
                        qry = qry.Where(a => DbFunctions.TruncateTime(a.StartDateTime) >= (fromDate));
                    }
                    if (toDateTime.HasValue)
                    {
                        var toDate = toDateTime.Value.Date;
                        qry = qry.Where(a => DbFunctions.TruncateTime(a.StartDateTime) < (toDate));
                    }

                    // Location Filter
                    if (locationIds.Any())
                    {
                        qry = qry.Where(a => locationIds.Contains(a.LocationId ?? 0));
                    }

                    // Schedule Filter
                    if (scheduleIds.Any())
                    {
                        qry = qry.Where(a => scheduleIds.Contains(a.ScheduleId ?? 0));
                    }

                    // Get the unique combination of location/schedule/date for the selected group
                    var occurrenceDates = qry
                                          .Select(a => new
                    {
                        a.LocationId,
                        a.ScheduleId,
                        Date = DbFunctions.TruncateTime(a.StartDateTime)
                    })
                                          .Distinct()
                                          .ToList();

                    // Get the locations for each unique location id
                    var selectedlocationIds = occurrenceDates.Select(o => o.LocationId).Distinct().ToList();

                    var locations = locationService
                                    .Queryable().AsNoTracking()
                                    .Where(l => selectedlocationIds.Contains(l.Id))
                                    .Select(l => new { l.Id, l.ParentLocationId, l.Name })
                                    .ToList();
                    var locationNames = new Dictionary <int, string>();
                    locations.ForEach(l => locationNames.Add(l.Id, l.Name));

                    // Get the parent location path for each unique location
                    var parentlocationPaths = new Dictionary <int, string>();
                    locations
                    .Where(l => l.ParentLocationId.HasValue)
                    .Select(l => l.ParentLocationId.Value)
                    .Distinct()
                    .ToList()
                    .ForEach(l => parentlocationPaths.Add(l, locationService.GetPath(l)));
                    var locationPaths = new Dictionary <int, string>();
                    locations
                    .Where(l => l.ParentLocationId.HasValue)
                    .ToList()
                    .ForEach(l => locationPaths.Add(l.Id, parentlocationPaths[l.ParentLocationId.Value]));

                    // Get the schedules for each unique schedule id
                    var selectedScheduleIds = occurrenceDates.Select(o => o.ScheduleId).Distinct().ToList();
                    var schedules           = scheduleService
                                              .Queryable().AsNoTracking()
                                              .Where(s => selectedScheduleIds.Contains(s.Id))
                                              .ToList();
                    var scheduleNames      = new Dictionary <int, string>();
                    var scheduleStartTimes = new Dictionary <int, TimeSpan>();
                    schedules
                    .ForEach(s =>
                    {
                        scheduleNames.Add(s.Id, s.Name);
                        scheduleStartTimes.Add(s.Id, s.StartTimeOfDay);
                    });

                    foreach (var occurrence in occurrenceDates.Where(o => o.Date.HasValue))
                    {
                        occurrences.Add(
                            new ScheduleOccurrence(
                                occurrence.Date.Value,
                                occurrence.ScheduleId.HasValue && scheduleStartTimes.ContainsKey(occurrence.ScheduleId.Value) ?
                                scheduleStartTimes[occurrence.ScheduleId.Value] : new TimeSpan(),
                                occurrence.ScheduleId,
                                occurrence.ScheduleId.HasValue && scheduleNames.ContainsKey(occurrence.ScheduleId.Value) ?
                                scheduleNames[occurrence.ScheduleId.Value] : string.Empty,
                                occurrence.LocationId,
                                occurrence.LocationId.HasValue && locationNames.ContainsKey(occurrence.LocationId.Value) ?
                                locationNames[occurrence.LocationId.Value] : string.Empty,
                                occurrence.LocationId.HasValue && locationPaths.ContainsKey(occurrence.LocationId.Value) ?
                                locationPaths[occurrence.LocationId.Value] : string.Empty
                                ));
                    }
                }

                // Load the attendance data for each occurrence
                if (loadSummaryData && occurrences.Any())
                {
                    var minDate       = occurrences.Min(o => o.Date);
                    var maxDate       = occurrences.Max(o => o.Date).AddDays(1);
                    var attendanceQry = attendanceService
                                        .Queryable().AsNoTracking()
                                        .Where(a =>
                                               a.GroupId.HasValue &&
                                               a.GroupId == group.Id &&
                                               a.StartDateTime >= minDate &&
                                               a.StartDateTime < maxDate &&
                                               a.PersonAlias != null &&
                                               a.PersonAliasId.HasValue)
                                        .Select(a => new
                    {
                        a.LocationId,
                        a.ScheduleId,
                        a.StartDateTime,
                        a.DidAttend,
                        a.DidNotOccur,
                        a.PersonAliasId,
                        PersonId = a.PersonAlias.PersonId
                    });

                    if (campusId.HasValue)
                    {
                        var familyGroupType = GroupTypeCache.Get(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid());
                        var campusQry       = new GroupMemberService(rockContext)
                                              .Queryable()
                                              .Where(g =>
                                                     g.Group != null &&
                                                     g.Group.GroupTypeId == familyGroupType.Id &&
                                                     g.Group.CampusId.HasValue &&
                                                     g.Group.CampusId.Value == campusId.Value
                                                     )
                                              .Select(m => m.PersonId);

                        attendanceQry = attendanceQry
                                        .Where(s => campusQry.Contains(s.PersonId));
                    }

                    var attendances = attendanceQry.ToList();

                    foreach (var summary in attendances
                             .GroupBy(a => new
                    {
                        a.LocationId,
                        a.ScheduleId,
                        Date = a.StartDateTime.Date
                    })
                             .Select(a => new
                    {
                        a.Key.LocationId,
                        a.Key.ScheduleId,
                        a.Key.Date,
                        DidAttendCount = a
                                         .Where(t => t.DidAttend.HasValue && t.DidAttend.Value)
                                         .Select(t => t.PersonAliasId.Value)
                                         .Distinct()
                                         .Count(),
                        DidNotOccurCount = a
                                           .Where(t => t.DidNotOccur.HasValue && t.DidNotOccur.Value)
                                           .Select(t => t.PersonAliasId.Value)
                                           .Distinct()
                                           .Count(),
                        TotalCount = a
                                     .Select(t => t.PersonAliasId)
                                     .Distinct()
                                     .Count()
                    }))
                    {
                        var occurrence = occurrences
                                         .Where(o =>
                                                o.ScheduleId.Equals(summary.ScheduleId) &&
                                                o.LocationId.Equals(summary.LocationId) &&
                                                o.Date.Equals(summary.Date))
                                         .FirstOrDefault();
                        if (occurrence != null)
                        {
                            occurrence.DidAttendCount   = summary.DidAttendCount;
                            occurrence.DidNotOccurCount = summary.DidNotOccurCount;
                            occurrence.TotalCount       = summary.TotalCount;
                        }
                    }
                }

                // Create any missing occurrences from the group's schedule (not location schedules)
                Schedule groupSchedule = null;
                if (group.ScheduleId.HasValue)
                {
                    groupSchedule = group.Schedule;
                    if (groupSchedule == null)
                    {
                        groupSchedule = new ScheduleService(rockContext).Get(group.ScheduleId.Value);
                    }
                }

                if (groupSchedule != null)
                {
                    var newOccurrences = new List <ScheduleOccurrence>();

                    var existingDates = occurrences
                                        .Where(o => o.ScheduleId.Equals(groupSchedule.Id))
                                        .Select(o => o.Date)
                                        .Distinct()
                                        .ToList();

                    var startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths(-2);
                    var endDate   = toDateTime.HasValue ? toDateTime.Value : RockDateTime.Today.AddDays(1);

                    if (!string.IsNullOrWhiteSpace(groupSchedule.iCalendarContent))
                    {
                        // If schedule has an iCal schedule, get all the past occurrences
                        foreach (var occurrence in groupSchedule.GetOccurrences(startDate, endDate))
                        {
                            var scheduleOccurrence = new ScheduleOccurrence(
                                occurrence.Period.StartTime.Date, occurrence.Period.StartTime.TimeOfDay, groupSchedule.Id, groupSchedule.Name);
                            if (!existingDates.Contains(scheduleOccurrence.Date))
                            {
                                newOccurrences.Add(scheduleOccurrence);
                            }
                        }
                    }
                    else
                    {
                        // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week
                        if (groupSchedule.WeeklyDayOfWeek.HasValue)
                        {
                            // default to start with date 2 months earlier
                            startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths(-2);
                            if (existingDates.Any(d => d < startDate))
                            {
                                startDate = existingDates.Min();
                            }

                            // Back up start time to the correct day of week
                            while (startDate.DayOfWeek != groupSchedule.WeeklyDayOfWeek.Value)
                            {
                                startDate = startDate.AddDays(-1);
                            }

                            // Add the start time
                            if (groupSchedule.WeeklyTimeOfDay.HasValue)
                            {
                                startDate = startDate.Add(groupSchedule.WeeklyTimeOfDay.Value);
                            }

                            // Create occurrences up to current time
                            while (startDate < endDate)
                            {
                                if (!existingDates.Contains(startDate.Date))
                                {
                                    var scheduleOccurrence = new ScheduleOccurrence(startDate.Date, startDate.TimeOfDay, groupSchedule.Id, groupSchedule.Name);
                                    newOccurrences.Add(scheduleOccurrence);
                                }

                                startDate = startDate.AddDays(7);
                            }
                        }
                    }

                    if (newOccurrences.Any())
                    {
                        // Filter Exclusions
                        var groupType = GroupTypeCache.Get(group.GroupTypeId);
                        foreach (var exclusion in groupType.GroupScheduleExclusions)
                        {
                            if (exclusion.Start.HasValue && exclusion.End.HasValue)
                            {
                                foreach (var occurrence in newOccurrences.ToList())
                                {
                                    if (occurrence.Date >= exclusion.Start.Value &&
                                        occurrence.Date < exclusion.End.Value.AddDays(1))
                                    {
                                        newOccurrences.Remove(occurrence);
                                    }
                                }
                            }
                        }
                    }

                    foreach (var occurrence in newOccurrences)
                    {
                        occurrences.Add(occurrence);
                    }
                }
            }

            return(occurrences);
        }