Skip to content

Commit b8fa770

Browse files
committed
- PR Changes
1 parent fdbc08d commit b8fa770

6 files changed

Lines changed: 178 additions & 46 deletions

File tree

Rock.Blocks/Crm/NcoaResults.cs

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,18 @@ private string FormattedAddress( string street1, string street2, string city, st
243243
return result;
244244
}
245245

246-
public Dictionary<int, string> GetPersonNamesForFamilies( List<int> familyIds )
246+
/// <summary>
247+
/// Gets a comma-separated string of member names for each of the specified family group IDs.
248+
/// Excludes deceased members.
249+
/// </summary>
250+
/// <param name="familyIds">The family group IDs to look up.</param>
251+
/// <returns>A dictionary mapping family group ID to a comma-separated member name string.</returns>
252+
private Dictionary<int, string> GetPersonNamesForFamilies( List<int> familyIds )
247253
{
248-
var groupMemberService = new GroupMemberService( RockContext );
249254
var familyGroupTypeGuid = Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid();
250255

251-
var families = groupMemberService.Queryable()
256+
return new GroupMemberService( RockContext )
257+
.Queryable()
252258
.Where( gm =>
253259
familyIds.Contains( gm.GroupId ) &&
254260
gm.Group.GroupType.Guid == familyGroupTypeGuid &&
@@ -264,8 +270,67 @@ public Dictionary<int, string> GetPersonNamesForFamilies( List<int> familyIds )
264270
g => g.Key,
265271
g => string.Join( ", ", g.Select( x => x.FullName ) )
266272
);
273+
}
274+
275+
/// <summary>
276+
/// For individual move records, returns a dictionary mapping person alias ID to a
277+
/// comma-separated string of other family member names. A non-empty entry indicates
278+
/// a split family move — the individual moved but other members remain at the address.
279+
/// Excludes deceased members.
280+
/// </summary>
281+
/// <param name="ncoaHistoryData">The paged NCOA history records.</param>
282+
/// <returns>A dictionary mapping person alias ID to other family member names.</returns>
283+
private Dictionary<int, string> GetOtherFamilyMembersForIndividualMoves( List<NcoaHistory> ncoaHistoryData )
284+
{
285+
var individualMoves = ncoaHistoryData
286+
.Where( h => h.MoveType == MoveType.Individual )
287+
.ToList();
288+
289+
if ( !individualMoves.Any() )
290+
{
291+
return new Dictionary<int, string>();
292+
}
293+
294+
// Resolve alias IDs to person IDs.
295+
var aliasIds = individualMoves.Select( h => h.PersonAliasId ).ToList();
296+
var aliasToPersonId = new PersonAliasService( RockContext )
297+
.Queryable()
298+
.Where( pa => aliasIds.Contains( pa.Id ) )
299+
.Select( pa => new { AliasId = pa.Id, pa.PersonId } )
300+
.ToList()
301+
.ToDictionary( x => x.AliasId, x => x.PersonId );
302+
303+
// Fetch all non-deceased members of the specific family groups from the NCOA records.
304+
// Scoping to FamilyId (not all groups the person belongs to) matches the web forms behavior.
305+
var individualMoveFamilyIds = individualMoves.Select( h => h.FamilyId ).Distinct().ToList();
306+
var allFamilyMembers = new GroupMemberService( RockContext )
307+
.Queryable()
308+
.Where( gm => individualMoveFamilyIds.Contains( gm.GroupId ) && !gm.Person.IsDeceased )
309+
.Select( gm => new { gm.GroupId, gm.PersonId, FullName = gm.Person.NickName + " " + gm.Person.LastName } )
310+
.ToList();
267311

268-
return families;
312+
var result = new Dictionary<int, string>();
313+
314+
foreach ( var move in individualMoves )
315+
{
316+
if ( !aliasToPersonId.TryGetValue( move.PersonAliasId, out var personId ) )
317+
{
318+
continue;
319+
}
320+
321+
var otherMembers = allFamilyMembers
322+
.Where( m => m.GroupId == move.FamilyId && m.PersonId != personId )
323+
.Select( m => m.FullName )
324+
.Distinct()
325+
.ToList();
326+
327+
if ( otherMembers.Any() )
328+
{
329+
result[move.PersonAliasId] = string.Join( ", ", otherMembers );
330+
}
331+
}
332+
333+
return result;
269334
}
270335

271336
#endregion
@@ -360,7 +425,7 @@ public BlockActionResult GetNcoaData(int pageNumber)
360425

361426
if ( dateRange.End.HasValue )
362427
{
363-
ncoaQuery = ncoaQuery.Where( i => i.NcoaRunDateTime <= dateRange.End );
428+
ncoaQuery = ncoaQuery.Where( i => i.NcoaRunDateTime < dateRange.End );
364429
}
365430
}
366431

@@ -438,6 +503,17 @@ public BlockActionResult GetNcoaData(int pageNumber)
438503

439504
var familyNamesKey = GetPersonNamesForFamilies( familyIds );
440505

506+
// Fetch the actual family group names for all records on this page.
507+
var allFamilyIds = ncoaHistoryData.Select( h => h.FamilyId ).Distinct().ToList();
508+
var familyGroupNames = new GroupService( RockContext )
509+
.Queryable()
510+
.Where( g => allFamilyIds.Contains( g.Id ) )
511+
.Select( g => new { g.Id, g.Name } )
512+
.ToList()
513+
.ToDictionary( g => g.Id, g => g.Name );
514+
515+
var otherFamilyMembersByAliasId = GetOtherFamilyMembersForIndividualMoves( ncoaHistoryData );
516+
441517
var ncoaPersonAliasIds = ncoaHistoryData.Select( d => d.PersonAliasId ).ToList();
442518

443519
var personData = new PersonAliasService( RockContext ).Queryable().AsNoTracking()
@@ -460,9 +536,11 @@ public BlockActionResult GetNcoaData(int pageNumber)
460536
IdKey = i.Id.AsIdKey(),
461537
FamilyId = i.FamilyId,
462538
Type = i.NcoaType.ToString(),
539+
MoveType = i.MoveType.ToString(),
463540
IndividualIdKey = individual.personId.AsIdKey(),
464541
IndividualName = individual.NickName + ' ' + individual.LastName,
465-
FamilyMembers = familyNamesKey.ContainsKey( i.FamilyId ) ? familyNamesKey[i.FamilyId] : string.Empty,
542+
FamilyMembers = i.MoveType != MoveType.Individual && familyNamesKey.ContainsKey( i.FamilyId ) ? familyNamesKey[i.FamilyId] : string.Empty,
543+
OtherFamilyMembers = otherFamilyMembersByAliasId.TryGetValue( i.PersonAliasId, out var otherMembers ) ? otherMembers : string.Empty,
466544

467545
OriginalAddress = FormattedAddress(
468546
i.OriginalStreet1, i.OriginalStreet2, i.OriginalCity, i.OriginalState, i.OriginalPostalCode )
@@ -472,7 +550,7 @@ public BlockActionResult GetNcoaData(int pageNumber)
472550
i.UpdatedStreet1, i.UpdatedStreet2, i.UpdatedCity, i.UpdatedState, i.UpdatedPostalCode )
473551
.ConvertCrLfToHtmlBr(),
474552

475-
MoveDate = i.MoveDate.ToShortDateString(),
553+
MoveDate = i.MoveDate?.ToShortDateString(),
476554
MoveDistance = i.MoveDistance,
477555
ProcessStatus = i.Processed == Processed.Complete ? "Processed" : "Not Processed",
478556
AddressStatus = i.AddressStatus.ToString()
@@ -483,7 +561,7 @@ public BlockActionResult GetNcoaData(int pageNumber)
483561
.GroupBy( n => n.FamilyId )
484562
.Select( g => new NcoaFamilyGroupBag
485563
{
486-
FamilyName = familyNamesKey.ContainsKey( g.Key ) ? familyNamesKey[g.Key].Split( ' ' ).Last() : null,
564+
FamilyName = familyGroupNames.TryGetValue( g.Key, out var groupName ) ? groupName : null,
487565
NcoaItems = g.ToList()
488566
} ).ToList();
489567

Rock.JavaScript.Obsidian.Blocks/src/Crm/ncoaResults.obs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</template>
1313

1414
<div v-for="(family, idx) in ncoaResults" :key="idx" class="m-4">
15-
<h3 class="pl-0">{{ family.familyName || family.ncoaItems?.[0]?.individualName?.split(' ')?.[1] }} Family</h3>
15+
<h3 class="pl-0">{{ family.familyName }}</h3>
1616

1717
<div v-for="(row, rowIdx) in family.ncoaItems" :key="row.idKey ?? rowIdx">
1818
<div class="well">
@@ -22,7 +22,7 @@
2222
<div class="col-lg-6 col-md-12">
2323
<h4 v-if="row.type?.toLowerCase() === 'move'">
2424
<span class="label label-success">
25-
{{ row.familyMembers?.trim() ? "Family" : "Individual" }}
25+
{{ row.moveType }}
2626
</span>
2727
</h4>
2828
<h4 v-else-if="row.type?.toLowerCase() === 'month48move'">
@@ -37,18 +37,34 @@
3737
</dl>
3838
</div>
3939
<div class="col-lg-6 col-md-12">
40-
<dl>
41-
<dt>{{ row.familyMembers?.trim() ? "Family Members" : "Individual" }}</dt>
42-
<dd v-for="(member, i) in (row.familyMembers?.trim()
43-
? row.familyMembers.split(',').map(m => m.trim())
44-
: [row.individualName])"
40+
<dl v-if="row.familyMembers?.trim()">
41+
<dt>Family Members</dt>
42+
<dd v-for="(member, i) in row.familyMembers.split(',').map(m => m.trim())"
4543
:key="i">
4644
{{ member }}
4745
<a v-if="i === 0" :href="'/Person/' + row.individualIdKey" target="_blank">
4846
<i class="ti ti-users"></i>
4947
</a>
5048
</dd>
5149
</dl>
50+
<template v-else>
51+
<dl>
52+
<dt>Individual</dt>
53+
<dd>
54+
{{ row.individualName }}
55+
<a :href="'/Person/' + row.individualIdKey" target="_blank">
56+
<i class="ti ti-users"></i>
57+
</a>
58+
</dd>
59+
</dl>
60+
<dl v-if="row.otherFamilyMembers?.trim()">
61+
<dt>Other Family Members</dt>
62+
<dd v-for="(member, i) in row.otherFamilyMembers.split(',').map(m => m.trim())"
63+
:key="i">
64+
{{ member }}
65+
</dd>
66+
</dl>
67+
</template>
5268
</div>
5369
</div>
5470
</div>
@@ -96,9 +112,12 @@
96112
</div>
97113
</div>
98114
</div>
99-
<div v-if="warnings[row.idKey ?? '']" class="row">
115+
<div v-if="row.otherFamilyMembers?.trim() || warnings[row.idKey ?? '']" class="row">
100116
<div class="col-xs-12">
101-
<div class="alert alert-warning font-size-sm">
117+
<div v-if="row.otherFamilyMembers?.trim()" class="alert alert-warning font-size-sm">
118+
<p>Auto processing this move would result in a split family.</p>
119+
</div>
120+
<div v-if="warnings[row.idKey ?? '']" class="alert alert-warning font-size-sm">
102121
<p>{{ warnings[row.idKey ?? ''] }}</p>
103122
</div>
104123
</div>
@@ -217,6 +236,7 @@
217236

218237
await preferences.save();
219238

239+
currentPage.value = 1;
220240
loadNcoaResults();
221241
},
222242
{ deep: true }
@@ -274,14 +294,5 @@
274294
loadNcoaResults();
275295
}
276296

277-
watch(
278-
gridSettings,
279-
async () => {
280-
await preferences.save();
281297

282-
currentPage.value = 1;
283-
loadNcoaResults();
284-
},
285-
{ deep: true }
286-
);
287298
</script>

Rock.JavaScript.Obsidian/Framework/ViewModels/Blocks/Crm/NcoaResults/ncoaDataBag.d.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,56 @@
2121
// </copyright>
2222
//
2323

24+
/** Represents a single NCOA history record for display in the NCOA Results block. */
2425
export type NcoaDataBag = {
26+
/** The address status string (e.g., "Invalid", "Valid"). */
2527
addressStatus?: string | null;
2628

29+
/** The identifier of the family group associated with this NCOA record. */
2730
familyId: number;
2831

32+
/**
33+
* A comma-separated list of family member names for family move records.
34+
* Empty for individual move records.
35+
*/
2936
familyMembers?: string | null;
3037

38+
/** The encrypted identifier key for this NCOA history record. */
3139
idKey?: string | null;
3240

41+
/** The encrypted identifier key of the primary individual on this NCOA record. */
3342
individualIdKey?: string | null;
3443

44+
/** The full name of the primary individual on this NCOA record. */
3545
individualName?: string | null;
3646

47+
/** The short date string of the move date. Only populated for Move records. */
3748
moveDate?: string | null;
3849

50+
/** The distance of the move in miles. Only populated for Move records. */
3951
moveDistance?: number | null;
4052

53+
/**
54+
* The move type for Move records (e.g., "Individual", "Family", "Business").
55+
* Only meaningful when Rock.ViewModels.Blocks.Crm.NcoaResults.NcoaDataBag.Type is "Move".
56+
*/
57+
moveType?: string | null;
58+
59+
/** The formatted new address HTML string. Only populated for Move records. */
4160
newAddress?: string | null;
4261

62+
/** The formatted original address HTML string. */
4363
originalAddress?: string | null;
4464

65+
/**
66+
* A comma-separated list of other family members who remain at the original
67+
* address when this is an individual move. Non-empty indicates a split family move.
68+
*/
69+
otherFamilyMembers?: string | null;
70+
71+
/** The human-readable processing status (e.g., "Processed", "Not Processed"). */
4572
processStatus?: string | null;
4673

74+
/** The NCOA type of this record (e.g., "Move", "Month48Move"). */
4775
type?: string | null;
4876
};

Rock.JavaScript.Obsidian/Framework/ViewModels/Blocks/Crm/NcoaResults/ncoaFamilyGroupBag.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323

2424
import { NcoaDataBag } from "@Obsidian/ViewModels/Blocks/Crm/NcoaResults/ncoaDataBag";
2525

26-
/** Family group of Ncoa history items */
26+
/** Represents a family group of NCOA history items for display in the NCOA Results block. */
2727
export type NcoaFamilyGroupBag = {
28+
/** The name of the family group (from the Rock family group record). */
2829
familyName?: string | null;
2930

31+
/** The list of NCOA history items belonging to this family group. */
3032
ncoaItems?: NcoaDataBag[] | null;
3133
};

0 commit comments

Comments
 (0)