> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/appertafoundation/openeyes/llms.txt
> Use this file to discover all available pages before exploring further.

# Institution Management

> Multi-institution support and site configuration in OpenEyes

OpenEyes is designed to support multiple institutions and sites within a single deployment, enabling shared infrastructure while maintaining data segregation and institution-specific configurations.

## Institution Architecture

The multi-institution system is built on a hierarchical structure:

```
Installation (System-wide)
  └── Institution 1
      ├── Site A
      ├── Site B
      └── Site C
  └── Institution 2
      ├── Site D
      └── Site E
```

## Institution Model

**File:** `protected/models/Institution.php`

<ParamField path="id" type="integer">
  Unique institution identifier
</ParamField>

<ParamField path="name" type="string" required>
  Full institution name (e.g., "Moorfields Eye Hospital")
</ParamField>

<ParamField path="short_name" type="string" required>
  Abbreviated name for display (e.g., "MEH")
</ParamField>

<ParamField path="remote_id" type="integer" required>
  External system reference ID
</ParamField>

<ParamField path="pas_key" type="string">
  Patient Administration System key (max 10 characters)
</ParamField>

<ParamField path="contact_id" type="integer">
  Link to Contact record with address and contact details
</ParamField>

<ParamField path="logo_id" type="integer">
  Institution logo for correspondence and reports
</ParamField>

<ParamField path="any_number_search_allowed" type="boolean">
  Whether to allow searching across different number types
</ParamField>

### Institution Relations

```php theme={null}
// protected/models/Institution.php:96
public function relations()
{
    return array(
        'contact' => array(self::BELONGS_TO, 'Contact', 'contact_id'),
        'sites' => array(
            self::HAS_MANY, 
            'Site', 
            'institution_id',
            'condition' => 'sites.active = 1',
        ),
        'authenticationMethods' => array(
            self::HAS_MANY,
            'InstitutionAuthentication',
            'institution_id',
            'condition' => 'authenticationMethods.active = 1',
        ),
        'logo' => array(self::BELONGS_TO, 'SiteLogo', 'logo_id'),
    );
}
```

## Creating an Institution

<Steps>
  <Step title="Access Institution Admin">
    Navigate to Admin → Institutions (typically `/admin/institutions`)
  </Step>

  <Step title="Add Institution Details">
    * Enter full name and short name
    * Set remote ID for external system integration
    * Add PAS key if using a Patient Administration System
  </Step>

  <Step title="Configure Contact Information">
    Link or create a Contact record with:

    * Address
    * Phone/fax numbers
    * Email address
  </Step>

  <Step title="Set Up Authentication">
    Create at least one InstitutionAuthentication method
  </Step>

  <Step title="Add Sites">
    Create one or more sites within the institution
  </Step>
</Steps>

<Warning>
  Once an institution has users or patient data associated with it, it should not be deleted. Instead, mark it as inactive.
</Warning>

## Multi-Tenancy & Authentication

Institutions can be "tenanted" - meaning they have their own authentication system and user base.

### Checking if Institution is Tenanted

```php theme={null}
// protected/models/Institution.php:146
public function isTenanted(): bool
{
    return count($this->authenticationMethods) > 0;
}
```

### Getting Tenanted Institutions

```php theme={null}
// Get all tenanted institutions
$institutions = Institution::model()->getTenanted();

// Get tenanted institutions user is member of
$userInstitutions = Institution::model()->getTenanted(
    '', 
    [], 
    $user_must_be_member = true
);

// Get as array for dropdown
$institutionList = Institution::model()->getTenantedList(
    $current_institution_only = false,
    $user_must_be_member = true
);
```

## InstitutionAuthentication

Each institution can have multiple authentication methods configured:

```php theme={null}
class InstitutionAuthentication extends BaseActiveRecord
{
    public $institution_id;              // Link to institution
    public $user_authentication_method;  // 'BASIC', 'LDAP', 'SAML', 'OIDC'
    public $description;                 // Display name
    public $active;                      // Whether method is active
    public $site_id;                     // Optional site restriction
}
```

### Authentication Flow

<Steps>
  <Step title="User Attempts Login">
    Enters username on login page
  </Step>

  <Step title="System Identifies Institution">
    Looks up username in `user_authentication` table

    Joins to `institution_authentication` to determine institution and auth method
  </Step>

  <Step title="Apply Auth Method">
    * **BASIC**: Verify against local password hash
    * **LDAP**: Forward to LDAP server
    * **SAML**: Redirect to SAML IdP
    * **OIDC**: Redirect to OIDC provider
  </Step>

  <Step title="Set Session Context">
    On success, set:

    * Selected institution
    * Selected site (default for institution)
    * Selected firm (user's last or default)
  </Step>
</Steps>

## Site Management

Sites represent physical locations within an institution where clinical services are delivered.

### Site Model

```php theme={null}
class Site extends BaseActiveRecord
{
    public $id;
    public $name;                    // Site name
    public $short_name;             // Abbreviated name
    public $institution_id;         // Parent institution
    public $contact_id;             // Site contact details
    public $telephone;              // Main phone
    public $fax;                    // Fax number
    public $active;                 // Whether site is active
}
```

### Site-Specific Settings

Settings can be configured at the site level using the `setting_site` table:

```php theme={null}
// Example: Site-specific correspondence letterhead
SettingMetadata::model()->getSetting(
    'correspondence_letterhead',
    $element_type = null,
    $return_object = false,
    $allowed_classes = ['SettingSite']
);
```

## Current Institution and Site

### Getting Current Context

```php theme={null}
// Get current institution
$institution = Institution::model()->getCurrent();
// or from session
$institutionId = Yii::app()->session['selected_institution_id'];

// Get current site  
$site = Site::model()->getCurrent();
// or from session
$siteId = Yii::app()->session['selected_site_id'];

// Get current firm/context
$firm = Firm::model()->findByPk(
    Yii::app()->session['selected_firm_id']
);
```

<Info>
  The current institution context is stored in the session and used throughout the application to filter data and apply institution-specific settings.
</Info>

### Changing Institution Context

Users can switch between institutions they have access to:

```php theme={null}
// In controller
public function actionChangeSiteAndFirm()
{
    $institutionId = $_POST['institution_id'];
    $siteId = $_POST['site_id'];
    $firmId = $_POST['firm_id'];
    
    // Validate user has access
    // Update session
    Yii::app()->session['selected_institution_id'] = $institutionId;
    Yii::app()->session['selected_site_id'] = $siteId;
    Yii::app()->session['selected_firm_id'] = $firmId;
    
    // Trigger session change event
    SessionSiteChangedSystemEvent::dispatch();
}
```

## Institution-Specific Data

Many models support institution-specific instances:

### ReferenceData Model

Used by models like CommonOphthalmicDisorder, this provides multi-level data support:

```php theme={null}
// Level constants (protected/models/ReferenceData.php)
const LEVEL_INSTALLATION = 1;
const LEVEL_INSTITUTION = 2;
const LEVEL_SITE = 4;
const LEVEL_SUBSPECIALTY = 8;
const LEVEL_SPECIALTY = 16;
const LEVEL_FIRM = 32;

// Finding data for current institution
$disorders = CommonOphthalmicDisorder::model()->findAllAtLevels(
    ReferenceData::LEVEL_INSTITUTION | ReferenceData::LEVEL_SUBSPECIALTY,
    $criteria,
    $current_institution,
    null,  // site
    null,  // specialty  
    $subspecialty
);
```

### Managing Institution-Specific Data

<CodeGroup>
  ```php Admin Controller Example theme={null}
  // protected/controllers/AdminController.php:50
  public function actionEditCommonOphthalmicDisorderGroups()
  {
      // Get current institution or use installation level
      $current_institution = $this->request->getParam('institution_id')
          ? Institution::model()->find('id = :id', [
              ':id' => $this->request->getParam('institution_id')
            ])
          : (!$this->checkAccess('admin') 
              ? Institution::model()->getCurrent() 
              : null);
      
      // Determine level mask
      $level_mask = $current_institution 
          ? ReferenceData::LEVEL_INSTITUTION 
          : ReferenceData::LEVEL_INSTALLATION;
      
      // Query data at appropriate level
      $groups = CommonOphthalmicDisorderGroup::model()->findAllAtLevels(
          $level_mask,
          $criteria,
          $current_institution
      );
  }
  ```

  ```php Creating Mappings theme={null}
  // Adding institution mapping to reference data
  public function actionAddMapping()
  {
      $model = $_POST['model']::model();
      $level = $_POST['mapping_level'];  // e.g., 'institution'
      $ids = Yii::app()->request->getPost('selected');
      
      $records = $model->findAllByPk($ids);
      foreach ($records as $record) {
          $record->createMapping(
              $level, 
              $model->getIdForLevel($level)
          );
      }
  }
  ```
</CodeGroup>

## User-Institution Relationships

### Finding Institution Users

```php theme={null}
// Get all users for an institution
$institution_user_ids = Yii::app()->db->createCommand()
    ->selectDistinct('ua.user_id')
    ->from('institution_authentication ia')
    ->join('user_authentication ua', 
           'ua.institution_authentication_id = ia.id')
    ->where('ia.institution_id = :institution_id')
    ->bindValue(':institution_id', $institutionId)
    ->queryColumn();

// Get users from current institution only
$users = User::model()->getUsersFromCurrentInstitution();
```

### User Access Control

Non-admin users are typically restricted to their institution:

```php theme={null}
// protected/controllers/AdminController.php:881
if (!$this->checkAccess('admin')) {
    // Get institution-specific users only
    $institution = Yii::app()->session['selected_institution_id'];
    $institution_user_ids = /* query above */;
    
    // Exclude installation admins
    $admin_user_ids = /* admin query */;
    $user_ids = array_diff($institution_user_ids, $admin_user_ids);
    
    $criteria->addInCondition('t.id', $user_ids);
}
```

## Institution Settings

Institution-level settings override installation defaults:

```sql theme={null}
-- setting_institution table
CREATE TABLE setting_institution (
    id INT PRIMARY KEY,
    institution_id INT NOT NULL,
    key VARCHAR(64) NOT NULL,
    value TEXT,
    element_type_id INT,
    FOREIGN KEY (institution_id) 
        REFERENCES institution(id),
    UNIQUE KEY (institution_id, key, element_type_id)
);
```

Accessing institution settings:

```php theme={null}
// Get setting for current institution
$value = SettingMetadata::model()->getSetting(
    'setting_key',
    $element_type = null,
    $return_object = false,
    $allowed_classes = ['SettingInstitution', 'SettingInstallation']
);
```

## Patient Numbers

Patients can have different identification numbers at different institutions:

```php theme={null}
class PatientIdentifier extends BaseActiveRecord
{
    public $patient_id;
    public $num_type_id;      // NHS Number, Hospital Number, etc.
    public $value;            // The actual number
    public $institution_id;   // Institution where number applies
}

// Get patient hospital number for current institution
$hos_num = $patient->getHos(
    Yii::app()->session['selected_institution_id']
);

// Get NHS number
$nhs_num = $patient->getNhs(
    Yii::app()->session['selected_institution_id']
);
```

## API Reference

### Institution Methods

* `getCurrent()` - Get current institution from session
* `isTenanted()` - Check if institution has authentication
* `getTenanted($condition, $params, $user_must_be_member)` - Get tenanted institutions
* `getTenantedList($current_only, $user_member, $json)` - Get as list
* `getList($current_institution_only)` - Get institutions as array

### AdminController Institution Methods

**File:** `protected/controllers/AdminController.php`

* `actionEditCommonOphthalmicDisorderGroups()` - Line 46
* `actionEditCommonOphthalmicDisorder()` - Line 366
* `actionAddMapping()` - Line 243
* `actionRemoveMapping()` - Line 274

## Best Practices

<CardGroup cols={2}>
  <Card title="Plan Your Structure" icon="sitemap">
    Design institution/site hierarchy before deployment
  </Card>

  <Card title="Use Tenancy" icon="building">
    Enable multi-tenancy for true institution isolation
  </Card>

  <Card title="Test Data Segregation" icon="shield-alt">
    Verify users can only access their institution's data
  </Card>

  <Card title="Configure Settings Hierarchically" icon="layer-group">
    Use institution settings for institution-specific customization
  </Card>
</CardGroup>

## Related Documentation

* [System Configuration](/admin/configuration)
* [User Management](/admin/users-permissions)
* [System Settings](/admin/system-settings)
