Contact Chapter Membership
Data Entity
Description
Junction entity linking a contact to an organization unit (chapter). A contact in NHF may belong to up to 5 local chapters simultaneously. This relationship is critical for multi-chapter membership handling and for cross-chapter duplicate activity detection to prevent double-counting in Bufdir reporting.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Surrogate primary key for the membership record. Used for stable foreign-key references in audit logs and duplicate-warning event logs. | PKrequiredunique |
contact_id |
uuid |
Foreign key referencing the contact who holds this chapter membership. Part of the composite unique constraint preventing duplicate memberships. | required |
organization_unit_id |
uuid |
Foreign key referencing the organization unit (local chapter) the contact belongs to. Must resolve to a unit of type 'chapter' in the organization hierarchy. | required |
role_in_chapter |
string |
Optional free-text role label describing the contact's function within this specific chapter (e.g., 'peer_mentor', 'board_member', 'volunteer'). Not normalized; used for display and reporting only. | - |
joined_at |
datetime |
Timestamp (UTC) recording when the contact joined or was added to this chapter. Used to order affiliations chronologically and for audit purposes. | required |
is_primary |
boolean |
Indicates whether this is the contact's primary chapter for reporting attribution. Exactly one membership per contact per organization may be primary. Enforced by a partial unique index at the database layer. | required |
status |
enum |
Membership lifecycle state. Active memberships are used for activity queries and Bufdir aggregation. Inactive records are retained for audit history. | required |
created_by |
uuid |
User ID of the coordinator or admin who created this membership record. Stored for audit trail and coordinator-scope validation. | - |
created_at |
datetime |
Server-side creation timestamp. Set automatically on insert; never updated. | required |
updated_at |
datetime |
Server-side last-modification timestamp. Updated via Supabase trigger on every row mutation. | required |
Database Indexes
idx_contact_chapter_unique_membership
Columns: contact_id, organization_unit_id
idx_contact_chapter_contact_id
Columns: contact_id
idx_contact_chapter_org_unit_id
Columns: organization_unit_id
idx_contact_chapter_primary_per_contact
Columns: contact_id
idx_contact_chapter_status_active
Columns: contact_id, status
idx_contact_chapter_joined_at
Columns: contact_id, joined_at
Validation Rules
contact_id_required_and_exists
error
Validation failed
organization_unit_id_required_and_exists
error
Validation failed
max_active_memberships_count
error
Validation failed
role_in_chapter_max_length
error
Validation failed
joined_at_not_future
warning
Validation failed
status_transition_validity
error
Validation failed
reactivation_respects_max_limit
error
Validation failed
multi_chapter_affiliation_chip_ui_limit
info
Validation failed
Business Rules
max_five_chapters_per_contact
A single contact may belong to at most 5 local chapters simultaneously. This is an NHF-specific constraint reflecting the maximum simultaneous chapter memberships allowed by the organization. Any attempt to add a 6th active membership must be rejected with a clear user-facing error.
single_primary_chapter_per_contact
Each contact may have at most one primary chapter (is_primary = true) across all their active memberships. When a new primary is designated, any existing primary membership for that contact must be demoted to is_primary = false atomically within the same transaction.
no_duplicate_chapter_membership
The combination of contact_id and organization_unit_id must be unique across active memberships. Re-activating a previously inactive membership is permitted by updating status rather than inserting a new row.
chapter_unit_type_enforcement
The referenced organization_unit_id must correspond to a unit with type 'chapter' (leaf-level node) in the organizational hierarchy. Regional or national units cannot be used as chapter membership targets.
cross_chapter_activity_deduplication
When a contact belongs to multiple chapters and an activity is submitted, the cross-chapter-activity-query must be used to detect if the same activity (same contact_id, activity_type, and date) already exists in any of the contact's other chapters before allowing submission. This prevents Bufdir double-counting.
coordinator_scope_enforcement
A coordinator may only add or remove chapter memberships for contacts within chapters they are assigned to. The Supabase RLS policy and service-layer validation both enforce this restriction.
soft_delete_on_removal
Chapter memberships are never hard-deleted from the database. Removing a contact from a chapter sets status to 'inactive' and records updated_at. Historical membership data is preserved for audit and Bufdir reporting traceability.
CRUD Operations
Storage Configuration
Entity Relationships
A contact (especially in NHF) may belong to up to 5 local chapters simultaneously through the contact_chapter junction
Each contact-chapter membership links to a specific organization unit (local chapter) in the hierarchy