@@ -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
0 commit comments