Organization Hierarchy Node
Data Entity
Description
Represents a single node in a multi-level organizational hierarchy tree. Each node belongs to an organization and may have a parent node, enabling modeling of nested structures such as national associations → regions → local chapters. Used by NHF (12 national associations, 9 regions, 1,400 local chapters) and other tenants with nested org structures. Supports activity distribution across hierarchy levels, Bufdir reporting unit mapping, and role-scoped visibility for coordinators and org admins.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key. Stable identifier for this hierarchy node. | PKrequiredunique |
organization_id |
uuid |
FK to organizations. The tenant this node belongs to. All nodes in a hierarchy tree share the same organization_id. | required |
parent_node_id |
uuid |
Self-referential FK to organization_hierarchy_nodes. NULL for root nodes (e.g. national association level). Non-null for all child nodes. | - |
node_type |
enum |
Structural level of this node within the hierarchy. Determines display label and reporting semantics. | required |
name |
string |
Canonical internal name of this node (e.g. 'NHF Oslo og Akershus'). Used in admin UI and reports. | required |
display_name |
string |
Optional override for how this node is presented in user-facing UI. Falls back to name if null. | - |
external_id |
string |
Identifier used in external member registry or accounting system (e.g. Dynamics, Xledger) to cross-reference this org unit. Nullable for nodes not mapped to external systems. | - |
bufdir_unit_id |
string |
Bufdir reporting unit identifier for this node, if it is a distinct reporting unit in grant compliance reports. Nullable; nodes without a Bufdir ID are not independently reportable. | - |
path |
string |
Materialized path string representing the full ancestor chain, e.g. '/root-id/mid-id/this-id/'. Maintained by hierarchy-service on create/move. Enables efficient subtree queries without recursive CTEs. | requiredunique |
depth |
integer |
Zero-based depth of this node in the tree. Root node depth = 0. Derived from parent chain but stored for query efficiency. | required |
sort_order |
integer |
Display order among siblings sharing the same parent_node_id. Lower values appear first. Defaults to 0. | required |
status |
enum |
Lifecycle state of this node. Inactive nodes are hidden from mobile module registry and coordinator views but retained for historical reporting. | required |
metadata |
json |
Arbitrary org-specific metadata for this node (e.g. contact email, postal address, meeting schedule). Not queried structurally; for display and export use only. | - |
created_at |
datetime |
UTC timestamp when this node was created. | required |
updated_at |
datetime |
UTC timestamp of last modification. Updated on any field change including moves. | required |
created_by |
uuid |
FK to users. The org admin who created this node. Nullable to support seed/migration data. | - |
Database Indexes
idx_ohn_organization_id
Columns: organization_id
idx_ohn_parent_node_id
Columns: parent_node_id
idx_ohn_path
Columns: path
idx_ohn_organization_status
Columns: organization_id, status
idx_ohn_organization_depth
Columns: organization_id, depth
idx_ohn_bufdir_unit_id
Columns: bufdir_unit_id
Validation Rules
name_not_blank
error
Validation failed
node_type_valid_enum
error
Validation failed
depth_matches_parent
error
Validation failed
sort_order_non_negative
warning
Validation failed
external_id_length
error
Validation failed
bufdir_unit_id_format
error
Validation failed
Business Rules
root_node_no_parent
A node with parent_node_id IS NULL is the root of its organization's tree. Each organization may have at most one root node. Attempting to create a second root for the same organization is rejected.
no_cross_organization_parent
parent_node_id must reference a node belonging to the same organization_id. Cross-organization parent links are forbidden.
no_circular_reference
A node may not be set as its own ancestor. On every parent assignment or node move, the service walks the ancestor chain to verify the target parent is not a descendant of the node being moved.
path_consistency
The path column must be kept consistent with the actual parent chain. When a node is moved (parent_node_id changes), hierarchy-service recomputes path and depth for the moved node and all its descendants in a single transaction.
delete_requires_no_children
A node with active or inactive child nodes cannot be deleted. The delete endpoint returns a 409 listing the child count. Archiving is offered as the safe alternative.
depth_limit
Hierarchy depth is capped at 5 levels (depth 0–4) to prevent unbounded recursion in path-based queries and to keep the admin UI manageable. Attempts to create a node deeper than depth 4 are rejected.
inactive_node_hidden_from_registry
Nodes with status != 'active' are excluded from the module registry bootstrap response. Coordinator and peer mentor mobile views never surface inactive org units.
bufdir_unit_scoped_reporting
Bufdir report generation aggregates activity data scoped to nodes where bufdir_unit_id is non-null. Nodes without a bufdir_unit_id roll up into their nearest ancestor that has one.