Skip to main content

Org::Membership

Purpose

An Org::Membership represents a user's access to a specific company on the Jod Platform. It links an Identities::User to an Org::Company, defines their role, and determines what they can see and do within that company.

A single user can have memberships in multiple companies. For example, an HR lead at a hotel group may have an hq_manager membership at three different hotel properties. They log in once and switch between companies from the UI.

Org::Membership replaces the concept of Org::UserProfile from the initial system design. While the talent side uses Talent::Profile to represent "who is this worker" (skills, experience, ratings), the employer side is fundamentally about access — which company, what role, which outlets. This is why the model is called Membership, not UserProfile.

Key Concepts

Roles

Each membership has a role that defines what the user can do. Roles have fixed permissions — there is no per-company permission customization.

RoleScopeCapabilities
hq_managerAll outlets in the companyFull access: manage jobs, users, outlets, billing, credits, job templates
area_managerAssigned outlets onlyManage jobs and candidates at assigned outlets. View credit history. Cannot manage users or outlets.
outlet_managerSingle assigned outletManage jobs and candidates at their outlet. View credit history. Cannot manage users or outlets.

Why three roles are sufficient: Analysis of the legacy system (JodGig) showed 277 granular permissions across 315 companies, but only 7 distinct permission sets. The differences between area and outlet managers are purely about scope (which outlets), not capabilities. Per-company permission customization was built in JodGig but barely used. See JodGig Org Statistics for the full analysis.

Outlet Assignment

area_manager and outlet_manager roles are scoped to specific outlets via Org::OutletAssignment:

  • hq_manager: no outlet assignments needed — implicit access to all outlets in the company
  • area_manager: assigned to N outlets (direct user-to-outlet assignment, not a named group)
  • outlet_manager: assigned to exactly 1 outlet

This is a direct assignment model, not a persistent grouping model. There is no "area" or "zone" entity — an area manager simply has a set of outlet assignments. If the company reorganizes which outlets each manager oversees, the outlet assignments are updated directly.

Ownership

Each company has exactly one owner — the user who created the company (via self-serve sign-up) or was designated as owner during admin onboarding.

Ownership is a flag on the membership, not a separate role:

Org::Membership (role: hq_manager, is_owner: true)

The owner:

  • Cannot be removed by other hq_manager members — only by Identities::Admin or by transferring ownership
  • Can transfer ownership to another hq_manager in the same company
  • Has the same permissions as any other hq_manager — ownership is about protection from accidental removal, not additional capabilities

Multi-Company Access

A single Identities::User can have multiple memberships — one per company:

Identities::User (myname@hotel.com)
├── Org::Membership → Grand Copthorne Hotel, role: hq_manager (is_default: true)
├── Org::Membership → Orchard Hotel, role: hq_manager
└── Org::Membership → M Hotel, role: area_manager
└── OutletAssignments: [Lobby Bar, Banquet Hall]

One membership is marked as is_default: true — this is the company the user sees when they log in. They can switch companies from the UI without logging out.

This replaces the legacy SUPER_HQ_EXTERNAL role and the user_company pivot table. Multi-company access is no longer a special role — it is simply having more than one membership.

Invitation Flow

New users are added to a company through an invitation:

  1. An hq_manager (or Identities::Admin) invites a user by email
  2. System creates an Org::Invite with the intended role (and optionally, outlet assignments)
  3. Invitee receives the invitation email
  4. Invitee accepts — system creates Identities::User (if new) and Org::Membership
  5. If outlet assignments were specified in the invitation, Org::OutletAssignment records are created

If the invitee already has an Identities::User (e.g., they already use Jod at another company), accepting the invitation creates an additional Org::Membership — they now have multi-company access.

Model Context

ContextDetails
EntityOrg::Membership (aggregate root — owns OutletAssignments)
LayerOrganisation Structure — Access Control
Upstream dependenciesIdentities::User (the authenticated person), Org::Company (the company being accessed)
Downstream dependentsOrg::OutletAssignment (outlet scoping), Org::Invite (invitation creates membership)

State Machine

FromToTriggerNotes
(new)activeUC-1, UC-2, or UC-3: Membership createdUser can access the company immediately.
activesuspendedUC-6: Admin suspends membershipUser cannot access the company. Data is preserved.
suspendedactiveUC-7: Admin reactivates membershipAccess restored.
activerevokedUC-5: Membership removedUser loses access. Historical data preserved.
suspendedrevokedUC-5: Membership removed while suspendedUser loses access.

Use Cases

IDUse CaseTriggerActor
UC-1Create membership during company sign-upEmployer creates a company (UC-1a of Org::Company)Employer (new)
UC-2Create membership via invitationExisting member invites a new user by emailhq_manager or Admin
UC-3Admin creates membership directlyAdmin adds a user to a company during onboardingAdmin
UC-4Assign outlets to a membershipUser needs scoped access to specific outletshq_manager or Admin
UC-5Remove a membershipUser leaves the company or access is revokedhq_manager or Admin
UC-6Suspend a membershipUser temporarily cannot access the companyhq_manager or Admin
UC-7Reactivate a suspended membershipSuspension is liftedhq_manager or Admin
UC-8Transfer ownershipOwner leaves the company or delegates ownershipOwner or Admin
UC-9Change a membership's roleUser's responsibilities change within the companyhq_manager or Admin
UC-10Set default companyUser with multiple memberships chooses their landing companyEmployer
UC-11View members of a companyhq_manager reviews team membershq_manager

UC-1: Create membership during company sign-up

FieldDetails
ActorIdentities::User who does not yet have an Org::Membership
TriggerEmployer logs in and the system detects they have no Membership — shows the company creation form

Preconditions:

  • Identities::User already exists (created earlier via the sign-up/registration endpoint)
  • The user has no existing Org::Membership

System Behavior:

  1. User logs in. System detects they have no Org::Membership.
  2. System shows the company creation form (company name, registration number, address, etc.)
  3. User submits the form. System creates Org::Company and Org::Membership in the same transaction:
    • Org::Company with parent_company_id = null, is_enabled = true
    • Org::Membership with role: hq_manager, is_owner: true, is_default: true

Business Rules:

  • The first membership in a self-serve created company is always hq_manager with is_owner: true
  • Org::Company and Org::Membership are created together in one transaction — you cannot create a Membership without a Company in this flow
  • The employer cannot create a Membership for an existing company through this flow — this is strictly for new company creation. To join an existing company, use the invitation flow (UC-2).

Postconditions:

  • Org::Company exists with is_enabled = true
  • Membership exists with role: hq_manager, is_owner: true, is_default: true
  • User can immediately access and manage the company

UC-2: Create membership via invitation

FieldDetails
Actorhq_manager of the company, or Identities::Admin
TriggerExisting member invites a new user by email

Preconditions:

  • Actor has hq_manager role in the company (or is admin)
  • No active membership exists for the invitee's email in this company

System Behavior:

  1. Actor enters the invitee's email address and selects a role
  2. Optionally, actor selects outlets to assign (for area_manager or outlet_manager)
  3. System creates an Org::Invite with status pending, the intended role, and outlet assignments
  4. System sends invitation email to the invitee
  5. When invitee accepts:
    • If the invitee has no Identities::User: system creates one
    • System creates Org::Membership with the specified role
    • If outlet assignments were specified: system creates Org::OutletAssignment records
    • If this is the user's first membership: is_default is set to true

Business Rules:

  • Only hq_manager members (or admins) can invite new users
  • The inviter must specify the role at invitation time
  • A user cannot have two active memberships in the same company
  • If the invitee already has a membership at another company, accepting creates a second membership — multi-company access
  • Invitation has an expiry period (configured by system)

Postconditions:

  • Org::Invite exists with status pending (or accepted after acceptance)
  • On acceptance: Org::Membership exists with the specified role
  • On acceptance: Org::OutletAssignment records exist if outlets were specified

UC-3: Admin creates membership directly

FieldDetails
ActorIdentities::Admin
TriggerAdmin adds a user to a company during onboarding

Preconditions:

  • Org::Company exists
  • Identities::User exists (or admin creates one)

System Behavior:

  1. Admin selects the company, user, and role
  2. System creates Org::Membership with the specified role
  3. If role requires outlet scoping: admin assigns outlets

Business Rules:

  • Admin can create memberships with any role
  • Admin can set is_owner: true during onboarding
  • This path skips the invitation flow — membership is created immediately

Postconditions:

  • Membership exists with the specified role
  • User can access the company

UC-4: Assign outlets to a membership

FieldDetails
Actorhq_manager of the company, or Identities::Admin
TriggerUser needs scoped access to specific outlets

Preconditions:

  • Membership exists with role area_manager or outlet_manager
  • Target outlets belong to the same company as the membership

System Behavior:

  1. Actor selects outlets to assign to the membership
  2. System creates Org::OutletAssignment records linking the membership to the outlets

Business Rules:

  • hq_manager memberships do not need outlet assignments — they have implicit access to all outlets
  • area_manager can be assigned to multiple outlets
  • outlet_manager must be assigned to exactly 1 outlet
  • Outlets must belong to the same Org::Company as the membership — no cross-company outlet assignments
  • Assignments can be added or removed at any time by an hq_manager or admin

Postconditions:

  • Org::OutletAssignment records exist for the membership
  • The user can now see and manage jobs at the assigned outlets

UC-5: Remove a membership

FieldDetails
Actorhq_manager of the company, or Identities::Admin
TriggerUser leaves the company or access is revoked

Preconditions:

  • Membership exists and is active or suspended
  • Target membership does not have is_owner: true (unless actor is admin)

System Behavior:

  1. Actor removes the membership
  2. System sets membership status to revoked
  3. System soft-deletes all Org::OutletAssignment records for this membership (sets revoked_at = now)
  4. If this was the user's default company and they have other memberships: system sets another membership as default

Business Rules:

  • An hq_manager can remove any non-owner membership in their company
  • An hq_manager cannot remove the owner — only admin or the owner themselves can do this
  • Owner must transfer ownership (UC-8) before their membership can be removed
  • Removal is a soft operation — both the membership record and its outlet assignments are preserved for audit
  • Soft-deleted outlet assignments are excluded from active queries but remain queryable for historical audit ("which outlets did this user manage before they left?")
  • If the user has no remaining active memberships, they lose all Org domain access (they may still have a Talent::Profile for worker features)

Postconditions:

  • Membership status is revoked
  • User can no longer access this company
  • OutletAssignments soft-deleted (revoked_at set)

UC-6: Suspend a membership

FieldDetails
Actorhq_manager of the company, or Identities::Admin
TriggerUser temporarily cannot access the company (e.g., staff left or was reassigned)

Preconditions:

  • Membership exists and is active
  • Target membership does not have is_owner: true (unless actor is admin)

System Behavior:

  1. Actor suspends the membership
  2. System sets status to suspended

Business Rules:

  • hq_manager can suspend any non-owner membership in their company
  • hq_manager cannot suspend the owner — only admin can
  • Suspension preserves all data (outlet assignments, role)
  • The user cannot access the company while suspended
  • Suspension is reversible (UC-7)

Postconditions:

  • Membership status is suspended
  • User cannot access the company

UC-7: Reactivate a suspended membership

FieldDetails
Actorhq_manager of the company, or Identities::Admin
TriggerSuspension is lifted (e.g., staff returns or issue resolved)

Preconditions:

  • Membership exists and is suspended

System Behavior:

  1. Actor reactivates the membership
  2. System sets status to active

Business Rules:

  • hq_manager can reactivate any suspended membership in their company
  • All previous outlet assignments are restored (they were preserved during suspension)

Postconditions:

  • Membership status is active
  • User can access the company again with the same role and outlet assignments

UC-8: Transfer ownership

FieldDetails
ActorCurrent owner, or Identities::Admin
TriggerOwner leaves the company or delegates ownership

Preconditions:

  • Current owner has is_owner: true on their membership
  • Target user has an active hq_manager membership in the same company

System Behavior:

  1. Actor selects the new owner from the list of hq_manager members
  2. System sets is_owner: false on the current owner's membership
  3. System sets is_owner: true on the new owner's membership

Business Rules:

  • Ownership can only be transferred to an existing hq_manager in the same company
  • There is exactly one owner per company at all times
  • After transfer, the previous owner remains as a regular hq_manager
  • Admin can force-transfer ownership without the current owner's consent

Postconditions:

  • New owner has is_owner: true
  • Previous owner has is_owner: false but retains hq_manager role

UC-9: Change a membership's role

FieldDetails
Actorhq_manager of the company, or Identities::Admin
TriggerUser's responsibilities change within the company

Preconditions:

  • Membership exists and is active
  • Target membership does not have is_owner: true (owners must stay hq_manager)

System Behavior:

  1. Actor selects the new role for the membership
  2. System updates the role
  3. If changing from hq_manager to a scoped role: actor must assign outlets
  4. If changing from a scoped role to hq_manager: system removes outlet assignments (no longer needed)

Business Rules:

  • The owner's role cannot be changed — they must always be hq_manager
  • When downgrading from hq_manager to area_manager or outlet_manager, outlet assignments must be provided
  • When upgrading to hq_manager, existing outlet assignments are removed (implicit full access)

Postconditions:

  • Membership reflects the new role
  • Outlet assignments updated accordingly

UC-10: Set default company

FieldDetails
ActorThe user themselves
TriggerUser with multiple memberships chooses their landing company

Preconditions:

  • User has more than one active Org::Membership

System Behavior:

  1. User selects a company from their membership list
  2. System sets is_default: true on the selected membership
  3. System sets is_default: false on all other memberships for this user

Business Rules:

  • Exactly one membership per user has is_default: true
  • If a user has only one membership, it is automatically the default
  • The default company is the one shown when the user logs in

Postconditions:

  • Selected membership has is_default: true
  • Next login lands on this company's dashboard

UC-11: View members of a company

FieldDetails
Actorhq_manager of the company
Triggerhq_manager reviews team members

Preconditions:

  • Actor has hq_manager role in the company

System Behavior:

  1. System displays all active and suspended memberships for the company
  2. Each membership shows: user name, email, role, outlet assignments, owner status, last login
  3. Filterable by role and status

Business Rules:

  • Only hq_manager can view the full member list
  • area_manager and outlet_manager cannot view other members
  • Revoked memberships are not shown (they are historical records)

Postconditions:

  • Read-only operation — no data changes

Invariants

  1. Each Org::Membership links exactly one Identities::User to exactly one Org::Company
  2. A user cannot have two active memberships in the same company
  3. Each company has exactly one membership with is_owner: true — this membership must have role hq_manager
  4. The owner's membership cannot be removed by another hq_manager — only by admin or by ownership transfer
  5. The owner's role cannot be changed — it must always be hq_manager
  6. outlet_manager must have exactly 1 Org::OutletAssignment
  7. area_manager must have at least 1 Org::OutletAssignment
  8. hq_manager must have zero Org::OutletAssignment records — access to all outlets is implicit
  9. Outlet assignments must reference outlets belonging to the same company as the membership
  10. Exactly one membership per user has is_default: true across all their memberships
  11. Memberships are never hard-deleted — revoked memberships are preserved for audit
  12. Role determines capabilities (fixed, not customizable per company). Outlet assignments determine scope.

Model Interactions

Related ModelRelationshipInteraction
Identities::UserMembership belongs_to UserThe authenticated person. One user can have many memberships across different companies.
Org::CompanyMembership belongs_to CompanyThe company being accessed. One company has many memberships (its team members).
Org::OutletAssignmentMembership has_many OutletAssignmentsScoping mechanism for area_manager and outlet_manager. Links membership to specific outlets.
Org::OutletOutletAssignment belongs_to OutletThe physical branch. Referenced by outlet assignments for scoped access.
Org::InviteInvitation creates MembershipInvitation carries the intended role and outlet assignments. Acceptance creates the membership.
Billing::Account(indirect, via Company)hq_manager can view and manage the company's billing. Other roles have view-only access to credit history.
Careers::Job(indirect, via Company + Outlet)Jobs are scoped by role: hq_manager sees all company jobs, scoped roles see only jobs at assigned outlets.
Gig::TempJob(indirect, via Company + Outlet)Same scoping as Careers::Job. Gig jobs at assigned outlets only.

Schema Gaps

GapImpactSuggested Resolution
org_user_profiles table needs to be renamed or replacedCurrent table does not have role, is_owner, is_default, or status columnsResolved in DBML. org_memberships table defined with all required columns (role, is_owner, is_default, status). Migrate existing data from org_user_profiles.
No org_outlet_assignments table existsCannot scope area_manager or outlet_manager to specific outletsResolved in DBML. org_outlet_assignments table defined with membership_id, outlet_id, revoked_at foreign keys and unique index.
Org::Invite does not carry role or outlet assignmentsInvitation cannot specify the intended role or outlet scope for the new memberResolved in DBML. org_invites table has role column. org_invite_outlets join table defined for outlet pre-assignment.