Skip to main content

Entity Relationships and Data Model

For full diagrams: docu/docs/domain/system-architecture.md (functional) and docu/docs/technical/architecture.md (technical ERD).

Core Entities

  • User — one account per person; superadmin boolean for global admin
  • Organization — has profile, billing, addresses, workspaces (via OrgWorkspace), teams (directly on org, not workspace)
  • Workspace — base class; two subclasses: OrgWorkspace and UserWorkspace
  • OrgWorkspace — org-owned; has orgId, workspacePurpose (STAFF/CLIENT/MIXED); always has exactly one OrgPublicProfile
  • UserWorkspace — personal workspace; has userId; auto-created on signup
  • Teamorg-scoped (not workspace-scoped); has orgId, teamType, plan; assigned to workspaces via TeamWorkspaceAssignment; no baseRole field
  • Member — links User to Team; has a direct role: MemberRole field (ADMIN | MANAGER | MEMBER | DEVELOPER | CLIENT | PARTNER); no roleOverride, no baseRole inheritance
  • OrgMember — links User to Organization; has role: OrgRole (OWNER | ADMIN | MEMBER) and status: MemberStatus; handles invitations (inviteEmail, inviteFirstName, inviteLastName, sentDate, expiresAt)
  • Client — external applicant; orgId FK directly on org (not workspace-scoped); can be assigned to teams with MemberRole.CLIENT; invitation state on this record
  • TeamWorkspaceAssignment — join table (teamId, workspaceId); grants all team members access to a workspace
  • OrgPublicProfile — public-facing org profile; one per OrgWorkspace; synced=true mirrors private org profile

Key relationships

  • Organization → Team: 1-m directly (team.orgId) — teams are NOT workspace-scoped
  • Team → Workspace: many-to-many via TeamWorkspaceAssignment
  • Client → Organization: m-1 direct FK (client.orgId) — clients are org-scoped
  • OrgWorkspace → OrgPublicProfile: 1-1 invariant — must always exist

Workspace inheritance constraints

  • Always instantiate OrgWorkspace or UserWorkspace — never plain Workspace
  • JPQL queries for subclass fields must target the subclass: SELECT w FROM OrgWorkspace w WHERE w.orgId = :orgId
  • OrgWorkspace.orgId is insertable=false, updatable=false — null in-memory after save(); use orgWs.getOrganization().getId() immediately after saving
  • Do NOT add type-specific columns to the Workspace base class — use subclasses