Esempio n. 1
0
        /// <summary>
        /// Adds the missing relationship groups.
        /// Copied from Rock Cleanup Job
        /// </summary>
        /// <param name="relationshipGroupType">Type of the relationship group.</param>
        /// <param name="ownerRoleGuid">The owner role unique identifier.</param>
        private static void AddMissingRelationshipGroups()
        {
            var relationshipGroupType = CachedTypes.KnownRelationshipGroupType;
            var ownerRoleGuid = CachedTypes.KnownRelationshipOwnerRoleGuid;

            if ( relationshipGroupType != null )
            {
                var ownerRoleId = relationshipGroupType.Roles
                    .Where( r => r.Guid.Equals( ownerRoleGuid ) ).Select( a => ( int? ) a.Id ).FirstOrDefault();
                if ( ownerRoleId.HasValue )
                {
                    var rockContext = new RockContext();
                    var personService = new PersonService( rockContext );
                    var memberService = new GroupMemberService( rockContext );

                    var qryGroupOwnerPersonIds = memberService.Queryable( true )
                        .Where( m => m.GroupRoleId == ownerRoleId.Value ).Select( a => a.PersonId );

                    var personIdsWithoutKnownRelationshipGroup = personService.Queryable( true, true ).Where( p => !qryGroupOwnerPersonIds.Contains( p.Id ) ).Select( a => a.Id ).ToList();

                    var groupsToInsert = new List<Group>();
                    var groupGroupMembersToInsert = new Dictionary<Guid, GroupMember>();
                    foreach ( var personId in personIdsWithoutKnownRelationshipGroup )
                    {
                        var groupMember = new GroupMember();
                        groupMember.PersonId = personId;
                        groupMember.GroupRoleId = ownerRoleId.Value;

                        var group = new Group();
                        group.Name = relationshipGroupType.Name;
                        group.Guid = Guid.NewGuid();
                        group.GroupTypeId = relationshipGroupType.Id;

                        groupGroupMembersToInsert.Add( group.Guid, groupMember );

                        groupsToInsert.Add( group );
                    }

                    if ( groupsToInsert.Any() )
                    {
                        // use BulkInsert just in case there are a large number of groups and group members to insert
                        rockContext.BulkInsert( groupsToInsert );

                        Dictionary<Guid, int> groupIdLookup = new GroupService( rockContext ).Queryable().Where( a => a.GroupTypeId == relationshipGroupType.Id ).Select( a => new { a.Id, a.Guid } ).ToDictionary( k => k.Guid, v => v.Id );
                        var groupMembersToInsert = new List<GroupMember>();
                        foreach ( var groupGroupMember in groupGroupMembersToInsert )
                        {
                            var groupMember = groupGroupMember.Value;
                            groupMember.GroupId = groupIdLookup[groupGroupMember.Key];
                            groupMembersToInsert.Add( groupMember );
                        }

                        rockContext.BulkInsert( groupMembersToInsert );
                    }

                    rockContext.Dispose();
                }
            }
        }
Esempio n. 2
0
 /// <summary>
 /// Saves the new user logins.
 /// </summary>
 /// <param name="newUserLogins">The new user logins.</param>
 /// <param name="newStaffMembers">The new staff members.</param>
 private static void SaveUsers(List <UserLogin> newUserLogins, List <GroupMember> newStaffMembers)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(newUserLogins);
         rockContext.BulkInsert(newStaffMembers);
     }
 }
Esempio n. 3
0
 /// <summary>
 /// Saves the activities.
 /// </summary>
 /// <param name="activityList">The activity list.</param>
 private static void SaveActivities(List <ConnectionRequestActivity> activityList)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(activityList);
     }
 }
Esempio n. 4
0
 /// <summary>
 /// Saves the prayer requests.
 /// </summary>
 /// <param name="prayerList">The prayer list.</param>
 private static void SavePrayerRequests(List <PrayerRequest> prayerList)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(prayerList);
     }
 }
Esempio n. 5
0
 /// <summary>
 /// Saves the pledges.
 /// </summary>
 /// <param name="newPledges">The new pledges.</param>
 private static void SavePledges(List <FinancialPledge> newPledges)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(newPledges);
     }
 }
Esempio n. 6
0
 /// <summary>
 /// Saves the family address.
 /// </summary>
 /// <param name="newGroupLocations">The new group locations.</param>
 private static void SaveFamilyAddress(List <GroupLocation> newGroupLocations)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(newGroupLocations);
     }
 }
Esempio n. 7
0
 /// <summary>
 /// Saves the bank accounts.
 /// </summary>
 /// <param name="newBankAccounts">The new bank accounts.</param>
 private static void SaveBankAccounts(List <FinancialPersonBankAccount> newBankAccounts)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(newBankAccounts);
     }
 }
Esempio n. 8
0
 /// <summary>
 /// Saves the new group members.
 /// </summary>
 /// <param name="newGroupMembers">The new group members.</param>
 private static void SaveGroupMembers(List <GroupMember> newGroupMembers)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(newGroupMembers);
     }
 }
Esempio n. 9
0
 /// <summary>
 /// Saves the financial batches.
 /// </summary>
 /// <param name="newBatches">The new batches.</param>
 private static void SaveFinancialBatches(List <FinancialBatch> newBatches)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(newBatches);
     }
 }
Esempio n. 10
0
 /// <summary>
 /// Saves the connection requests.
 /// </summary>
 /// <param name="communicationList">The communication list.</param>
 private static void SaveConnectionRequests(List <ConnectionRequest> connectionRequests)
 {
     using (var rockContext = new RockContext())
     {
         rockContext.BulkInsert(connectionRequests);
     }
 }
Esempio n. 11
0
 /// <summary>
 /// Saves the benevolence requests.
 /// </summary>
 /// <param name="newBenevolences">The new benevolences.</param>
 private static void SaveBenevolenceRequests(List <BenevolenceRequest> newBenevolences)
 {
     if (newBenevolences.Count > 0)
     {
         using (var rockContext = new RockContext())
         {
             rockContext.BulkInsert(newBenevolences);
         }
     }
 }
Esempio n. 12
0
        /// <summary>
        /// Logs the interactions.
        /// </summary>
        /// <param name="interactionTransactionInfos">The interaction transaction infos to process.</param>
        /// <param name="rockContext">The rock context.</param>
        private void LogInteractions(List <InteractionTransactionInfo> interactionTransactionInfos, RockContext rockContext)
        {
            List <Interaction> interactionsToInsert = new List <Interaction>();

            var interactionService = new InteractionService(rockContext);

            foreach (var info in interactionTransactionInfos.Where(a => a.InteractionComponentId.HasValue))
            {
                /*
                 * 2020-06-29 - JH
                 *
                 * The 'CreateInteraction(...)' method called below sets the following properties on the Interaction object:
                 *
                 * - InteractionComponentId
                 * - InteractionDateTime (but with the wrong value)
                 * - InteractionSessionId
                 * - Source
                 * - Medium
                 * - Campaign
                 * - Content
                 * - Term
                 */
                var interaction = interactionService.CreateInteraction(info.InteractionComponentId.Value, info.UserAgent, info.InteractionData, info.IPAddress, info.BrowserSessionId);

                // The rest of the properties need to be manually set.
                interaction.EntityId               = info.InteractionEntityId;
                interaction.Operation              = info.InteractionOperation.IsNotNullOrWhiteSpace() ? info.InteractionOperation.Trim() : "View";
                interaction.InteractionSummary     = info.InteractionSummary?.Trim();
                interaction.PersonAliasId          = info.PersonAliasId;
                interaction.InteractionDateTime    = info.InteractionDateTime;
                interaction.InteractionTimeToServe = info.InteractionTimeToServe;
                interaction.RelatedEntityTypeId    = info.InteractionRelatedEntityTypeId;
                interaction.RelatedEntityId        = info.InteractionRelatedEntityId;
                interaction.ChannelCustom1         = info.InteractionChannelCustom1?.Trim();
                interaction.ChannelCustom2         = info.InteractionChannelCustom2?.Trim();
                interaction.ChannelCustomIndexed1  = info.InteractionChannelCustomIndexed1?.Trim();

                interaction.SetInteractionData(info.InteractionData?.Trim());
                interactionsToInsert.Add(interaction);
            }

            rockContext.BulkInsert(interactionsToInsert);

            // This logic is normally handled in the Interaction.PostSave method, but since the BulkInsert bypasses those
            // model hooks, streaks need to be updated here. Also, it is not necessary for this logic to complete before this
            // transaction can continue processing and exit, so update the streak using a task.
            interactionsToInsert.ForEach(i => Task.Run(() => StreakTypeService.HandleInteractionRecord(i)));
        }
Esempio n. 13
0
        /// <summary>
        /// Executes this instance.
        /// </summary>
        public void Execute()
        {
            var historyRecordsToInsert = new List <History>();

            while (HistoryRecordsToInsert.TryDequeue(out History historyRecord))
            {
                historyRecordsToInsert.Add(historyRecord);
            }

            if (!historyRecordsToInsert.Any())
            {
                return;
            }

            using (var rockContext = new RockContext())
            {
                rockContext.BulkInsert(historyRecordsToInsert);
            }
        }
Esempio n. 14
0
        /// <summary>
        /// Refresh the recipients list.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <returns></returns>
        public void RefreshCommunicationRecipientList(RockContext rockContext)
        {
            if (!ListGroupId.HasValue)
            {
                return;
            }

            var segmentDataViewGuids = this.Segments.SplitDelimitedValues().AsGuidList();
            var segmentDataViewIds   = new DataViewService(rockContext).GetByGuids(segmentDataViewGuids).Select(a => a.Id).ToList();

            var qryCommunicationListMembers = GetCommunicationListMembers(rockContext, ListGroupId, this.SegmentCriteria, segmentDataViewIds);

            // NOTE: If this is scheduled communication, don't include Members that were added after the scheduled FutureSendDateTime.
            // However, don't exclude if the date added can't be determined or they will never be sent a scheduled communication.
            if (this.FutureSendDateTime.HasValue)
            {
                var memberAddedCutoffDate = this.FutureSendDateTime;

                qryCommunicationListMembers = qryCommunicationListMembers.Where(a => (a.DateTimeAdded.HasValue && a.DateTimeAdded.Value < memberAddedCutoffDate) ||
                                                                                (a.CreatedDateTime.HasValue && a.CreatedDateTime.Value < memberAddedCutoffDate) ||
                                                                                (!a.DateTimeAdded.HasValue && !a.CreatedDateTime.HasValue));
            }

            var communicationRecipientService = new CommunicationRecipientService(rockContext);

            var recipientsQry = GetRecipientsQry(rockContext);

            // Get all the List member which is not part of communication recipients yet
            var newMemberInList = qryCommunicationListMembers
                                  .Include(c => c.Person)
                                  .Where(a => !recipientsQry.Any(r => r.PersonAlias.PersonId == a.PersonId))
                                  .AsNoTracking()
                                  .ToList();

            var emailMediumEntityType = EntityTypeCache.Get(SystemGuid.EntityType.COMMUNICATION_MEDIUM_EMAIL.AsGuid());
            var smsMediumEntityType   = EntityTypeCache.Get(SystemGuid.EntityType.COMMUNICATION_MEDIUM_SMS.AsGuid());
            var pushMediumEntityType  = EntityTypeCache.Get(SystemGuid.EntityType.COMMUNICATION_MEDIUM_PUSH_NOTIFICATION.AsGuid());

            var recipientsToAdd = newMemberInList.Select(m => new CommunicationRecipient
            {
                PersonAliasId      = m.Person.PrimaryAliasId.Value,
                Status             = CommunicationRecipientStatus.Pending,
                CommunicationId    = Id,
                MediumEntityTypeId = DetermineMediumEntityTypeId(
                    emailMediumEntityType.Id,
                    smsMediumEntityType.Id,
                    pushMediumEntityType.Id,
                    CommunicationType,
                    m.CommunicationPreference,
                    m.Person.CommunicationPreference)
            });

            rockContext.BulkInsert <CommunicationRecipient>(recipientsToAdd);

            // Get all pending communication recipients that are no longer part of the group list member, then delete them from the Recipients
            var missingMemberInList = recipientsQry.Where(a => a.Status == CommunicationRecipientStatus.Pending)
                                      .Where(a => !qryCommunicationListMembers.Any(r => r.PersonId == a.PersonAlias.PersonId));

            rockContext.BulkDelete <CommunicationRecipient>(missingMemberInList);

            rockContext.SaveChanges();
        }
Esempio n. 15
0
        /// <summary>
        /// creates or updates the entity set on the basis of campaign connection configuration, and returns the Id of the entitySetId
        /// </summary>
        /// <param name="campaignConfiguration">The campaign configuration.</param>
        /// <returns></returns>
        public static int GetEntitySet(CampaignItem campaignConfiguration)
        {
            var rockContext = new RockContext();

            var connectionOpportunityService = new ConnectionOpportunityService(rockContext);
            var connectionRequestService     = new ConnectionRequestService(rockContext);
            var entitySetService             = new Rock.Model.EntitySetService(rockContext);

            var connectionOpportunity = connectionOpportunityService.Get(campaignConfiguration.OpportunityGuid);

            // list of person on the basis of Dataview result and optout group.
            var filteredPersonIds = GetFilteredPersonIds(campaignConfiguration, rockContext);

            // get the last connection datetime.
            var lastConnectionDateTime = RockDateTime.Now.AddDays(-campaignConfiguration.DaysBetweenConnection);

            // if DaysBetweenConnection is 0 then check for connection request for any time period.
            if (campaignConfiguration.DaysBetweenConnection == default(int))
            {
                lastConnectionDateTime = DateTime.MinValue;
            }

            // list of person that has active connection request OR has connection closed in the past number of days between connection.
            var excludedPersonIds = connectionRequestService
                                    .Queryable()
                                    .Where(a =>
                                           a.ConnectionOpportunityId == connectionOpportunity.Id && (
                                               a.ConnectionState == ConnectionState.Active ||
                                               a.ConnectionState == ConnectionState.FutureFollowUp ||
                                               ((a.ConnectionState == ConnectionState.Connected || a.ConnectionState == ConnectionState.Inactive) && a.ModifiedDateTime > lastConnectionDateTime)))
                                    .Select(a => a.PersonAlias.PersonId)
                                    .ToList();

            // filtered list of person removing all the personIds found in excludedPersonIds List
            filteredPersonIds = filteredPersonIds.Where(a => !excludedPersonIds.Contains(a)).ToList();

            // get the ordered list of personIds based on the oldest previous connection request and connection opportunity

            /* 2020-05-06 MDP
             * If there are many filteredPersonIds, we'll get a SQL Exception, so let's get *all* the Connected connection Requests first,
             * and then use C# to filter.
             */

            var orderedLastCompletedRequestForPerson = connectionRequestService
                                                       .Queryable()
                                                       .Where(a => a.ConnectionOpportunityId == connectionOpportunity.Id &&
                                                              a.ConnectionState == ConnectionState.Connected)
                                                       .GroupBy(a => a.PersonAlias.PersonId)
                                                       .Select(a => new
            {
                PersonId = a.Key,
                LastConnectionDateTime = a.OrderByDescending(b => b.ModifiedDateTime).Select(b => b.ModifiedDateTime).FirstOrDefault()
            })
                                                       .OrderBy(a => a.LastConnectionDateTime)
                                                       .Select(a => a.PersonId).ToList();

            // Use C# to filter persons so we can avoid a SQL Exception
            orderedLastCompletedRequestForPerson = orderedLastCompletedRequestForPerson.Where(a => filteredPersonIds.Contains(a)).ToList();

            var random = new Random();

            //// get the final ordered list of personIds based on the oldest previous connection request and
            //// connection opportunity otherwise order randomly for the person who don't have any previous connection request.
            var orderedPersonIds = filteredPersonIds
                                   .OrderBy(a =>
            {
                var index = orderedLastCompletedRequestForPerson.IndexOf(a);
                if (index == -1)
                {
                    return(random.Next(orderedLastCompletedRequestForPerson.Count, int.MaxValue));
                }
                else
                {
                    return(index);
                }
            }).ToList();

            EntitySet entitySet = null;

            if (campaignConfiguration.EntitySetId != default(int))
            {
                entitySet = entitySetService.Get(campaignConfiguration.EntitySetId);
            }

            List <Rock.Model.EntitySetItem> entitySetItems = new List <Rock.Model.EntitySetItem>();
            var personEntityTypeId = EntityTypeCache.Get <Rock.Model.Person>().Id;

            if (entitySet == null || entitySet.EntityTypeId != personEntityTypeId)
            {
                entitySet = new Rock.Model.EntitySet();
                entitySet.EntityTypeId   = personEntityTypeId;
                entitySet.ExpireDateTime = null;
                entitySetService.Add(entitySet);
            }
            else
            {
                var entitySetItemQry = new EntitySetItemService(rockContext)
                                       .Queryable().AsNoTracking()
                                       .Where(i => i.EntitySetId == entitySet.Id);
                rockContext.BulkDelete(entitySetItemQry);
            }

            // Update the EntitySet name
            entitySet.Name = campaignConfiguration.Name;

            var orderIndex = 0;

            foreach (var personId in orderedPersonIds)
            {
                try
                {
                    var item = new Rock.Model.EntitySetItem();
                    item.Order    = orderIndex++;
                    item.EntityId = personId;
                    entitySetItems.Add(item);
                }
                catch
                {
                    // ignore
                }
            }

            rockContext.SaveChanges();
            entitySetItems.ForEach(a =>
            {
                a.EntitySetId = entitySet.Id;
            });

            rockContext.BulkInsert(entitySetItems);

            return(entitySet.Id);
        }
Esempio n. 16
0
            /// <summary>
            /// Called after the save operation has been executed
            /// </summary>
            /// <remarks>
            /// This method is only called if <see cref="M:Rock.Data.EntitySaveHook`1.PreSave" /> returns
            /// without error.
            /// </remarks>
            protected override void PostSave()
            {
                var rockContext = ( RockContext )this.RockContext;

                if (HistoryChanges != null)
                {
                    foreach (var historyItem in HistoryChanges)
                    {
                        int personId = historyItem.PersonId > 0 ? historyItem.PersonId : Entity.PersonId;

                        // if GroupId is 0, it is probably a Group that wasn't saved yet, so get the GroupId from historyItem.Group.Id instead
                        if (historyItem.GroupId == 0)
                        {
                            historyItem.GroupId = historyItem.Group?.Id;
                        }

                        var changes = HistoryService.GetChanges(
                            typeof(Person),
                            Rock.SystemGuid.Category.HISTORY_PERSON_GROUP_MEMBERSHIP.AsGuid(),
                            personId,
                            historyItem.PersonHistoryChangeList,
                            historyItem.Caption,
                            typeof(Group),
                            historyItem.GroupId,
                            Entity.ModifiedByPersonAliasId,
                            rockContext.SourceOfChange);

                        if (changes.Any())
                        {
                            Task.Run(async() =>
                            {
                                // Wait 1 second to allow all post save actions to complete
                                await Task.Delay(1000);
                                try
                                {
                                    using (var insertRockContext = new RockContext())
                                    {
                                        insertRockContext.BulkInsert(changes);
                                    }
                                }
                                catch (SystemException ex)
                                {
                                    ExceptionLogService.LogException(ex, null);
                                }
                            });
                        }

                        var groupMemberChanges = HistoryService.GetChanges(
                            typeof(GroupMember),
                            Rock.SystemGuid.Category.HISTORY_GROUP_CHANGES.AsGuid(),
                            Entity.Id,
                            historyItem.GroupMemberHistoryChangeList,
                            historyItem.Caption,
                            typeof(Group),
                            historyItem.GroupId,
                            Entity.ModifiedByPersonAliasId,
                            rockContext.SourceOfChange);

                        if (groupMemberChanges.Any())
                        {
                            Task.Run(async() =>
                            {
                                // Wait 1 second to allow all post save actions to complete
                                await Task.Delay(1000);
                                try
                                {
                                    using (var insertRockContext = new RockContext())
                                    {
                                        insertRockContext.BulkInsert(groupMemberChanges);
                                    }
                                }
                                catch (SystemException ex)
                                {
                                    ExceptionLogService.LogException(ex, null);
                                }
                            });
                        }
                    }
                }

                base.PostSave();

                // if this is a GroupMember record on a Family, ensure that AgeClassification, PrimaryFamily,
                // GivingLeadId, and GroupSalution is updated
                // NOTE: This is also done on Person.PostSaveChanges in case Birthdate changes
                var groupTypeFamilyRoleIds = GroupTypeCache.GetFamilyGroupType()?.Roles?.Select(a => a.Id).ToList();

                if (groupTypeFamilyRoleIds?.Any() == true)
                {
                    if (groupTypeFamilyRoleIds.Contains(Entity.GroupRoleId))
                    {
                        PersonService.UpdatePersonAgeClassification(Entity.PersonId, rockContext);
                        PersonService.UpdatePrimaryFamily(Entity.PersonId, rockContext);
                        PersonService.UpdateGivingLeaderId(Entity.PersonId, rockContext);


                        GroupService.UpdateGroupSalutations(Entity.GroupId, rockContext);

                        if (_preSaveChangesOldGroupId.HasValue && _preSaveChangesOldGroupId.Value != Entity.GroupId)
                        {
                            // if person was moved to a different family, the old family will need its GroupSalutations updated
                            GroupService.UpdateGroupSalutations(_preSaveChangesOldGroupId.Value, rockContext);
                        }
                    }
                }

                if (State == EntityContextState.Added || State == EntityContextState.Modified)
                {
                    if (Entity.Group != null && Entity.Person != null)
                    {
                        if (Entity.Group?.IsSecurityRoleOrSecurityGroupType() == true)
                        {
                            /* 09/27/2021 MDP
                             *
                             * If this GroupMember record results in making this Person having a higher AccountProtectionProfile level,
                             * update the Person's AccountProtectionProfile.
                             * Note: If this GroupMember record could result in making this Person having a *lower* AccountProtectionProfile level,
                             * don't lower the AccountProtectionProfile here, because other rules have to be considered before
                             * lowering the AccountProtectionProfile level. So we'll let the RockCleanup job take care of making sure the
                             * AccountProtectionProfile is updated after factoring in all the rules.
                             *
                             */

                            if (Entity.Group.ElevatedSecurityLevel >= Utility.Enums.ElevatedSecurityLevel.Extreme &&
                                Entity.Person.AccountProtectionProfile < Utility.Enums.AccountProtectionProfile.Extreme)
                            {
                                Entity.Person.AccountProtectionProfile = Utility.Enums.AccountProtectionProfile.Extreme;
                                rockContext.SaveChanges();
                            }
                            else if (Entity.Group.ElevatedSecurityLevel >= Utility.Enums.ElevatedSecurityLevel.High &&
                                     Entity.Person.AccountProtectionProfile < Utility.Enums.AccountProtectionProfile.High)
                            {
                                Entity.Person.AccountProtectionProfile = Utility.Enums.AccountProtectionProfile.High;
                                rockContext.SaveChanges();
                            }
                        }
                    }
                }
            }
Esempio n. 17
0
        /// <summary>
        /// BulkInserts Interaction Records
        /// </summary>
        /// <remarks>
        /// If any PersonAliasId references a PersonAliasId record that doesn't exist, the field value will be set to null.
        /// Also, if the InteractionComponent Id (or Guid) is specified, but references a Interaction Component record that doesn't exist
        /// the Interaction will not be recorded.
        /// </remarks>
        /// <param name="interactionsImport">The interactions import.</param>
        internal static void BulkInteractionImport(InteractionsImport interactionsImport)
        {
            if (interactionsImport == null)
            {
                throw new Exception("InteractionsImport must be assigned a value.");
            }

            var interactionImportList = interactionsImport.Interactions;

            if (interactionImportList == null || !interactionImportList.Any())
            {
                // if there aren't any return
                return;
            }

            /* 2020-05-14 MDP
             * Make sure that all the PersonAliasIds in the import exist in the database.
             * For performance reasons, look them up all at one and keep a list of valid ones.
             *
             * If there are any PersonAliasIds that aren't valid,
             * we decided that just set the PersonAliasId to null (we want ignore bad data).
             */

            HashSet <int> validPersonAliasIds = interactionsImport.GetValidPersonAliasIds();

            List <Interaction> interactionsToInsert = new List <Interaction>();

            foreach (InteractionImport interactionImport in interactionImportList)
            {
                if (interactionImport.Interaction == null)
                {
                    throw new ArgumentNullException("InteractionImport.Interaction can not be null");
                }

                // Determine which Channel this should be set to
                if (interactionImport.InteractionChannelId.HasValue)
                {
                    // make sure it is a valid Id
                    interactionImport.InteractionChannelId = InteractionChannelCache.Get(interactionImport.InteractionChannelId.Value)?.Id;
                }

                // Determine which Channel Type Medium this should be set to
                if (interactionImport.InteractionChannelChannelTypeMediumValueId.HasValue)
                {
                    // make sure it is a valid Id
                    interactionImport.InteractionChannelChannelTypeMediumValueId = DefinedValueCache.Get(interactionImport.InteractionChannelChannelTypeMediumValueId.Value)?.Id;
                }

                if (!interactionImport.InteractionChannelChannelTypeMediumValueId.HasValue)
                {
                    if (interactionImport.InteractionChannelChannelTypeMediumValueGuid.HasValue)
                    {
                        interactionImport.InteractionChannelChannelTypeMediumValueId = DefinedValueCache.GetId(interactionImport.InteractionChannelChannelTypeMediumValueGuid.Value);
                    }
                }

                if (!interactionImport.InteractionChannelId.HasValue)
                {
                    if (interactionImport.InteractionChannelGuid.HasValue)
                    {
                        interactionImport.InteractionChannelId = InteractionChannelCache.GetId(interactionImport.InteractionChannelGuid.Value);
                    }

                    // if InteractionChannelId is still null, lookup (or create) an InteractionChannel from InteractionChannelForeignKey (if it is specified)
                    if (interactionImport.InteractionChannelId == null && interactionImport.InteractionChannelForeignKey.IsNotNullOrWhiteSpace())
                    {
                        interactionImport.InteractionChannelId = InteractionChannelCache.GetCreateChannelIdByForeignKey(interactionImport.InteractionChannelForeignKey, interactionImport.InteractionChannelName, interactionImport.InteractionChannelChannelTypeMediumValueId);
                    }
                    else
                    {
                        /* 2020-05-14 MDP
                         *  Discussed this and decided that if we tried InteractionChannelId and InteractionChannelGuid, and InteractionChannelForeignKey was not specified,
                         *  we'll just skip over this record
                         */
                        continue;
                    }
                }

                // Determine which Component this should be set to
                if (interactionImport.InteractionComponentId.HasValue)
                {
                    // make sure it is a valid Id
                    interactionImport.InteractionComponentId = InteractionComponentCache.Get(interactionImport.InteractionComponentId.Value)?.Id;
                }

                if (!interactionImport.InteractionComponentId.HasValue)
                {
                    if (interactionImport.InteractionComponentGuid.HasValue)
                    {
                        interactionImport.InteractionComponentId = InteractionComponentCache.GetId(interactionImport.InteractionComponentGuid.Value);
                    }

                    // if InteractionComponentId is still null, lookup (or create) an InteractionComponent from the ForeignKey and ChannelId
                    if (interactionImport.InteractionComponentForeignKey.IsNotNullOrWhiteSpace())
                    {
                        interactionImport.InteractionComponentId = InteractionComponentCache.GetComponentIdByForeignKeyAndChannelId(
                            interactionImport.InteractionComponentForeignKey,
                            interactionImport.InteractionChannelId.Value,
                            interactionImport.InteractionComponentName);
                    }
                    else
                    {
                        /* 2020-05-14 MDP
                         *  Discussed this and decided that and if we tried InteractionComponentId and InteractionComponentGuid, and InteractionComponentForeignKey was not specified,
                         *  we'll just skip over this record
                         */
                        continue;
                    }
                }
            }

            foreach (InteractionImport interactionImport in interactionImportList.Where(a => a.InteractionComponentId.HasValue))
            {
                Interaction interaction = new Interaction
                {
                    InteractionComponentId = interactionImport.InteractionComponentId.Value
                };

                interaction.InteractionDateTime = interactionImport.Interaction.InteractionDateTime;

                // if operation is over 25, truncate it
                interaction.Operation = interactionImport.Interaction.Operation.Truncate(25);

                interaction.InteractionComponentId = interactionImport.InteractionComponentId.Value;
                interaction.EntityId = interactionImport.Interaction.EntityId;
                if (interactionImport.Interaction.RelatedEntityTypeId.HasValue)
                {
                    /* 2020-05-14 MDP
                     * We want to ignore bad data, so first see if the RelatedEntityTypeId exists by looking it up in a cache.
                     * If it doesn't exist, it'll set RelatedEntityTypeId to null (so that we don't get a database constraint error)
                     */

                    interaction.RelatedEntityTypeId = EntityTypeCache.Get(interactionImport.Interaction.RelatedEntityTypeId.Value)?.Id;
                }

                interaction.RelatedEntityId = interactionImport.Interaction.RelatedEntityId;

                if (interactionImport.Interaction.PersonAliasId.HasValue)
                {
                    /* 2020-05-14 MDP
                     * We want to ignore bad data, so see if the specified PersonAliasId exists in the validPersonAliasIds that we lookup up
                     * If it doesn't exist, we'll leave interaction.PersonAliasId null (so that we don't get a database constraint error)
                     */

                    if (validPersonAliasIds.Contains(interactionImport.Interaction.PersonAliasId.Value))
                    {
                        interaction.PersonAliasId = interactionImport.Interaction.PersonAliasId.Value;
                    }
                }

                // BulkImport doesn't include Session information TODO???
                interaction.InteractionSessionId = null;

                // if the summary is over 500 chars, truncate with addEllipsis=true
                interaction.InteractionSummary = interactionImport.Interaction.InteractionSummary.Truncate(500, true);

                interaction.InteractionData  = interactionImport.Interaction.InteractionData;
                interaction.PersonalDeviceId = interactionImport.Interaction.PersonalDeviceId;

                interaction.InteractionEndDateTime = interactionImport.Interaction.InteractionEndDateTime;

                // Campaign related fields, we'll truncate those if they are too long
                interaction.Source      = interactionImport.Interaction.Source.Truncate(25);
                interaction.Medium      = interactionImport.Interaction.Medium.Truncate(25);
                interaction.Campaign    = interactionImport.Interaction.Campaign.Truncate(50);
                interaction.Content     = interactionImport.Interaction.Content.Truncate(50);
                interaction.Term        = interactionImport.Interaction.Term.Truncate(50);
                interaction.ForeignId   = interactionImport.Interaction.ForeignId;
                interaction.ForeignKey  = interactionImport.Interaction.ForeignKey;
                interaction.ForeignGuid = interactionImport.Interaction.ForeignGuid;

                interaction.ChannelCustom1         = interactionImport.Interaction.ChannelCustom1.Truncate(500, true);
                interaction.ChannelCustom2         = interactionImport.Interaction.ChannelCustom2.Truncate(2000, true);
                interaction.ChannelCustomIndexed1  = interactionImport.Interaction.ChannelCustomIndexed1.Truncate(500, true);
                interaction.InteractionLength      = interactionImport.Interaction.InteractionLength;
                interaction.InteractionTimeToServe = interactionImport.Interaction.InteractionTimeToServe;

                interactionsToInsert.Add(interaction);
            }

            using (var rockContext = new RockContext())
            {
                rockContext.BulkInsert(interactionsToInsert);
            }

            // This logic is normally handled in the Interaction.PostSave method, but since the BulkInsert bypasses those
            // model hooks, streaks need to be updated here. Also, it is not necessary for this logic to complete before this
            // transaction can continue processing and exit, so update the streak using a task.

            // Only launch this task if there are StreakTypes configured that have interactions. Otherwise several
            // database calls are made only to find out there are no streak types defined.
            if (StreakTypeCache.All().Any(s => s.IsInteractionRelated))
            {
                // Ids do not exit for the interactions in the collection since they were bulk imported.
                // Read their ids from their guids and append the id.
                var insertedGuids = interactionsToInsert.Select(i => i.Guid).ToList();

                var interactionIds = new InteractionService(new RockContext()).Queryable()
                                     .Where(i => insertedGuids.Contains(i.Guid))
                                     .Select(i => new { i.Id, i.Guid })
                                     .ToList();

                foreach (var interactionId in interactionIds)
                {
                    var interaction = interactionsToInsert.Where(i => i.Guid == interactionId.Guid).FirstOrDefault();
                    if (interaction != null)
                    {
                        interaction.Id = interactionId.Id;
                    }
                }

                // Launch task
                interactionsToInsert.ForEach(i => Task.Run(() => StreakTypeService.HandleInteractionRecord(i.Id)));
            }
        }
Esempio n. 18
0
        public void Execute(IJobExecutionContext context)
        {
            JobDataMap dataMap     = context.JobDetail.JobDataMap;
            var        rockContext = new RockContext();

            var commandTimeout = dataMap.GetString("CommandTimeout").AsIntegerOrNull() ?? 3600;

            rockContext.Database.CommandTimeout = commandTimeout;

            AttributeValueService attributeValueService = new AttributeValueService(rockContext);
            PersonService         personService         = new PersonService(rockContext);
            var personAliasQry       = new PersonAliasService(rockContext).Queryable();
            var familyGroupMemberQry = new GroupMemberService(rockContext).Queryable().Where(gm => gm.Group.GroupTypeId == 10);
            var groupQry             = new GroupService(rockContext).Queryable().Where(g => g.GroupTypeId == 10);
            var attendanceQry        = new AttendanceService(rockContext).Queryable().Where(a => a.DidAttend == true);

            var currentlyEraAttribue         = AttributeCache.Get("CE5739C5-2156-E2AB-48E5-1337C38B935E");
            var firstAttendanceAttribute     = AttributeCache.Get("8F404727-82A5-4855-9714-62DFAB834BB2");
            var eraStartDateAttribute        = AttributeCache.Get("A106610C-A7A1-469E-4097-9DE6400FDFC2");
            var attendanceVerificationMethod = AttributeCache.Get("7244EF82-FD1E-4B00-B2A1-114A79F09555");
            var attendanceVerifiedType       = AttributeCache.Get("64C05D55-1697-4773-AE14-5C9596B71FF4");
            var historyCategory     = CategoryCache.Get(Rock.SystemGuid.Category.HISTORY_PERSON_DEMOGRAPHIC_CHANGES);
            var personEntityType    = EntityTypeCache.Get(typeof(Rock.Model.Person));
            var attributeEntityType = EntityTypeCache.Get(typeof(Rock.Model.Attribute));

            var inEraQry = attributeValueService.Queryable()
                           .Where(av => av.AttributeId == currentlyEraAttribue.Id && av.Value == "True")
                           .Select(av => av.EntityId);

            var firstAttQry = attributeValueService.Queryable()
                              .Where(av => av.AttributeId == firstAttendanceAttribute.Id && av.Value != null && av.Value != "")
                              .Select(av => av.EntityId);

            var eraStartQry = attributeValueService.Queryable()
                              .Where(av => av.AttributeId == eraStartDateAttribute.Id);

            var take = dataMap.GetString("Take").AsIntegerOrNull() ?? 1000;

            //Linq!
            var people = personService.Queryable()                //Get all the people
                         .Where(p => inEraQry.Contains(p.Id))     //Who are era
                         .Where(p => !firstAttQry.Contains(p.Id)) //And don't have a first attendance
                         .Take(take)
                         .Join(                                   //Get the ERA Start Date
                eraStartQry,
                p => p.Id,
                a => a.EntityId,
                (p, a) => new { Person = p, EraStartDate = a.Value })
                         .GroupJoin(                            //Get group membership for all family groups
                familyGroupMemberQry,
                o => o.Person.Id,
                gm => gm.PersonId,
                (o, gm) => new
            {
                o.Person,
                o.EraStartDate,
                FirstAttendance = gm.Select(gm2 => gm2.Group).SelectMany(g => g.Members.Select(gm3 => gm3.Person)) //Get all family members
                                  .GroupJoin(
                    personAliasQry,                                                                                //Get all person alias ids for all family members
                    p => p.Id,
                    pa => pa.PersonId,
                    (p, pa) =>
                    pa.GroupJoin(
                        attendanceQry,                          //Get all attendance records for all family members
                        pa2 => pa2.Id,
                        a => a.PersonAliasId,
                        (pa2, a) => a)
                    .SelectMany(a => a))                           //Compact
                                  .SelectMany(a => a)              //Compact
                                  .OrderBy(a2 => a2.StartDateTime) //Sort
                                  .FirstOrDefault()                //Get first
            })
                         .ToList();

            var counter = 0;

            foreach (var person in people)
            {
                try
                {
                    //Before we continue. We need to remove any existing attribute values.
                    var removeQry = string.Format(@"delete from AttributeValue where EntityId = {0} and AttributeId in ({1},{2},{3})",
                                                  person.Person.Id, firstAttendanceAttribute.Id, attendanceVerificationMethod.Id, attendanceVerifiedType.Id);
                    rockContext.Database.ExecuteSqlCommand(removeQry);


                    //Lets add in the attribute values!
                    if (person.FirstAttendance != null)
                    {
                        var methodAV = new AttributeValue
                        {
                            IsSystem    = false,
                            AttributeId = attendanceVerificationMethod.Id,
                            EntityId    = person.Person.Id,
                            Value       = "Automatic"
                        };

                        var methodHistory = new History
                        {
                            IsSystem            = false,
                            CategoryId          = historyCategory.Id,
                            EntityTypeId        = personEntityType.Id,
                            EntityId            = person.Person.Id,
                            RelatedEntityTypeId = attributeEntityType.Id,
                            RelatedEntityId     = attendanceVerificationMethod.Id,
                            Verb       = "MODIFY",
                            ChangeType = "Property",
                            ValueName  = "Attendance Verification Method",
                            NewValue   = "Automatic"
                        };

                        var attendanceTypeAV = new AttributeValue
                        {
                            IsSystem    = false,
                            AttributeId = attendanceVerifiedType.Id,
                            EntityId    = person.Person.Id,
                            Value       = "SECC Attendance"
                        };

                        var attendanceTypeHistory = new History
                        {
                            IsSystem            = false,
                            CategoryId          = historyCategory.Id,
                            EntityTypeId        = personEntityType.Id,
                            EntityId            = person.Person.Id,
                            RelatedEntityTypeId = attributeEntityType.Id,
                            RelatedEntityId     = attendanceVerifiedType.Id,
                            Verb       = "MODIFY",
                            ChangeType = "Property",
                            ValueName  = "Attendance Verification Type",
                            NewValue   = "SECC Attendance"
                        };

                        var firstAttendanceAV = new AttributeValue
                        {
                            IsSystem    = false,
                            AttributeId = firstAttendanceAttribute.Id,
                            EntityId    = person.Person.Id,
                            Value       = person.FirstAttendance.StartDateTime.Date.ToString("MM/dd/yyyy")
                        };

                        var firstAttendanceHistory = new History
                        {
                            IsSystem            = false,
                            CategoryId          = historyCategory.Id,
                            EntityTypeId        = personEntityType.Id,
                            EntityId            = person.Person.Id,
                            RelatedEntityTypeId = attributeEntityType.Id,
                            RelatedEntityId     = firstAttendanceAttribute.Id,
                            Verb       = "MODIFY",
                            ChangeType = "Property",
                            ValueName  = "Attendance 1st Verified Date",
                            NewValue   = person.FirstAttendance.StartDateTime.Date.ToString("MM/dd/yyyy")
                        };

                        rockContext.BulkInsert(new List <AttributeValue> {
                            methodAV, attendanceTypeAV, firstAttendanceAV
                        });
                        rockContext.BulkInsert(new List <History> {
                            methodHistory, attendanceTypeHistory, firstAttendanceHistory
                        });
                    }
                    else
                    {
                        var methodAV = new AttributeValue
                        {
                            IsSystem    = false,
                            AttributeId = attendanceVerificationMethod.Id,
                            EntityId    = person.Person.Id,
                            Value       = "Automatic"
                        };

                        var methodHistory = new History
                        {
                            IsSystem            = false,
                            CategoryId          = historyCategory.Id,
                            EntityTypeId        = personEntityType.Id,
                            EntityId            = person.Person.Id,
                            RelatedEntityTypeId = attributeEntityType.Id,
                            RelatedEntityId     = attendanceVerificationMethod.Id,
                            Verb       = "MODIFY",
                            ChangeType = "Property",
                            ValueName  = "Attendance Verification Method",
                            NewValue   = "Automatic"
                        };

                        var attendanceTypeAV = new AttributeValue
                        {
                            IsSystem    = false,
                            AttributeId = attendanceVerifiedType.Id,
                            EntityId    = person.Person.Id,
                            Value       = "eRA Start Date"
                        };

                        var attendanceTypeHistory = new History
                        {
                            IsSystem            = false,
                            CategoryId          = historyCategory.Id,
                            EntityTypeId        = personEntityType.Id,
                            EntityId            = person.Person.Id,
                            RelatedEntityTypeId = attributeEntityType.Id,
                            RelatedEntityId     = attendanceVerifiedType.Id,
                            Verb       = "MODIFY",
                            ChangeType = "Property",
                            ValueName  = "Attendance Verification Type",
                            NewValue   = "eRA Start Date"
                        };

                        var firstAttendanceAV = new AttributeValue
                        {
                            IsSystem    = false,
                            AttributeId = firstAttendanceAttribute.Id,
                            EntityId    = person.Person.Id,
                            Value       = person.EraStartDate.AsDateTime().Value.Date.ToString("MM/dd/yyyy")
                        };

                        var firstAttendanceHistory = new History
                        {
                            IsSystem            = false,
                            CategoryId          = historyCategory.Id,
                            EntityTypeId        = personEntityType.Id,
                            EntityId            = person.Person.Id,
                            RelatedEntityTypeId = attributeEntityType.Id,
                            RelatedEntityId     = firstAttendanceAttribute.Id,
                            Verb       = "MODIFY",
                            ChangeType = "Property",
                            ValueName  = "Attendance 1st Verified Date",
                            NewValue   = person.EraStartDate.AsDateTime().Value.Date.ToString("MM/dd/yyyy")
                        };

                        rockContext.BulkInsert(new List <AttributeValue> {
                            methodAV, attendanceTypeAV, firstAttendanceAV
                        });
                        rockContext.BulkInsert(new List <History> {
                            methodHistory, attendanceTypeHistory, firstAttendanceHistory
                        });
                    }
                    counter++;
                }
                catch (Exception e)
                {
                    var ex = string.Format("Could not set first Attendance data for {0} - {1}", person.Person.FullName, person.Person.Id);
                    ExceptionLogService.LogException(new Exception(ex, e));
                }

                if (counter % 100 == 0)
                {
                    var jobId      = context.GetJobId();
                    var jobService = new ServiceJobService(rockContext);
                    var job        = jobService.Get(jobId);
                    if (job != null)
                    {
                        job.LastStatusMessage = string.Format("Updated {0} People of {1}", counter, people.Count);
                        rockContext.SaveChanges(false);
                    }
                }
            }
        }
Esempio n. 19
0
        /// <summary>
        /// Updates the last login, writes to the person's history log, and saves changes to the database
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        public static void UpdateLastLogin(string userName)
        {
            if (string.IsNullOrWhiteSpace(userName))
            {
                return;
            }

            int? personId     = null;
            bool impersonated = userName.StartsWith("rckipid=");

            using (var rockContext = new RockContext())
            {
                if (!impersonated)
                {
                    var userLogin = new UserLoginService(rockContext).Queryable().Where(a => a.UserName == userName).FirstOrDefault();
                    if (userLogin != null)
                    {
                        userLogin.LastLoginDateTime = RockDateTime.Now;
                        personId = userLogin.PersonId;
                        rockContext.SaveChanges();
                    }
                }
                else
                {
                    var impersonationToken = userName.Substring(8);
                    personId = new PersonService(rockContext).GetByImpersonationToken(impersonationToken, false, null)?.Id;
                }
            }

            if (personId == null)
            {
                return;
            }

            var relatedDataBuilder  = new System.Text.StringBuilder();
            int?relatedEntityTypeId = null;
            int?relatedEntityId     = null;

            if (impersonated)
            {
                var impersonatedByUser = HttpContext.Current?.Session?["ImpersonatedByUser"] as UserLogin;

                relatedEntityTypeId = EntityTypeCache.GetId <Rock.Model.Person>();
                relatedEntityId     = impersonatedByUser?.PersonId;

                if (impersonatedByUser != null)
                {
                    relatedDataBuilder.Append($" impersonated by { impersonatedByUser.Person.FullName }");
                }
            }

            if (HttpContext.Current != null && HttpContext.Current.Request != null)
            {
                string cleanUrl = PersonToken.ObfuscateRockMagicToken(HttpContext.Current.Request.UrlProxySafe().AbsoluteUri);

                // obfuscate the URL specified in the returnurl, just in case it contains any sensitive information (like a rckipid)
                Regex returnurlRegEx = new Regex(@"returnurl=([^&]*)");
                cleanUrl = returnurlRegEx.Replace(cleanUrl, "returnurl=XXXXXXXXXXXXXXXXXXXXXXXXXXXX");

                string clientIPAddress;
                try
                {
                    clientIPAddress = Rock.Web.UI.RockPage.GetClientIpAddress();
                }
                catch
                {
                    // if we get an exception getting the IP Address, just ignore it
                    clientIPAddress = "";
                }

                relatedDataBuilder.AppendFormat(
                    " to <span class='field-value'>{0}</span>, from <span class='field-value'>{1}</span>",
                    cleanUrl,
                    clientIPAddress);
            }

            var historyChangeList = new History.HistoryChangeList();
            var historyChange     = historyChangeList.AddChange(History.HistoryVerb.Login, History.HistoryChangeType.Record, userName);

            if (relatedDataBuilder.Length > 0)
            {
                historyChange.SetRelatedData(relatedDataBuilder.ToString(), null, null);
            }

            var historyList = HistoryService.GetChanges(typeof(Rock.Model.Person), Rock.SystemGuid.Category.HISTORY_PERSON_ACTIVITY.AsGuid(), personId.Value, historyChangeList, null, null, null, null, null);

            if (historyList.Any())
            {
                Task.Run(async() =>
                {
                    // Wait 1 second to allow all post save actions to complete
                    await Task.Delay(1000);
                    try
                    {
                        using (var rockContext = new RockContext())
                        {
                            rockContext.BulkInsert(historyList);
                        }
                    }
                    catch (SystemException ex)
                    {
                        ExceptionLogService.LogException(ex, null);
                    }
                });
            }
        }
Esempio n. 20
0
        /// <summary>
        /// Updates GroupLocationHistorical for any group locations in groups that have data group history enabled
        /// </summary>
        /// <param name="context">The context.</param>
        public void UpdateGroupLocationHistorical(IJobExecutionContext context)
        {
            var rockContext = new RockContext();
            var groupLocationHistoricalService = new GroupLocationHistoricalService(rockContext);
            var groupLocationService           = new GroupLocationService(rockContext);

            var groupLocationsWithHistoryEnabledQuery = groupLocationService.Queryable().Where(a => a.Group.GroupType.EnableGroupHistory == true).AsNoTracking();
            var groupLocationsHistoricalCurrentQuery  = groupLocationHistoricalService.Queryable().Where(a => a.CurrentRowIndicator == true).AsNoTracking();

            // Mark GroupLocationHistorical Rows as History ( CurrentRowIndicator = false, etc ) if any of the tracked field values change
            var groupLocationHistoricalNoLongerCurrentQuery = groupLocationsHistoricalCurrentQuery.Join(
                groupLocationsWithHistoryEnabledQuery,
                glh => glh.GroupLocationId,
                gl => gl.Id, (glh, gl) => new
            {
                GroupLocation           = gl,
                GroupLocationHistorical = glh
            })
                                                              .Where(a =>
                                                                     a.GroupLocation.GroupId != a.GroupLocationHistorical.GroupId ||
                                                                     (a.GroupLocation.GroupLocationTypeValueId != a.GroupLocation.GroupLocationTypeValueId) ||
                                                                     (a.GroupLocation.GroupLocationTypeValueId.HasValue && a.GroupLocation.GroupLocationTypeValue.Value != a.GroupLocationHistorical.GroupLocationTypeName) ||
                                                                     a.GroupLocation.LocationId != a.GroupLocationHistorical.LocationId ||
                                                                     a.GroupLocation.Location.ModifiedDateTime != a.GroupLocationHistorical.LocationModifiedDateTime ||
                                                                     (a.GroupLocation.Schedules.Select(s => new { ScheduleId = s.Id, s.ModifiedDateTime }).Except(a.GroupLocationHistorical.GroupLocationHistoricalSchedules.Select(hs => new { hs.ScheduleId, ModifiedDateTime = hs.ScheduleModifiedDateTime }))).Any()
                                                                     );

            var effectiveExpireDateTime            = RockDateTime.Now;
            int groupLocationsLoggedToHistory      = 0;
            int groupLocationsSaveToHistoryCurrent = 0;

            if (groupLocationHistoricalNoLongerCurrentQuery.Any())
            {
                var groupLocationHistoricalNoLongerCurrent = groupLocationHistoricalNoLongerCurrentQuery.Select(a => a.GroupLocationHistorical).AsNoTracking();

                groupLocationsLoggedToHistory = rockContext.BulkUpdate(groupLocationHistoricalNoLongerCurrent, glh => new GroupLocationHistorical
                {
                    CurrentRowIndicator = false,
                    ExpireDateTime      = effectiveExpireDateTime
                });
            }

            // Insert Group Locations (that have a group with GroupType.EnableGroupHistory) that don't have a CurrentRowIndicator row yet ( or don't have a CurrentRowIndicator because it was stamped with CurrentRowIndicator=false )
            var groupLocationsToAddToHistoricalCurrentsQuery = groupLocationsWithHistoryEnabledQuery.Where(gl => !groupLocationsHistoricalCurrentQuery.Any(glh => glh.GroupLocationId == gl.Id));

            if (groupLocationsToAddToHistoricalCurrentsQuery.Any())
            {
                List <GroupLocationHistorical> groupLocationHistoricalCurrentsToInsert = groupLocationsToAddToHistoricalCurrentsQuery
                                                                                         .Include(a => a.GroupLocationTypeValue)
                                                                                         .Include(a => a.Location).ToList()
                                                                                         .Select(gl => GroupLocationHistorical.CreateCurrentRowFromGroupLocation(gl, effectiveExpireDateTime)).ToList();

                groupLocationsSaveToHistoryCurrent = groupLocationHistoricalCurrentsToInsert.Count();

                // get the current max GroupLocatiionHistorical.Id to help narrow down which ones were inserted
                int groupLocationHistoricalStartId = groupLocationHistoricalService.Queryable().Max(a => ( int? )a.Id) ?? 0;

                rockContext.BulkInsert(groupLocationHistoricalCurrentsToInsert);

                // since we used BulkInsert, we'll need to go back and get the Ids and the associated GroupLocation's Schedules for the GroupLocationHistorical records that we just inserted
                var insertedGroupLocationHistoricalIdsWithSchedules = groupLocationHistoricalService.Queryable()
                                                                      .Where(a => a.Id > groupLocationHistoricalStartId && a.GroupLocation.Schedules.Any()).ToList()
                                                                      .Select(a => new { GroupLocationHistoricalId = a.Id, a.GroupLocation.Schedules });

                List <GroupLocationHistoricalSchedule> groupLocationHistoricalScheduleCurrentsToInsert = new List <GroupLocationHistoricalSchedule>();
                foreach (var insertedGroupLocationHistoricalIdWithSchedules in insertedGroupLocationHistoricalIdsWithSchedules)
                {
                    foreach (Schedule schedule in insertedGroupLocationHistoricalIdWithSchedules.Schedules)
                    {
                        groupLocationHistoricalScheduleCurrentsToInsert.Add(new GroupLocationHistoricalSchedule
                        {
                            GroupLocationHistoricalId = insertedGroupLocationHistoricalIdWithSchedules.GroupLocationHistoricalId,
                            ScheduleId               = schedule.Id,
                            ScheduleName             = schedule.ToString(),
                            ScheduleModifiedDateTime = schedule.ModifiedDateTime
                        });
                    }
                }

                if (groupLocationHistoricalScheduleCurrentsToInsert.Any())
                {
                    rockContext.BulkInsert(groupLocationHistoricalScheduleCurrentsToInsert);
                }
            }

            if (groupLocationsLoggedToHistory > 0)
            {
                _jobStatusMessages.Add($"Logged {groupLocationsLoggedToHistory} {"group location history snapshot".PluralizeIf( groupLocationsLoggedToHistory != 0 )}");
            }

            if (groupLocationsSaveToHistoryCurrent > 0)
            {
                int newGroupLocationsAddedToHistory = groupLocationsSaveToHistoryCurrent - groupLocationsLoggedToHistory;
                if (newGroupLocationsAddedToHistory > 0)
                {
                    _jobStatusMessages.Add($"Added {newGroupLocationsAddedToHistory} new {"group location history snapshot".PluralizeIf( newGroupLocationsAddedToHistory != 0 )}");
                }
            }
        }
        /// <summary>
        /// Logs the interactions.
        /// </summary>
        /// <param name="interactionTransactionInfos">The interaction transaction infos to process.</param>
        /// <param name="rockContext">The rock context.</param>
        private void LogInteractions(List <InteractionTransactionInfo> interactionTransactionInfos, RockContext rockContext)
        {
            List <Interaction> interactionsToInsert = new List <Interaction>();

            var interactionService = new InteractionService(rockContext);

            foreach (var info in interactionTransactionInfos.Where(a => a.InteractionComponentId.HasValue))
            {
                /*
                 * 2020-06-29 - JH
                 *
                 * The 'CreateInteraction(...)' method called below sets the following properties on the Interaction object:
                 *
                 * - InteractionComponentId
                 * - InteractionDateTime (but with the wrong value)
                 * - InteractionSessionId
                 * - Source
                 * - Medium
                 * - Campaign
                 * - Content
                 * - Term
                 */
                var interaction = interactionService.CreateInteraction(info.InteractionComponentId.Value, info.UserAgent, info.InteractionData, info.IPAddress, info.BrowserSessionId);

                // The rest of the properties need to be manually set.
                interaction.EntityId               = info.InteractionEntityId;
                interaction.Operation              = info.InteractionOperation.IsNotNullOrWhiteSpace() ? info.InteractionOperation.Trim() : "View";
                interaction.InteractionSummary     = info.InteractionSummary?.Trim();
                interaction.PersonAliasId          = info.PersonAliasId;
                interaction.InteractionDateTime    = info.InteractionDateTime;
                interaction.InteractionTimeToServe = info.InteractionTimeToServe;
                interaction.RelatedEntityTypeId    = info.InteractionRelatedEntityTypeId;
                interaction.RelatedEntityId        = info.InteractionRelatedEntityId;
                interaction.ChannelCustom1         = info.InteractionChannelCustom1?.Trim();
                interaction.ChannelCustom2         = info.InteractionChannelCustom2?.Trim();
                interaction.ChannelCustomIndexed1  = info.InteractionChannelCustomIndexed1?.Trim();
                interaction.Source                 = interaction.Source ?? info.InteractionSource?.Trim();
                interaction.Medium                 = interaction.Medium ?? info.InteractionMedium?.Trim();
                interaction.Campaign               = interaction.Campaign ?? info.InteractionCampaign?.Trim();
                interaction.Content                = interaction.Content ?? info.InteractionContent?.Trim();
                interaction.Term                   = interaction.Term ?? info.InteractionTerm?.Trim();
                interaction.InteractionLength      = info.InteractionLength;
                interaction.InteractionEndDateTime = info.InteractionEndDateTime;

                interaction.SetInteractionData(info.InteractionData?.Trim());
                interactionsToInsert.Add(interaction);
            }

            rockContext.BulkInsert(interactionsToInsert);

            // This logic is normally handled in the Interaction.PostSave method, but since the BulkInsert bypasses those
            // model hooks, streaks need to be updated here. Also, it is not necessary for this logic to complete before this
            // transaction can continue processing and exit, so update the streak using a task.

            // Only launch this task if there are StreakTypes configured that have interactions. Otherwise several
            // database calls are made only to find out there are no streak types defined.
            if (StreakTypeCache.All().Any(s => s.IsInteractionRelated))
            {
                // Ids do not exit for the interactions in the collection since they were bulk imported.
                // Read their ids from their guids and append the id.
                var insertedGuids = interactionsToInsert.Select(i => i.Guid).ToList();

                var interactionIds = new InteractionService(new RockContext()).Queryable()
                                     .Where(i => insertedGuids.Contains(i.Guid))
                                     .Select(i => new { i.Id, i.Guid })
                                     .ToList();

                foreach (var interactionId in interactionIds)
                {
                    var interaction = interactionsToInsert.Where(i => i.Guid == interactionId.Guid).FirstOrDefault();
                    if (interaction != null)
                    {
                        interaction.Id = interactionId.Id;
                    }
                }

                // Launch task
                interactionsToInsert.ForEach(i => Task.Run(() => StreakTypeService.HandleInteractionRecord(i.Id)));
            }
        }
        /// <summary>
        /// Migrates the scheduled transaction notes to history.
        /// </summary>
        public void MigrateScheduledTransactionNotesToHistory()
        {
            var rockContext = new RockContext();

            rockContext.Database.CommandTimeout = _commandTimeout;
            var noteService       = new NoteService(rockContext);
            var historyCategoryId = CategoryCache.Get(Rock.SystemGuid.Category.HISTORY_FINANCIAL_TRANSACTION.AsGuid())?.Id;
            var entityTypeIdScheduledTransaction = EntityTypeCache.GetId(Rock.SystemGuid.EntityType.FINANCIAL_SCHEDULED_TRANSACTION.AsGuid());
            var noteTypeIdScheduledTransaction   = NoteTypeCache.GetId(Rock.SystemGuid.NoteType.SCHEDULED_TRANSACTION_NOTE.AsGuid());

            if (!historyCategoryId.HasValue || !entityTypeIdScheduledTransaction.HasValue || !noteTypeIdScheduledTransaction.HasValue)
            {
                return;
            }

            var historyService = new HistoryService(rockContext);

            var historyQuery      = historyService.Queryable().Where(a => a.EntityTypeId == entityTypeIdScheduledTransaction.Value);
            var captionsToConvert = new string[]
            {
                "Created Transaction"
                , "Updated Transaction"
                , "Cancelled Transaction"
                , "Reactivated Transaction"
            };

            var notesToConvertToHistory = noteService.Queryable()
                                          .Where(a => a.NoteTypeId == noteTypeIdScheduledTransaction.Value && captionsToConvert.Contains(a.Caption) && a.EntityId.HasValue)
                                          .Where(a => !historyQuery.Any(h => h.EntityId == a.EntityId));

            var notesToConvertToSummaryList = noteService.Queryable()
                                              .Where(a => a.NoteTypeId == noteTypeIdScheduledTransaction.Value && a.Caption == "Created Transaction" && !string.IsNullOrEmpty(a.Text) && a.EntityId.HasValue)
                                              .AsNoTracking().ToList();

            List <History> historyRecordsToInsert = notesToConvertToHistory.AsNoTracking()
                                                    .ToList()
                                                    .Select(n =>
            {
                var historyRecord = new History
                {
                    CategoryId              = historyCategoryId.Value,
                    EntityTypeId            = entityTypeIdScheduledTransaction.Value,
                    EntityId                = n.EntityId.Value,
                    Guid                    = Guid.NewGuid(),
                    CreatedByPersonAliasId  = n.CreatedByPersonAliasId,
                    ModifiedByPersonAliasId = n.ModifiedByPersonAliasId,
                    CreatedDateTime         = n.CreatedDateTime,
                    ModifiedDateTime        = n.ModifiedDateTime
                };

                if (n.Caption == "Cancelled Transaction")
                {
                    historyRecord.Verb       = "MODIFY";
                    historyRecord.ChangeType = "Property";
                    historyRecord.ValueName  = "Is Active";
                    historyRecord.NewValue   = "False";
                }
                else if (n.Caption == "Reactivated Transaction")
                {
                    historyRecord.Verb       = "MODIFY";
                    historyRecord.ChangeType = "Property";
                    historyRecord.ValueName  = "Is Active";
                    historyRecord.NewValue   = "True";
                }
                else if (n.Caption == "Updated Transaction")
                {
                    historyRecord.Verb      = "MODIFY";
                    historyRecord.ValueName = "Transaction";
                }
                else
                {
                    historyRecord.Verb       = "ADD";
                    historyRecord.ChangeType = "Record";
                    historyRecord.ValueName  = "Transaction";
                }

                return(historyRecord);
            }).ToList();

            rockContext.BulkInsert(historyRecordsToInsert);
            var qryNotesToDelete = noteService.Queryable().Where(a => a.NoteTypeId == noteTypeIdScheduledTransaction && captionsToConvert.Contains(a.Caption));

            rockContext.BulkDelete(qryNotesToDelete);

            foreach (var noteToConvertToSummary in notesToConvertToSummaryList)
            {
                using (var updatedSummaryContext = new RockContext())
                {
                    var scheduledTransactionService = new FinancialScheduledTransactionService(updatedSummaryContext);
                    var scheduledTransaction        = scheduledTransactionService.Get(noteToConvertToSummary.EntityId.Value);
                    if (scheduledTransaction != null && scheduledTransaction.Summary.IsNullOrWhiteSpace())
                    {
                        scheduledTransaction.Summary = noteToConvertToSummary.Text;
                        updatedSummaryContext.SaveChanges(disablePrePostProcessing: true);
                    }
                }
            }
        }
Esempio n. 23
0
        /// <summary>
        /// Updates Group Historical for any groups that have data group history enabled
        /// </summary>
        /// <param name="context">The context.</param>
        public void UpdateGroupHistorical(IJobExecutionContext context)
        {
            var rockContext            = new RockContext();
            var groupHistoricalService = new GroupHistoricalService(rockContext);
            var groupService           = new GroupService(rockContext);

            // Note that this query utilizes .AsNoFilter() to avoid having archived groups filtered out by the GroupConfiguration class.
            var groupsWithHistoryEnabledQuery = groupService.AsNoFilter()
                                                .Where(a => a.GroupType.EnableGroupHistory == true)
                                                .AsNoTracking();

            var groupHistoricalsCurrentQuery = groupHistoricalService.Queryable().Where(a => a.CurrentRowIndicator == true).AsNoTracking();

            // Mark GroupHistorical Rows as History ( CurrentRowIndicator = false, etc ) if any of the tracked field values change
            var groupHistoricalNoLongerCurrent = groupHistoricalsCurrentQuery.Join(
                groupsWithHistoryEnabledQuery,
                gh => gh.GroupId,
                g => g.Id, (gh, g) => new
            {
                Group           = g,
                GroupHistorical = gh
            })
                                                 .Where(a =>
                                                        a.Group.Name != a.GroupHistorical.GroupName ||
                                                        a.Group.GroupType.Name != a.GroupHistorical.GroupTypeName ||
                                                        a.Group.CampusId != a.GroupHistorical.CampusId ||
                                                        a.Group.ParentGroupId != a.GroupHistorical.ParentGroupId ||
                                                        a.Group.ScheduleId != a.GroupHistorical.ScheduleId ||
                                                        (a.Group.ScheduleId.HasValue && (a.Group.Schedule.ModifiedDateTime != a.GroupHistorical.ScheduleModifiedDateTime)) ||
                                                        a.Group.Description != a.GroupHistorical.Description ||
                                                        a.Group.StatusValueId != a.GroupHistorical.StatusValueId ||
                                                        a.Group.IsArchived != a.GroupHistorical.IsArchived ||
                                                        a.Group.ArchivedDateTime != a.GroupHistorical.ArchivedDateTime ||
                                                        a.Group.ArchivedByPersonAliasId != a.GroupHistorical.ArchivedByPersonAliasId ||
                                                        a.Group.IsActive != a.GroupHistorical.IsActive ||
                                                        a.Group.InactiveDateTime != a.GroupHistorical.InactiveDateTime
                                                        ).Select(a => a.GroupHistorical).AsNoTracking();

            var effectiveExpireDateTime = RockDateTime.Now;

            int groupsLoggedToHistory      = 0;
            int groupsSaveToHistoryCurrent = 0;

            if (groupHistoricalNoLongerCurrent.Any())
            {
                groupsLoggedToHistory = rockContext.BulkUpdate(groupHistoricalNoLongerCurrent, gh => new GroupHistorical
                {
                    CurrentRowIndicator = false,
                    ExpireDateTime      = effectiveExpireDateTime
                });
            }

            // Insert Groups (that have GroupType.EnableGroupHistory) that don't have a CurrentRowIndicator row yet ( or don't have a CurrentRowIndicator because it was stamped with CurrentRowIndicator=false )
            var groupsToAddToHistoricalCurrentsQuery = groupsWithHistoryEnabledQuery.Where(g => !groupHistoricalsCurrentQuery.Any(gh => gh.GroupId == g.Id)).AsNoTracking();

            if (groupsToAddToHistoricalCurrentsQuery.Any())
            {
                List <GroupHistorical> groupHistoricalCurrentsToInsert = groupsToAddToHistoricalCurrentsQuery
                                                                         .Include(a => a.GroupType)
                                                                         .Include(a => a.Schedule)
                                                                         .ToList()
                                                                         .Select(g => GroupHistorical.CreateCurrentRowFromGroup(g, effectiveExpireDateTime)).ToList();

                groupsSaveToHistoryCurrent = groupHistoricalCurrentsToInsert.Count();

                rockContext.BulkInsert(groupHistoricalCurrentsToInsert);
            }

            if (groupsLoggedToHistory > 0)
            {
                _jobStatusMessages.Add($"Logged {groupsLoggedToHistory} {"group history snapshot".PluralizeIf( groupsLoggedToHistory != 0 )}");
            }

            if (groupsSaveToHistoryCurrent > 0)
            {
                int newGroupsAddedToHistory = groupsSaveToHistoryCurrent - groupsLoggedToHistory;
                if (newGroupsAddedToHistory > 0)
                {
                    _jobStatusMessages.Add($"Added {newGroupsAddedToHistory} new {"group history snapshot".PluralizeIf( newGroupsAddedToHistory != 0 )}");
                }
            }
        }
        /// <summary>
        /// Handles the Click event of the btnCreateCommunication 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 btnCreateCommunication_Click(object sender, EventArgs e)
        {
            // Create communication
            var rockContext          = new RockContext();
            var communicationService = new Rock.Model.CommunicationService(rockContext);
            var communication        = new Rock.Model.Communication();

            communication.IsBulkCommunication = false;
            communication.Status = CommunicationStatus.Transient;
            communication.SenderPersonAliasId = this.CurrentPersonAliasId;

            if (this.Request != null && this.Request.Url != null)
            {
                communication.UrlReferrer = this.Request.UrlProxySafe().AbsoluteUri.TrimForMaxLength(communication, "UrlReferrer");
            }

            communicationService.Add(communication);

            // save communication to get Id
            rockContext.SaveChanges();

            int[]      scheduleIds    = lbSchedules.SelectedValuesAsInt.ToArray();
            int[]      locationIds    = cblLocations.SelectedValuesAsInt.ToArray();
            List <int> parentGroupIds = gpGroups.SelectedValuesAsInt().ToList();

            var allGroupIds = new List <int>();

            allGroupIds.AddRange(parentGroupIds);

            if (cbIncludeChildGroups.Checked)
            {
                var groupService = new GroupService(rockContext);
                foreach (var groupId in parentGroupIds)
                {
                    // just the first level of child groups, not all decendants
                    var childGroupIds = groupService.Queryable()
                                        .Where(a => a.ParentGroupId == groupId && a.GroupType.IsSchedulingEnabled && !a.DisableScheduling)
                                        .Select(a => a.Id).ToList();
                    allGroupIds.AddRange(childGroupIds);
                }
            }

            allGroupIds = allGroupIds.Distinct().ToList();

            var attendanceOccurrenceService = new AttendanceOccurrenceService(rockContext);

            var sundayDate = ddlWeek.SelectedValue.AsDateTime() ?? RockDateTime.Now.SundayDate();

            var attendanceOccurrenceQuery = attendanceOccurrenceService
                                            .Queryable()
                                            .Where(a => a.ScheduleId.HasValue && a.LocationId.HasValue && a.GroupId.HasValue)
                                            .WhereDeducedIsActive()
                                            .Where(a => allGroupIds.Contains(a.GroupId.Value))
                                            .Where(a => locationIds.Contains(a.LocationId.Value))
                                            .Where(a => scheduleIds.Contains(a.ScheduleId.Value))
                                            .Where(a => a.SundayDate == sundayDate);

            ScheduledAttendanceItemStatus[] selectedInviteStatus = cblInviteStatus.SelectedValues
                                                                   .Select(a => a.ConvertToEnum <ScheduledAttendanceItemStatus>())
                                                                   .ToArray();

            // limit attendees to ones based on the selected invite status
            var scheduledAttendancesForOccurrenceQuery = attendanceOccurrenceQuery
                                                         .SelectMany(a => a.Attendees)
                                                         .WhereHasScheduledAttendanceItemStatus(selectedInviteStatus);

            var personIds = scheduledAttendancesForOccurrenceQuery.Select(a => a.PersonAlias.PersonId).Distinct().ToList();

            if (!personIds.Any())
            {
                nbCommunicationWarning.Text    = "No people found to send communication to.";
                nbCommunicationWarning.Visible = true;
                return;
            }

            nbCommunicationWarning.Visible = false;

            var personAliasService = new Rock.Model.PersonAliasService(new Rock.Data.RockContext());

            // Get the primary aliases
            List <Rock.Model.PersonAlias> primaryAliasList = new List <PersonAlias>(personIds.Count);

            // get the data in chunks just in case we have a large list of PersonIds (to avoid a SQL Expression limit error)
            var chunkedPersonIds = personIds.Take(1000);
            int skipCount        = 0;

            while (chunkedPersonIds.Any())
            {
                var chunkedPrimaryAliasList = personAliasService.Queryable()
                                              .Where(p => p.PersonId == p.AliasPersonId && chunkedPersonIds.Contains(p.PersonId)).AsNoTracking().ToList();
                primaryAliasList.AddRange(chunkedPrimaryAliasList);
                skipCount       += 1000;
                chunkedPersonIds = personIds.Skip(skipCount).Take(1000);
            }

            // NOTE: Set CreatedDateTime, ModifiedDateTime, etc manually set we are using BulkInsert
            var currentDateTime      = RockDateTime.Now;
            var currentPersonAliasId = this.CurrentPersonAliasId;

            var communicationRecipientList = primaryAliasList.Select(a => new Rock.Model.CommunicationRecipient
            {
                CommunicationId         = communication.Id,
                PersonAliasId           = a.Id,
                CreatedByPersonAliasId  = currentPersonAliasId,
                ModifiedByPersonAliasId = currentPersonAliasId,
                CreatedDateTime         = currentDateTime,
                ModifiedDateTime        = currentDateTime
            }).ToList();

            // BulkInsert to quickly insert the CommunicationRecipient records. Note: This is much faster, but will bypass EF and Rock processing.
            var communicationRecipientRockContext = new RockContext();

            communicationRecipientRockContext.BulkInsert(communicationRecipientList);

            var    pageRef = this.RockPage.Site.CommunicationPageReference;
            string communicationUrl;

            if (pageRef.PageId > 0)
            {
                pageRef.Parameters.AddOrReplace("CommunicationId", communication.Id.ToString());
                communicationUrl = pageRef.BuildUrl();
            }
            else
            {
                communicationUrl = "~/Communication/{0}";
            }

            if (communicationUrl.Contains("{0}"))
            {
                communicationUrl = string.Format(communicationUrl, communication.Id);
            }

            UserPreferenceConfiguration userPreferenceConfiguration = this.GetBlockUserPreference(UserPreferenceKey.UserPreferenceConfigurationJSON).FromJsonOrNull <UserPreferenceConfiguration>() ?? new UserPreferenceConfiguration();

            userPreferenceConfiguration.GroupIds           = gpGroups.SelectedValuesAsInt().ToArray();
            userPreferenceConfiguration.IncludeChildGroups = cbIncludeChildGroups.Checked;
            userPreferenceConfiguration.InviteStatuses     = cblInviteStatus.SelectedValues.ToArray();
            userPreferenceConfiguration.ScheduleIds        = lbSchedules.SelectedValuesAsInt.ToArray();
            userPreferenceConfiguration.LocationIds        = cblLocations.SelectedValuesAsInt.ToArray();
            this.SetBlockUserPreference(UserPreferenceKey.UserPreferenceConfigurationJSON, userPreferenceConfiguration.ToJson());

            Page.Response.Redirect(communicationUrl, false);
            Context.ApplicationInstance.CompleteRequest();
        }
Esempio n. 25
0
        /// <summary>
        /// Updates GroupMemberHistorical for any group members in groups that have data group history enabled
        /// </summary>
        /// <param name="context">The context.</param>
        public void UpdateGroupMemberHistorical(IJobExecutionContext context)
        {
            var rockContext = new RockContext();
            var groupMemberHistoricalService = new GroupMemberHistoricalService(rockContext);
            var groupMemberService           = new GroupMemberService(rockContext);

            var groupMembersWithHistoryEnabledQuery = groupMemberService.AsNoFilter().Where(a => a.Group.GroupType.EnableGroupHistory == true).AsNoTracking();
            var groupMemberHistoricalsCurrentQuery  = groupMemberHistoricalService.Queryable().Where(a => a.CurrentRowIndicator == true).AsNoTracking();

            // Mark GroupMemberHistorical Rows as History ( CurrentRowIndicator = false, etc ) if any of the tracked field values change
            var groupMemberHistoricalNoLongerCurrent = groupMemberHistoricalsCurrentQuery.Join(
                groupMembersWithHistoryEnabledQuery,
                gmh => gmh.GroupMemberId,
                gm => gm.Id, (gmh, gm) => new
            {
                GroupMember           = gm,
                GroupMemberHistorical = gmh
            })
                                                       .Where(a =>
                                                              a.GroupMember.GroupRoleId != a.GroupMemberHistorical.GroupRoleId ||
                                                              a.GroupMember.GroupId != a.GroupMemberHistorical.GroupId ||
                                                              a.GroupMember.GroupRole.Name != a.GroupMemberHistorical.GroupRoleName ||
                                                              a.GroupMember.GroupRole.IsLeader != a.GroupMemberHistorical.IsLeader ||
                                                              a.GroupMember.GroupMemberStatus != a.GroupMemberHistorical.GroupMemberStatus ||
                                                              a.GroupMember.IsArchived != a.GroupMemberHistorical.IsArchived ||
                                                              a.GroupMember.ArchivedDateTime != a.GroupMemberHistorical.ArchivedDateTime ||
                                                              a.GroupMember.ArchivedByPersonAliasId != a.GroupMemberHistorical.ArchivedByPersonAliasId ||
                                                              a.GroupMember.InactiveDateTime != a.GroupMemberHistorical.InactiveDateTime
                                                              ).Select(a => a.GroupMemberHistorical).AsNoTracking();

            var effectiveExpireDateTime = RockDateTime.Now;

            int groupMembersLoggedToHistory      = 0;
            int groupMembersSaveToHistoryCurrent = 0;

            if (groupMemberHistoricalNoLongerCurrent.Any())
            {
                groupMembersLoggedToHistory = rockContext.BulkUpdate(groupMemberHistoricalNoLongerCurrent, gmh => new GroupMemberHistorical
                {
                    CurrentRowIndicator = false,
                    ExpireDateTime      = effectiveExpireDateTime
                });
            }

            // Insert Group Members (that have a group with GroupType.EnableGroupHistory) that don't have a CurrentRowIndicator row yet ( or don't have a CurrentRowIndicator because it was stamped with CurrentRowIndicator=false )
            var groupMembersToAddToHistoricalCurrentsQuery = groupMembersWithHistoryEnabledQuery.Where(gm => !groupMemberHistoricalsCurrentQuery.Any(gmh => gmh.GroupMemberId == gm.Id));

            if (groupMembersToAddToHistoricalCurrentsQuery.Any())
            {
                List <GroupMemberHistorical> groupMemberHistoricalCurrentsToInsert = groupMembersToAddToHistoricalCurrentsQuery
                                                                                     .Include(a => a.GroupRole)
                                                                                     .ToList()
                                                                                     .Select(gm => GroupMemberHistorical.CreateCurrentRowFromGroupMember(gm, effectiveExpireDateTime)).ToList();

                groupMembersSaveToHistoryCurrent = groupMemberHistoricalCurrentsToInsert.Count();

                rockContext.BulkInsert(groupMemberHistoricalCurrentsToInsert);
            }

            if (groupMembersLoggedToHistory > 0)
            {
                _jobStatusMessages.Add($"Logged {groupMembersLoggedToHistory} {"group member history snapshot".PluralizeIf( groupMembersLoggedToHistory != 0 )}");
            }

            if (groupMembersSaveToHistoryCurrent > 0)
            {
                int newGroupMembersAddedToHistory = groupMembersSaveToHistoryCurrent - groupMembersLoggedToHistory;
                if (newGroupMembersAddedToHistory > 0)
                {
                    _jobStatusMessages.Add($"Added {newGroupMembersAddedToHistory} new {"group member history snapshot".PluralizeIf( newGroupMembersAddedToHistory != 0 )}");
                }
            }
        }