# ProcessesMultipleTenants Trait - Usage Guide

## 📋 Overview

**Location:** `src/App/Traits/ProcessesMultipleTenants.php`  
**Purpose:** Standardize tenant iteration in console commands  
**Status:** ✅ Available but not yet adopted (created Oct 2025)  
**Benefit:** Eliminates ~50-100 lines of boilerplate per command

---

## 🎯 Problem It Solves

### Current Pattern (Inconsistent)

```php
// Each command reinvents the wheel (~80 lines)
class TokenUpdate extends Command
{
    public function handle()
    {
        $hasErrors = false;
        
        Instance::chunk(100, function($instances) use (&$hasErrors) {
            foreach ($instances as $instance) {
                try {
                    // Setup connection
                    $tenantDb = 'sx_db_' . $instance->database;
                    DB::purge('tenant_connection');
                    config(['database.connections.tenant_connection.database' => $tenantDb]);
                    DB::reconnect('tenant_connection');
                    
                    // Do work
                    $this->processInstance($instance);
                    
                    Log::info('Processed tenant', ['subdomain' => $instance->subdomain]);
                } catch (\Exception $e) {
                    $hasErrors = true;
                    Log::error('Failed processing tenant', [
                        'subdomain' => $instance->subdomain,
                        'error' => $e->getMessage(),
                    ]);
                }
            }
        });
        
        return $hasErrors ? Command::FAILURE : Command::SUCCESS;
    }
}
```

**Problems:**
- ❌ Duplicated code across 20+ commands
- ❌ Inconsistent error handling
- ❌ Inconsistent logging format
- ❌ Manual connection management
- ❌ No standardized statistics
- ❌ Hard to maintain

---

## ✅ Solution with Trait (5 lines)

```php
use App\Traits\ProcessesMultipleTenants;

class TokenUpdate extends Command
{
    use ProcessesMultipleTenants;
    
    public function handle()
    {
        // Just define the business logic
        $stats = $this->processTenants(function($tenant) {
            $this->processInstance($tenant);
        });
        
        return $stats['failed'] > 0 ? Command::FAILURE : Command::SUCCESS;
    }
}
```

**Benefits:**
- ✅ Automatic connection management
- ✅ Standardized error handling
- ✅ Consistent logging format
- ✅ Built-in statistics
- ✅ Optional filtering by subdomain
- ✅ Only focus on business logic

---

## 📖 API Reference

### Main Method: `processTenants()`

```php
protected function processTenants(callable $callback, ?string $filter = null): array
```

**Parameters:**
- `$callback` - Function to execute for each tenant
- `$filter` - (Optional) Filter tenants by subdomain (LIKE match)

**Returns:**
```php
[
    'processed' => 15,    // Successfully processed
    'failed' => 2,        // Failed with errors
    'total' => 17,        // Total tenants
    'errors' => [         // Array of error details
        ['tenant' => 'acme', 'error' => 'Connection failed'],
        ['tenant' => 'demo', 'error' => 'Timeout'],
    ]
]
```

**Features:**
- ✅ Automatic tenant DB connection switching
- ✅ Try/catch around each tenant
- ✅ Structured logging (info on success, error on failure)
- ✅ Progress output to console
- ✅ Automatic cleanup (switchToSystem at end)
- ✅ Statistics collection

---

### Helper Methods

#### `switchToTenant($tenant)`

```php
protected function switchToTenant($tenant): void
```

Manually switch to a specific tenant context.

**Use case:** When you need to process a single tenant outside `processTenants()`.

---

#### `switchToSystem()`

```php
protected function switchToSystem(): void
```

Return to system context (no tenant). Called automatically after `processTenants()`.

---

#### `getTenantBySubdomain($subdomain)`

```php
protected function getTenantBySubdomain(string $subdomain)
```

Fetch a specific tenant instance by subdomain.

**Returns:** Instance object or null

---

#### `forTenant($subdomain, $callback)`

```php
protected function forTenant(string $subdomain, callable $callback): bool
```

Execute callback for a single specific tenant.

**Example:**
```php
$success = $this->forTenant('acme', function($tenant) {
    $this->info("Processing only ACME tenant");
    // Do work...
});
```

**Returns:** `true` on success, `false` on failure

**Features:**
- ✅ Automatic connection switch
- ✅ Error handling
- ✅ Cleanup in finally block
- ✅ Logging

---

## 🚀 Migration Examples

### Example 1: Simple Iteration

**Before:**
```php
class MapDateFieldsToDB extends Command
{
    public function handle()
    {
        $instances = Instance::all();
        
        foreach ($instances as $instance) {
            try {
                setupTenantConnection("sx_db_{$instance->database}");
                $this->info("Processing: {$instance->subdomain}");
                
                // Business logic
                $this->mapDateFields();
                
            } catch (\Exception $e) {
                $this->error("Failed: {$instance->subdomain}");
                Log::error($e->getMessage());
            }
        }
        
        setupTenantConnection(null);
    }
}
```

**After:**
```php
class MapDateFieldsToDB extends Command
{
    use ProcessesMultipleTenants;
    
    public function handle()
    {
        $this->processTenants(function($tenant) {
            $this->mapDateFields();
        });
    }
}
```

**Savings:** ~30 lines → 5 lines

---

### Example 2: With Filtering

**Before:**
```php
class SyncData extends Command
{
    public function handle()
    {
        $subdomain = $this->option('tenant');
        
        if ($subdomain) {
            $instances = Instance::where('subdomain', 'like', "%{$subdomain}%")->get();
        } else {
            $instances = Instance::all();
        }
        
        foreach ($instances as $instance) {
            // ... 50 lines of setup/error handling ...
        }
    }
}
```

**After:**
```php
class SyncData extends Command
{
    use ProcessesMultipleTenants;
    
    public function handle()
    {
        $filter = $this->option('tenant');
        
        $this->processTenants(function($tenant) {
            $this->syncData($tenant);
        }, $filter);
    }
}
```

---

### Example 3: With Statistics

**Before:**
```php
class ProcessRecords extends Command
{
    public function handle()
    {
        $processed = 0;
        $failed = 0;
        
        foreach (Instance::all() as $instance) {
            try {
                // ... setup ...
                $this->process($instance);
                $processed++;
            } catch (\Exception $e) {
                $failed++;
            }
        }
        
        $this->info("Processed: {$processed}, Failed: {$failed}");
        return $failed > 0 ? 1 : 0;
    }
}
```

**After:**
```php
class ProcessRecords extends Command
{
    use ProcessesMultipleTenants;
    
    public function handle()
    {
        $stats = $this->processTenants(function($tenant) {
            $this->process($tenant);
        });
        
        // Stats automatically tracked and logged
        return $stats['failed'] > 0 ? Command::FAILURE : Command::SUCCESS;
    }
}
```

---

## 📊 Commands Ready for Migration

**Currently 20 commands iterate over tenants manually:**

| Command | Lines Saved | Priority |
|---------|-------------|----------|
| TokenUpdate.php | ~80 | High (runs hourly) |
| DownloadDataNetSuite.php | ~60 | High (frequent) |
| ProcessDelayedRecords.php | ~70 | High (performance) |
| MapAccountTenantPermissions.php | ~50 | Medium |
| MapDateFieldsToDB.php | ~40 | Medium |
| MapExistingCustomFieldsToDB.php | ~40 | Medium |
| CronSchedule.php | ~30 | Medium |
| MigrateTenants.php | ~100 | Low (rare) |
| SeedTenantsTable.php | ~40 | Low (rare) |
| ... | ... | ... |

**Total potential savings:** ~800-1000 lines of boilerplate

---

## 🎯 When to Use

### ✅ Use the Trait When:

- Command needs to iterate over multiple tenants
- Need consistent error handling
- Want structured logging
- Need statistics (processed/failed counts)
- Processing tenants in batch operations

### ❌ Don't Use When:

- Only processing a single tenant (use `forTenant()` instead)
- Need very custom connection logic
- Non-tenant related commands
- Need custom chunking strategies

---

## 🔧 Testing

The trait includes comprehensive error handling and logging. When migrating a command:

1. Test with single tenant: `php artisan command:name --tenant=test`
2. Test with multiple tenants
3. Test error scenarios (invalid tenant, DB errors)
4. Verify logs in `storage/logs/tenants/*/`
5. Check statistics output

---

## 📝 Logging Format

**Automatic logs generated:**

```
[2025-10-23 12:00:00] info: Batch tenant processing started {
    "total_tenants": 15,
    "filter": null,
    "command": "App\\Console\\Commands\\TokenUpdate"
}

[2025-10-23 12:00:01] info: Tenant processed successfully {
    "subdomain": "acme",
    "database": "sx_db_123"
}

[2025-10-23 12:00:02] error: Failed processing tenant {
    "subdomain": "demo",
    "error": "Connection timeout",
    "trace": "..."
}

[2025-10-23 12:00:10] info: Batch tenant processing completed {
    "processed": 14,
    "failed": 1,
    "total": 15
}
```

---

## 🚀 Quick Start

1. **Add trait to command:**
   ```php
   use App\Traits\ProcessesMultipleTenants;
   ```

2. **Replace manual loop with `processTenants()`:**
   ```php
   $this->processTenants(function($tenant) {
       // Your business logic here
   });
   ```

3. **Test it:**
   ```bash
   php artisan your:command
   ```

4. **Done!** ✅

---

## 📚 Related Documentation

- Multi-Tenant Logging System: `docs/development/TENANT_LOGGING_SYSTEM.md`
- Tenant Connection Helpers: `src/App/Helpers/helpers.php` (setupTenantConnection)
- Instance Model: `src/Domain/Instances/Models/Instance.php`

---

**Created:** October 2025  
**Last Updated:** December 2025  
**Status:** Available for gradual adoption
