Skip to content

Commit db7d8f6

Browse files
Merge remote-tracking branch 'origin/hotfix-19.1' into develop
2 parents 441daf8 + dfc84e4 commit db7d8f6

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)