User Organization Membership
Data Entity
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.
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
Columns: user_id
idx_user_org_memberships_organization_id
Columns: organization_id
idx_user_org_memberships_local_association_id
Columns: local_association_id
idx_user_org_memberships_user_org
Columns: user_id, organization_id
idx_user_org_memberships_user_org_local_unique
Columns: user_id, local_association_id
idx_user_org_memberships_active_user
Columns: user_id, is_active
idx_user_org_memberships_primary_per_org
Columns: user_id, organization_id, is_primary
idx_user_org_memberships_external_member_id
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
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
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
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
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
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
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
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
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
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
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.
CRUD Operations
Storage Configuration
Entity Relationships
Each membership is associated with a specific local association within the organization
Each membership record is scoped to a specific top-level organization
A user may belong to multiple local associations simultaneously across one or more organizations