feat(relationship): editable relationships with LocalDate+DatePrecision dates and notes (#841)
All checks were successful
CI / Unit & Component Tests (push) Successful in 5m10s
CI / OCR Service Tests (push) Successful in 24s
CI / Backend Unit Tests (push) Successful in 5m14s
CI / fail2ban Regex (push) Successful in 50s
CI / Semgrep Security Scan (push) Successful in 27s
CI / Compose Bucket Idempotency (push) Successful in 1m5s
All checks were successful
CI / Unit & Component Tests (push) Successful in 5m10s
CI / OCR Service Tests (push) Successful in 24s
CI / Backend Unit Tests (push) Successful in 5m14s
CI / fail2ban Regex (push) Successful in 50s
CI / Semgrep Security Scan (push) Successful in 27s
CI / Compose Bucket Idempotency (push) Successful in 1m5s
Closes #837 Makes `PersonRelationship` fully editable (type, related person, dates, notes), migrates its dates from `Integer fromYear/toYear` to `LocalDate + DatePrecision` (mirroring the #773 person pattern, ADR-039 / V76), activates the previously-dead `notes` column, and gives the Zeitstrahl's derived **Heirat** events full date precision for free. Both Open Decisions confirmed as adopted: **no `@Version`** (last-write-wins, single-writer archive) and **`DELETE` ownership-mismatch aligned 403 → 404** (anti-enumeration, matching the new `PUT`). ## What's in it - **V78** migrates `person_relationships.from_year/to_year` → `from_date`/`to_date` + NOT-NULL `*_date_precision` (default `UNKNOWN`); pre-check abort on corrupt years, `YYYY-01-01`/`YEAR` backfill, 5 named CHECK constraints, year columns dropped. - **`PUT /api/persons/{id}/relationships/{relId}`** (`@RequirePermission(WRITE_ALL)`) re-runs every create invariant (self / coherence / order / reverse-PARENT_OF / duplicate) and re-flags family membership; orientation preserved per viewpoint. - New `ErrorCode.INVALID_RELATIONSHIP_DATES` registered in all four sites (§3.6). - `TimelineEventService` sources the derived marriage date from `SPOUSE_OF.fromDate` + precision. - Frontend: `RelationshipDateField` (DAY/MONTH/YEAR), upsert-capable `AddRelationshipForm` (pre-fill + notes + in-flight submit lock), `RelationshipChip` Edit affordance, `updateRelationship` server action, read-view date range + notes, `formatRelationshipDateRange` helper. `api.ts` regenerated. - Docs: ADR-044, db-orm/db-relationships diagrams, DEPLOYMENT §5 deploy note, RTM REQ-001…REQ-019. ## Requirements All 19 EARS requirements implemented red/green and marked `Done` in `.specify/rtm.md`. ## Test plan - **Backend** (targeted, green): `RelationshipMigrationTest` (Testcontainers pg16, 8), `RelationshipServiceTest` (22), `RelationshipControllerTest` (15), `RelationshipServiceIntegrationTest` (real DB, 10), `DerivedEventsAssemblyTest` (17), `ArchitectureTest` (14); `clean package` builds. - **Frontend** (green): `relationshipDates.spec.ts`, `AddRelationshipForm.svelte.spec.ts`, `RelationshipChip.svelte.spec.ts`, `PersonRelationshipsCard.svelte.test.ts`, `page.server.spec.ts`, `messages.spec.ts`. `npm run check` = 798 (below the ~834 baseline); `npm run lint` clean. ## Notes for reviewers - **Spec deviation:** the edit form was built by making `AddRelationshipForm` upsert-capable rather than a duplicate `EditRelationshipForm` (DRY); RTM rows reference `AddRelationshipForm.svelte.spec.ts`. - `api.ts` regenerated from the live spec; only relationship-relevant hunks remain (one springdoc `PageableObject` field-reorder pruned). - **Deploy:** V78 is one-way and not rolling-deploy-safe — stop old JAR → start new JAR (Flyway runs first); targeted `pg_restore -t person_relationships` for rollback. No maintenance window. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Marcel <marcel@familienarchiv> Reviewed-on: #841
This commit was merged in pull request #841.
This commit is contained in:
@@ -651,6 +651,7 @@
|
||||
"error_invalid_date_range": "Das Enddatum darf nicht vor dem Startdatum liegen.",
|
||||
"error_birth_after_death": "Geburtsdatum muss vor dem Sterbedatum liegen. Tipp: Falls nur das Todesjahr bekannt ist und der Geburtstag spät im selben Jahr lag, bitte das Folgejahr eintragen.",
|
||||
"error_invalid_date_precision": "Datum und Genauigkeit stimmen nicht überein.",
|
||||
"error_invalid_relationship_dates": "Das Ende-Datum darf nicht vor dem Beginn-Datum liegen.",
|
||||
"validation_last_name_required": "Nachname ist Pflichtfeld.",
|
||||
"validation_first_name_required": "Vorname ist Pflichtfeld.",
|
||||
"error_ocr_service_unavailable": "Der OCR-Dienst ist nicht verfügbar.",
|
||||
@@ -1221,6 +1222,16 @@
|
||||
"relation_form_field_from_year": "Von Jahr",
|
||||
"relation_form_field_to_year": "Bis Jahr",
|
||||
"relation_form_year_placeholder": "z.B. 1920",
|
||||
"relation_label_from_date": "Beginn (Datum)",
|
||||
"relation_label_to_date": "Ende (Datum)",
|
||||
"relation_label_date_precision": "Genauigkeit",
|
||||
"relation_precision_day": "Genaues Datum (Tag)",
|
||||
"relation_precision_month": "Monat bekannt",
|
||||
"relation_precision_year": "Nur Jahreszahl",
|
||||
"relation_label_notes": "Notizen",
|
||||
"relation_notes_placeholder": "Optionaler Hinweis zu dieser Beziehung",
|
||||
"relation_date_placeholder_hint": "Leer lassen, wenn unbekannt",
|
||||
"relation_edit": "Beziehung bearbeiten",
|
||||
"person_relationships_heading": "Beziehungen",
|
||||
"person_relationships_empty": "Noch keine Beziehungen bekannt.",
|
||||
"timeline_aria_label": "Zeitachse Dokumentdichte",
|
||||
|
||||
@@ -651,6 +651,7 @@
|
||||
"error_invalid_date_range": "The end date must not be before the start date.",
|
||||
"error_birth_after_death": "Birth date must be before death date. Tip: if only the death year is known and the birthday is late in the same year, enter the following year.",
|
||||
"error_invalid_date_precision": "Date and precision do not match.",
|
||||
"error_invalid_relationship_dates": "The end date must not be before the start date.",
|
||||
"validation_last_name_required": "Last name is required.",
|
||||
"validation_first_name_required": "First name is required.",
|
||||
"error_ocr_service_unavailable": "The OCR service is not available.",
|
||||
@@ -1221,6 +1222,16 @@
|
||||
"relation_form_field_from_year": "From year",
|
||||
"relation_form_field_to_year": "To year",
|
||||
"relation_form_year_placeholder": "e.g. 1920",
|
||||
"relation_label_from_date": "Start date",
|
||||
"relation_label_to_date": "End date",
|
||||
"relation_label_date_precision": "Precision",
|
||||
"relation_precision_day": "Exact date (day)",
|
||||
"relation_precision_month": "Month known",
|
||||
"relation_precision_year": "Year only",
|
||||
"relation_label_notes": "Notes",
|
||||
"relation_notes_placeholder": "Optional note about this relationship",
|
||||
"relation_date_placeholder_hint": "Leave empty if unknown",
|
||||
"relation_edit": "Edit relationship",
|
||||
"person_relationships_heading": "Relationships",
|
||||
"person_relationships_empty": "No relationships known yet.",
|
||||
"timeline_aria_label": "Document density timeline",
|
||||
|
||||
@@ -651,6 +651,7 @@
|
||||
"error_invalid_date_range": "La fecha final no puede ser anterior a la inicial.",
|
||||
"error_birth_after_death": "La fecha de nacimiento debe ser anterior a la de defunción.",
|
||||
"error_invalid_date_precision": "La fecha y la precisión no coinciden.",
|
||||
"error_invalid_relationship_dates": "La fecha de fin no puede ser anterior a la de inicio.",
|
||||
"validation_last_name_required": "El apellido es obligatorio.",
|
||||
"validation_first_name_required": "El nombre es obligatorio.",
|
||||
"error_ocr_service_unavailable": "El servicio OCR no está disponible.",
|
||||
@@ -1221,6 +1222,16 @@
|
||||
"relation_form_field_from_year": "Desde año",
|
||||
"relation_form_field_to_year": "Hasta año",
|
||||
"relation_form_year_placeholder": "ej. 1920",
|
||||
"relation_label_from_date": "Fecha de inicio",
|
||||
"relation_label_to_date": "Fecha de fin",
|
||||
"relation_label_date_precision": "Precisión",
|
||||
"relation_precision_day": "Fecha exacta (día)",
|
||||
"relation_precision_month": "Mes conocido",
|
||||
"relation_precision_year": "Solo año",
|
||||
"relation_label_notes": "Notas",
|
||||
"relation_notes_placeholder": "Nota opcional sobre esta relación",
|
||||
"relation_date_placeholder_hint": "Dejar vacío si es desconocido",
|
||||
"relation_edit": "Editar relación",
|
||||
"person_relationships_heading": "Relaciones",
|
||||
"person_relationships_empty": "Aún no se conocen relaciones.",
|
||||
"timeline_aria_label": "Cronología de densidad de documentos",
|
||||
|
||||
Reference in New Issue
Block a user