core PK: id 11 required 1 unique

Description

Join entity linking a user to a specific organization and local association simultaneously. A single user may hold multiple membership records for different local associations, enabling multi-org scenarios, active context switching, and prevention of double-reporting in Bufdir submissions.

13
Attributes
8
Indexes
10
Validation Rules
39
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key. Auto-generated UUID for each membership record.
PKrequiredunique
user_id uuid Foreign key referencing the users table. Identifies the user who holds this membership.
required
organization_id uuid Foreign key referencing the organizations table. Identifies the top-level organization this membership belongs to (e.g. NHF, Blindeforbundet, HLF).
required
local_association_id uuid Foreign key referencing the local_associations table. Identifies the specific local chapter or branch at which the user holds membership. Activities are attributed upward through this node for Bufdir reporting.
required
is_primary boolean Indicates whether this membership record is the user's primary affiliation for the given organization. Used to resolve the default active context after login and for cases where a single reporting affiliation must be chosen.
required
is_active boolean Indicates whether this membership is currently active. Inactive memberships are preserved for audit history but excluded from active context selection, authorization checks, and Bufdir data scoping.
required
joined_at datetime Timestamp when the user formally joined this local association. Used in activity attribution windows and Bufdir report eligibility calculations. Defaults to record creation time if not explicitly provided by a sync source.
required
left_at datetime Timestamp when the user's membership at this local association ended. Null for active memberships. Set on deactivation. Must be after joined_at.
-
synced_from_system enum Identifies the source system that created or last synced this membership record. Used for conflict resolution, sync audit, and distinguishing manual entries from integration-driven records.
required
external_member_id string The member identifier as recorded in the external member system (Cornerstone or Consio). Populated by sync services. Used to correlate app membership records with external membership databases and prevent duplicate sync writes.
-
context_priority integer Integer ordering value used when presenting the user's list of organization memberships in the context-switcher UI. Lower values appear first. Defaults to 0; the primary membership is conventionally assigned priority 0.
required
created_at datetime Record creation timestamp, set automatically by the database. Used for audit trail and sync log correlation.
required
updated_at datetime Timestamp of the most recent update to this record. Automatically maintained by a database trigger. Used to detect stale cached membership state in mobile clients.
required

Database Indexes

idx_user_org_memberships_user_id
btree

Columns: user_id

idx_user_org_memberships_organization_id
btree

Columns: organization_id

idx_user_org_memberships_local_association_id
btree

Columns: local_association_id

idx_user_org_memberships_user_org
btree

Columns: user_id, organization_id

idx_user_org_memberships_user_org_local_unique
btree unique

Columns: user_id, local_association_id

idx_user_org_memberships_active_user
btree

Columns: user_id, is_active

idx_user_org_memberships_primary_per_org
btree

Columns: user_id, organization_id, is_primary

idx_user_org_memberships_external_member_id
btree

Columns: external_member_id

Validation Rules

user_id_references_existing_user error

Validation failed

organization_id_references_existing_organization error

Validation failed

local_association_id_references_existing_local_association error

Validation failed

joined_at_not_in_future error

Validation failed

left_at_after_joined_at error

Validation failed

left_at_only_when_inactive error

Validation failed

external_member_id_max_length error

Validation failed

context_priority_non_negative error

Validation failed

synced_from_system_valid_enum error

Validation failed

no_duplicate_active_memberships_for_same_local_association error

Validation failed

Business Rules

unique_user_per_local_association
on_create

A user may hold at most one membership record per local_association_id, regardless of active state. Attempting to create a second membership for the same (user_id, local_association_id) pair must be rejected. This enforces structural uniqueness at the data layer via the unique index.

single_primary_per_organization
on_create

A user may have at most one membership record marked is_primary=true per organization_id. When a new membership is set as primary for an organization, any existing primary membership for that organization must be demoted (is_primary set to false) in the same transaction.

primary_must_be_active
on_update

A membership record cannot be marked is_primary=true when is_active=false. If a primary membership is deactivated, the system must either promote another active membership for the same organization to primary or clear is_primary entirely.

local_association_belongs_to_organization
on_create

The local_association_id must belong to the organizational hierarchy rooted at organization_id. The local_association's parent region must belong to a national_association that belongs to the specified organization. Violations indicate data inconsistency or an incorrect API payload.

active_membership_required_for_scoped_access
always

Authorization and data scoping checks must only consider memberships where is_active=true. Inactive memberships must not grant access to any organizational data. The Role Authorization Service must filter memberships by is_active before resolving the active context.

bufdir_deduplication_across_memberships
always

When generating Bufdir reports, activities attributed to a user must not be counted more than once even when that user holds memberships in multiple local associations. The Bufdir Report Generator and Duplicate Report Prevention Service must resolve multi-membership users and apply a single primary affiliation for attribution.

sync_source_idempotency
on_create

Membership sync operations from external systems (Cornerstone, Consio) must be idempotent. If a membership record with the same (user_id, local_association_id) already exists, the sync service must update the existing record rather than creating a duplicate. The external_member_id field is used as the correlation key.

context_switch_requires_active_membership
always

The Organization Context BLoC must only allow context switching to an organization for which the user holds at least one active membership. Attempting to switch to an organization with no active membership must be rejected and the user directed to the No-Access Screen.

rls_self_read_restriction
always

Supabase RLS policies must allow users to read only their own membership records. Coordinators and administrators may read memberships of users within their organizational scope. Cross-organization reads are restricted to the deduplication service running under a privileged service role.

deactivation_preserves_history
on_delete

Membership records must never be hard-deleted during normal operations. Deactivating a membership must set is_active=false and left_at=now(). This preserves the audit history required for Bufdir compliance checks, particularly for organizations where historical attribution must be traceable.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

local_associations
outgoing many_to_one

Each membership is associated with a specific local association within the organization

optional
organizations
outgoing many_to_one

Each membership record is scoped to a specific top-level organization

required
users
incoming one_to_many

A user may belong to multiple local associations simultaneously across one or more organizations

optional cascade delete