Skip to content

Commit fc01f11

Browse files
- Update the Registration Entry registrant eligibility check so family members with unknown details (like age or grade) can show on the Registration Entry dropdown as potentially eligible instead of being blocked. The full requirements are still enforced at submission.
1 parent a361af2 commit fc01f11

4 files changed

Lines changed: 205 additions & 30 deletions

File tree

Rock.Blocks/Event/RegistrationEntry.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
using Rock.Financial;
3737
using Rock.Model;
3838
using Rock.Model.Event.RegistrationInstance.Options;
39+
using Rock.Model.Event.RegistrationTemplate.Options;
3940
using Rock.Pdf;
4041
using Rock.Security;
4142
using Rock.Tasks;
@@ -3975,6 +3976,15 @@ RegistrationGuid argument.
39753976
var registrationTemplate = registrationTemplateService.Get( context.RegistrationSettings.RegistrationTemplateId );
39763977
var registrantEligibilityEvaluator = registrationTemplateService.GetRegistrantEligibility( registrationTemplate );
39773978

3979+
// Use Lax mode here so that a family member who is missing data needed by an
3980+
// eligibility requirement (e.g. age, grade) is still surfaced in the dropdown
3981+
// as "potentially eligible" rather than being marked Ineligible. The strict
3982+
// check is still enforced at registration submission.
3983+
var familyMemberEligibilityOptions = new RegistrantEligibilityEvaluationOptions
3984+
{
3985+
Mode = RegistrantEligibilityEvaluationMode.Lax
3986+
};
3987+
39783988
var currentPerson = GetCurrentPerson();
39793989
var familyMembers = context.RegistrationSettings.AreCurrentFamilyMembersShown ?
39803990
currentPerson.GetFamilyMembers( true, rockContext )
@@ -3991,7 +4001,7 @@ RegistrationGuid argument.
39914001
FamilyGuid = gm.FamilyGuid,
39924002
FullName = gm.Person.FullName,
39934003
FieldValues = GetCurrentValueFieldValues( context, rockContext, gm.Person, null, formModels, false ),
3994-
IsIneligible = !registrantEligibilityEvaluator.Evaluate( gm.Person )
4004+
IsIneligible = !registrantEligibilityEvaluator.Evaluate( gm.Person, familyMemberEligibilityOptions )
39954005
} )
39964006
.ToList() :
39974007
new List<RegistrationEntryFamilyMemberBag>();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// <copyright>
2+
// Copyright by the Spark Development Network
3+
//
4+
// Licensed under the Rock Community License (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.rockrms.com/license
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace Rock.Model.Event.RegistrationTemplate.Options
18+
{
19+
/// <summary>
20+
/// Controls how a registrant eligibility evaluator treats a person who is missing data
21+
/// required to fully evaluate an eligibility requirement (e.g. age, grade, gender, age classification).
22+
/// </summary>
23+
public enum RegistrantEligibilityEvaluationMode
24+
{
25+
/// <summary>
26+
/// A person who is missing data required by a configured eligibility requirement is
27+
/// considered ineligible. This is the default and is appropriate for the final
28+
/// eligibility check at registration submission.
29+
/// </summary>
30+
Strict = 0,
31+
32+
/// <summary>
33+
/// A person who is missing data required by a configured eligibility requirement is
34+
/// considered eligible for that requirement. This is appropriate for surfacing
35+
/// "potentially eligible" individuals in user interfaces (for example, the family
36+
/// members dropdown on a registration entry block) where the person can supply the
37+
/// missing data later.
38+
/// </summary>
39+
Lax = 1
40+
}
41+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// <copyright>
2+
// Copyright by the Spark Development Network
3+
//
4+
// Licensed under the Rock Community License (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.rockrms.com/license
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace Rock.Model.Event.RegistrationTemplate.Options
18+
{
19+
/// <summary>
20+
/// Options that control how a <see cref="RegistrantEligibilityEvaluator"/> evaluates a person
21+
/// against the configured eligibility requirements of a registration template.
22+
/// </summary>
23+
public class RegistrantEligibilityEvaluationOptions
24+
{
25+
/// <summary>
26+
/// Gets or sets the mode that controls how the evaluator treats a person who is missing
27+
/// data required by a configured eligibility requirement.
28+
/// </summary>
29+
/// <remarks>
30+
/// <para>
31+
/// When <see cref="RegistrantEligibilityEvaluationMode.Strict"/> (the default) is used,
32+
/// a missing field that is needed by a configured requirement (for example, an unspecified
33+
/// age when a minimum age is configured) causes the person to be reported as ineligible.
34+
/// </para>
35+
/// <para>
36+
/// When <see cref="RegistrantEligibilityEvaluationMode.Lax"/> is used, the person is
37+
/// treated as eligible for that specific requirement; this is appropriate when surfacing
38+
/// "potentially eligible" individuals in a UI where the missing data can be supplied later.
39+
/// The eligibility Data View check, when configured, is always applied regardless of mode
40+
/// because it's not currently possible to check whether a person is missing data that might
41+
/// make them eligible against an arbitrary Data View.
42+
/// </para>
43+
/// </remarks>
44+
public RegistrantEligibilityEvaluationMode Mode { get; set; } = RegistrantEligibilityEvaluationMode.Strict;
45+
}
46+
}

Rock/Model/Event/RegistrationTemplate/RegistrantEligibilityEvaluator.cs

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright>
1+
// <copyright>
22
// Copyright by the Spark Development Network
33
//
44
// Licensed under the Rock Community License (the "License");
@@ -17,6 +17,8 @@
1717
using System;
1818
using System.Linq;
1919

20+
using Rock.Model.Event.RegistrationTemplate.Options;
21+
2022
namespace Rock.Model
2123
{
2224
/// <summary>
@@ -53,22 +55,57 @@ internal RegistrantEligibilityEvaluator( RegistrationTemplate registrationTempla
5355
}
5456

5557
/// <summary>
56-
/// Determines whether the specified registrant meets the eligibility criteria defined by the current settings.
58+
/// Determines whether the specified registrant meets the eligibility criteria defined by
59+
/// the current settings using <see cref="RegistrantEligibilityEvaluationMode.Strict"/> mode.
5760
/// </summary>
5861
/// <param name="registrantPerson">The person to evaluate for eligibility. Returns <see langword="false"/> if <see langword="null"/>.</param>
5962
/// <returns><see langword="true"/> if the registrant meets all eligibility requirements; otherwise, <see langword="false"/>.</returns>
6063
public bool Evaluate( Person registrantPerson )
6164
{
62-
return Evaluate( registrantPerson, out var _ );
65+
return Evaluate( registrantPerson, options: null, out var _ );
6366
}
6467

6568
/// <summary>
66-
/// Determines whether the specified registrant meets the eligibility criteria defined by the current settings.
69+
/// Determines whether the specified registrant meets the eligibility criteria defined by
70+
/// the current settings using <see cref="RegistrantEligibilityEvaluationMode.Strict"/> mode.
6771
/// </summary>
6872
/// <param name="registrantPerson">The person to evaluate for eligibility. Returns <see langword="false"/> if <see langword="null"/>.</param>
6973
/// <param name="error">The first friendly error explaining why the person is not eligible.</param>
7074
/// <returns><see langword="true"/> if the registrant meets all eligibility requirements; otherwise, <see langword="false"/>.</returns>
7175
public bool Evaluate( Person registrantPerson, out string error )
76+
{
77+
return Evaluate( registrantPerson, options: null, out error );
78+
}
79+
80+
/// <summary>
81+
/// Determines whether the specified registrant meets the eligibility criteria defined by
82+
/// the current settings, using the supplied <paramref name="options"/> to control how
83+
/// missing-data scenarios are handled.
84+
/// </summary>
85+
/// <param name="registrantPerson">The person to evaluate for eligibility. Returns <see langword="false"/> if <see langword="null"/>.</param>
86+
/// <param name="options">
87+
/// Options that control how the evaluator treats missing data on the person. If
88+
/// <see langword="null"/>, defaults to <see cref="RegistrantEligibilityEvaluationMode.Strict"/>.
89+
/// </param>
90+
/// <returns><see langword="true"/> if the registrant meets all eligibility requirements; otherwise, <see langword="false"/>.</returns>
91+
public bool Evaluate( Person registrantPerson, RegistrantEligibilityEvaluationOptions options )
92+
{
93+
return Evaluate( registrantPerson, options, out var _ );
94+
}
95+
96+
/// <summary>
97+
/// Determines whether the specified registrant meets the eligibility criteria defined by
98+
/// the current settings, using the supplied <paramref name="options"/> to control how
99+
/// missing-data scenarios are handled.
100+
/// </summary>
101+
/// <param name="registrantPerson">The person to evaluate for eligibility. Returns <see langword="false"/> if <see langword="null"/>.</param>
102+
/// <param name="options">
103+
/// Options that control how the evaluator treats missing data on the person. If
104+
/// <see langword="null"/>, defaults to <see cref="RegistrantEligibilityEvaluationMode.Strict"/>.
105+
/// </param>
106+
/// <param name="error">The first friendly error explaining why the person is not eligible.</param>
107+
/// <returns><see langword="true"/> if the registrant meets all eligibility requirements; otherwise, <see langword="false"/>.</returns>
108+
public bool Evaluate( Person registrantPerson, RegistrantEligibilityEvaluationOptions options, out string error )
72109
{
73110
error = null;
74111

@@ -86,18 +123,25 @@ public bool Evaluate( Person registrantPerson, out string error )
86123
return true;
87124
}
88125

126+
// Resolve the evaluation mode. A null options instance falls back to the default
127+
// (Strict), preserving the behavior of the parameterless Evaluate overloads.
128+
var evaluationMode = ( options ?? new RegistrantEligibilityEvaluationOptions() ).Mode;
129+
89130
// Minimum Age
90131
if ( _registrantEligibilitySettings.MinimumAge.HasValue )
91132
{
92133
var minAgeError = $"{registrantPerson.FullName} does not meet the minimum age requirement for this {_registrationTemplate.RegistrationTerm} ({_registrantEligibilitySettings.MinimumAge.Value} years old or older).";
93134

94135
if ( !registrantPerson.Age.HasValue )
95136
{
96-
error = minAgeError;
97-
return false;
137+
// Age is required to evaluate this rule. Strict mode rejects, Lax mode skips.
138+
if ( evaluationMode == RegistrantEligibilityEvaluationMode.Strict )
139+
{
140+
error = minAgeError;
141+
return false;
142+
}
98143
}
99-
100-
if ( registrantPerson.Age.Value < _registrantEligibilitySettings.MinimumAge.Value )
144+
else if ( registrantPerson.Age.Value < _registrantEligibilitySettings.MinimumAge.Value )
101145
{
102146
error = minAgeError;
103147
return false;
@@ -107,16 +151,19 @@ public bool Evaluate( Person registrantPerson, out string error )
107151
// Maximum Age
108152
if ( _registrantEligibilitySettings.MaximumAge.HasValue )
109153
{
110-
var isMaxAgeInteger = _registrantEligibilitySettings.MaximumAge.Value.IsInteger();
154+
var isMaxAgeAnInteger = _registrantEligibilitySettings.MaximumAge.Value.IsInteger();
111155
var maxAgeError = $"{registrantPerson.FullName} does not meet the maximum age requirement for this {_registrationTemplate.RegistrationTerm} ({_registrantEligibilitySettings.MaximumAge.Value} years old or younger).";
112156

113157
if ( !registrantPerson.Age.HasValue )
114158
{
115-
error = maxAgeError;
116-
return false;
159+
// Age is required to evaluate this rule. Strict mode rejects, Lax mode skips.
160+
if ( evaluationMode == RegistrantEligibilityEvaluationMode.Strict )
161+
{
162+
error = maxAgeError;
163+
return false;
164+
}
117165
}
118-
119-
if ( isMaxAgeInteger )
166+
else if ( isMaxAgeAnInteger )
120167
{
121168
// If max age is an integer number of years old, then treat it as "up to and including that age".
122169
// So if max age is 18, then a registrant who is 18 years and 11 months old would still be eligible,
@@ -139,13 +186,13 @@ public bool Evaluate( Person registrantPerson, out string error )
139186
}
140187
}
141188
}
142-
189+
143190
// Age Classification
144191
if ( _registrantEligibilitySettings.AgeClassification.HasValue )
145192
{
146193
// Typically age classification is set in the person save hook,
147194
// but that won't run until a running transaction is committed.
148-
// Resolve it here so we can determine registrant eligibility when a new person is created for a registration.
195+
// Resolve it here using the same logic so we can determine registrant eligibility when a new person is created for a registration.
149196
var resolvedAgeClassification = registrantPerson.AgeClassification;
150197
if ( resolvedAgeClassification == AgeClassification.Unknown && registrantPerson.Age.HasValue )
151198
{
@@ -159,9 +206,21 @@ public bool Evaluate( Person registrantPerson, out string error )
159206
}
160207
}
161208

162-
if ( resolvedAgeClassification != _registrantEligibilitySettings.AgeClassification.Value )
209+
var ageClassificationError = $"{registrantPerson.FullName} does not meet the age requirement for this {_registrationTemplate.RegistrationTerm} ({_registrantEligibilitySettings.AgeClassification.GetDisplayName()}).";
210+
211+
if ( resolvedAgeClassification == AgeClassification.Unknown )
163212
{
164-
error = $"{registrantPerson.FullName} does not meet the age requirement for this {_registrationTemplate.RegistrationTerm} ({_registrantEligibilitySettings.AgeClassification.GetDisplayName()}).";
213+
// Age classification could not be determined (no explicit value and no age to derive it).
214+
// Strict mode rejects, Lax mode skips this rule.
215+
if ( evaluationMode == RegistrantEligibilityEvaluationMode.Strict )
216+
{
217+
error = ageClassificationError;
218+
return false;
219+
}
220+
}
221+
else if ( resolvedAgeClassification != _registrantEligibilitySettings.AgeClassification.Value )
222+
{
223+
error = ageClassificationError;
165224
return false;
166225
}
167226
}
@@ -170,14 +229,17 @@ public bool Evaluate( Person registrantPerson, out string error )
170229
if ( _registrantEligibilitySettings.MaximumGradeOffset.HasValue )
171230
{
172231
var minGradeError = $"{registrantPerson.FullName} does not meet the minimum grade requirement for this {_registrationTemplate.RegistrationTerm}.";
173-
232+
174233
if ( !registrantPerson.GradeOffset.HasValue )
175234
{
176-
error = minGradeError;
177-
return false;
235+
// Grade is required to evaluate this rule. Strict mode rejects, Lax mode skips.
236+
if ( evaluationMode == RegistrantEligibilityEvaluationMode.Strict )
237+
{
238+
error = minGradeError;
239+
return false;
240+
}
178241
}
179-
180-
if ( registrantPerson.GradeOffset.Value > _registrantEligibilitySettings.MaximumGradeOffset.Value )
242+
else if ( registrantPerson.GradeOffset.Value > _registrantEligibilitySettings.MaximumGradeOffset.Value )
181243
{
182244
error = minGradeError;
183245
return false;
@@ -188,14 +250,17 @@ public bool Evaluate( Person registrantPerson, out string error )
188250
if ( _registrantEligibilitySettings.MinimumGradeOffset.HasValue )
189251
{
190252
var maxGradeError = $"{registrantPerson.FullName} does not meet the maximum grade requirement for this {_registrationTemplate.RegistrationTerm}.";
191-
253+
192254
if ( !registrantPerson.GradeOffset.HasValue )
193255
{
194-
error = maxGradeError;
195-
return false;
256+
// Grade is required to evaluate this rule. Strict mode rejects, Lax mode skips.
257+
if ( evaluationMode == RegistrantEligibilityEvaluationMode.Strict )
258+
{
259+
error = maxGradeError;
260+
return false;
261+
}
196262
}
197-
198-
if ( registrantPerson.GradeOffset.Value < _registrantEligibilitySettings.MinimumGradeOffset.Value )
263+
else if ( registrantPerson.GradeOffset.Value < _registrantEligibilitySettings.MinimumGradeOffset.Value )
199264
{
200265
error = maxGradeError;
201266
return false;
@@ -205,14 +270,27 @@ public bool Evaluate( Person registrantPerson, out string error )
205270
// Gender
206271
if ( _registrantEligibilitySettings.Gender.HasValue )
207272
{
208-
if ( registrantPerson.Gender != _registrantEligibilitySettings.Gender.Value )
273+
var genderError = $"{registrantPerson.FullName} does not meet the gender requirement for this {_registrationTemplate.RegistrationTerm} ({_registrantEligibilitySettings.Gender.Value.GetDisplayName()}).";
274+
275+
if ( registrantPerson.Gender == Gender.Unknown )
276+
{
277+
// Gender is required to evaluate this rule. Strict mode rejects, Lax mode skips.
278+
if ( evaluationMode == RegistrantEligibilityEvaluationMode.Strict )
279+
{
280+
error = genderError;
281+
return false;
282+
}
283+
}
284+
else if ( registrantPerson.Gender != _registrantEligibilitySettings.Gender.Value )
209285
{
210-
error = $"{registrantPerson.FullName} does not meet the gender requirement for this {_registrationTemplate.RegistrationTerm} ({_registrantEligibilitySettings.Gender.Value.GetDisplayName()}).";
286+
error = genderError;
211287
return false;
212288
}
213289
}
214290

215291
// Data View
292+
// The Data View check is based on concrete membership in a configured query rather than
293+
// missing-data semantics, so it is enforced regardless of the evaluation mode.
216294
if ( _eligibleDataViewPersonQuery != null )
217295
{
218296
if ( !_eligibleDataViewPersonQuery.Any( p => p.Id == registrantPerson.Id ) )

0 commit comments

Comments
 (0)