diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java index 87be2361..a32bfbfb 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java @@ -55,7 +55,7 @@ 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"))); + return ResponseEntity.ok(personService.updatePerson(id, firstName.trim(), lastName.trim(), body.get("alias"), body.get("notes"))); } @PostMapping("/{id}/merge") diff --git a/backend/src/main/java/org/raddatz/familienarchiv/model/Person.java b/backend/src/main/java/org/raddatz/familienarchiv/model/Person.java index aa704a29..fbad2f5e 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/model/Person.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/model/Person.java @@ -28,4 +28,8 @@ public class Person { // Optional: Aliasse für die Suche (z.B. "Opa Hans") private String alias; + + // Optional: Free-text biographical notes + @Column(columnDefinition = "TEXT") + private String notes; } \ No newline at end of file diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java index 8e7a2bc9..98e3f067 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java @@ -58,12 +58,13 @@ 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, String notes) { 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.setNotes(notes == null || notes.isBlank() ? null : notes.trim()); return personRepository.save(person); } diff --git a/backend/src/main/resources/db/migration/V5__add_notes_to_persons.sql b/backend/src/main/resources/db/migration/V5__add_notes_to_persons.sql new file mode 100644 index 00000000..8e6514f1 --- /dev/null +++ b/backend/src/main/resources/db/migration/V5__add_notes_to_persons.sql @@ -0,0 +1 @@ +ALTER TABLE persons ADD COLUMN notes TEXT; diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java index 2246106e..532d6e12 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java @@ -83,6 +83,32 @@ class PersonServiceTest { verify(personRepository).findByAliasIgnoreCase("Clara Cram"); } + // ─── updatePerson (notes) ──────────────────────────────────────────────── + + @Test + void updatePerson_persistsNotes() { + 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, "Some notes here."); + + assertThat(result.getNotes()).isEqualTo("Some notes here."); + } + + @Test + void updatePerson_clearsNotes_whenBlank() { + UUID id = UUID.randomUUID(); + Person person = Person.builder().id(id).firstName("Anna").lastName("Alt").notes("old notes").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, " "); + + assertThat(result.getNotes()).isNull(); + } + // ─── mergePersons ───────────────────────────────────────────────────────── @Test diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 9d994075..cf123bbe 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -111,6 +111,8 @@ "person_btn_merge": "Zusammenführen", "person_btn_merge_confirm": "Ja, zusammenführen", "person_merge_warning": "Achtung: Diese Aktion ist nicht rückgängig zu machen.", + "person_label_notes": "Notizen", + "person_placeholder_notes": "Biographische Hinweise, Besonderheiten…", "person_docs_heading": "Gesendete Dokumente", "person_no_docs": "Diese Person ist noch nicht als Absender verknüpft.", diff --git a/frontend/messages/en.json b/frontend/messages/en.json index cb4ec322..2376ee67 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -111,6 +111,8 @@ "person_btn_merge": "Merge", "person_btn_merge_confirm": "Yes, merge", "person_merge_warning": "Warning: This action cannot be undone.", + "person_label_notes": "Notes", + "person_placeholder_notes": "Biographical notes, remarks…", "person_docs_heading": "Sent documents", "person_no_docs": "This person has not yet been linked as a sender.", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 90ce4108..13997395 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -111,6 +111,8 @@ "person_btn_merge": "Fusionar", "person_btn_merge_confirm": "Sí, fusionar", "person_merge_warning": "Atención: Esta acción no se puede deshacer.", + "person_label_notes": "Notas", + "person_placeholder_notes": "Notas biográficas, observaciones…", "person_docs_heading": "Documentos enviados", "person_no_docs": "Esta persona aún no está vinculada como remitente.", diff --git a/frontend/src/lib/generated/api.ts b/frontend/src/lib/generated/api.ts index 9de906e7..e009bac5 100644 --- a/frontend/src/lib/generated/api.ts +++ b/frontend/src/lib/generated/api.ts @@ -307,6 +307,7 @@ export interface components { firstName: string; lastName: string; alias?: string; + notes?: string; }; DocumentUpdateDTO: { title?: string; diff --git a/frontend/src/routes/persons/[id]/+page.server.ts b/frontend/src/routes/persons/[id]/+page.server.ts index daef64f9..bd5d803b 100644 --- a/frontend/src/routes/persons/[id]/+page.server.ts +++ b/frontend/src/routes/persons/[id]/+page.server.ts @@ -28,6 +28,7 @@ export const actions = { const firstName = formData.get('firstName')?.toString().trim(); const lastName = formData.get('lastName')?.toString().trim(); const alias = formData.get('alias')?.toString().trim() || undefined; + const notes = formData.get('notes')?.toString().trim() || undefined; if (!firstName || !lastName) { return fail(400, { updateError: 'Vor- und Nachname sind Pflichtfelder.' }); @@ -36,7 +37,7 @@ export const actions = { const api = createApiClient(fetch); const { error: apiError } = await api.PUT('/api/persons/{id}', { params: { path: { id: params.id } }, - body: { firstName, lastName, ...(alias ? { alias } : {}) } + body: { firstName, lastName, ...(alias ? { alias } : {}), ...(notes ? { notes } : {}) } }); if (apiError) { diff --git a/frontend/src/routes/persons/[id]/+page.svelte b/frontend/src/routes/persons/[id]/+page.svelte index 3b7b8f3e..4d10f4da 100644 --- a/frontend/src/routes/persons/[id]/+page.svelte +++ b/frontend/src/routes/persons/[id]/+page.svelte @@ -82,6 +82,16 @@ class="block w-full border border-gray-300 rounded px-3 py-2 font-serif text-brand-navy focus:outline-none focus:border-brand-navy" /> +
+ + +
@@ -126,6 +136,13 @@ "{person.alias}"
{/if} + + {#if person.notes} +
+ {m.person_label_notes()} +

{person.notes}

+
+ {/if}