Organizations¶
Organizations represent companies, departments, or other entities that users can be affiliated with. They support hierarchical structures (parent/child), role-based membership, and domain-based association.
Data Model¶
Organization¶
| Field | Type | Description |
|---|---|---|
id |
uuid | Primary key |
name |
varchar(255) | Unique name (within active orgs) |
address |
text | Physical or mailing address |
website |
varchar(500) | Web URL |
domain |
varchar(255) | Email domain (e.g. acme.com) for auto-association |
parent_id |
uuid | FK to parent organization (nullable) |
contacts |
jsonb | Array of contact objects |
created_by |
uuid | FK to user who created the org |
deleted_at |
timestamp | Soft-delete sentinel (1970-01-01 = active) |
created_at |
timestamp | Creation timestamp |
updated_at |
timestamp | Last update timestamp |
Organization Membership¶
| Field | Type | Description |
|---|---|---|
id |
uuid | Primary key |
organization_id |
uuid | FK to organization |
user_id |
uuid | FK to user |
role |
varchar(50) | member, signatory, or approver |
added_by |
uuid | FK to user who added this member |
created_at |
timestamp | When the membership was created |
A unique constraint on (organization_id, user_id, role) allows a user to hold multiple distinct roles in the same organization.
Contact Object (JSON)¶
{
"name": "Jane Doe",
"email": "jane@acme.com",
"phone": "+1-555-0100",
"title": "Chief Technology Officer"
}
All fields except name are nullable.
Membership Roles¶
| Role | Description |
|---|---|
member |
User whose primary affiliation is with this organization. The org typically controls their email domain and account. |
signatory |
User authorized to approve actions or sign documents on behalf of the organization. |
approver |
User authorized to approve administrative actions, such as authorizing other members to join projects or groups. |
These roles are extensible -- new values can be added without schema changes since the column is a plain varchar.
Hierarchy¶
Organizations support arbitrary parent-child hierarchies via the parent_id self-reference. The system prevents circular references by walking the parent chain before accepting a new parent_id value.
API Endpoints¶
All endpoints are under /api/organizations. Authentication is required for all routes.
List Organizations¶
GET /api/organizations?page=1&pageSize=20&search=acme&sortBy=name&sortOrder=asc&includeDeleted=false
Permission: org:read
Returns a paginated list of organizations with member counts and parent org names.
Create Organization¶
POST /api/organizations
Content-Type: application/json
{
"name": "Acme Corp",
"domain": "acme.com",
"website": "https://acme.com",
"address": "123 Main Street, Springfield",
"parentId": null,
"contacts": [
{ "name": "Jane Doe", "email": "jane@acme.com", "phone": null, "title": "CTO" }
]
}
Permission: org:manage
Returns 201 with the full organization detail including members and children.
Get Organization¶
Permission: org:read
Returns the organization with its members, child organizations, and parent name.
Update Organization¶
PUT /api/organizations/:id
Content-Type: application/json
{
"name": "Acme Inc",
"domain": "acme.com",
"website": "https://acme.com",
"contacts": [...]
}
Permission: org:manage
All fields are optional. Returns 409 if a name conflict exists.
Delete Organization (Soft)¶
Permission: org:manage
Soft-deletes the organization. Returns 200 with a confirmation message.
Restore Organization¶
Permission: org:manage
Restores a soft-deleted organization.
Add Member¶
POST /api/organizations/:id/members
Content-Type: application/json
{
"userId": "uuid-of-user",
"role": "signatory"
}
Permission: org:manage
Assigns a role to a user. A user can hold multiple roles in the same organization. Returns 409 if the exact user+role combination already exists.
Remove Member Role¶
Permission: org:manage
Removes a specific role assignment from a user.
List Child Organizations¶
Permission: org:read
Returns an array of { id, name } for direct children.
Permissions¶
| Permission | Description | Default Roles |
|---|---|---|
org:read |
View organizations and memberships | admin, approver, resource_manager, requestor |
org:manage |
Create, edit, delete organizations and manage members | admin |
Audit Events¶
The following actions are logged in the audit trail:
organization.createdorganization.updatedorganization.soft_deletedorganization.restoredorganization.permanently_deletedorganization.member_added(includesuserIdandrolein metadata)organization.member_removed(includesuserIdandrolein metadata)
Frontend¶
The Organizations feature is accessible from the sidebar (requires org:read permission) and provides:
- Organization List (
/organizations): Paginated table with name, domain, parent, member count, and CRUD actions. - Organization Detail (
/organizations/:id): Full view with contact cards, child organization tags, and a members table with role badges. Supports adding/removing members with role selection.
Migration¶
Migration 020_organizations.ts creates the organization and organization_membership tables, the v_organization soft-delete view, and indexes for name uniqueness, domain lookups, and parent traversal.
Future Extensibility¶
- New membership roles: Add values like
billing,technical_contact, orexecutivewithout schema changes. - Project sponsorship: Add a
sponsor_org_idFK column to theprojecttable to link projects to sponsoring organizations. - Domain-based auto-association: The
OrganizationService.resolveByDomain()method can match user email domains to organizations during onboarding. - Org-scoped permissions: Role-based permissions can be extended to be org-scoped (e.g., "approver for Acme Corp only").