diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..5f8b362 --- /dev/null +++ b/404.html @@ -0,0 +1,1316 @@ + + + +
+ + + + + + + + + + + + + +Tags
+Occasionally a block's attribute value will get so messed up that you can't even load the page to fix it. (Usually caused by a bad lava template). These instructions will help to fix that issue.
+Sometimes you can't load the page itself, but you can still access its block settings through Admin Tools > CMS Configuration > Pages
. If that page also errors out, then SQL is probably your only option.
This query will give you a list of all the blocks on the specified page.
+DECLARE @PageId int = 3; --Replace with the Id of your broken page
+
+SELECT
+ b.[Id] 'BlockId'
+ ,bt.[Name] 'BlockType'
+ ,b.[Name]
+FROM
+ [Block] b
+ JOIN [BlockType] bt ON b.[BlockTypeId] = bt.[Id]
+WHERE b.[PageId] = @PageId
+
This query will list all of the attributes of the specified block.
+DECLARE @BlockId int = 10; --Replace with the Id of the block that has the corrupted attribute
+
+DECLARE @BlockEntityTypeId int = ( SELECT [Id] FROM [EntityType] WHERE [Name] = 'Rock.Model.Block' );
+SELECT
+ av.[Id] 'AttributeValueId'
+ ,a.[Name]
+ ,av.[Value]
+FROM
+ [Attribute] a
+ JOIN [AttributeValue] av ON a.[Id] = av.[AttributeId]
+WHERE
+ a.[EntityTypeId] = @BlockEntityTypeId
+ AND av.[EntityId] = @BlockId
+
There are 2 options here. We can either try to save the work we've done by commenting it out, or we can delete the corrupted value and let Rock set it back to the default value.
+Be careful. A typo in this step can destroy data!
+Option 1 This query will attempt to "comment out" the value. You can use this in cases of a bad lava template causing issues, but it won't work with any other type of attribute.
+DECLARE @AttributeValueId int = 123; --Replace with the Id of the corrupted attribute
+
+UPDATE [AttributeValue]
+SET [Value] = '{% comment %}{% raw %}' + [Value] + '{% endraw %}{% endcomment %}'
+WHERE [Id] = @AttributeValueId
+
Option 2 This query will delete the corrupted attribute; causing Rock to set it back to its default value.
+DECLARE @AttributeValueId int = 123; --Replace with the Id of the corrupted attribute
+
+DELETE
+FROM [AttributeValue]
+WHERE [Id] = @AttributeValueId
+
Once you have cleared the cache and reloaded the site, you should be able to access the page again.
+ + + +Tags
+Returns a list of all HTML content blocks on a specified page, along with their current contents.
+DECLARE @PageId int = 12; --Which page to report on?
+DECLARE @HtmlContentBlockType int = ( SELECT [Id] FROM [BlockType] WHERE [Guid] = '19B61D65-37E3-459F-A44F-DEF0089118A3' );
+
+SELECT
+ b.[Id] 'BlockId'
+ ,b.[Zone]
+ --,b.[Order]
+ ,b.[Name]
+ ,h.[Id] 'HtmlContentId'
+ ,h.[Content]
+ --,h.[Version]
+FROM
+ [Block] b
+ JOIN [HtmlContent] h ON b.[Id] = h.[BlockId]
+WHERE
+ b.[BlockTypeId] = @HtmlContentBlockType
+ AND b.[PageId] = @PageId
+ORDER BY
+ b.[Zone]
+ ,b.[Order]
+ ,h.[Version] DESC
+
Tags
+++Credit: Randy Aufrecht
+
Returns a list of all content channel items in a specified content channel. Includes media watch stats for the media element linked to them.
+Name | +Description | +
---|---|
Id | +Content Channel Item Id | +
MediaId | +Media Element Id | +
Title | +Content Channel Item Title | +
MinutesWatched | +Total number of minutes watched | +
TotalInteractions | +Total number of times someone has interacted with the video | +
AvgMinutesPerInteracction | +MinutesWatched ÷ TotalInteractions | +
UniqueUsers | +Number of unique users that interacted with the content (must have been logged in) | +
AvgMinutesPerUser | +MinutesWatched ÷ UniqueUsers | +
CreatedDateTime | +Created Date Time for the Media Element | +
/* Messages */
+DECLARE @ContentChannelId int = 5;
+DECLARE @VideoAttributeId int = 12233;
+
+/* Worhsip Archives - Worship
+DECLARE @ContentChannelId int = 68;
+DECLARE @VideoAttributeId int = 12528;
+*/
+/* Worhsip Archives - Multiviewer
+DECLARE @ContentChannelId int = 68;
+DECLARE @VideoAttributeId int = 12523;
+*/
+
+DECLARE @MediaEventsInteractionChannelId int = (SELECT [Id] FROM [InteractionChannel] WHERE [Guid] = 'D5B9BDAF-6E52-40D5-8E74-4E23973DF159');
+
+SELECT
+ cci.[Id]
+ ,me.[Id] 'MediaId'
+ ,cci.[Title]
+ ,ROUND((SUM(i.[InteractionLength]) / 100 * me.[DurationSeconds]) / 60, 0) 'MinutesWatched'
+ ,COUNT(i.[Id]) 'TotalInteractions'
+ ,ROUND(((SUM(i.[InteractionLength]) / 100 * me.[DurationSeconds]) / 60) / NULLIF(COUNT(i.[Id]),0), 1) 'AvgMinutesPerInteraction'
+ ,COUNT(DISTINCT pa.[PersonId]) 'UniqueUsers-LoggedIn'
+ ,ROUND(((SUM(i.[InteractionLength]) / 100 * me.[DurationSeconds]) / 60) / NULLIF(COUNT(DISTINCT pa.[PersonId]),0), 1) 'AvgMinutesPerUser'
+ ,me.[CreatedDateTime]
+FROM
+ [ContentChannelItem] cci
+ INNER JOIN [AttributeValue] av ON
+ av.[AttributeId] = @VideoAttributeId
+ AND cci.[Id] = av.[EntityId]
+ INNER JOIN [MediaElement] me ON TRY_CAST(av.[Value] AS UNIQUEIDENTIFIER) = me.[Guid]
+ LEFT JOIN [InteractionComponent] ic ON
+ ic.[InteractionChannelId] = @MediaEventsInteractionChannelId
+ AND ic.[EntityId] = me.[Id]
+ LEFT JOIN [Interaction] i ON i.[InteractionComponentId] = ic.[Id]
+ LEFT JOIN [PersonAlias] pa ON i.[PersonAliasId] = pa.[Id]
+WHERE cci.[ContentChannelId] = @ContentChannelId
+GROUP BY
+ cci.[Id]
+ ,cci.[Title]
+ ,cci.[StartDateTime]
+ ,me.[Id]
+ ,me.[DurationSeconds]
+ ,me.[CreatedDateTime]
+ORDER BY cci.[StartDateTime] DESC
+
Tags
+Rock stores a person's graduation year, not their grade. It turns out that is is actually quite difficult to convert that value into a grade string since you have to take into account the transition date set in Rock.
+/* Setup Info */
+DECLARE @GradeDefinedTypeId int = ( SELECT [Id] FROM [DefinedType] WHERE [Guid] = '24E5A79F-1E62-467A-AD5D-0D10A2328B4D' );
+DECLARE @GradeTransitionDateAttributeId int = ( SELECT [Id] FROM [Attribute] WHERE [Guid] = '265734a6-c888-45b4-a7a5-9a26478306b8' );
+DECLARE @ThisYear nvarchar(4) = ( SELECT FORMAT( GETDATE(), 'yyyy' ) );
+DECLARE @GradeTransitionDate datetime = ( SELECT CAST( [Value] + '/' + @ThisYear AS datetime ) FROM [AttributeValue] WHERE [AttributeId] = @GradeTransitionDateAttributeId );
+
+/* Person Info */
+DECLARE @PersonId int = 50532;
+DECLARE @GradYear int = ( SELECT [GraduationYear] FROM [Person] WHERE [Id] = @PersonId );
+DECLARE @GradeOffset int = ( SELECT dbo.[ufnCrm_GetGradeOffset]( @GradYear, @GradeTransitionDate ) );
+
+SELECT [Description]
+FROM [DefinedValue]
+WHERE
+ [DefinedTypeId] = @GradeDefinedTypeId
+ AND [Value] = @GradeOffset
+;
+
Tags
+++Credit: Daniel Hazelbaker
+
Finds all people with a home address but no mailing address. Optionally sets the mailing address flag on their first home address.
+BEGIN TRANSACTION
+
+CREATE TABLE #HasHomeAddress ([PersonId] int)
+CREATE TABLE #HasMailingAddress ([PersonId] int)
+CREATE TABLE #MissingMailingFlag ([PersonId] int, [GroupLocationId] int NULL)
+
+DECLARE @FamilyGroupTypeId int = (SELECT [Id] FROM [GroupType] WHERE [Guid] = '790E3215-3B10-442B-AF69-616C0DCB998E')
+DECLARE @HomeAddressValueId int = (SELECT [Id] FROM [DefinedValue] WHERE [Guid] = '8C52E53C-2A66-435A-AE6E-5EE307D9A0DC')
+DECLARE @PersonRecordTypeValueId int = (SELECT [Id] FROM [DefinedValue] WHERE [Guid] = '36CF10D6-C695-413D-8E7C-4546EFEF385E')
+
+-- Find every Person who has a Home address type.
+INSERT INTO #HasHomeAddress
+SELECT DISTINCT P.[Id]
+FROM
+ [Person] AS P
+ INNER JOIN [GroupMember] AS GM ON GM.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS G ON G.[Id] = GM.[GroupId]
+ INNER JOIN [GroupLocation] AS GL ON GL.[GroupId] = G.[Id]
+WHERE
+ G.[GroupTypeId] = @FamilyGroupTypeId
+ AND P.[RecordTypeValueId] = @PersonRecordTypeValueId
+ AND GL.[GroupLocationTypeValueId] = @HomeAddressValueId
+
+-- Find every Person who has a mailing address.
+INSERT INTO #HasMailingAddress
+SELECT DISTINCT P.[Id]
+FROM
+ [Person] AS P
+ INNER JOIN [GroupMember] AS GM ON GM.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS G ON G.[Id] = GM.[GroupId]
+ INNER JOIN [GroupLocation] AS GL ON GL.[GroupId] = G.[Id]
+WHERE
+ G.[GroupTypeId] = @FamilyGroupTypeId
+ AND GL.[IsMailingLocation] = 1
+
+-- Find every Person who has a Home address but does NOT have a Mailing address.
+INSERT INTO #MissingMailingFlag ([PersonId])
+SELECT H.[PersonId]
+FROM
+ #HasHomeAddress AS H
+ LEFT JOIN #HasMailingAddress AS M ON M.[PersonId] = H.[PersonId]
+WHERE M.[PersonId] IS NULL
+
+-- Update the list of people with their first found Home address.
+UPDATE M
+SET M.[GroupLocationId] = (
+ SELECT TOP 1 GL.[Id]
+ FROM
+ [Person] AS P
+ INNER JOIN [GroupMember] AS GM ON GM.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS G ON G.[Id] = GM.[GroupId]
+ INNER JOIN [GroupLocation] AS GL ON GL.[GroupId] = G.[Id]
+ WHERE
+ P.[Id] = M.[PersonId]
+ AND G.[GroupTypeId] = @FamilyGroupTypeId
+ AND GL.[GroupLocationTypeValueId] = @HomeAddressValueId
+)
+FROM #MissingMailingFlag AS M
+
+SELECT * FROM #MissingMailingFlag
+
+-- Uncomment the following to mark the first Home address as mailing
+/*
+UPDATE GL
+SET GL.[IsMailingLocation] = 1
+FROM
+ [GroupLocation] AS GL
+ INNER JOIN #MissingMailingFlag AS M ON M.[GroupLocationId] = GL.[Id]
+*/
+
+DROP TABLE #HasHomeAddress
+DROP TABLE #HasMailingAddress
+DROP TABLE #MissingMailingFlag
+
+ROLLBACK TRANSACTION
+--COMMIT TRANSACTION
+
Tags
+++Credit: Daniel Hazelbaker
+
Finds all child records that have the same email as a parent, and removes the email from the child. It also records the change to person history for tracking purposes.
+DECLARE @FamilyTypeId int = (SELECT [Id] FROM [GroupType] WHERE [Guid] = '790E3215-3B10-442B-AF69-616C0DCB998E');
+DECLARE @ChildEmailPersonIds TABLE ([Id] int);
+
+-- Find all < 18 people that have the same email as a family member that is > 18
+INSERT INTO @ChildEmailPersonIds
+SELECT DISTINCT P.[Id]
+FROM
+ [Person] AS P
+ INNER JOIN [Person] AS P2 ON P2.[Email] = P.[Email]
+ INNER JOIN [GroupMember] AS FM ON FM.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS F ON F.[Id] = FM.[GroupId]
+ INNER JOIN [GroupMember] AS FM2 ON FM2.[PersonId] = P2.[Id]
+ INNER JOIN [Group] AS F2 ON F2.[Id] = FM2.[GroupId]
+WHERE
+ F.[GroupTypeId] = @FamilyTypeId
+ AND F2.[GroupTypeId] = @FamilyTypeId
+ AND F.[Id] = F2.[Id]
+ AND dbo.ufnCrm_GetAge(P.[BirthDate]) < 18
+ AND dbo.ufnCrm_GetAge(P2.[BirthDate]) >= 18
+ AND P.[Email] IS NOT NULL
+ AND P.[Email] != ''
+;
+-- Find all child relationship Person records that have the same e-mail as their parent.
+DECLARE @KnownRelationshipTypeId int = (SELECT [Id] FROM [GroupType] WHERE [Guid] = 'E0C5A0E2-B7B3-4EF4-820D-BBF7F9A374EF');
+DECLARE @OwnerRelationshipId int = (SELECT [Id] FROM [GroupTypeRole] WHERE [Guid] = '7BC6C12E-0CD1-4DFD-8D5B-1B35AE714C42');
+DECLARE @ChildRelationshipId int = (SELECT [Id] FROM [GroupTypeRole] WHERE [Guid] = 'F87DF00F-E86D-4771-A3AE-DBF79B78CF5D');
+DECLARE @StepChildRelationshipId int = (SELECT [Id] FROM [GroupTypeRole] WHERE [Guid] = 'EFD2D6D1-A407-4EFB-9086-5DF1F19B7D93');
+DECLARE @ChildRelationshipEmailPersonIds TABLE ([Id] int);
+
+INSERT INTO @ChildRelationshipEmailPersonIds
+SELECT DISTINCT P2.[Id]
+FROM
+ [Person] AS P
+ INNER JOIN [GroupMember] AS FM ON FM.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS KR ON KR.[Id] = FM.[GroupId]
+ INNER JOIN [Group] AS KR2 ON KR2.[Id] = KR.[Id]
+ INNER JOIN [GroupMember] AS FM2 ON FM2.[GroupId] = KR2.[Id]
+ INNER JOIN [Person] AS P2 ON P2.[Id] = FM2.[PersonId]
+WHERE
+ KR.[GroupTypeId] = @KnownRelationshipTypeId
+ AND KR2.[GroupTypeId] = @KnownRelationshipTypeId
+ AND KR.[Id] = KR2.[Id]
+ AND P.[Id] != P2.[Id]
+ AND FM.[GroupRoleId] = @OwnerRelationshipId
+ AND ( FM2.[GroupRoleId] = @ChildRelationshipId OR FM2.[GroupRoleId] = @StepChildRelationshipId )
+ AND P.[Email] = P2.[Email]
+ AND P.[Email] IS NOT NULL
+ AND P.[Email] != ''
+;
+-- Combine both lists into one.
+DECLARE @PersonIds TABLE ([Id] int, [OldValue] varchar(100))
+INSERT INTO @PersonIds
+SELECT DISTINCT IQ.[Id], NULL
+FROM (
+ SELECT [Id] FROM @ChildEmailPersonIds
+ UNION
+ SELECT [Id] FROM @ChildRelationshipEmailPersonIds
+) AS IQ
+ORDER BY IQ.[Id]
+;
+-- Add the current (old) email to the list of people.
+UPDATE P
+SET P.[OldValue] = P2.[Email]
+FROM
+ @PersonIds AS P
+ INNER JOIN [Person] AS P2 ON P2.[Id] = P.[Id]
+;
+-- Add History records showing that we are removing the email.
+INSERT INTO [History] (
+ [IsSystem]
+ ,[CategoryId]
+ ,[EntityTypeId]
+ ,[EntityId]
+ ,[Guid]
+ ,[CreatedDateTime]
+ ,[ModifiedDateTime]
+ ,[Verb]
+ ,[ChangeType]
+ ,[ValueName]
+ ,[OldValue]
+ ,[IsSensitive]
+)
+SELECT
+ 0
+ ,133
+ ,15
+ ,P.[Id]
+ ,NEWID()
+ ,GETDATE()
+ ,GETDATE()
+ ,'MODIFY'
+ ,'Property'
+ ,'Email'
+ ,P.OldValue
+ ,0
+FROM @PersonIds AS P
+;
+-- Remove the email addresses.
+UPDATE [Person] SET [Email] = '' WHERE [Id] IN (SELECT [Id] FROM @PersonIds);
+
Tags
+++Credit: Daniel Hazelbaker
+
Finds all child records that have the same mobile phone as a parent, and removes the email from the child. It also records the change to person history for tracking purposes.
+DECLARE @MobilePhoneTypeId int = (SELECT [Id] FROM [DefinedValue] WHERE [Guid] = '407E7E45-7B2E-4FCD-9605-ECB1339F2453');
+DECLARE @FamilyTypeId int = (SELECT [Id] FROM [GroupType] WHERE [Guid] = '790E3215-3B10-442B-AF69-616C0DCB998E');
+DECLARE @ChildPhonePersonIds TABLE ([Id] int);
+
+-- Find all < 18 people that have the same mobile number as a family member that is > 18
+INSERT INTO @ChildPhonePersonIds
+SELECT DISTINCT P.[Id]
+FROM
+ [PhoneNumber] AS PN
+ INNER JOIN [PhoneNumber] AS PN2 ON PN2.[Number] = PN.[Number] AND PN2.[Id] != PN.[Id]
+ INNER JOIN [Person] AS P ON P.[Id] = PN.[PersonId]
+ INNER JOIN [Person] AS P2 ON P2.[Id] = PN2.[PersonId]
+ INNER JOIN [GroupMember] AS FM ON FM.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS F ON F.[Id] = FM.[GroupId]
+ INNER JOIN [GroupMember] AS FM2 ON FM2.[PersonId] = P2.[Id]
+ INNER JOIN [Group] AS F2 ON F2.[Id] = FM2.[GroupId]
+WHERE
+ PN.[NumberTypeValueId] = @MobilePhoneTypeId
+ AND F.[GroupTypeId] = @FamilyTypeId
+ AND F2.[GroupTypeId] = @FamilyTypeId
+ AND F.[Id] = F2.[Id]
+ AND dbo.ufnCrm_GetAge(P.[BirthDate]) < 18
+ AND dbo.ufnCrm_GetAge(P2.[BirthDate]) >= 18
+;
+-- Find all child relationship Person records that have the same mobile phone as their parent.
+DECLARE @KnownRelationshipTypeId int = (SELECT [Id] FROM [GroupType] WHERE [Guid] = 'E0C5A0E2-B7B3-4EF4-820D-BBF7F9A374EF');
+DECLARE @OwnerRelationshipId int = (SELECT [Id] FROM [GroupTypeRole] WHERE [Guid] = '7BC6C12E-0CD1-4DFD-8D5B-1B35AE714C42');
+DECLARE @ChildRelationshipId int = (SELECT [Id] FROM [GroupTypeRole] WHERE [Guid] = 'F87DF00F-E86D-4771-A3AE-DBF79B78CF5D');
+DECLARE @StepChildRelationshipId int = (SELECT [Id] FROM [GroupTypeRole] WHERE [Guid] = 'EFD2D6D1-A407-4EFB-9086-5DF1F19B7D93');
+DECLARE @ChildRelationshipPhonePersonIds TABLE ([Id] int);
+
+INSERT INTO @ChildRelationshipPhonePersonIds
+SELECT DISTINCT P2.[Id]
+FROM
+ [Person] AS P
+ INNER JOIN [GroupMember] AS FM ON FM.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS KR ON KR.[Id] = FM.[GroupId]
+ INNER JOIN [PhoneNumber] AS PN ON PN.[PersonId] = P.[Id]
+ INNER JOIN [Group] AS KR2 ON KR2.[Id] = KR.[Id]
+ INNER JOIN [GroupMember] AS FM2 ON FM2.[GroupId] = KR2.[Id]
+ INNER JOIN [Person] AS P2 ON P2.[Id] = FM2.[PersonId]
+ INNER JOIN [PhoneNumber] AS PN2 ON PN2.[PersonId] = P2.[Id]
+WHERE
+ KR.[GroupTypeId] = @KnownRelationshipTypeId
+ AND KR2.[GroupTypeId] = @KnownRelationshipTypeId
+ AND KR.[Id] = KR2.[Id]
+ AND P.[Id] != P2.[Id]
+ AND FM.[GroupRoleId] = @OwnerRelationshipId
+ AND ( FM2.[GroupRoleId] = @ChildRelationshipId OR FM2.[GroupRoleId] = @StepChildRelationshipId )
+ AND PN.[NumberTypeValueId] = @MobilePhoneTypeId
+ AND PN2.[NumberTypeValueId] = @MobilePhoneTypeId
+ AND PN.[Number] = PN2.[Number]
+;
+-- Combine both lists into one.
+DECLARE @PersonIds TABLE ([Id] int, [OldValue] varchar(100))
+INSERT INTO @PersonIds
+SELECT DISTINCT IQ.[Id], NULL
+FROM (
+ SELECT [Id] FROM @ChildPhonePersonIds
+ UNION
+ SELECT [Id] FROM @ChildRelationshipPhonePersonIds
+) AS IQ
+ORDER BY IQ.[Id]
+;
+-- Add the current (old) number to the list of people.
+UPDATE P
+SET P.[OldValue] = PN.[NumberFormatted]
+FROM @PersonIds AS P
+INNER JOIN [PhoneNumber] AS PN ON PN.[PersonId] = P.[Id] AND PN.[NumberTypeValueId] = @MobilePhoneTypeId
+;
+-- Add History records showing that we are removing the phone number.
+INSERT INTO [History] (
+ [IsSystem]
+ ,[CategoryId]
+ ,[EntityTypeId]
+ ,[EntityId]
+ ,[Guid]
+ ,[CreatedDateTime]
+ ,[ModifiedDateTime]
+ ,[Verb]
+ ,[ChangeType]
+ ,[ValueName]
+ ,[OldValue]
+ ,[IsSensitive]
+)
+SELECT
+ 0
+ ,133
+ ,15
+ ,P.[Id]
+ ,NEWID()
+ ,GETDATE()
+ ,GETDATE()
+ ,'MODIFY'
+ ,'Property'
+ ,'Mobile Phone'
+ ,P.OldValue
+ ,0
+FROM @PersonIds AS P
+;
+-- Remove the phone numbers.
+DELETE FROM [PhoneNumber] WHERE [PersonId] IN (SELECT [Id] FROM @PersonIds) AND [NumberTypeValueId] = @MobilePhoneTypeId;
+
Tags
+This will lookup all group types under a given check-in config, then redirect to the check-in page with those areas selected.
+checkin-launch, checkin-launch/{KioskId}/{CheckinConfigId}
{% assign kioskId = 'Global' | PageParameter:'KioskId' | AsInteger %}
+{% assign configId = 'Global' | PageParameter:'CheckinConfigId' | AsInteger %}
+
+{% unless kioskId and configId and kioskId > 0 and configId > 0 %}
+ <div class="alert alert-warning">Error: Invalid parameters</div>
+{% else %}
+ {% sql %}
+ DECLARE @CheckinConfigId int = {{ configId }};
+
+ WITH CheckInAreas AS
+ (
+ SELECT
+ parent.[Id] AS ParentId
+ ,parent.[Name] AS Parent
+ ,child.[Id] AS ChildId
+ ,child.[Name] AS Child
+ FROM
+ [GroupTypeAssociation] gta
+ INNER JOIN [GroupType] parent ON gta.[GroupTypeId] = parent.[Id]
+ INNER JOIN [GroupType] child ON gta.[ChildGroupTypeId] = child.[Id]
+ WHERE
+ parent.[Id] = @CheckinConfigId
+
+ UNION ALL
+
+ SELECT
+ parent.[Id] AS ParentId
+ ,parent.[Name] AS Parent
+ ,child.[Id] AS ChildId
+ ,child.[Name] AS Child
+ FROM
+ [CheckinAreas] ca
+ JOIN [GroupTypeAssociation] gta ON ca.[ChildId] = gta.[GroupTypeId]
+ INNER JOIN [GroupType] parent ON gta.[GroupTypeId] = parent.[Id]
+ INNER JOIN [GroupType] child ON gta.[ChildGroupTypeId] = child.[Id]
+ WHERE
+ parent.[Id] <> child.[Id]
+ )
+ SELECT STRING_AGG(ChildId, ',') 'GroupTypeIds'
+ FROM [CheckInAreas]
+ {% endsql %}
+
+ {% capture url %}/checkin/{{ kioskId }}/{{ configId }}/{{ results | First | Property:'GroupTypeIds' }}{% endcapture%}
+
+ {% assign canEdit = 'Global' | Page:'Id' | HasRightsTo:'Edit','Rock.Model.Page' %}
+ {% if canEdit %}
+ <p class="alert alert-warning">If you could not edit this page you would be redirected to: <a href="{{ url }}">{{ url }}</a>.</p>
+ {% else %}
+ {{ url | PageRedirect }}
+ {% endif %}
+{% endunless %}
+
Tags
+++Credit: Chris @ Life.Church
+
This will list out all of your checkin configs, areas, and groups.
+NOTE: It doesn't show groups that are multiple group types deep
+DECLARE
+ @ConfigPurposeID int
+ ,@FilterPurposeID int;
+
+SELECT @ConfigPurposeID = [Id] FROM [DefinedValue] DV WHERE DV.[Guid] = '4A406CB0-495B-4795-B788-52BDFDE00B01';
+SELECT @FilterPurposeID = [Id] FROM [DefinedValue] DV WHERE DV.[Guid] = '6BCED84C-69AD-4F5A-9197-5C0F9C02DD34';;
+
+WITH CheckInAreas AS
+(
+ SELECT
+ PT.[Id] 'ConfigId'
+ ,CT.[Id] 'AreaId'
+ ,PT.[Name] 'Config'
+ ,PT.[Order] 'ConfigSort'
+ ,CT.[Name] 'Area'
+ ,CT.[Order] 'AreaSort'
+ FROM
+ [GroupTypeAssociation] GTA
+ INNER JOIN [GroupType] PT ON GTA.[GroupTypeId] = PT.[Id]
+ INNER JOIN [GroupType] CT ON GTA.[ChildGroupTypeId] = CT.[Id]
+ WHERE
+ PT.[GroupTypePurposeValueId] = @ConfigPurposeID
+ AND CT.[GroupTypePurposeValueId] <> @FilterPurposeID
+
+ UNION ALL
+
+ SELECT
+ CA.[ConfigId]
+ ,GTA.[ChildGroupTypeId] 'AreaId'
+ ,CA.[Config]
+ ,CA.[ConfigSort]
+ ,CT.[Name] 'Area'
+ ,CT.[Order] 'AreaSort'
+ FROM
+ [GroupTypeAssociation] GTA
+ INNER JOIN [CheckInAreas] CA ON CA.[AreaId] = GTA.[GroupTypeId]
+ INNER JOIN [GroupType] CT ON GTA.[ChildGroupTypeId] = CT.[Id]
+ WHERE
+ CT.[GroupTypePurposeValueId] <> @FilterPurposeId
+ AND GTA.[GroupTypeId] <> GTA.[ChildGroupTypeId]
+)
+SELECT
+ A.[ConfigId]
+ ,A.[AreaId]
+ ,G.[Id] 'GroupId'
+ ,A.[Config]
+ ,A.[Area]
+ ,G.[Name] 'Group'
+FROM
+ CheckInAreas A
+ INNER JOIN [Group] G ON A.[AreaId] = G.[GroupTypeId]
+WHERE G.[IsActive] = 1
+ORDER BY
+ A.[ConfigSort]
+ ,A.[AreaSort]
+ ,G.[Order]
+
Tags
+Filters:
+{% unless PageParameter.ParentGroup and PageParameter.ParentGroup != empty and PageParameter.ServiceTime and PageParameter.ServiceTime != empty and PageParameter.SundayDate and PageParameter.SundayDate != empty %}
+SELECT 'Please fill in the filters above.';
+{% else %}
+DECLARE @ParentGroupID int = ( SELECT [Id] FROM [Group] WHERE [Guid] = '{{ PageParameter.ParentGroup | SanitizeSql }}' );
+DECLARE @ScheduleID int = ( SELECT [Id] FROM [Schedule] WHERE [Guid] = '{{ PageParameter.ServiceTime | SanitizeSql }}' );
+DECLARE @Date date = CAST ( '{{ PageParameter.SundayDate | SanitizeSql }}' AS date );
+
+SELECT
+ FORMAT( a.[CreatedDateTime], 'HH:mm:ss' ) 'Check in time'
+ ,p.[FirstName]
+ ,p.[LastName]
+ ,g.[Name]
+ ,p.[Id]
+FROM
+ [Group] g
+ JOIN [AttendanceOccurrence] ao ON g.[Id] = ao.[GroupId]
+ JOIN [Attendance] a ON ao.[Id] = a.[OccurrenceId]
+ JOIN [PersonAlias] pa ON a.[PersonAliasId] = pa.[Id]
+ JOIN [Person] p ON pa.[PersonId] = p.[Id]
+WHERE
+ g.[ParentGroupId] = @ParentGroupID
+ AND a.[DidAttend] = 1
+ AND ao.[SundayDate] = @Date
+ AND ao.[ScheduleId] = @ScheduleID
+ORDER BY
+ a.[CreatedDateTime]
+{% endunless %}
+
Tags
+List all people that checked into the specified group types on the specified date, along with the time that they checked in.
+DECLARE @GroupTypes varchar(max) = '58,59'; --Serve Team GroupType IDs
+
+SELECT
+ DISTINCT(p.Id)
+ ,a.CreatedDateTime 'CheckinDateTime'
+ ,p.FirstName
+ ,p.LastName
+ ,FORMAT(a.CreatedDateTime, 'h:mm tt') 'CheckinTime'
+ ,g.Name 'Group'
+FROM
+ [Attendance] a
+ JOIN [AttendanceOccurrence] ao ON a.OccurrenceId = ao.Id
+ JOIN [Group] g on AO.GroupId = g.Id
+ JOIN [PersonAlias] pa ON a.PersonAliasId = pa.Id
+ JOIN [Person] p ON pa.PersonId = p.Id
+WHERE
+ g.GroupTypeId IN ( SELECT * FROM dbo.ufnUtility_CsvToTable( @GroupTypes ) )
+ AND ao.OccurrenceDate = @Date
+ORDER BY a.CreatedDateTime
+
Tags
+Lists everyone who received a specific communication, if the message was sent or opened, and if the recipient viewed a specific page. Since it is looking at interactions to see who visited the page, it relies on the person being logged in or using a tokenized link in the email.
+Field Type | +Key | +Description | +
---|---|---|
Communication Id | +CommunicationId | +The ID of the communication | +
Page Id | +PageId | +The Id of the page | +
CommunicationId=0;PageId=0
--DECLARE @CommunicationId int = 54489;
+--DECLARE @PageId int = 1066;
+
+DECLARE @ComponentId int = (
+ SELECT TOP 1 [Id]
+ FROM [InteractionComponent]
+ WHERE [EntityId] = @PageId
+ ORDER BY [CreatedDateTime] DESC
+);
+
+SELECT
+ p.[Id]
+ ,p.[NickName]
+ ,p.[LastName]
+ ,[dbo].ufnCrm_GetFamilyTitleFromGivingId( p.[GivingId] ) 'Family'
+ ,CONVERT( bit, CASE
+ WHEN cr.[Status] IN (1, 4) THEN 1 -- (1 = Sent, 4 = Opened)
+ ELSE 0
+ END ) 'EmailSent'
+ ,CONVERT( bit, CASE
+ WHEN cr.[Status] = 4 THEN 1 -- (4 = Opened)
+ ELSE 0
+ END ) 'EmailOpened'
+ ,CONVERT( bit, CASE
+ WHEN views.[PersonId] IS NOT NULL THEN 1
+ ELSE 0
+ END ) 'PageViewed'
+ ,CASE
+ WHEN views.[PersonId] IS NOT NULL
+ THEN CONCAT( 'Last viewed on ', FORMAT( views.[LastViewed], 'dddd, MMM. d' ), ', at ', FORMAT( views.[LastViewed], 'h:mm tt' ) )
+ WHEN cr.[Status] NOT IN (1, 4) THEN cr.[StatusNote] -- (1 = Sent, 4 = Opened)
+ ELSE ''
+ END 'Note'
+FROM
+ [CommunicationRecipient] cr
+ JOIN [PersonAlias] pa ON cr.[PersonAliasId] = pa.[Id]
+ JOIN [Person] p ON pa.[PersonId] = p.[Id]
+ LEFT JOIN (
+ SELECT
+ pa.[PersonId]
+ ,MAX( I.[InteractionDateTime] ) 'LastViewed'
+ FROM
+ [Interaction] i
+ JOIN [PersonAlias] pa ON i.[PersonAliasId] = pa.[Id]
+ WHERE
+ I.[InteractionComponentId] = @ComponentId
+ AND I.[Operation] = 'View'
+ GROUP BY
+ pa.[PersonId]
+ ) views ON p.[Id] = views.[PersonId]
+WHERE cr.[CommunicationId] = @CommunicationId
+ORDER BY
+ p.[LastName]
+ ,p.[NickName]
+;
+
Tags
+First, you will want to run a select to make sure it is finding all the requests you want to change, and none that you don't.
+DECLARE @ConnectionOpportunityId int = 4; -- Which Opportunity?
+
+SELECT
+ cr.[CreatedDateTime]
+ ,p.[FirstName]
+ ,p.[LastName]
+ ,cr.[Comments]
+
+FROM
+ [ConnectionRequest] cr
+ JOIN [PersonAlias] pa ON cr.[PersonAliasId] = pa.[Id]
+ JOIN [Person] p ON pa.[PersonId] = p.[Id]
+
+WHERE
+ cr.[ConnectionOpportunityId] = @ConnectionOpportunityId
+ AND cr.[ConnectionState] = 0 --active
+
Once you have verified that it found the right requests, you can run this update to make the change.
+ + + + +Tags
+Delete any saved accounts from Anonymous Giver that are also associated with another person.
+DELETE
+FROM [FinancialPersonBankAccount]
+WHERE
+ [AccountNumberSecured] IN (
+ --Acccounts that have more than 1 person assocciated...
+ SELECT [AccountNumberSecured]
+ FROM [FinancialPersonBankAccount]
+ GROUP BY [AccountNumberSecured]
+ HAVING COUNT(1) > 1
+ )
+ AND [PersonAliasId] IN (
+ --...and one of those people is anonymous giver
+ SELECT [Id]
+ FROM [PersonAlias]
+ WHERE [PersonId] = 2
+ )
+
Tags
+Returns a count of known and anonymous giving units for every month between the provided start and end dates.
+DECLARE @StartDate Date = '2014-01-01';
+DECLARE @EndDate Date = '2020-12-31';
+
+SELECT * FROM
+(
+ -- Distinct count of non-anonymous
+ SELECT
+ COUNT( DISTINCT p.GivingLeaderId ) 'Units'
+ ,FORMAT( ft.TransactionDateTime, 'yyyy-MM-01' ) 'Month'
+ ,'Known' AS 'Type'
+ FROM
+ [FinancialTransaction] ft
+ JOIN [PersonAlias] pa ON ft.AuthorizedPersonAliasId = pa.Id
+ JOIN [Person] p ON pa.PersonId = p.Id
+ WHERE
+ ft.transactionTypeValueId = 53
+ AND ft.TransactionDateTime BETWEEN LEFT( @StartDate, 10 ) AND LEFT( @EndDate, 10 )
+ AND p.Id <> 2 --Anonymous
+ GROUP BY FORMAT( ft.TransactionDateTime, 'yyyy-MM-01' )
+
+ UNION
+
+ -- Individual count of anonymous
+ SELECT
+ COUNT( p.GivingLeaderId ) 'Units'
+ ,FORMAT( ft.TransactionDateTime, 'yyyy-MM-01' ) 'Month'
+ ,'Anonymous' AS 'Type'
+ FROM
+ [FinancialTransaction] ft
+ JOIN [PersonAlias] pa ON ft.AuthorizedPersonAliasId = pa.Id
+ JOIN [Person] p ON pa.PersonId = p.Id
+ WHERE
+ ft.transactionTypeValueId = 53
+ AND ft.TransactionDateTime BETWEEN LEFT( @StartDate, 10 ) AND LEFT( @EndDate, 10 )
+ AND p.Id = 2 --Anonymous
+ GROUP BY FORMAT( ft.TransactionDateTime, 'yyyy-MM-01' )
+
+) AS x
+PIVOT(
+ SUM( Units )
+ FOR Type IN ( [Known], [Anonymous] )
+) AS y
+ORDER BY Month
+
Tags
+Lists every giving unit that gave in a specified date range (Sunday Dates), and how much was given to each account.
+1 Row per giving unit, with a column per account
+DECLARE @StartDate date = '2022-01-01';
+DECLARE @EndDate date = '2022-12-31';
+
+DROP TABLE IF EXISTS #TempData;
+
+CREATE TABLE #TempData(
+ [GivingId] varchar(30)
+ ,[Name] varchar(max)
+ ,[Account] varchar(max)
+ ,[Ammount] decimal
+);
+
+INSERT INTO #TempData(
+ [GivingId]
+ ,[Account]
+ ,[Ammount]
+)
+SELECT
+ p.[GivingId]
+ ,fa.[Name]
+ ,ftd.[Amount]
+FROM
+ [FinancialTransaction] ft
+ JOIN [FinancialTransactionDetail] ftd ON ftd.[TransactionId] = ft.[Id]
+ JOIN [FinancialAccount] fa ON ftd.[AccountId] = fa.[Id]
+ JOIN [PersonAlias] pa ON ft.[AuthorizedPersonAliasId] = pa.[Id]
+ JOIN [Person] p ON pa.[PersonId] = p.[Id]
+WHERE
+ ft.[TransactionTypeValueId] = 53 --Contribution
+ AND ft.[SundayDate] BETWEEN @StartDate AND @EndDate
+
+-- Use Dynamic SQL to Pivot to 1 col per row
+DECLARE @DynamicCol nvarchar(max);
+DECLARE @sql nvarchar(max);
+
+SELECT @DynamicCol = STUFF( (
+ SELECT DISTINCT ', ' + QUOTENAME( [Account] ) FROM #TempData FOR XML PATH ('')
+), 1, 2, '' );
+
+SET @Sql = '
+SELECT
+ [GivingId]
+ ,dbo.ufnCrm_GetFamilyTitleFromGivingId( [GivingId] ) AS [Name]
+ ,'+@DynamicCol+'
+FROM (
+ SELECT * FROM #TempData
+) AS Src
+PIVOT (
+ SUM( [Ammount] ) FOR [Account] IN ( '+@DynamicCol+' )
+) AS Pvt
+ORDER BY
+ [Name]
+';
+
+PRINT @Sql;
+EXEC(@sql);
+
+DROP TABLE #TempData;
+
Tags
+++Originally shared by Josh Crews (from Simple Donation) at RX2019 +Modified by Micheal Allen to consider GivingLeaderId vs everyone individually
+
This SQL searches your Rock database for "recent lapsed givers".
+The definition of a recent lapsed giver is someone who gives $3,000 / yr, who has given less than $100 in the past 8 weeks, who was giving normally this same 8-week period last year.
+That last clause is key, because many people give a lot, but can have non-giving periods. Maybe they take the summer off. Maybe the winter off. Maybe they give a large amount in December, then don't start again til March. Without comparing their giving to the same period last year it's hard to know.
+DECLARE @ANNUAL_GIVING_THRESHOLD AS INTEGER = 3000;
+DECLARE @MAXIMUM_RECENT_GIVING_DOLLARS AS INTEGER = 100;
+DECLARE @RECENT_PERIOD_WEEKS AS TINYINT = 8;
+
+SELECT
+ Person.Id
+ ,Person.NickName
+ ,Person.LastName
+ ,Person.Email
+ ,FORMAT( PastYear.Amount, 'c2' ) 'Past Year'
+ ,FORMAT( SamePeriodLastYear.Amount, 'c2' ) 'Same Period Last Year'
+ ,FORMAT( RecentPeriod.Amount, 'c2' ) 'Recent Period'
+ ,FinancialScheduledTransactions.Count 'Active Scheduled Transactions'
+FROM [Person]
+ LEFT JOIN (
+ SELECT
+ SUM( td.Amount ) 'Amount'
+ ,P.GivingLeaderId 'PersonId'
+ FROM
+ FinancialTransaction t
+ INNER JOIN FinancialTransactionDetail td ON t.Id = td.TransactionId
+ INNER JOIN PersonAlias pa ON t.AuthorizedPersonAliasId = pa.Id
+ INNER JOIN Person p ON pa.PersonId = p.Id
+ WHERE
+ t.TransactionTypeValueId != 54
+ AND t.TransactionDateTime < DATEADD( year, -1, getdate() )
+ AND t.TransactionDateTime > DATEADD( week, -1 * @RECENT_PERIOD_WEEKS, DATEADD( year, -1, getdate() ) )
+ GROUP BY p.GivingLeaderId
+ ) [SamePeriodLastYear] ON SamePeriodLastYear.PersonId = Person.Id
+ LEFT JOIN (
+ SELECT
+ SUM( td.Amount ) 'Amount'
+ ,P.GivingLeaderId 'PersonId'
+ FROM
+ FinancialTransaction t
+ INNER JOIN FinancialTransactionDetail td ON t.Id = td.TransactionId
+ INNER JOIN PersonAlias pa ON t.AuthorizedPersonAliasId = pa.Id
+ INNER JOIN Person p ON pa.PersonId = p.Id
+ WHERE
+ t.TransactionTypeValueId != 54
+ AND t.TransactionDateTime > DATEADD( week, -1 * @RECENT_PERIOD_WEEKS, DATEADD( year, -1, getdate() ) )
+ GROUP BY p.GivingLeaderId
+ ) [PastYear] ON PastYear.PersonId = Person.Id
+ LEFT JOIN (
+ SELECT
+ SUM( td.Amount ) 'Amount'
+ ,P.GivingLeaderId 'PersonId'
+ FROM
+ FinancialTransaction t
+ INNER JOIN FinancialTransactionDetail td ON t.Id = td.TransactionId
+ INNER JOIN PersonAlias pa ON t.AuthorizedPersonAliasId = pa.Id
+ INNER JOIN Person p ON pa.PersonId = p.Id
+ WHERE
+ t.TransactionTypeValueId != 54
+ AND t.TransactionDateTime > DATEADD( week, -1 * @RECENT_PERIOD_WEEKS, getdate() )
+ GROUP BY p.GivingLeaderId
+ ) [RecentPeriod] ON RecentPeriod.PersonId = Person.Id
+ LEFT JOIN (
+ SELECT
+ COUNT( fst.Id ) 'Count'
+ ,P.GivingLeaderId 'PersonId'
+ FROM
+ FinancialScheduledTransaction fst
+ INNER JOIN PersonAlias pa ON fst.AuthorizedPersonAliasId = pa.Id
+ INNER JOIN Person p ON pa.PersonId = p.Id
+ WHERE fst.IsActive = 1
+ GROUP BY p.GivingLeaderId
+ ) [FinancialScheduledTransactions] ON FinancialScheduledTransactions.PersonId = Person.Id
+WHERE
+ PastYear.Amount IS NOT NULL
+ AND PastYear.Amount > @ANNUAL_GIVING_THRESHOLD
+ AND ( RecentPeriod.Amount < @MAXIMUM_RECENT_GIVING_DOLLARS OR RecentPeriod.Amount IS NULL )
+ AND ( SamePeriodLastYear.Amount / PastYear.Amount ) > ( ( @RECENT_PERIOD_WEEKS * 1.0 ) / 60 ) -- and last year this period they gave a normal amount for them
+ --AND FinancialScheduledTransactions.Count IS NULL
+ORDER BY LastName
+
Tags
+Return a list of everyone that created a new pledge last week. (Monday - Sunday by default. See note below)
+Note: This query uses the GetPreviousSundayDate
function. That means that it will respect your configured "Starting Day of Week" under "Admin Tools > System Settings > System Configuration".
DECLARE @PledgeAccountID int = 59; --Which account to look at?
+
+SELECT
+ CONCAT_WS( ' '
+ ,p.FirstName
+ ,NULLIF( p.MiddleName, '' )
+ ,p.LastName
+ ,( SELECT Value FROM [DefinedValue] WHERE ID = p.SuffixValueId )
+ ) 'FormalName'
+ ,CONCAT_WS( ' '
+ ,p.NickName
+ ,p.LastName
+ ,( SELECT Value FROM [DefinedValue] WHERE ID = p.SuffixValueId )
+ ) 'FullName'
+ ,p.NickName
+ ,CONCAT_WS( ' '
+ ,l.Street1
+ ,NULLIF( l.Street2, '' )
+ ) 'Street'
+ ,COALESCE( l.City, '' ) 'City'
+ ,COALESCE( l.State, '' ) 'State'
+ ,COALESCE( l.PostalCode, '' ) 'PostalCode'
+FROM
+ [FinancialPledge] fp
+ JOIN [PersonAlias] pa ON fp.PersonAliasId = pa.Id
+ JOIN [Person] p ON pa.PersonId = p.Id
+ JOIN [Group] g ON p.PrimaryFamilyId = g.Id
+ LEFT JOIN [GroupLocation] gl
+ ON g.Id = gl.GroupId
+ AND gl.GroupLocationTypeValueId = 19 --Home
+ LEFT JOIN [Location] l
+ ON gl.LocationId = l.Id
+ AND l.ISActive = 1
+WHERE
+ fp.AccountId = @PledgeAccountID
+ AND fp.CreatedDateTime BETWEEN --Monday through Sunday (inclusive)
+ DATEADD( day, -6, dbo.[ufnUtility_GetPreviousSundayDate]() )
+ AND dbo.[ufnUtility_GetPreviousSundayDate]()
+ORDER BY p.LastName, p.FirstName
+
Tags
+++Credit: I think part of this sql came from Josh Crews @ SimpleDonation
+
Return a single number representing the number of "regular givers" (Gave at least 1 time in each of the past 3 months) that have recurring giving setup. This is intended to be used as the source for a metric.
+DECLARE @Today DATETIME = GETDATE();
+
+SELECT
+ CAST(COUNT(RecurringGivers.[PersonId]) AS DECIMAL)
+ / SUM(
+ CASE
+ WHEN
+ LastMonthGivers.[PersonId] IS NOT NULL
+ AND TwoMonthsAgoGivers.[PersonId] IS NOT NULL
+ AND ThreeMonthsAgoGivers.[PersonId] IS NOT NULL
+ THEN 1
+ ELSE 0
+ END
+ )
+ * 100 AS [Percent]
+FROM (
+ SELECT
+ pa.[PersonId]
+ FROM FinancialTransaction [t]
+ INNER JOIN PersonAlias [pa]
+ ON t.[AuthorizedPersonAliasId] = pa.[Id]
+ WHERE t.[TransactionDateTime] > DATEADD(DAY, 1, EOMONTH(@today,-2))
+ AND t.[TransactionDateTime] < DATEADD(DAY, 1, EOMONTH(@today,-1))
+ AND t.[TransactionTypeValueId] != 54
+ GROUP BY
+ pa.[PersonId]
+) [LastMonthGivers]
+LEFT JOIN (
+ SELECT
+ pa.[PersonId]
+ FROM FinancialTransaction [t]
+ INNER JOIN PersonAlias [pa]
+ ON t.[AuthorizedPersonAliasId] = pa.[Id]
+ WHERE t.[TransactionDateTime] > DATEADD(DAY, 1, EOMONTH(@today,-3))
+ AND t.[TransactionDateTime] < DATEADD(DAY, 1, EOMONTH(@today,-2))
+ AND t.[TransactionTypeValueId] != 54
+ GROUP BY
+ pa.[PersonId]
+) [TwoMonthsAgoGivers]
+ ON TwoMonthsAgoGivers.[PersonId] = LastMonthGivers.[PersonId]
+LEFT JOIN (
+ SELECT
+ pa.[PersonId]
+ FROM FinancialTransaction [t]
+ INNER JOIN PersonAlias [pa]
+ ON t.[AuthorizedPersonAliasId] = pa.[Id]
+ WHERE t.[TransactionDateTime] > DATEADD(DAY, 1, EOMONTH(@today,-4))
+ AND t.[TransactionDateTime] < DATEADD(DAY, 1, EOMONTH(@today,-3))
+ AND t.[TransactionTypeValueId] != 54
+ GROUP BY
+ pa.[PersonId]
+) [ThreeMonthsAgoGivers]
+ ON ThreeMonthsAgoGivers.[PersonId] = LastMonthGivers.[PersonId]
+LEFT JOIN (
+ SELECT
+ pa.[PersonId]
+ FROM FinancialScheduledTransaction [fst]
+ INNER JOIN PersonAlias [pa]
+ ON fst.[AuthorizedPersonAliasId] = pa.[Id]
+ WHERE fst.[IsActive] = 1
+ GROUP BY
+ pa.[PersonId]
+) [RecurringGivers]
+ ON RecurringGivers.[PersonId] = LastMonthGivers.[PersonId]
+
Tags
+++Original idea from Kevin Rutledge - Recipe Link
+
+Rewritten by Michael Allen to add support for fiscal years, and to fix a few display bugs
This is a fairly complicated report. Read Kevin's recipe (linked above) for a more thorough explanation.
+/* BEGIN Configuration */
+DECLARE @Years INT = 4; --How many years to pull? (Including the current year; Changing this will break the lava template)
+DECLARE @StartMonth INT = 1; --What month does your FY start on?
+DECLARE @AccountList VARCHAR(MAX) = '5,12,24,34,55'; --Comma seperated list of AccountIds to include
+/* END Configuration */
+
+DECLARE @StartDate DATE = DATEFROMPARTS( DATEPART( YEAR, GETDATE() ) - ( @Years - 1 ) , @StartMonth, 1 );
+DECLARE @EndDate DATE = DATEADD( YEAR, ( @Years - 1 ), @StartDate );
+
+/* If we haven't started this FY yet */
+IF ( DATEPART( MONTH, GETDATE() ) < @StartMonth ) BEGIN
+ SET @StartDate = DATEADD( YEAR, -1, @StartDate )
+ SET @EndDate = DATEADD( YEAR, -1, @EndDate )
+END;
+
+DECLARE @Results TABLE( Total DECIMAL(20,2), Year INT, Month INT );
+
+INSERT INTO @Results
+SELECT
+ SUM( ftd.Amount ) 'Total'
+ ,DATEPART( YEAR, ft.TransactionDateTime ) 'Year'
+ ,DATEPART( MONTH, ft.TransactionDateTime ) 'Month'
+FROM
+ [FinancialTransactionDetail] ftd
+ JOIN [FinancialTransaction] ft ON ftd.TransactionId = ft.Id
+ --Using an inner join to filter the accounts that are included
+ INNER JOIN ufnUtility_CsvToTable( @AccountList ) acct ON ftd.AccountId = acct.Item
+WHERE
+ ft.TransactionDateTime >= @StartDate
+ AND ft.TransactionDateTime < @EndDate
+ AND ft.TransactionTypeValueId = 53 --Contribution
+GROUP BY
+ DATEPART( YEAR, ft.TransactionDateTime )
+ ,DATEPART( MONTH, ft.TransactionDateTime )
+;
+
+/* table1 = current year */
+SET @StartDate = @EndDate;
+SET @EndDate = DATEFROMPARTS( DATEPART( YEAR, GETDATE() ), DATEPART( MONTH, GETDATE() ), 1 );
+
+SELECT(
+ SELECT
+ SUM( ftd.Amount ) 'Total'
+ ,DATEPART( YEAR, ft.TransactionDateTime ) 'Year'
+ ,DATEPART( MONTH, ft.TransactionDateTime ) 'Month'
+ FROM
+ [FinancialTransactionDetail] ftd
+ JOIN [FinancialTransaction] ft ON ftd.TransactionId = ft.Id
+ --Using an inner join to filter the accounts that are included
+ INNER JOIN ufnUtility_CsvToTable( @AccountList ) acct ON ftd.AccountId = acct.Item
+ WHERE
+ ft.TransactionDateTime >= @StartDate
+ AND ft.TransactionDateTime < @EndDate
+ AND ft.TransactionTypeValueId = 53 --Contribution
+ GROUP BY
+ DATEPART( YEAR, ft.TransactionDateTime )
+ ,DATEPART( MONTH, ft.TransactionDateTime )
+ ORDER BY 'Year', 'Month'
+ FOR JSON PATH
+) 'json';
+
+/* table2, table3, table4 = previous 3 years*/
+DECLARE @i INT = 0;
+
+WHILE @i < ( @Years - 1 ) BEGIN
+ SELECT (
+ SELECT * FROM @Results
+ ORDER BY 'Year', 'Month'
+ OFFSET ( @i * 12 ) ROWS
+ FETCH NEXT 12 ROWS ONLY
+ FOR JSON PATH
+ ) 'json';
+
+ SET @i = @i + 1;
+END;
+
+/* table5 = totals */
+SELECT(
+ SELECT
+ SUM( Total ) 'Total'
+ ,Month
+ ,MIN( Year ) 'Year'
+ FROM @Results
+ GROUP BY Month
+ ORDER BY 'Year'
+ FOR JSON PATH
+) 'json';
+
{% assign current = table1.rows[0].json | FromJSON %}
+{% assign 3yearsAgo = table2.rows[0].json | FromJSON %}
+{% assign 2yearsAgo = table3.rows[0].json | FromJSON %}
+{% assign 1yearsAgo = table4.rows[0].json | FromJSON %}
+{% assign totals = table5.rows[0].json | FromJSON %}
+
+{% assign 3yearsAgoTotal = 0 %}
+{% assign 2yearsAgoTotal = 0 %}
+{% assign 1yearsAgoTotal = 0 %}
+{% assign currentTotal = 0 %}
+
+{% for row in 3yearsAgo %}
+ {% assign 3yearsAgoTotal = 3yearsAgoTotal | Plus:row.Total %}
+{% endfor %}
+{% for row in 2yearsAgo %}
+ {% assign 2yearsAgoTotal = 2yearsAgoTotal | Plus:row.Total %}
+{% endfor %}
+{% for row in 1yearsAgo %}
+ {% assign 1yearsAgoTotal = 1yearsAgoTotal | Plus:row.Total %}
+{% endfor %}
+{% for row in current %}
+ {% assign currentTotal = currentTotal | Plus:row.Total %}
+{% endfor %}
+{% assign grandTotal = 3yearsAgoTotal | Plus:2yearsAgoTotal | Plus:1yearsAgoTotal %}
+
+{% capture multipliers %}
+ [
+ {% for row in totals %}
+ {{ row.Total | DividedBy:grandTotal,4 }}
+ {% unless forloop.last %},{% endunless %}
+ {% endfor %}
+ ]
+{% endcapture %}
+{% assign multipliers = multipliers | FromJSON %}
+
+<style>
+.year {
+ text-align:center;
+ font-weight: bold;
+}
+.text-right {
+ font-family: monospace;
+ text-align: right;
+}
+.table {
+ font-size: 0.9em;
+}
+</style>
+
+<div class="row">
+ <div class="col-lg-3 col-md-6">
+ <h5 class="year">{{ 3yearsAgo | Select:'Year' | Uniq | Join:'-' }}</h5>
+ <table class="text-right table">
+ <tr>
+ <th class="text-right">Month</th>
+ <th class="text-right">Total Given</th>
+ <th class="text-right">% of Total</th>
+ </tr>
+{% for month in 3yearsAgo %}
+ <tr>
+ <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
+ <td>{{ month.Total | FormatAsCurrency }}</td>
+ <td>{{ month.Total | DividedBy:3yearsAgoTotal,4 | Format:'p' }}</td>
+ </tr>
+{% endfor %}
+ </table>
+ </div>
+ <div class="col-lg-3 col-md-6">
+ <h5 class="year">{{ 2yearsAgo | Select:'Year' | Uniq | Join:'-' }}</h5>
+ <table class="text-right table">
+ <tr>
+ <th class="text-right">Month</th>
+ <th class="text-right">Total Given</th>
+ <th class="text-right">% of Total</th>
+ </tr>
+{% for month in 2yearsAgo %}
+ <tr>
+ <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
+ <td>{{ month.Total | FormatAsCurrency }}</td>
+ <td>{{ month.Total | DividedBy:2yearsAgoTotal,4 | Format:'p' }}</td>
+ </tr>
+{% endfor %}
+ </table>
+ </div>
+ <div class="col-lg-3 col-md-6">
+ <h5 class="year">{{ 1yearsAgo | Select:'Year' | Uniq | Join:'-' }}</h5>
+ <table class="text-right table">
+ <tr>
+ <th class="text-right">Month</th>
+ <th class="text-right">Total Given</th>
+ <th class="text-right">% of Total</th>
+ </tr>
+{% for month in 1yearsAgo %}
+ <tr>
+ <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
+ <td>{{ month.Total | FormatAsCurrency }}</td>
+ <td>{{ month.Total | DividedBy:1yearsAgoTotal,4 | Format:'p' }}</td>
+ </tr>
+{% endfor %}
+ </table>
+ </div>
+ <div class="col-lg-3 col-md-6">
+ <h5 class="year">3 Year Total</h5>
+ <table class="text-right table">
+ <tr>
+ <th class="text-right">Month</th>
+ <th class="text-right">Total Given</th>
+ <th class="text-right">% of Total</th>
+ </tr>
+{% for month in totals %}
+ <tr>
+ <td>{{ month.Month | Append:'/01/2000' | Date:'MMM.' }}</td>
+ <td>{{ month.Total | FormatAsCurrency }}</td>
+ <td>{{ multipliers[forloop.index0] | Format:'p' }}</td>
+ </tr>
+{% endfor %}
+ </table>
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-6">
+ <h5 class="year">Current Year: {{ current | Select:'Year' | Uniq | Join:'-' }} </h5>
+{% if currentTotal == 0 %}
+ <p>At least 1 month must be complete.</p>
+{% else %}
+ <table class="text-right table">
+ <tr>
+ <th class="text-right">Month</th>
+ <th class="text-right">Total Given</th>
+ <th class="text-right">% Multiplier</th>
+ <th class="text-right">Year End Projection</th>
+ </tr>
+ {% assign cumulativePercent = 0 %}
+ {% for month in current %}
+ {% assign cumulativePercent = cumulativePercent | Plus:multipliers[forloop.index0] %}
+ <tr>
+ <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
+ <td>{{ month.Total | FormatAsCurrency }}</td>
+ <td>{{ multipliers[forloop.index0] | Format:'p' }}</td>
+ <td>{{ month.Total | DividedBy:multipliers[forloop.index0] | FormatAsCurrency }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+{% endif %}
+ </div>
+ <div class="col-lg-3 col-md-6">
+ <h5 class="year">Yearly Totals</h5>
+ <table class="text-right table">
+ <tr>
+ <th class="text-right">Year</th>
+ <th class="text-right">Total</th>
+ </tr>
+ <tr>
+ <td>{{ 3yearsAgo | Select:'Year' | Uniq | Join:'-' }}</td>
+ <td>{{ 3yearsAgoTotal | FormatAsCurrency }}</td>
+ </tr>
+ <tr>
+ <td>{{ 2yearsAgo | Select:'Year' | Uniq | Join:'-' }}</td>
+ <td>{{ 2yearsAgoTotal | FormatAsCurrency }}</td>
+ </tr>
+ <tr>
+ <td>{{ 1yearsAgo | Select:'Year' | Uniq | Join:'-' }}</td>
+ <td>{{ 1yearsAgoTotal | FormatAsCurrency }}</td>
+ </tr>
+{% if currentTotal > 0 %}
+ <tr>
+ <td>{{ current | Select:'Year' | Uniq | Join:'-' }}</td>
+ <td>{{ currentTotal | FormatAsCurrency }}</td>
+ </tr>
+{% endif %}
+ </table>
+ <div class="alert-success alert" style="margin-top:20px;">
+ <h5>{{ current | Select:'Year' | Uniq | Join:'-' }} Year End Projection:</h5>
+{% if currentTotal == 0 %}
+ <p>At least 1 month must be complete.</p>
+{% else %}
+ <p>{{ currentTotal | DividedBy:cumulativePercent | FormatAsCurrency }}</p>
+{% endif %}
+ </div>
+ </div>
+</div>
+<div class="row">
+{% assign currentSize = current | Size | Minus:1 %}
+{% capture chartData %}
+ [
+ {% for i in (0..11) %}
+ {
+ name: '{{ 3yearsAgo[i].Month | Append:'/01/' | Append:3yearsAgo[i].Year | Date:'MMM.' }}'
+ ,3year: '{{ 3yearsAgo[i].Total }}'
+ ,2year: '{{ 2yearsAgo[i].Total }}'
+ ,1year: '{{ 1yearsAgo[i].Total }}'
+ {% unless i > currentSize %}
+ ,current: '{{ current[i].Total }}'
+ {% endunless %}
+ }
+ {% unless forloop.last %},{% endunless %}
+ {% endfor %}
+ ]
+{% endcapture %}
+{% assign chartData = chartData | FromJSON %}
+
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"> </script>
+ <div class="chart-container" style="position:relative;height:300px;width:100%;margin-bottom:50px;">
+ <h5>Giving History By Month</h5>
+ <canvas id="myChart"></canvas>
+ </div>
+</div>
+
+<script>
+ var ctx = document.getElementById("myChart").getContext('2d');
+ var myChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: [ "{{ chartData | Select:'name' | Join:'","' }}" ],
+ datasets: [
+ {
+ label: '{{ 3yearsAgo | Select:'Year' | Uniq | Join:'-' }}',
+ fill: false,
+ data: [ {{ chartData | Select:'3year' | Join:',' }} ],
+ backgroundColor: [ "rgba(54,162,235,1)" ],
+ borderColor: [ "rgba(54,162,235,1)" ],
+ borderWidth: 1
+ },
+ {
+ label: '{{ 2yearsAgo | Select:'Year' | Uniq | Join:'-' }}',
+ fill: false,
+ data: [ {{ chartData | Select:'2year' | Join:',' }} ],
+ backgroundColor: [ "rgba(75,192,192,1)" ],
+ borderColor: [ "rgba(75,192,192,1)" ],
+ borderWidth: 1
+ },
+ {
+ label: '{{ 1yearsAgo | Select:'Year' | Uniq | Join:'-' }}',
+ fill: false,
+ data: [ {{ chartData | Select:'1year' | Join:',' }} ],
+ backgroundColor: [ "rgba(255,159,64,1)" ],
+ borderColor: [ "rgba(255,159,64,1)" ],
+ borderWidth: 1
+ },
+ {
+ label: '{{ current | Select:'Year' | Uniq | Join:'-' }}',
+ fill: false,
+ data: [ {{ chartData | Select:'current' | Join:',' }} ],
+ backgroundColor: [ "rgba(153,102,255,1)" ],
+ borderColor: [ "rgba(153,102,255,1)" ],
+ borderWidth: 1
+ },
+ ]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ animation: {
+ duration: 2500,
+ },
+ legend: {
+ position: 'right'
+ },
+ scales: {
+ yAxes: [{
+ ticks: {
+ beginAtZero:false,
+ callback: function(value, index, values) {
+ return '$' + value/1000 + 'k';
+ }
+ }
+ }]
+ },
+ tooltips: {
+ callbacks: {
+ label: function(tooltipItem, data) {
+ var label = data.datasets[tooltipItem.datasetIndex].label || '';
+
+ if (label) {
+ label += ': ';
+ }
+ label += '$' + Math.round(tooltipItem.yLabel / 100) / 10 + 'k';
+ return label;
+ }
+ }
+ }
+ }
+ });
+</script>
+
Tags
+For every year between the specified years, return the number of giving units that have given more than the specified minimum amount. If you don't want a minimum amount, you can set it to 0.
+DECLARE @Years nvarchar(9) = '2018-2020';
+DECLARE @Minimum decimal = 200.00; -- How much must they give to be counted?
+
+WITH cte_totals AS (
+ SELECT
+ p.GivingLeaderId
+ ,DATEPART( YEAR, ft.SundayDate ) 'Year'
+ ,SUM( ftd.Amount ) 'Total'
+ FROM
+ [FinancialTransaction] ft
+ JOIN [FinancialTransactionDetail] ftd ON ft.Id = ftd.TransactionId
+ JOIN [PersonAlias] pa ON ft.AuthorizedPersonAliasId = pa.Id
+ JOIN [Person] p ON pa.PersonId = p.Id
+ WHERE
+ ft.transactionTypeValueId = 53
+ AND p.Id <> 2 --Exclude Anonymous Giver
+ AND DATEPART( YEAR, ft.SundayDate ) BETWEEN LEFT( @Years, 4 ) AND RIGHT( @Years, 4 )
+ GROUP BY
+ p.GivingLeaderId
+ ,DATEPART( YEAR, ft.SundayDate )
+ HAVING
+ SUM( ftd.Amount ) >= @Minimum
+)
+SELECT
+ Year
+ ,COUNT( 1 ) 'Units'
+FROM cte_totals
+GROUP BY Year
+ORDER BY Year ASC
+
Tags
+@StartDate
(Date)@EndDate
(Date)SELECT
+ g.Id 'GroupId'
+ ,ao.Id 'OccurrenceId'
+ ,ao.OccurrenceDate 'Date'
+ ,CONCAT( '<a href="/page/570?GroupId=', g.Id, '">', g.Name, '</a>' ) 'Group'
+ ,ao.Notes
+FROM
+ [AttendanceOccurrence] ao
+ JOIN [Group] g ON ao.GroupId = g.Id
+ JOIN [GroupType] gt ON g.GroupTypeId = gt.Id
+WHERE
+ gt.Guid = @GroupType
+ AND ao.OccurrenceDate BETWEEN LEFT( @StartDate, 10 ) AND LEFT( @EndDate, 10 )
+ AND ao.Notes <> ''
+ORDER BY
+ ao.OccurrenceDate DESC
+ ,g.Name
+
Tags
+Return anyone that attended any group of the specified types for the first time between @StartDate
and @EndDate
.
DECLARE @GroupTypes varchar(max) = '38,41,58,59'; --Serve Teams
+DECLARE @StartDate Date = '2019-11-01';
+DECLARE @EndDate Date = '2019-12-31';
+
+WITH cte_Ranked AS (
+ SELECT
+ a.PersonAliasId
+ ,g.Id 'GroupId'
+ ,ao.OccurrenceDate
+ ,ROW_NUMBER() OVER ( PARTITION BY a.PersonAliasId ORDER BY ao.OccurrenceDate ASC ) 'Rank'
+ FROM
+ [AttendanceOccurrence] ao
+ JOIN [Attendance] a
+ ON ao.Id = a.OccurrenceId
+ AND a.DidAttend = 1
+ INNER JOIN [Group] g
+ ON ao.GroupId = g.Id
+ AND g.GroupTypeId IN ( SELECT * FROM dbo.ufnUtility_CsvToTable( @GroupTypes ) )
+ AND g.IsActive = 1
+ AND g.IsArchived = 0
+ WHERE
+ ao.DidNotOccur = 0
+)
+SELECT
+ r.OccurrenceDate 'First Attendance'
+ ,p.Id 'PersonId'
+ ,p.FirstName
+ ,p.LastName
+ ,g.Id 'GroupId'
+ ,g.Name 'GroupName'
+FROM
+ [cte_Ranked] r
+ JOIN [Group] g ON r.GroupId = g.Id
+ JOIN [PersonAlias] pa ON r.PersonAliasId = pa.Id
+ JOIN [Person] p ON pa.PersonId = p.Id
+WHERE
+ r.Rank = 1
+ AND r.OccurrenceDate BETWEEN @StartDate AND @EndDate
+ORDER BY
+ r.OccurrenceDate DESC
+ ,p.LastName
+ ,p.FirstName
+
Tags
+Lists everyone in a group of the specified types that hasn't attended at least once in the past X months.
+DECLARE @GroupTypes varchar(max) = '38,41,58'; --Serve Teams (KW, Students, Weekend)
+DECLARE @Months int = 6; --Filter out people that have attended in the past X months
+DECLARE @AttendanceTable TABLE (
+ PersonId int
+ ,GroupId int
+ ,OccurrenceDate Date
+ ,Rank int
+);
+
+INSERT INTO @AttendanceTable ( PersonId, GroupId, OccurrenceDate, Rank )
+(
+ SELECT
+ pa.PersonId
+ ,g.Id 'GroupId'
+ ,ao.OccurrenceDate
+ ,ROW_NUMBER() OVER (
+ PARTITION BY pa.PersonId, g.Id
+ ORDER BY ao.OccurrenceDate DESC
+ )
+ FROM
+ [AttendanceOccurrence] ao
+ JOIN [Attendance] a
+ ON ao.Id = a.OccurrenceId
+ AND a.DidAttend = 1
+ INNER JOIN [Group] g ON ao.GroupId = g.Id
+ JOIN [PersonAlias] pa ON a.PersonAliasId = pa.Id
+ WHERE
+ ao.DidNotOccur = 0
+ OR ao.DidNotOccur IS NULL
+);
+
+
+SELECT
+ p.Id 'PersonId'
+ ,g.Id 'GroupId'
+ ,g.Name 'Group'
+ ,CONCAT( p.NickName, ' ', p.LastName) 'Person'
+ ,a.OccurrenceDate 'LastCheckin'
+FROM
+ [GroupMember] gm
+ INNER JOIN [Group] g
+ ON gm.GroupId = g.Id
+ AND g.GroupTypeId IN (
+ SELECT * FROM dbo.ufnUtility_CsvToTable( @GroupTypes )
+ )
+ AND g.IsActive = 1
+ AND g.IsArchived = 0
+ JOIN [Person] p ON gm.PersonId = p.Id
+ LEFT JOIN @AttendanceTable a
+ ON g.Id = a.GroupId
+ AND p.Id = a.PersonId
+ AND a.Rank = 1
+WHERE
+ gm.GroupMemberStatus = 1 --Active
+ AND gm.IsArchived = 0
+ AND (
+ a.OccurrenceDate < DATEADD( m, -1 * @Months, GETDATE() )
+ OR a.OccurrenceDate IS NULL
+ )
+ORDER BY
+ g.Name
+ ,LastCheckin desc
+ ,p.LastName
+ ,p.FirstName
+
Tags
+For each group of the specified type, list the most recent attendance occurrence, the number of people attending, and any attendance notes entered.
+DECLARE @GroupType int = 25; --Life Groups
+
+WITH cte_Ranked AS (
+ SELECT
+ g.[Id] 'GroupId'
+ ,ao.[Id] 'AttendanceOccurrenceId'
+ ,RANK() OVER ( PARTITION BY g.[Id] ORDER BY ao.[OccurrenceDate] DESC ) 'Rank'
+ FROM
+ [Group] g
+ LEFT JOIN [AttendanceOccurrence] ao
+ ON g.[Id] = ao.[GroupId]
+ AND ( ao.[DidNotOccur] IS NULL OR ao.[DidNotOccur] = 0 )
+
+ WHERE
+ g.[GroupTypeId] = @GroupType
+ AND g.[IsActive] = 1
+ AND g.[IsArchived] = 0
+)
+SELECT
+ g.[Id]
+ ,g.[Name]
+ ,ao.[OccurrenceDate] 'Last Attendance'
+ ,NULLIF( (
+ SELECT COUNT( 1 )
+ FROM [Attendance]
+ WHERE
+ [OccurrenceId] = r.[AttendanceOccurrenceId]
+ AND [DidAttend] = 1
+ ), 0 ) 'Attendee Count'
+ ,ao.[Notes] 'Attendance Note'
+FROM
+ [cte_Ranked] r
+ JOIN [Group] g ON r.[GroupId] = g.[Id]
+ LEFT JOIN [AttendanceOccurrence] ao ON r.[AttendanceOccurrenceId] = ao.[Id]
+WHERE r.[Rank] = 1
+ORDER BY ao.[OccurrenceDate] DESC
+
Tags
+Returns a list of all of the known relationships for the provided person
+DECLARE @PersonId int = 515; --Who are we looking at?
+DECLARE @OwnerRoleId int = 5; --Owner
+
+DECLARE @KnownRelationshipGroupId int = (
+ SELECT TOP 1 GroupId
+ FROM [GroupMember]
+ WHERE
+ PersonId = @PersonId
+ AND GroupRoleId = @OwnerRoleId
+);
+
+SELECT
+ r.Name 'Role'
+ ,CONCAT( p.NickName, ' ', p.LastName ) 'Person'
+FROM
+ [Group] g
+ JOIN [GroupMember] gm ON g.Id = gm.GroupId
+ JOIN [GroupTypeRole] r ON gm.GroupRoleId = r.Id
+ JOIN [Person] p ON gm.PersonId = p.Id
+WHERE
+ g.Id = @KnownRelationshipGroupId
+ AND gm.GroupRoleId <> @OwnerRoleId
+ORDER BY 'Role', 'Person'
+
Tags
+Search every group of the specified type, or any of its child types, to find the first time the specified person attended.
+This can be easily modified to return their most recent attendance datetime by changing line 18 from MIN
to MAX
.
Given the following checkin structure:
+Volunteer Checkin (Checkin Configuration / Group Type)
+ - Weekend Volunteers (Checkin Area / Group Type)
+ - Worship (Group)
+ - Production (Group)
+ - Camera (Group)
+ - Audio (Group)
+ - Non-Weekend Volunteers (Checkin Area / Group Type)
+ - Facilities (Group)
+ - Reception (Group)
+
If you were to run this on "Volunteer Checkin", it will look through every group in the tree, regardless of how many levels deep it is.
+Note, this query will blow up if you have any circular group type associations
+DECLARE @parentGroupType INT = 49; --Volunteer Checkin
+DECLARE @personId INT = 515;
+
+WITH cte_grouptypes AS (
+ SELECT CAST( @parentGroupType AS INT ) 'Id'
+
+ UNION ALL
+
+ SELECT
+ ChildGroupTypeId 'Id'
+ FROM
+ [GroupTypeAssociation] gta
+ INNER JOIN cte_grouptypes cte ON gta.GroupTypeId = cte.Id
+ WHERE
+ gta.GroupTypeID <> gta.ChildGroupTypeID
+)
+SELECT
+ CONCAT( MIN( OccurrenceDate ),'T00:00:00' )
+FROM
+ [Attendance] a
+ JOIN [PersonAlias] pa ON a.PersonAliasId = pa.Id
+ JOIN [AttendanceOccurrence] ao ON a.OccurrenceId = ao.Id
+ JOIN [Group] g ON ao.GroupId = g.Id
+ INNER JOIN [cte_grouptypes] cte ON g.GroupTypeId = cte.Id
+WHERE
+ a.DidAttend = 1
+ AND pa.PersonId = @personId
+
Tags
+Traverses a group tree and returns all descendant groups.
+Data returned includes Id, Names, and Path. The path is in the following format:
+DECLARE @parentGroupId int = 41;
+
+DECLARE @groups table("Id" int, "Name" varchar(max), "Path" varchar(max));
+
+-- Recursively get all groups under the parent
+WITH CTE AS (
+ SELECT
+ g.Id
+ ,g.ParentGroupId
+ ,CAST( g.Name AS Varchar(max) ) 'Name'
+ ,CAST( g.Name AS Varchar(max) ) 'Path'
+ FROM [Group] g
+ WHERE g.ParentGroupId = @parentGroupId
+
+ UNION ALL
+
+ SELECT
+ g.Id
+ ,g.ParentGroupId
+ ,CAST( g.Name AS varchar(max) ) 'Name'
+ ,CAST( CONCAT( CTE.Path, ' :: ', g.Name ) AS Varchar(max) ) 'Path'
+ FROM
+ [Group] g
+ INNER JOIN CTE ON g.ParentGroupId = CTE.Id
+)
+INSERT INTO @groups
+SELECT Id, Name, Path
+FROM CTE;
+
+-- Preview the selected groups
+SELECT * FROM @groups ORDER BY Path;
+
Tags
+Returns a list of tasks and projects, assigned to the specified user, that are due in the next X days, or are overdue.
+We use this in a weekly email to keep people from forgetting the tasks and projects that are assigned to them.
+{% sql %}
+DECLARE @Person int = {{ Person.Id | AsInteger }};
+DECLARE @LookAheadDays int = 7;
+DECLARE @Today datetime = CAST( CAST( GETDATE() AS date ) AS datetime );
+
+SELECT
+ p.[Id]
+ ,p.[Name]
+ ,p.[DueDate]
+ ,CASE
+ WHEN p.[DueDate] < @Today THEN 'Past Due'
+ ELSE NULL
+ END 'PastDue'
+FROM
+ [_com_blueboxmoon_ProjectManagement_Project] p
+ JOIN [_com_blueboxmoon_ProjectManagement_ProjectAssignee] pas ON p.[Id] = pas.[ProjectId]
+ JOIN [PersonAlias] pa ON pas.[PersonAliasId] = pa.[Id]
+WHERE
+ [IsActive] = 1
+ AND p.[State] = 'Active'
+ AND pa.[PersonId] = @Person
+ AND p.[DueDate] <= DATEADD( day, @LookAheadDays, @Today )
+
+UNION
+
+SELECT
+ t.[ProjectId]
+ ,CONCAT( '[', p.[Name], '] ', t.[Name] ) 'Name'
+ ,t.[DueDate]
+ ,CASE
+ WHEN t.[DueDate] < @Today THEN 'Past Due'
+ ELSE NULL
+ END 'PastDue'
+FROM
+ [_com_blueboxmoon_ProjectManagement_Task] t
+ JOIN [PersonAlias] pa ON t.[AssignedToPersonAliasId] = pa.[Id]
+ JOIN [_com_blueboxmoon_ProjectManagement_Project] p ON t.[ProjectId] = p.[Id]
+WHERE
+ t.[IsActive] = 1
+ AND t.[State] = 'Active'
+ AND pa.[PersonId] = @Person
+ AND t.[DueDate] <= DATEADD( day, @LookAheadDays, @Today )
+{% endsql %}
+
+{% assign items = results | OrderBy:'DueDate' %}
+{% for item in items %}
+ <p style:'font-size: 15px;'>
+ <a href="{{ 'Global' | Attribute:'InternalApplicationRoot' }}/Project/{{ item.Id }}">
+ {{ item.Name }}<br>
+ </a>
+ {{ item.DueDate | Date:'dddd, MMM d, yyyy'}}
+ {% if item.PastDue %}
+ <span style='color: red;'>- ({{ item.PastDue }})</span>
+ {% endif %}
+ </p>
+{% endfor %}
+
Tags
+This is a fairly complicated report that attempts to show everything that you would need to change when someone transitions off of your staff team. It also, when possible, provides links to the edit pages of the various things that need to be changed.
+It looks in the following places:
+There are several "magic numbers" in these queries. You will want to check all of the Entity IDs, Attribute IDs, and Page numbers against your own system and update as needed.
+Field Type | +Key | +Description | +
---|---|---|
Person | +Person | +The person being off-boarded | +
Person=00000000-0000-0000-0000-000000000000
-- Get their Person Id
+DECLARE @PersonId int = ( SELECT [PersonId] FROM [PersonAlias] WHERE [Guid] = CAST( @Person AS uniqueidentifier ) );
+
+-- Get all of their PersonAlias IDs and GUIDs
+SELECT pa.[Id], pa.[Guid] INTO #PersonAliasIds FROM [PersonAlias] pa WHERE pa.[PersonId] = @PersonId;
+
+-- Get their email
+DECLARE @PersonEmail varchar(max) = ( SELECT NULLIF( [Email], '' ) FROM [Person] WHERE [Id] = @PersonId );
+
+-- 1. Security Group Membership
+SELECT
+ g.[Id]
+ ,g.[Name] 'Group'
+ ,g.[IsActive]
+ ,g.[IsArchived]
+ ,gs.[SyncDataViewId]
+FROM
+ [Group] g
+ INNER JOIN [GroupMember] gm ON
+ g.[Id] = gm.[GroupId]
+ AND gm.[IsArchived] != 1
+ LEFT JOIN [GroupSync] gs ON g.[Id] = gs.[GroupId]
+WHERE
+ gm.[PersonId] = @PersonId
+ AND g.[IsSecurityRole] = 1
+ORDER BY g.[Name]
+;
+
+-- 2. Room Reservation Approval Groups
+SELECT
+ g.[Id]
+ ,g.[Name] 'Group'
+ ,g.[IsActive]
+ ,g.[IsArchived]
+ ,gs.[SyncDataViewId]
+FROM
+ [Group] g
+ INNER JOIN [GroupMember] gm ON
+ g.[Id] = gm.[GroupId]
+ AND gm.[IsArchived] != 1
+ LEFT JOIN [GroupSync] gs ON g.[Id] = gs.[GroupId]
+WHERE
+ gm.[PersonId] = @PersonId
+ AND g.[GroupTypeId] = 82
+ORDER BY g.[Name]
+;
+
+-- 3. Connector Group Memberships
+SELECT
+ g.[Id]
+ ,g.[Name] 'Group'
+ ,g.[IsActive]
+ ,g.[IsArchived]
+ ,gs.[SyncDataViewId]
+FROM
+ [Group] g
+ INNER JOIN [GroupMember] gm ON
+ g.[Id] = gm.[GroupId]
+ AND gm.[IsArchived] != 1
+ LEFT JOIN [GroupSync] gs ON g.[Id] = gs.[GroupId]
+WHERE
+ gm.[PersonId] = @PersonId
+ AND g.[ParentGroupId] = 60
+ORDER BY g.[Name]
+;
+
+-- 4. Group Leadership (All Group Types)
+SELECT
+ g.[Id]
+ ,g.[Name] 'Group'
+ ,g.[IsActive]
+ ,g.[IsArchived]
+FROM
+ [Group] g
+ INNER JOIN [GroupMember] gm ON
+ g.[Id] = gm.[GroupId]
+ AND gm.[IsArchived] != 1
+ INNER JOIN [GroupTypeRole] gtr ON
+ gm.[GroupRoleId] = gtr.[Id]
+WHERE
+ gm.[PersonId] = @PersonId
+ AND gtr.[IsLeader] = 1
+ORDER BY g.[Name]
+;
+
+-- 5. Default Connector
+SELECT
+ co.[Id]
+ ,co.[ConnectionTypeId]
+ ,co.[Name]
+ ,c.[Name] 'CampusName'
+ ,co.[IsActive]
+FROM
+ [ConnectionOpportunity] co
+ INNER JOIN [ConnectionOpportunityCampus] coc ON co.[Id] = coc.[ConnectionOpportunityId]
+ INNER JOIN [Campus] c ON coc.[CampusId] = c.[Id]
+ INNER JOIN #PersonAliasIds pa ON coc.[DefaultConnectorPersonAliasId] = pa.[Id]
+ORDER BY
+ co.Name
+ ,c.Name
+;
+
+-- 6. Active Connections
+SELECT
+ cr.[Id]
+ ,co.[Name] 'ConnectionOpportunity'
+ ,p.[NickName]+' '+p.[LastName] 'PersonName'
+FROM
+ [ConnectionRequest] cr
+ INNER JOIN [ConnectionOpportunity] co ON cr.[ConnectionOpportunityId] = co.[Id]
+ INNER JOIN [PersonAlias] pa ON cr.[PersonAliasId] = pa.[Id]
+ INNER JOIN [Person] p ON pa.[PersonId] = p.[Id]
+ INNER JOIN #PersonAliasIds pa2 ON cr.[ConnectorPersonAliasId] = pa2.[Id]
+WHERE
+ cr.[ConnectionState] = 0
+ORDER BY
+ co.[Name]
+ ,cr.[Id]
+;
+
+-- 7. Active Event Contact
+SELECT
+ eio.[Id]
+ ,ei.[Name]
+FROM
+ [EventItem] ei
+ INNER JOIN [EventItemOccurrence] eio ON ei.[Id] = eio.[EventItemId]
+ INNER JOIN [Schedule] s ON eio.[ScheduleId] = s.[Id]
+ INNER JOIN #PersonAliasIds pa ON eio.[ContactPersonAliasId] = pa.[Id]
+WHERE
+ ei.[IsActive] = 1
+ AND s.[EffectiveEndDate] > GETDATE()
+;
+
+-- 8. Active Registration Contact
+SELECT
+ ri.[Id]
+ ,ri.[Name]
+FROM
+ [RegistrationInstance] ri
+ INNER JOIN [Registrationtemplate] rt on ri.[RegistrationTemplateId] = rt.[Id]
+ INNER JOIN #PersonAliasIds pa ON ri.[ContactPersonAliasId] = pa.[Id]
+WHERE
+ ri.[IsActive] = 1
+ AND ri.[EndDateTime] > GETDATE()
+;
+
+-- 9. Org Chart Group Membership
+SELECT
+ g.[Id]
+ ,g.[Name] 'Group'
+ ,g.[IsActive]
+ ,g.[IsArchived]
+ ,gs.[SyncDataViewId]
+FROM
+ [Group] g
+ INNER JOIN [GroupMember] gm ON
+ g.[Id] = gm.[GroupId]
+ AND gm.[IsArchived] != 1
+ LEFT JOIN [GroupSync] gs ON g.[Id] = gs.[GroupId]
+WHERE
+ gm.[PersonId] = @PersonId
+ AND g.[GroupTypeId] = 28
+ORDER BY g.[Name]
+;
+
+-- 10. Miscellaneous Security Settings
+SELECT DISTINCT
+ p.[InternalName] 'PageName'
+ ,g.[Name] 'GroupName'
+ ,a.[EntityTypeId] 'EntityType'
+ ,et.[FriendlyName] 'EntityName'
+ ,a.[EntityId] 'EntityId'
+ ,rt.[Name] 'RegName'
+ ,dv.[Name] 'Dataview'
+ ,r.[Name] 'Report'
+ ,b.[Name] 'Block'
+ ,b.[PageId]
+ ,s.[Name] 'Schedule'
+FROM
+ [Auth] a
+ INNER JOIN [PersonAlias] pa ON
+ a.PersonAliasId = pa.Id
+ AND pa.Guid = @person
+ INNER JOIN [EntityType] et ON
+ a.EntityTypeId = et.Id
+ LEFT JOIN [Page] p ON
+ a.[EntityId] = p.[Id]
+ AND et.[Id] = 2
+ LEFT JOIN [Block] b ON
+ a.[EntityId] = b.[Id]
+ AND et.[Id] = 9
+ LEFT JOIN [Group] g ON
+ a.[EntityId] = g.[Id]
+ AND et.[Id] = 16
+ LEFT JOIN [DataView] dv ON
+ a.[EntityId] = dv.[Id]
+ AND et.[Id] = 34
+ LEFT JOIN [Schedule] s ON
+ a.[EntityId] = s.[Id]
+ AND et.[Id] = 54
+ LEFT JOIN [Report] r ON
+ a.[EntityId] = r.[Id]
+ AND et.[Id] = 107
+ LEFT JOIN [RegistrationTemplate] rt ON
+ a.[EntityId] = rt.[Id]
+ AND et.[Id] = 234
+ORDER BY et.[FriendlyName]
+;
+
+-- 11. SMS From Values
+SELECT
+ dv.[Value]
+ ,dv.[Description]
+FROM
+ [DefinedValue] dv
+ INNER JOIN [AttributeValue] av ON
+ dv.[Id] = av.[EntityId]
+ AND av.[AttributeId] = 949
+ INNER JOIN [PersonAlias] pa ON av.[Value] = pa.[Guid]
+ INNER JOIN #PersonAliasIds pa2 ON pa.[Id] = pa2.[Id]
+WHERE [DefinedTypeId] = 32
+;
+
+
+-- 12. Room Reservations (Admin contact or Event contact)
+SELECT
+ r.[Id]
+ ,r.[Name]
+FROM
+ [_com_bemaservices_RoomManagement_Reservation] r
+WHERE
+ r.[LastOccurrenceEndDateTime] > GETDATE()
+ AND (
+ r.[EventContactPersonAliasId] IN ( SELECT [Id] FROM #PersonAliasIds )
+ OR r.[AdministrativeContactPersonAliasId] IN ( SELECT [Id] FROM #PersonAliasIds )
+ )
+;
+
+-- 13. Service Job Notifications
+SELECT
+ j.[IsActive]
+ ,j.[Id]
+ ,j.[Name]
+ ,j.[Class]
+FROM [ServiceJob] j
+WHERE
+ @PersonEmail IS NOT NULL
+ AND j.[NotificationEmails] LIKE '%' + @PersonEmail + '%'
+;
+
+-- 14. Communication Templates (From/CC/BCC)
+SELECT
+ ct.[Id]
+ ,ct.[Name]
+ ,ct.[IsActive]
+FROM [CommunicationTemplate] ct
+WHERE
+ @PersonEmail IS NOT NULL
+ AND (
+ ct.[FromEmail] LIKE '%' + @PersonEmail + '%'
+ OR ct.[CCEmails] LIKE '%' + @PersonEmail + '%'
+ OR ct.[BCCEmails] LIKE '%' + @PersonEmail + '%'
+ )
+;
+
+-- 15. System Communications (From/CC/BCC)
+SELECT
+ sc.[Id]
+ ,sc.[Title]
+ ,sc.[IsActive]
+FROM [SystemCommunication] sc
+WHERE
+ @PersonEmail IS NOT NULL
+ AND (
+ sc.[From] LIKE '%' + @PersonEmail + '%'
+ OR sc.[To] LIKE '%' + @PersonEmail + '%'
+ OR sc.[CC] LIKE '%' + @PersonEmail + '%'
+ OR sc.[BCC] LIKE '%' + @PersonEmail + '%'
+ )
+;
+
+-- 16. Assigned Workflows
+SELECT
+ w.[Id]
+ ,w.[Name]
+ ,wt.[Name] 'WorkflowType'
+ ,wat.[Name] 'ActivityType'
+FROM
+ [Workflow] w
+ INNER JOIN [WorkflowType] wt ON w.[WorkflowTypeId] = wt.[Id]
+ INNER JOIN [WorkflowActivity] wa ON wa.[WorkflowId] = w.[Id]
+ INNER JOIN [WorkflowActivityType] wat ON wa.[ActivityTypeId] = wat.[Id]
+ INNER JOIN #PersonAliasIds pa ON pa.[Id] = wa.[AssignedPersonAliasId]
+WHERE
+ w.[CompletedDateTime] IS NULL
+ AND wa.[CompletedDateTime] IS NULL
+;
+
+-- 17. Assigned Projects
+SELECT
+ j.[Id]
+ ,j.[Name]
+FROM
+ [_com_blueboxmoon_ProjectManagement_ProjectAssignee] ja
+ INNER JOIN [_com_blueboxmoon_ProjectManagement_Project] j ON ja.[ProjectId] = j.[Id]
+ INNER JOIN #PersonAliasIds pa ON ja.[PersonAliasId] = pa.[Id]
+WHERE j.[IsActive] = 1
+;
+
+-- 18. Assigned Project Tasks
+SELECT
+ t.[Name]
+ ,j.[Id] 'ProjectId'
+ ,j.[Name] 'ProjectName'
+FROM
+ [_com_blueboxmoon_ProjectManagement_Task] t
+ INNER JOIN [_com_blueboxmoon_ProjectManagement_Project] j ON t.[ProjectId] = j.[Id]
+ INNER JOIN #PersonAliasIds pa ON t.[AssignedToPersonAliasId] = pa.[Id]
+WHERE
+ t.[IsActive] = 1
+ AND j.[IsActive] = 1
+;
+
+-- 19. Watching Projects
+SELECT
+ j.[Id]
+ ,j.[Name]
+FROM
+ [_com_blueboxmoon_ProjectManagement_Watching] jw
+ JOIN [_com_blueboxmoon_ProjectManagement_Project] j ON jw.[ProjectId] = j.[Id]
+ INNER JOIN #PersonAliasIds pa ON jw.[PersonAliasId] = pa.[Id]
+WHERE
+ j.[IsActive] = 1
+ AND jw.[IsWatching] = 1
+;
+
+-- 20. Supervised Staff
+SELECT
+ p.[Id]
+ ,CONCAT_WS( ' ', p.[NickName], p.[LastName] ) 'Name'
+FROM
+ [AttributeValue] av
+ INNER JOIN #PersonAliasIds pa ON
+ av.[Value] <> ''
+ AND av.[Value] = pa.[Guid]
+ INNER JOIN [Person] p ON av.[EntityId] = p.[Id]
+WHERE
+ av.[AttributeId] = 9768 -- HR > Supervisor
+;
+
{% assign Set = 'Global' | PageParameter:'Person' %}
+{% if Set != '' %}
+ {% assign Person2 = 'Global' | PageParameter:'Person' | PersonByAliasGuid %}
+
+ <h2>{{Person2.FullName}}</h2>
+ {% if Person2.Email contains '@valorouschurch.com' %}
+ <h3>
+ <a href="/Person/{{ Person2.Id}}" class="btn btn-default"><i class="fa fa-pencil"></i></a>
+ Email: {{Person2.Email }}
+ <br><small>NOTE: Update their email last. Some of these searches are based on email.</small>
+ </h3>
+ {% endif %}
+ <br>
+ <div class="row">
+
+ {% comment %} SQL Table 1 - Security Group Membership {% endcomment %}
+ {% assign results = table1.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Security Group Membership' ]}
+ {% for row in table1.rows %}
+ {% if row.SyncDataViewId <> '' and row.SyncDataViewId %}
+ {% capture editurl %}/page/145?DataViewId={{ row.SyncDataViewId }}{% endcapture %}
+ {% else %}
+ {% capture editurl %}/page/113?GroupId={{ row.Id }}{% endcapture %}
+ {% endif %}
+ <b {% if row.IsActive != true or row.IsArchived != false %}style="color:Lightgrey;"{% endif %}><a href="{{ editurl }}" class="btn-xs btn-default">{% if row.SyncDataViewId <> '' and row.SyncDataViewId %}<i class="fa fa-sync-alt"></i>{%else%}<i class="fa fa-pencil"></i>{% endif %}</a> {{ row.Group }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 2 - Room Reservation Approval Groups {% endcomment %}
+ {% assign results = table2.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Room Reservation Approval Groups']}
+ {% for row in table2.rows %}
+ {% if row.SyncDataViewId <> '' and row.SyncDataViewId %}
+ {% capture editurl %}/page/145?DataViewId={{ row.SyncDataViewId }}{% endcapture %}
+ {% else %}
+ {% capture editurl %}/page/113?GroupId={{ row.Id }}{% endcapture %}
+ {% endif %}
+ <b {% if row.IsActive != true or row.IsArchived != false %}style="color:Lightgrey;"{% endif %}><a href="/page/113?GroupId={{ row.Id }}" class="btn-xs btn-default">{% if row.SyncDataViewId <> '' and row.SyncDataViewId %}<i class="fa fa-sync-alt"></i>{% else %}<i class="fa fa-pencil"></i>{% endif %}</a> {{ row.Group }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 3 - Connector Group Memberships {% endcomment %}
+ {% assign results = table3.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Connector Group Memberships']}
+ {% for row in table3.rows %}
+ {% if row.SyncDataViewId <> '' and row.SyncDataViewId %}
+ {% capture editurl %}/page/145?DataViewId={{ row.SyncDataViewId }}{% endcapture %}
+ {% else %}
+ {% capture editurl %}/page/113?GroupId={{ row.Id }}{% endcapture %}
+ {% endif %}
+ <b {% if row.IsActive != true or row.IsArchived != false %}style="color:Lightgrey;"{% endif %}><a href="/page/113?GroupId={{ row.Id }}" class="btn-xs btn-default">{% if row.SyncDataViewId <> '' and row.SyncDataViewId %}<i class="fa fa-sync-alt"></i>{% else %}<i class="fa fa-pencil"></i>{% endif %}</a> {{ row.Group }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 4 - Group Leadership {% endcomment %}
+ {% assign results = table4.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Group Leadership' ]}
+ {% for row in table4.rows %}
+ <b {% if row.IsActive != true or row.IsArchived != false %}style="color:Lightgrey;"{% endif %}><a href="/page/113?GroupId={{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Group }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 5 - Default Connector {% endcomment %}
+ {% assign results = table5.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Default Connector' ]}
+ {% for row in table5.rows %}
+ <b {% if row.IsActive != true or row.IsArchived != false %}style="color:Lightgrey;"{% endif %}><a href="/page/411?ConnectionOpportunityId={{ row.Id }}&ConnectionTypeId={{ row.ConnectionTypeId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }} | {{ row.CampusName }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 6 - Active Connections {% endcomment %}
+ {% assign results = table6.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Active Connections' ]}
+ {% for row in table6.rows %}
+ <b><a href="/page/408?ConnectionRequestId={{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.ConnectionOpportunity }} | {{ row.PersonName }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 7 - Active Event Contact {% endcomment %}
+ {% assign results = table7.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Active Event Contact' ]}
+ {% for row in table7.rows %}
+ <b><a href="/page/402?EventItemOccurrenceId={{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 8 - Active Registration Contact {% endcomment %}
+ {% assign results = table8.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Active Registration Contact' ]}
+ {% for row in table8.rows %}
+ <b><a href="/RegistrationInstance/{{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 9 - Org Chart Group Membership {% endcomment %}
+ {% assign results = table9.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Org Chart Group Membership' ]}
+ {% for row in table9.rows %}
+ {% if row.SyncDataViewId <> '' and row.SyncDataViewId %}
+ {% capture editurl %}/page/145?DataViewId={{ row.SyncDataViewId }}{% endcapture %}
+ {% else %}
+ {% capture editurl %}/page/113?GroupId={{ row.Id }}{% endcapture %}
+ {% endif %}
+ <b {% if row.IsActive != true or row.IsArchived != false %}style="color:Lightgrey;"{% endif %}><a href="{{ editurl }}" class="btn-xs btn-default">{% if row.SyncDataViewId <> '' and row.SyncDataViewId %}<i class="fa fa-sync-alt"></i>{% else %}<i class="fa fa-pencil"></i>{% endif %}</a> {{ row.Group }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 10 - Miscellaneous Security Authorizations {% endcomment %}
+ {% assign results = table10.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Miscellaneous Authorizations' ]}
+ {% for row in table10.rows %}
+ {% case row.EntityType %}
+ {% when '2' %}
+ <b><a href="/page/103?Page={{ row.EntityId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.PageName }} ({{row.EntityName}})</b><br>
+ {% when '9' %}
+ <b><a href="/page/103?Page={{ row.PageId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Block }} ({{row.EntityName}})</b><br>
+ {% when '16' %}
+ <b><a href="/page/113?GroupId={{ row.EntityId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.GroupName }} ({{row.EntityName}})</b><br>
+ {% when '34' %}
+ <b><a href="/page/145?DataViewId={{ row.EntityId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Dataview }} ({{row.EntityName}})</b><br>
+ {% when '54' %}
+ <b><a href="/Schedules?ScheduleId={{ row.EntityId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Schedule }} ({{row.EntityName}})</b><br>
+ {% when '107' %}
+ <b><a href="/page/149?ReportId={{ row.EntityId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Report }} ({{row.EntityName}})</b><br>
+ {% when '234' %}
+ <b><a href="/RegistrationInstance/{{ row.EntityId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.RegName }} ({{row.EntityName}})</b><br>
+ {% else %}
+ <b>{{ row.EntityName }} ({{ row.EntityType }}) | Id:{{ row.EntityId }}</b><br>
+ {% endcase %}
+ {% endfor %}
+ <hr><pre>DELETE FROM [Auth] WHERE [PersonAliasId] IN ( {{ Person2.Aliases | Select:'Id' | Join:', ' }} )</pre>
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 11 - SMS From Values {% endcomment %}
+ {% assign results = table11.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'SMS From Values' ]}
+ {% for row in table11.rows %}
+ <b><a href="/page/327" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Value }} | {{ row.Description }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 12 - Room Reservations {% endcomment %}
+ {% assign results = table12.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Room Reservations']}
+ {% for row in table12.rows %}
+ <b><a href="/ReservationDetail?ReservationId={{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 13 - Service Job Notifications {% endcomment %}
+ {% assign results = table13.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Service Job Notifications' ]}
+ {% for row in table13.rows %}
+ <b {%if row.IsActive !=1 %}style="color:Lightgrey;"{% endif %}><a href="/page/115?serviceJobId={{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ rowName }} ({{ row.Class }})</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 14 - Communication Templates {% endcomment %}
+ {% assign results = table14.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Communication Templates' ]}
+ {% for row in table14.rows %}
+ <b {%if row.IsActive !=1 %}style="color:Lightgrey;"{% endif %}><a href="/admin/communications/templates/{{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 15 - System Communications {% endcomment %}
+ {% assign results = table15.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'System Communications' ]}
+ {% for row in table15.rows %}
+ <b {%if row.IsActive !=1 %}style="color:Lightgrey;"{% endif %}><a href="/communications/system/{{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Title }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 16 - Assigned Workflows {% endcomment %}
+ {% assign results = table16.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Assigned Workflows' ]}
+ {% for row in table16.rows %}
+ <b><a href="/workflow/{{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }} ({{ row.WorkflowType }} - {{ row.ActivityType }})</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 17 - Assigned Projects {% endcomment %}
+ {% assign results = table17.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Assigned Projects' ]}
+ {% for row in table17.rows %}
+ <b><a href="/project/{{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 18 - Assigned Project Tasks {% endcomment %}
+ {% assign results = table18.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Assigned Project Tasks' ]}
+ {% for row in table18.rows %}
+ <b><a href="/project/{{ row.ProjectId }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }} ({{ row.ProjectName }})</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 19 - Watching Projects {% endcomment %}
+ {% assign results = table19.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Watching Projects' ]}
+ {% for row in table19.rows %}
+ <b><a href="/project/{{ row.Id }}" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }}</b><br>
+ {% endfor %}
+ <hr><pre>DELETE FROM [_com_blueboxmoon_ProjectManagement_Watching] WHERE [PersonAliasId] IN ( {{ Person2.Aliases | Select:'Id' | Join:', ' }} )</pre>
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ {% comment %} SQL Table 20 - Supervised Staff {% endcomment %}
+ {% assign results = table20.rows | Size %}
+ {% if results != 0 %}
+ <div class="col-md-4">
+ {[ panel title:'Supervised Staff' ]}
+ {% for row in table20.rows %}
+ <b><a href="/person/{{ row.Id }}/HR" class="btn-xs btn-default"><i class="fa fa-pencil"></i></a> {{ row.Name }}</b><br>
+ {% endfor %}
+ {[ endpanel ]}
+ </div>
+ {% endif %}
+
+ </div>
+{% else %}
+ <h2>Select a Person Above</h2>
+{% endif %}
+
Tags
+For every month in the specified year, return the average attendance (based on metrics) for each campus.
+This can be easily modified to report on monthly averages for any campus partitioned metric.
+DECLARE @CampusEntityTypeId int = (
+ SELECT Id FROM [EntityType]
+ WHERE Guid = '00096BED-9587-415E-8AD4-4E076AE8FBF0' --Campus
+);
+
+SELECT
+ m.Title 'Metric'
+ ,mp.Label 'Partition'
+ ,mp.Id 'Partition Id'
+FROM
+ [Metric] m
+ JOIN [MetricPartition] mp
+ ON mp.MetricId = m.Id
+ AND mp.EntityTypeId = @CampusEntityTypeId
+ORDER BY m.Title
+
DECLARE @Year int = 2018;
+
+WITH MetricValues as (
+ --Campuses
+ SELECT
+ SUM( mv.YValue ) 'Sum'
+ ,mvp.EntityId
+ ,[dbo].[ufnUtility_GetSundayDate]( mv.MetricValueDateTime ) 'SundayDate'
+ FROM
+ [MetricValuePartition] mvp
+ JOIN [MetricValue] mv ON mvp.MetricValueId = mv.Id
+ WHERE
+ mvp.MetricPartitionId IN ( 27, 29, 23, 25 ) --Campus Partitions
+ AND DATEPART( year, mv.MetricValueDateTime ) >= @Year
+ GROUP BY
+ [dbo].[ufnUtility_GetSundayDate]( mv.MetricValueDateTime )
+ ,mvp.EntityId
+
+ UNION
+
+ --ONLINE (Metric has no campus partition)
+ SELECT
+ SUM( mv.YValue ) 'Sum'
+ ,0 'EntityId'
+ ,[dbo].[ufnUtility_GetSundayDate]( mv.MetricValueDateTime ) 'SundayDate'
+ FROM
+ [MetricValue] mv
+ WHERE
+ mv.MetricId = 40 --Online Headcounts
+ AND DATEPART( year, mv.MetricValueDateTime ) >= @Year
+ GROUP BY
+ [dbo].[ufnUtility_GetSundayDate]( mv.MetricValueDateTime )
+)
+SELECT
+ ROUND( AVG( v.[Sum] ), 0 ) 'Average'
+ ,CASE v.EntityId
+ WHEN 0 THEN 'Online'
+ ELSE ( SELECT Name FROM Campus c WHERE c.Id = v.EntityId )
+ END 'Campus'
+ ,FORMAT( v.SundayDate, 'yyyy-MM' ) 'Month'
+FROM [MetricValues] v
+GROUP BY
+ EntityId
+ ,FORMAT( v.SundayDate, 'yyyy-MM' )
+ORDER BY
+ 'Campus'
+ ,'Month' ASC
+
Tags
+We needed a way to give permissions for someone to use the check scanner but didn't want to give them access to the Rock site. The settings below are the absolute minimum permissions needed for the check scanner app to work fully.
+We created a role APP - Check Scanner
with this narrow scope of permissions so that we can easily assign it to any user that needs access to the scanner app. This role provides all of the needed permissions and doesn't require the user to have any other roles (RSR - Staff Workers
, RSR - Finance Worker
, etc.)
Admin Tools > Security > Rest Controllers
Controller | +Method | +Path | +Verbs | +
---|---|---|---|
BinaryFileTypes | +GET | +api/BinaryFileTypes | +VIEW | +
Campuses | +GET | +api/Campuses | +VIEW | +
DefinedTypes | +GET | +api/DefinedTypes | +VIEW | +
DefinedValues | +GET | +api/DefinedValues | +VIEW | +
ExceptionLogs | +POST | +api/ExceptionLogs/LogException | +VIEW, EDIT | +
FinancialAccounts | +GET | +api/FinancialAccounts | +VIEW | +
FinancialBatches | +DELETE | +api/FinancialBatches/{0} | +VIEW, EDIT | +
FinancialBatches | +GET | +api/FinancialBatches | +VIEW | +
FinancialBatches | +GET | +api/FinancialBatches/GetControlTotals | +VIEW | +
FinancialBatches | +POST | +api/FinancialBatches | +VIEW, EDIT | +
FinancialBatches | +PUT | +api/FinancialBatches/{0} | +VIEW, EDIT | +
FinancialPaymentDetails | +GET | +api/FinancialPaymentDetails/{0} | +VIEW | +
FinancialPaymentDetails | +POST | +api/FinancialPaymentDetails/{0} | +VIEW, EDIT | +
FinancialTransactionDetails | +GET | +api/FinancialTransactionDetails | +VIEW | +
FinancialTransactionDetails | +POST | +api/FinancialTransactionDetails | +VIEW, EDIT | +
FinancialTransactionDetails | +PUT | +api/FinancialTransactionDetails/{0} | +VIEW, EDIT | +
FinancialTransactionImages | +GET | +api/FinancialTransactionImages | +VIEW | +
FinancialTransactionImages | +POST | +api/FinancialTransactionImages | +VIEW, EDIT | +
FinancialTransactions | +DELETE | +api/FinancialTransactions/{0} | +VIEW, EDIT | +
FinancialTransactions | +GET | +api/FinancialTransactions | +VIEW | +
FinancialTransactions | +POST | +api/FinancialTransactions | +VIEW, EDIT | +
FinancialTransactions | +POST | +api/FinancialTransactions/AlreadyScanned | +VIEW, EDIT | +
FinancialTransactions | +POST | +api/FinancialTransactions/PostScanned | +VIEW, EDIT | +
People | +GET | +api/People/GetByPersonAliasIs/{personAliasId} | +VIEW | +
People | +GET | +api/People/GetByUsername/{username} | +VIEW | +
Admin Tools > Security > Entity Administration
Entity | +Verbs | +
---|---|
Rock.Model.FinancialAccount | +VIEW | +
Rock.Model.FinancialBatch | +VIEW, EDIT, DELETE | +
Rock.Model.FinancialPaymentDetail | +EDIT | +
Rock.Model.FinancialTransaction | +VIEW, EDIT | +
Rock.Model.FinancialTransactionDetail | +VIEW | +
Rock.Model.FinancialTransactionImage | +VIEW | +
Admin Tools > General > File Types
File Type | +Verbs | +
---|---|
Transaction Image | +VIEW, EDIT | +
Tags
+This allows you to have a nicely formatted list of group members on a workflow form. Similar to what you would see on the group attendance page.
+Once the form is submitted, you will have a comma separated list of person alias guids which you can loop over and drop into a Person type attribute.
+Note: The person filling out the form must have view permissions on the group you are using. If that won't work for you, you can re-write the capture statement as an entity command with securityenabled:'false'
rather than referencing Group.Members
.
Group [Group]
+SelectedPeople [Text]
+Display the SelectedPeople
attribute on a form with the following pre and post html.
</div>
+{% capture select %}
+<div class="controls rockcheckboxlist rockcheckboxlist-horizontal in-columns in-columns-1">
+ {% assign group = Workflow | Attribute:'Group','Object' %}
+ {% assign members = group.Members | OrderBy:'Person.NickName,Person.LastName' %}
+ {% for member in members %}
+ <div class="checkbox mx-4">
+ <label for="{{ member.Id }}">
+ <input id="{{ member.Id }}" type="checkbox" name="{{ member.Id }}" value="{{ member.Person.PrimaryAlias.Guid }}">
+ <span class="label-text">
+ <img src="{{ member.Person.PhotoUrl }}&w=80" style="width:80px;border-radius:100%;" />
+ {{ member.Person.FullName }}
+ </span>
+ </label>
+ </div>
+ {% endfor %}
+</div>
+{% endcapture %}
+<script>
+Sys.Application.add_load( function () {
+ // Replace the control
+ var select = $.parseHTML( '{{ select | StripNewlines }}' );
+ $( '.gm-select input[type=text]' ).hide();
+ $( '.gm-select .control-wrapper' ).append( select );
+ // Event handler
+ $('.gm-select input[type=checkbox]').change( function(){
+ var selectedIDs = $('.gm-select input:checked').map( function(){ return this.value } ).get().join( ',' );
+ $( '.gm-select input[type=text]' ).val( selectedIDs );
+ })
+} );
+</script>
+
Tags
+This script will reassign all active instance of a workflow to a list of people in a round-robin fashion. It assigns the activity, and also updates a person attribute in the workflow to match the assignment.
+PersonId
s for assignmentActivityTypeId
that you want to assignAttributeId
that you want to setDECLARE @Workers TABLE ( [Id] int NOT NULL identity(1,1), [PersonId] int, [PrimaryAliasId] int, [PrimaryAliasGuid] uniqueidentifier );
+DECLARE @ToUpdate TABLE( [Id] int NOT NULL identity(1,1), [ActivityId] int, [AttributeId] int );
+
+-- Replace this with the ID of your activity type that you need to assign
+-- Use this to help find that Id: SELECT [Id], [Name] FROM [WorkflowActivityType] WHERE [WorkflowTypeId] = 141;
+DECLARE @ActivityTypeId int = 364;
+
+-- Replace this with the ID of the attribute that holds the person
+DECLARE @AttributeId int = 9501;
+
+-- Put your workers PersonIDs here. They have to be wrapped in ()
+INSERT INTO @Workers ([PersonId]) VALUES (515), (50532), (6992);
+
+/* Calculate the info we'll need later */
+DECLARE @NumWorkers int = ( SELECT COUNT(1) FROM @Workers );
+
+UPDATE w
+SET
+ w.[PrimaryAliasId] = pa.[Id]
+ ,w.[PrimaryAliasGuid] = pa.[Guid]
+FROM
+ @Workers w
+ JOIN [PersonAlias] pa
+ ON w.[PersonId] = pa.[PersonId]
+ AND w.[PersonId] = pa.[AliasPersonId]
+;
+
+INSERT INTO @ToUpdate ( [ActivityId], [AttributeId] )
+SELECT
+ act.[Id]
+ ,av.[Id]
+FROM
+ [WorkflowActivity] act
+ LEFT JOIN [AttributeValue] av
+ ON act.[WorkflowId] = av.[EntityId]
+ AND av.[AttributeId] = @AttributeId
+WHERE
+ act.[ActivityTypeId] = @ActivityTypeId
+ AND act.[CompletedDateTime] IS NULL
+;
+
+BEGIN TRANSACTION
+
+/* Update the activity assignment */
+UPDATE act
+ SET act.[AssignedPersonAliasId] = w.[PrimaryAliasId]
+FROM
+ @ToUpdate upd
+ JOIN @Workers w ON @NumWorkers - ( upd.[Id] % @NumWorkers ) = w.[Id]
+ JOIN [WorkflowActivity] act ON upd.[ActivityId] = act.[Id]
+;
+
+/* Update the attribute */
+UPDATE av
+ SET av.[Value] = w.[PrimaryAliasGuid]
+FROM
+ @ToUpdate upd
+ JOIN @Workers w ON @NumWorkers - ( upd.[Id] % @NumWorkers ) = w.[Id]
+ JOIN [AttributeValue] av ON upd.[AttributeId] = av.[Id]
+;
+
+COMMIT TRANSACTION
+
Tags
+This will show a table with all workflows of a particular type, with a column for every attribute in the workflow.
+{% workflow where:'WorkflowTypeId == "25"' %}
+ {% assign firstWorkflow = workflowItems | First %}
+
+ {% capture attributeNames -%}
+ {%- for av in firstWorkflow.AttributeValues -%}
+ {{ av.AttributeName | StripNewLines }}|
+ {%- endfor -%}
+ {%- endcapture %}
+
+ {% assign attributeNames = attributeNames | ReplaceLast:'|','' | Split:'|' %}
+
+<div class="table-wrapper">
+ <table id="example" class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th>Workflow Name</th>
+ {% for name in attributeNames %}
+ <th>{{ name }}</th>
+ {% endfor %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for w in workflowItems %}
+ <tr>
+ <td>{{ w.Name }}</td>
+ {% for a in w.AttributeValues %}
+ <td>{{ a.ValueFormatted | Default:'(none)' }}</td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</div>
+{% endworkflow %}
+
\n {translation(\"search.result.term.missing\")}: {...missing}\n
\n }\n