create table if not exists users ( id uuid primary key, email text not null unique, display_name_ciphertext bytea, display_name_nonce bytea, display_name_key_id text, password_hash text, is_active boolean not null default true, must_change_password boolean not null default false, initial_password_expires_at timestamptz, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), last_login_at timestamptz, constraint users_email_lowercase check (email = lower(email)), constraint users_display_name_encryption_complete check ( ( display_name_ciphertext is null and display_name_nonce is null and display_name_key_id is null ) or ( display_name_ciphertext is not null and display_name_nonce is not null and display_name_key_id is not null ) ) ); create table if not exists organizations ( id uuid primary key, display_name_ciphertext bytea, display_name_nonce bytea, display_name_key_id text, schema_name text unique, status text not null default 'pending_approval', registration_email text not null, setup_completed_at timestamptz, approved_by_user_id uuid references users(id), approved_at timestamptz, rejected_by_user_id uuid references users(id), rejected_at timestamptz, rejection_reason text, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint organizations_status_valid check ( status in ('pending_approval', 'approved', 'active', 'rejected', 'suspended') ), constraint organizations_registration_email_lowercase check (registration_email = lower(registration_email)), constraint organizations_schema_name_valid check ( schema_name is null or schema_name ~ '^company_[a-z0-9_]+$' ), constraint organizations_display_name_encryption_complete check ( ( display_name_ciphertext is null and display_name_nonce is null and display_name_key_id is null ) or ( display_name_ciphertext is not null and display_name_nonce is not null and display_name_key_id is not null ) ) ); create table if not exists user_organizations ( user_id uuid not null references users(id) on delete cascade, organization_id uuid not null references organizations(id) on delete cascade, status text not null default 'pending_invitation', invited_by_user_id uuid references users(id), invited_at timestamptz, accepted_at timestamptz, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), primary key (user_id, organization_id), constraint user_organizations_status_valid check ( status in ('pending_invitation', 'active', 'disabled') ) ); create table if not exists organization_domains ( id uuid primary key, organization_id uuid not null references organizations(id) on delete cascade, domain text not null unique, is_primary boolean not null default false, created_at timestamptz not null default now(), constraint organization_domains_domain_lowercase check (domain = lower(domain)) ); create index if not exists idx_user_organizations_organization_id on user_organizations (organization_id); create index if not exists idx_organizations_status on organizations (status);