feat(persons): add birth/death year fields (issue #18)
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 1m48s
CI / Backend Unit Tests (pull_request) Successful in 2m3s
CI / E2E Tests (pull_request) Failing after 17m10s

V5 Flyway migration adds birth_year and death_year INTEGER columns.
Service validates birthYear <= deathYear (400 otherwise). Frontend edit
form adds year number inputs; view mode renders * year / † year. Backed
by 3 backend service tests and 1 E2E test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-19 21:45:02 +01:00
parent fe9b4a9569
commit b07391541b
12 changed files with 138 additions and 3 deletions

View File

@@ -55,7 +55,11 @@ public class PersonController {
if (firstName == null || firstName.isBlank() || lastName == null || lastName.isBlank()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Vor- und Nachname sind Pflichtfelder");
}
return ResponseEntity.ok(personService.updatePerson(id, firstName.trim(), lastName.trim(), body.get("alias")));
Integer birthYear = body.get("birthYear") != null && !body.get("birthYear").isBlank()
? Integer.parseInt(body.get("birthYear")) : null;
Integer deathYear = body.get("deathYear") != null && !body.get("deathYear").isBlank()
? Integer.parseInt(body.get("deathYear")) : null;
return ResponseEntity.ok(personService.updatePerson(id, firstName.trim(), lastName.trim(), body.get("alias"), birthYear, deathYear));
}
@PostMapping("/{id}/merge")

View File

@@ -28,4 +28,7 @@ public class Person {
// Optional: Aliasse für die Suche (z.B. "Opa Hans")
private String alias;
private Integer birthYear;
private Integer deathYear;
}

View File

@@ -58,12 +58,17 @@ public class PersonService {
}
@Transactional
public Person updatePerson(UUID id, String firstName, String lastName, String alias) {
public Person updatePerson(UUID id, String firstName, String lastName, String alias, Integer birthYear, Integer deathYear) {
if (birthYear != null && deathYear != null && birthYear > deathYear) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Geburtsjahr darf nicht nach dem Todesjahr liegen");
}
Person person = personRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Person nicht gefunden"));
person.setFirstName(firstName);
person.setLastName(lastName);
person.setAlias(alias == null || alias.isBlank() ? null : alias.trim());
person.setBirthYear(birthYear);
person.setDeathYear(deathYear);
return personRepository.save(person);
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE persons ADD COLUMN birth_year INTEGER;
ALTER TABLE persons ADD COLUMN death_year INTEGER;

View File

@@ -83,6 +83,44 @@ class PersonServiceTest {
verify(personRepository).findByAliasIgnoreCase("Clara Cram");
}
// ─── updatePerson (birth/death years) ────────────────────────────────────
@Test
void updatePerson_persistsBirthAndDeathYear() {
UUID id = UUID.randomUUID();
Person person = Person.builder().id(id).firstName("Anna").lastName("Alt").build();
when(personRepository.findById(id)).thenReturn(Optional.of(person));
when(personRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
Person result = personService.updatePerson(id, "Anna", "Alt", null, 1890, 1965);
assertThat(result.getBirthYear()).isEqualTo(1890);
assertThat(result.getDeathYear()).isEqualTo(1965);
}
@Test
void updatePerson_throwsBadRequest_whenBirthYearAfterDeathYear() {
UUID id = UUID.randomUUID();
assertThatThrownBy(() -> personService.updatePerson(id, "Anna", "Alt", null, 1970, 1950))
.isInstanceOf(ResponseStatusException.class)
.extracting(e -> ((ResponseStatusException) e).getStatusCode().value())
.isEqualTo(400);
}
@Test
void updatePerson_allowsSameYear() {
UUID id = UUID.randomUUID();
Person person = Person.builder().id(id).firstName("Anna").lastName("Alt").build();
when(personRepository.findById(id)).thenReturn(Optional.of(person));
when(personRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
Person result = personService.updatePerson(id, "Anna", "Alt", null, 1900, 1900);
assertThat(result.getBirthYear()).isEqualTo(1900);
assertThat(result.getDeathYear()).isEqualTo(1900);
}
// ─── mergePersons ─────────────────────────────────────────────────────────
@Test