As a user I want to split a combined person entry into separate persons so I can clean up incorrectly merged import data #51

Open
opened 2026-03-23 10:58:55 +01:00 by marcel · 0 comments
Owner

User Journey

The user is on a person detail page and notices the person has a combined name like Clara Cram Gruyter//Ellen B-M — a result of how the original ODS data was structured. They click the Split button next to the existing Merge action. An inline panel appears below the name card with two rows. Each row has a person typeahead to search for an existing person; a "Neue Person anlegen" link below the typeahead switches that row to first name + last name inputs instead.

The user fills in both rows — one mapped to an existing person, one as a new person. They see a hint telling them how many documents will be reassigned. They click Aufteilen. The combined person is deleted, both resulting persons are linked to all the original documents, and the user is redirected to the persons list where they can now see the two separate entries.

If they need more than two parts, they click + Weiteren Teil hinzufügen to add another row before confirming.

E2E Scenarios

Scenario: Split a combined person into two new persons
  Given I am on the detail page of a person named "Clara Cram//Ellen B-M"
  When I click the "Split" button
  And I switch both rows to "Neue Person anlegen"
  And I enter "Clara" / "Cram" in the first row
  And I enter "Ellen" / "B-M" in the second row
  And I click "Aufteilen"
  Then I am redirected to the persons list
  And I see "Clara Cram" in the list
  And I see "Ellen B-M" in the list
  And "Clara Cram//Ellen B-M" no longer appears in the list

Scenario: Split a combined person using an existing person for one part
  Given I am on the detail page of a person named "Clara Cram//Ellen B-M"
  When I click the "Split" button
  And I search for and select an existing person in the first row
  And I switch the second row to "Neue Person anlegen" and enter a name
  And I click "Aufteilen"
  Then I am redirected to the persons list
  And the original combined person no longer appears

Scenario: Submit is disabled until all rows have a valid entry
  Given the split panel is open
  When at least one row has neither a selected person nor a filled-in name
  Then the "Aufteilen" button is disabled

Scenario: Documents are assigned to all resulting persons after split
  Given a combined person is linked to one or more documents as sender or receiver
  When I split that person into two parts
  Then both resulting persons appear on those documents

Implementation Plan

Backend

  1. SplitPersonRequest DTO — list of SplitPersonPart objects, each with either existingPersonId: UUID (nullable) or newFirstName + newLastName strings.

  2. PersonRepository — no new queries needed. Reuse existing merge helpers:

    • reassignSender(sourceId, firstTargetId) — called once for the first target
    • insertMissingReceiverReference(sourceId, targetId) — called in a loop for each target
    • deleteReceiverReferences(sourceId) — called once after all targets are added
    • deleteById(sourceId) — remove original
  3. PersonService.splitPerson(UUID sourceId, SplitPersonRequest):

    • Validate: at least 2 parts, source exists, all referenced existing persons exist
    • Create any new persons (reuse createPerson)
    • Call reassign/delete helpers (see above)
    • Return list of resulting Person entities
  4. PersonController — add POST /api/persons/{id}/split endpoint, requires WRITE_ALL permission, returns List<Person>.

  5. ErrorCode — no new codes needed; use ResponseStatusException for simple validation errors (< 2 parts, source = target, etc.).

Frontend

  1. Regenerate API types after backend changes (npm run generate:api).

  2. /persons/[id]/+page.svelte — add split panel:

    • Split button next to merge button
    • Inline collapsible panel below name card
    • Dynamic row list with PersonTypeahead / name input toggle per row
    • Document count hint
    • Submit calls POST /api/persons/{id}/split directly (client-side fetch, no form action needed since it's a destructive operation with redirect)
    • On success: goto('/persons')
  3. i18n — add translation keys in de.json, en.json, es.json for all new UI strings (button label, panel title, row labels, hint text, confirmation).

Out of Scope

  • Auto-detection / admin dashboard for combined names
  • Per-document assignment during split
  • Auto-parsing of separators (//, &, und, etc.) — user fills in the parts manually
## User Journey The user is on a person detail page and notices the person has a combined name like `Clara Cram Gruyter//Ellen B-M` — a result of how the original ODS data was structured. They click the **Split** button next to the existing Merge action. An inline panel appears below the name card with two rows. Each row has a person typeahead to search for an existing person; a "Neue Person anlegen" link below the typeahead switches that row to first name + last name inputs instead. The user fills in both rows — one mapped to an existing person, one as a new person. They see a hint telling them how many documents will be reassigned. They click **Aufteilen**. The combined person is deleted, both resulting persons are linked to all the original documents, and the user is redirected to the persons list where they can now see the two separate entries. If they need more than two parts, they click **+ Weiteren Teil hinzufügen** to add another row before confirming. ## E2E Scenarios ``` Scenario: Split a combined person into two new persons Given I am on the detail page of a person named "Clara Cram//Ellen B-M" When I click the "Split" button And I switch both rows to "Neue Person anlegen" And I enter "Clara" / "Cram" in the first row And I enter "Ellen" / "B-M" in the second row And I click "Aufteilen" Then I am redirected to the persons list And I see "Clara Cram" in the list And I see "Ellen B-M" in the list And "Clara Cram//Ellen B-M" no longer appears in the list Scenario: Split a combined person using an existing person for one part Given I am on the detail page of a person named "Clara Cram//Ellen B-M" When I click the "Split" button And I search for and select an existing person in the first row And I switch the second row to "Neue Person anlegen" and enter a name And I click "Aufteilen" Then I am redirected to the persons list And the original combined person no longer appears Scenario: Submit is disabled until all rows have a valid entry Given the split panel is open When at least one row has neither a selected person nor a filled-in name Then the "Aufteilen" button is disabled Scenario: Documents are assigned to all resulting persons after split Given a combined person is linked to one or more documents as sender or receiver When I split that person into two parts Then both resulting persons appear on those documents ``` ## Implementation Plan ### Backend 1. **`SplitPersonRequest` DTO** — list of `SplitPersonPart` objects, each with either `existingPersonId: UUID` (nullable) or `newFirstName` + `newLastName` strings. 2. **`PersonRepository`** — no new queries needed. Reuse existing merge helpers: - `reassignSender(sourceId, firstTargetId)` — called once for the first target - `insertMissingReceiverReference(sourceId, targetId)` — called in a loop for each target - `deleteReceiverReferences(sourceId)` — called once after all targets are added - `deleteById(sourceId)` — remove original 3. **`PersonService.splitPerson(UUID sourceId, SplitPersonRequest)`**: - Validate: at least 2 parts, source exists, all referenced existing persons exist - Create any new persons (reuse `createPerson`) - Call reassign/delete helpers (see above) - Return list of resulting `Person` entities 4. **`PersonController`** — add `POST /api/persons/{id}/split` endpoint, requires `WRITE_ALL` permission, returns `List<Person>`. 5. **`ErrorCode`** — no new codes needed; use `ResponseStatusException` for simple validation errors (< 2 parts, source = target, etc.). ### Frontend 6. **Regenerate API types** after backend changes (`npm run generate:api`). 7. **`/persons/[id]/+page.svelte`** — add split panel: - Split button next to merge button - Inline collapsible panel below name card - Dynamic row list with PersonTypeahead / name input toggle per row - Document count hint - Submit calls `POST /api/persons/{id}/split` directly (client-side fetch, no form action needed since it's a destructive operation with redirect) - On success: `goto('/persons')` 8. **i18n** — add translation keys in `de.json`, `en.json`, `es.json` for all new UI strings (button label, panel title, row labels, hint text, confirmation). ## Out of Scope - Auto-detection / admin dashboard for combined names - Per-document assignment during split - Auto-parsing of separators (`//`, `&`, `und`, etc.) — user fills in the parts manually
marcel added the featureperson labels 2026-03-23 11:00:07 +01:00
marcel changed title from feat: split combined person entries into separate persons to As a user I want to split a combined person entry into separate persons so I can clean up incorrectly merged import data 2026-03-23 11:01:12 +01:00
Sign in to join this conversation.
No Label feature person
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#51