feat(relationship): add generation to PersonNodeDTO + update all sites (#689)

PersonNodeDTO is a positional record. The optional Integer generation
field is inserted between deathYear and familyMember so all four
construction sites stay readable without a builder.

- RelationshipService.getFamilyNetwork → populates with
  person.getGeneration() (the Stammbaum's strict-rank source on the
  frontend).
- RelationshipInferenceService.findAllFor → populates the same way;
  inference UI does not consume it but the field travels along for
  consistency.
- RelationshipControllerTest fixtures pass null.

Refs #689

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-28 15:35:40 +02:00
parent a68a822c13
commit 40511535eb
5 changed files with 23 additions and 4 deletions

View File

@@ -96,7 +96,8 @@ public class RelationshipInferenceService {
if (p == null) continue; if (p == null) continue;
List<RelationToken> path = shortestPaths.get(id); List<RelationToken> path = shortestPaths.get(id);
PersonNodeDTO node = new PersonNodeDTO( PersonNodeDTO node = new PersonNodeDTO(
p.getId(), p.getDisplayName(), p.getBirthYear(), p.getDeathYear(), p.isFamilyMember()); p.getId(), p.getDisplayName(), p.getBirthYear(), p.getDeathYear(),
p.getGeneration(), p.isFamilyMember());
out.add(new InferredRelationshipWithPersonDTO(node, labelFor(path), path.size())); out.add(new InferredRelationshipWithPersonDTO(node, labelFor(path), path.size()));
} }
out.sort(Comparator.comparingInt(InferredRelationshipWithPersonDTO::hops) out.sort(Comparator.comparingInt(InferredRelationshipWithPersonDTO::hops)

View File

@@ -66,7 +66,8 @@ public class RelationshipService {
for (Person p : familyMembers) { for (Person p : familyMembers) {
familyIds.add(p.getId()); familyIds.add(p.getId());
nodes.add(new PersonNodeDTO( nodes.add(new PersonNodeDTO(
p.getId(), p.getDisplayName(), p.getBirthYear(), p.getDeathYear(), true)); p.getId(), p.getDisplayName(), p.getBirthYear(), p.getDeathYear(),
p.getGeneration(), true));
} }
List<PersonRelationship> familyEdges = relationshipRepository.findAllByRelationTypeIn( List<PersonRelationship> familyEdges = relationshipRepository.findAllByRelationTypeIn(

View File

@@ -10,5 +10,6 @@ public record PersonNodeDTO(
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) String displayName, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) String displayName,
Integer birthYear, Integer birthYear,
Integer deathYear, Integer deathYear,
Integer generation,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean familyMember @Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean familyMember
) {} ) {}

View File

@@ -93,7 +93,7 @@ class RelationshipControllerTest {
@Test @Test
@WithMockUser(username = "testuser", authorities = {"READ_ALL"}) @WithMockUser(username = "testuser", authorities = {"READ_ALL"})
void getNetwork_returns200_with_NetworkDTO_for_authenticated_user() throws Exception { void getNetwork_returns200_with_NetworkDTO_for_authenticated_user() throws Exception {
PersonNodeDTO node = new PersonNodeDTO(PERSON_ID, "Alice Müller", 1900, 1980, true); PersonNodeDTO node = new PersonNodeDTO(PERSON_ID, "Alice Müller", 1900, 1980, null, true);
RelationshipDTO edge = new RelationshipDTO( RelationshipDTO edge = new RelationshipDTO(
UUID.randomUUID(), PERSON_ID, OTHER_ID, UUID.randomUUID(), PERSON_ID, OTHER_ID,
"Alice Müller", 1900, 1980, "Alice Müller", 1900, 1980,
@@ -111,7 +111,7 @@ class RelationshipControllerTest {
@Test @Test
@WithMockUser(username = "testuser", authorities = {"READ_ALL"}) @WithMockUser(username = "testuser", authorities = {"READ_ALL"})
void getInferredRelationships_returns200_with_list_for_authenticated_user() throws Exception { void getInferredRelationships_returns200_with_list_for_authenticated_user() throws Exception {
PersonNodeDTO relative = new PersonNodeDTO(OTHER_ID, "Bob Müller", 1930, null, true); PersonNodeDTO relative = new PersonNodeDTO(OTHER_ID, "Bob Müller", 1930, null, null, true);
InferredRelationshipWithPersonDTO inferred = InferredRelationshipWithPersonDTO inferred =
new InferredRelationshipWithPersonDTO(relative, "Großvater", 2); new InferredRelationshipWithPersonDTO(relative, "Großvater", 2);
when(relationshipService.getInferredRelationships(PERSON_ID)) when(relationshipService.getInferredRelationships(PERSON_ID))

View File

@@ -237,6 +237,22 @@ class RelationshipServiceTest {
assertThat(result.edges().get(0).relatedPersonId()).isEqualTo(bob.getId()); assertThat(result.edges().get(0).relatedPersonId()).isEqualTo(bob.getId());
} }
@Test
void getFamilyNetwork_populates_generation_on_PersonNodeDTO() {
Person walter = Person.builder().id(UUID.randomUUID()).lastName("Raddatz")
.familyMember(true).generation(2).build();
Person clara = Person.builder().id(UUID.randomUUID()).lastName("Raddatz")
.familyMember(true).generation(3).build();
when(personService.findAllFamilyMembers()).thenReturn(List.of(walter, clara));
when(relationshipRepository.findAllByRelationTypeIn(any())).thenReturn(List.of());
NetworkDTO result = service.getFamilyNetwork();
assertThat(result.nodes()).hasSize(2);
assertThat(result.nodes().stream().map(n -> n.generation()).toList())
.containsExactlyInAnyOrder(2, 3);
}
// --- helpers --- // --- helpers ---
private static Person person(String name) { private static Person person(String name) {