Files
F-r-ldraledigplannerare/backend/app/models/workspace.py
T
jacobh2 8ebfcb9462 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
2026-09-14 18:54:34 +02:00

82 lines
2.0 KiB
Python

"""Pydantic schemas for workspace and invitation endpoints."""
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
from app.entities.workspace import Workspace
from app.entities.workspace_invitation import WorkspaceInvitation
class WorkspaceCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
class WorkspaceUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=100)
planning_goal: str | None = None
planning_mode: str | None = None
class WorkspaceResponse(BaseModel):
id: UUID
name: str
planning_goal: str
planning_mode: str
created_at: datetime | None
@classmethod
def from_entity(cls, ws: Workspace) -> "WorkspaceResponse":
return cls(
id=ws.id,
name=ws.name,
planning_goal=ws.planning_goal,
planning_mode=ws.planning_mode,
created_at=ws.created_at,
)
class PersonalDataResponse(BaseModel):
uuid: UUID
workspace_id: UUID
name: str
salary_type: str
employment_status: str
created_at: datetime | None
class PersonalDataCreate(BaseModel):
"""Request body for both adding a placeholder parent (POST) and renaming
one (PATCH) — both take exactly one field, so one schema serves both.
"""
display_name: str = Field(min_length=1, max_length=100)
class InviteResponse(BaseModel):
token: str # raw one-time token — share out-of-band; not stored on backend
class RedeemInviteRequest(BaseModel):
token: str
class InvitationResponse(BaseModel):
id: UUID
workspace_id: UUID
status: str
expires_at: datetime
created_at: datetime
@classmethod
def from_entity(cls, inv: WorkspaceInvitation) -> "InvitationResponse":
return cls(
id=inv.id,
workspace_id=inv.workspace_id,
status=inv.status,
expires_at=inv.expires_at,
created_at=inv.created_at,
)