# Multi-Tenancy Guide

## Overview

Qaya HRM implements a **single-database multi-tenancy** architecture, allowing multiple organizations to use the same application instance while maintaining complete data isolation and security.

---

## Multi-Tenancy Architecture

### Three-Tier User System

```
┌─────────────────────────────────────────────────────────┐
│                     System Level                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │  SystemAdmin                                      │  │
│  │  - Manages all organizations                      │  │
│  │  - No organization_id                             │  │
│  │  - Full system access                             │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
┌───────▼────────┐  ┌───────▼────────┐  ┌──────▼─────────┐
│ Organization A │  │ Organization B │  │ Organization C │
│                │  │                │  │                │
│ ┌────────────┐ │  │ ┌────────────┐ │  │ ┌────────────┐ │
│ │ Org Admin  │ │  │ │ Org Admin  │ │  │ │ Org Admin  │ │
│ └────────────┘ │  │ └────────────┘ │  │ └────────────┘ │
│ ┌────────────┐ │  │ ┌────────────┐ │  │ ┌────────────┐ │
│ │   Staff    │ │  │ │   Staff    │ │  │ │   Staff    │ │
│ └────────────┘ │  │ └────────────┘ │  │ └────────────┘ │
└────────────────┘  └────────────────┘  └────────────────┘
```

---

## Data Isolation Strategy

### Database Schema

**Organization Table**:
```sql
organizations
├── id (Primary Key)
├── name
├── slug (Unique)
├── logo
├── settings (JSON)
├── status
└── timestamps
```

**Tenant-Scoped Tables** (with organization_id):
```sql
users
├── id
├── organization_id (Foreign Key)
├── name
├── email
└── ...

employees
├── id
├── organization_id (Foreign Key)
├── user_id
├── employee_code
└── ...

departments
├── id
├── organization_id (Foreign Key)
├── name
└── ...
```

### Automatic Scoping

**HasTenantScope Trait**:
```php
<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Builder;
use App\Services\TenantContext;

trait HasTenantScope
{
    protected static function bootHasTenantScope()
    {
        // Automatically add organization_id to queries
        static::addGlobalScope('organization', function (Builder $builder) {
            if ($organizationId = TenantContext::getOrganizationId()) {
                $builder->where('organization_id', $organizationId);
            }
        });

        // Automatically set organization_id on create
        static::creating(function ($model) {
            if (!$model->organization_id) {
                $model->organization_id = TenantContext::getOrganizationId();
            }
        });
    }
}
```

**Usage in Models**:
```php
class Employee extends Model
{
    use HasTenantScope;
    
    // All queries automatically filtered by organization_id
    // All creates automatically include organization_id
}
```

---

## Authentication System

### Dual Login Paths

#### 1. SystemAdmin Login

**URL**: `/systemadmin/login`

**Features**:
- Separate login page
- Validates `is_system_admin = true`
- No organization context
- Access to all organizations

**Login Process**:
```
1. User visits /systemadmin/login
2. Enters credentials
3. System validates:
   - User exists
   - is_system_admin = true
   - Password correct
4. Redirects to SystemAdmin dashboard
5. Can manage all organizations
```

#### 2. Organization User Login

**URL**: `/login`

**Features**:
- Standard login page
- Validates organization membership
- Sets organization context
- Scoped to organization data

**Login Process**:
```
1. User visits /login
2. Enters credentials
3. System validates:
   - User exists
   - Has organization_id
   - Password correct
   - Organization is active
4. Sets organization context
5. Redirects to organization dashboard
6. All data scoped to organization
```

---

## Middleware Protection

### TenantScope Middleware

**Purpose**: Ensures users can only access their organization's data

**Implementation**:
```php
<?php

namespace App\Http\Middleware;

use Closure;
use App\Services\TenantContext;

class TenantScope
{
    public function handle($request, Closure $next)
    {
        $user = $request->user();
        
        // Skip for system admins
        if ($user && $user->is_system_admin) {
            return $next($request);
        }
        
        // Set organization context
        if ($user && $user->organization_id) {
            TenantContext::setOrganization($user->organization_id);
        } else {
            abort(403, 'No organization context');
        }
        
        return $next($request);
    }
}
```

**Route Protection**:
```php
Route::middleware(['auth', 'tenant_scope'])->group(function () {
    // All routes here are automatically scoped
    Route::get('/employees', [EmployeeController::class, 'index']);
    Route::get('/payroll', [PayrollController::class, 'index']);
    // ...
});
```

---

## Organization Management

### Creating Organizations

#### Via Command Line

**Create Organization**:
```bash
php artisan org:create "Company Name" admin@company.com "Admin Name"
```

**Output**:
```
Creating organization...
✓ Organization created: Company Name
✓ Admin user created: admin@company.com
✓ Default password: password (change immediately)
✓ Organization ID: 5
```

**List Organizations**:
```bash
php artisan org:list
```

**Output**:
```
Organizations:
┌────┬──────────────┬────────────────────┬────────┐
│ ID │ Name         │ Slug               │ Status │
├────┼──────────────┼────────────────────┼────────┤
│ 1  │ Acme Corp    │ acme-corp          │ active │
│ 2  │ Tech Solutions│ tech-solutions    │ active │
│ 3  │ Global Inc   │ global-inc         │ active │
└────┴──────────────┴────────────────────┴────────┘
```

#### Via SystemAdmin Dashboard

**Steps**:
1. Login as SystemAdmin
2. Navigate to Organizations
3. Click "Create Organization"
4. Fill in details:
   - Organization Name
   - Admin Email
   - Admin Name
   - Logo (optional)
5. Click "Create"
6. System creates:
   - Organization record
   - Admin user account
   - Default roles and permissions
   - Initial settings

---

## Organization Settings

### Settings Structure

**Stored as JSON in organizations.settings**:
```json
{
  "company": {
    "name": "Acme Corporation",
    "address": "123 Business St",
    "city": "Nairobi",
    "country": "Kenya",
    "phone": "+254712345678",
    "email": "info@acme.com",
    "website": "https://acme.com",
    "tax_id": "P051234567X"
  },
  "attendance": {
    "earliest_checkin": "06:00",
    "latest_checkin": "12:00",
    "earliest_checkout": "15:00",
    "latest_checkout": "23:59",
    "work_start": "09:00",
    "work_end": "17:00",
    "late_threshold": 15,
    "early_leave_threshold": 15
  },
  "payroll": {
    "currency": "KES",
    "pay_day": 25,
    "tax_enabled": true,
    "nhif_enabled": true,
    "nssf_enabled": true
  },
  "leave": {
    "annual_leave_days": 21,
    "sick_leave_days": 14,
    "carry_forward": true,
    "max_carry_forward": 5
  }
}
```

### Accessing Settings

**In Code**:
```php
// Get setting
$organization = auth()->user()->organization;
$annualLeaveDays = $organization->getSetting('leave.annual_leave_days', 21);

// Set setting
$organization->setSetting('leave.annual_leave_days', 25);
```

---

## Data Isolation Examples

### Query Examples

**Without Tenant Scope** (Manual):
```php
// ❌ Bad - Can access other organizations' data
$employees = Employee::all();
```

**With Tenant Scope** (Automatic):
```php
// ✅ Good - Automatically filtered
$employees = Employee::all(); // Only current organization's employees
```

**Explicit Organization Filter**:
```php
// ✅ Also works
$employees = Employee::where('organization_id', auth()->user()->organization_id)->get();
```

### Relationship Examples

**Automatic Scoping in Relationships**:
```php
// Employee belongs to Department
$employee = Employee::find(1);
$department = $employee->department; // Automatically scoped

// Department has many Employees
$department = Department::find(1);
$employees = $department->employees; // Automatically scoped
```

---

## Security Considerations

### Data Isolation Checks

1. **Global Scope Enforcement**
   - All tenant-scoped models use HasTenantScope trait
   - Queries automatically filtered
   - No manual filtering needed

2. **Middleware Protection**
   - TenantScope middleware on all routes
   - Validates organization context
   - Prevents cross-organization access

3. **Policy Authorization**
   - Laravel Policies check organization ownership
   - Additional layer of security
   - Fine-grained access control

4. **API Protection**
   - API routes also use tenant_scope middleware
   - Token-based authentication includes organization
   - Rate limiting per organization

### Testing Multi-Tenancy

**Test Command**:
```bash
php artisan test:multitenancy
```

**Test Cases**:
```
✓ User can only see their organization's employees
✓ User cannot access other organization's data
✓ SystemAdmin can see all organizations
✓ Organization context is set correctly
✓ Global scopes are applied
✓ Cross-organization queries fail
```

---

## Organization Lifecycle

### Activation

**Steps**:
1. Organization created
2. Status set to "active"
3. Admin user created
4. Default settings applied
5. Users can login

### Suspension

**Steps**:
1. SystemAdmin changes status to "suspended"
2. Users cannot login
3. Data preserved
4. Can be reactivated

### Deletion

**Steps**:
1. SystemAdmin initiates deletion
2. Confirmation required
3. Soft delete organization
4. Cascade soft delete related data
5. Can be restored within retention period

---

## Best Practices

### For System Administrators

1. **Organization Creation**
   - Use unique, descriptive names
   - Verify admin email before creation
   - Set appropriate initial settings
   - Document organization details

2. **Monitoring**
   - Regular activity monitoring
   - Resource usage tracking
   - Security audit logs
   - Performance metrics

3. **Maintenance**
   - Regular backups
   - Database optimization
   - Security updates
   - Capacity planning

### For Organization Admins

1. **User Management**
   - Assign appropriate roles
   - Regular access reviews
   - Remove inactive users
   - Strong password policies

2. **Data Management**
   - Regular data audits
   - Keep information current
   - Archive old records
   - Maintain data quality

3. **Settings Configuration**
   - Configure organization settings
   - Set up leave policies
   - Define attendance rules
   - Customize workflows

---

## Troubleshooting

### Common Issues

**Issue**: User cannot see any data
- **Solution**: Verify organization_id is set, check tenant_scope middleware

**Issue**: Seeing other organization's data
- **Solution**: Check HasTenantScope trait, verify global scope

**Issue**: Cannot create records
- **Solution**: Verify organization context, check permissions

**Issue**: SystemAdmin cannot access organization
- **Solution**: Verify is_system_admin flag, check middleware

---

## Migration from Single-Tenant

### Steps to Add Multi-Tenancy

1. **Add organization_id to tables**:
```php
Schema::table('employees', function (Blueprint $table) {
    $table->foreignId('organization_id')->after('id')->constrained();
});
```

2. **Add HasTenantScope trait to models**:
```php
class Employee extends Model
{
    use HasTenantScope;
}
```

3. **Add middleware to routes**:
```php
Route::middleware(['auth', 'tenant_scope'])->group(function () {
    // Routes
});
```

4. **Update existing data**:
```php
// Assign all existing records to default organization
Employee::withoutGlobalScope('organization')
    ->update(['organization_id' => 1]);
```

---

## Performance Optimization

### Database Indexing

**Add indexes on organization_id**:
```php
Schema::table('employees', function (Blueprint $table) {
    $table->index('organization_id');
});
```

### Query Optimization

**Eager load organization**:
```php
$users = User::with('organization')->get();
```

**Cache organization settings**:
```php
Cache::remember("org_{$orgId}_settings", 3600, function () use ($orgId) {
    return Organization::find($orgId)->settings;
});
```

---

## Future Enhancements

- [ ] Custom domains per organization
- [ ] Organization-specific branding
- [ ] Resource quotas and limits
- [ ] Usage analytics per organization
- [ ] Automated billing integration
- [ ] Organization templates
- [ ] Data export/import per organization
- [ ] Cross-organization reporting (SystemAdmin)

---

**Last Updated**: February 2026
