Add rename and remove for a workspace's placeholder partner

Settings is gaining a partner display, which hides the add-form once a
partner exists. Without these routes a typo'd partner name would be
permanently unfixable, since no endpoint could change or drop one.

PATCH /workspace/{id}/parents/{uuid} renames; DELETE soft-deletes.
Soft rather than hard delete because PersonalData holds PII and the
repository is a SoftDeleteRepository specifically so erasure stays
auditable.

Removal refuses (422) when the member still has leave wishes, planned
leave, parental-leave rows or vacation days, naming the blockers, so a
removal can never orphan plan data. It also refuses for the caller's own
profile, and for a member who has their own login -- the invite/redeem
flow re-points a real user's personal_data into the inviter's workspace,
so soft-deleting that row would silently break their login.

Errors carry message_key + data rather than prose, per the no-Swedish-on
-the-wire rule; NotFoundError/ValidationError gained the same optional
shape DubbeldagError already had, defaulting empty so existing call
sites are unaffected.

Also fixes seed_demo.py constructing WorkspaceService with a stale
3-arg signature, which would have crashed at runtime -- invisible to
pytest since it is a standalone script.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
This commit is contained in:
2026-09-14 18:54:34 +02:00
parent bd996245ba
commit 8ebfcb9462
9 changed files with 681 additions and 29 deletions
+10 -1
View File
@@ -122,7 +122,16 @@ def _build_services(conn: aiosqlite.Connection) -> DemoServices:
return DemoServices(
auth=AuthService(users, personal_data, workspaces),
workspace=WorkspaceService(workspaces, invitations, personal_data),
workspace=WorkspaceService(
workspaces,
invitations,
personal_data,
users,
leave_wishes,
planned_leave,
parental_leave,
vacation_repo,
),
child=ChildService(children),
vacation=VacationDaysService(vacation_repo),
day_override=DayOverrideService(day_overrides),