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;
superadminboolean for global admin - Organization — has profile, billing, addresses, workspaces (via OrgWorkspace), teams (directly on org, not workspace)
- Workspace — base class; two subclasses:
OrgWorkspaceandUserWorkspace - OrgWorkspace — org-owned; has
orgId,workspacePurpose(STAFF/CLIENT/MIXED); always has exactly oneOrgPublicProfile - UserWorkspace — personal workspace; has
userId; auto-created on signup - Team — org-scoped (not workspace-scoped); has
orgId,teamType,plan; assigned to workspaces viaTeamWorkspaceAssignment; nobaseRolefield - Member — links User to Team; has a direct
role: MemberRolefield (ADMIN | MANAGER | MEMBER | DEVELOPER | CLIENT | PARTNER); noroleOverride, nobaseRoleinheritance - OrgMember — links User to Organization; has
role: OrgRole(OWNER | ADMIN | MEMBER) andstatus: MemberStatus; handles invitations (inviteEmail, inviteFirstName, inviteLastName, sentDate, expiresAt) - Client — external applicant;
orgIdFK directly on org (not workspace-scoped); can be assigned to teams withMemberRole.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=truemirrors 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
OrgWorkspaceorUserWorkspace— never plainWorkspace - JPQL queries for subclass fields must target the subclass:
SELECT w FROM OrgWorkspace w WHERE w.orgId = :orgId OrgWorkspace.orgIdisinsertable=false, updatable=false— null in-memory aftersave(); useorgWs.getOrganization().getId()immediately after saving- Do NOT add type-specific columns to the
Workspacebase class — use subclasses