# Heat User - Multi-Tenant Update Guide

## 📋 Tổng quan

Hệ thống Heat User cần được update để support **multi-tenant architecture**.

### **Thay đổi chính:**

1. ✅ **Helper methods** đã thêm vào `HelperTenant.php`
2. 🔧 **Service** cần update tất cả query để dùng `->on($tenantConnection)`
3. ⚙️ **Cron job** cần loop qua tất cả tenants

---

## 🔧 STEP 1: Helper Methods (✅ Done)

**File:** `app/Helpers/HelperTenant.php`

Đã thêm 2 methods:

```php
/**
 * Get all active tenant codes for cron jobs
 */
public static function getActiveTenantCodes(): array
{
    return [
        'hocmai',
        'icc',
        // Add more tenant codes here
    ];
}

/**
 * Get all active tenant connections
 * Returns: ['hocmai' => 'lms_tenant_hocmai', 'icc' => 'lms_tenant_icc']
 */
public static function getActiveTenantConnections(): array
{
    // Implementation...
}
```

**TODO:** Update `getActiveTenantCodes()` với danh sách tenants thực tế của bạn!

---

## 🔧 STEP 2: Update Service (Multi-Tenant)

**File:** `app/Services/HeatUser/StudySessionService.php`

### **Pattern thay đổi:**

#### **BEFORE (Single Tenant):**
```php
$session = StudentStudySession::where('student_id', $studentId)
    ->where('id_history', $idHistory)
    ->first();
```

#### **AFTER (Multi-Tenant):**
```php
$tenantConnection = HelperTenant::getCurrentTenantConnection();

$session = StudentStudySession::on($tenantConnection)
    ->where('student_id', $studentId)
    ->where('id_history', $idHistory)
    ->first();
```

### **Tất cả methods cần update:**

1. ✅ `startSession()`
2. ✅ `continueSession()`
3. ✅ `heartbeat()`
4. ✅ `submitSession()`
5. ✅ `stopSession()`
6. ✅ `autoEndInactiveSessions()` - đặc biệt!
7. ✅ `getStudentStatistics()`
8. ✅ `getActiveSession()`
9. ✅ `checkExistingSession()`
10. ✅ `updateDailyStudyTime()`
11. ✅ `updateHistoryStudyTime()`
12. ✅ `updateContestStudyTime()`

---

## ⚙️ STEP 3: Update Cron Command (Multi-Tenant)

**File:** `app/Console/Commands/AutoEndInactiveSessions.php`

### **BEFORE (Single Tenant):**
```php
public function handle(StudySessionService $service)
{
    $endedCount = $service->autoEndInactiveSessions();
    $this->info("✓ Auto ended {$endedCount} sessions");
}
```

### **AFTER (Multi-Tenant):**
```php
public function handle(StudySessionService $service)
{
    $tenantConnections = HelperTenant::getActiveTenantConnections();
    $totalEnded = 0;
    
    foreach ($tenantConnections as $code => $connection) {
        $this->info("Processing tenant: {$code}");
        
        try {
            // Set tenant connection trong session (for service to use)
            session(['tenant_connection' => $connection]);
            
            $endedCount = $service->autoEndInactiveSessions();
            $totalEnded += $endedCount;
            
            $this->info("  ✓ Tenant {$code}: {$endedCount} sessions ended");
            
        } catch (\Exception $e) {
            $this->error("  ✗ Tenant {$code} failed: " . $e->getMessage());
        }
    }
    
    $this->info("Total: {$totalEnded} sessions ended across all tenants");
}
```

---

## 📝 Detailed Changes for Service

Do file Service quá dài (570+ lines), đây là pattern chung:

### **Add at top of each method:**
```php
$tenantConnection = HelperTenant::getCurrentTenantConnection();
```

### **Replace all Model queries:**
```php
// Pattern 1: find/first
StudentStudySession::on($tenantConnection)->find($id)
StudentStudySession::on($tenantConnection)->where(...)->first()

// Pattern 2: where + get
StudentDailyStudyTime::on($tenantConnection)->where(...)->get()

// Pattern 3: create
StudentStudySession::on($tenantConnection)->create([...])

// Pattern 4: firstOrNew / firstOrCreate
StudentHistoryStudyTime::on($tenantConnection)->firstOrNew([...])
StudentContestStudyTime::on($tenantConnection)->firstOrCreate([...])

// Pattern 5: updateOrCreate
StudentDailyStudyTime::on($tenantConnection)->updateOrCreate([...], [...])

// Pattern 6: update on existing model
$session->setConnection($tenantConnection)->update([...])
// OR refresh connection if model already loaded
$session->on($tenantConnection)->update([...])
```

### **Special case: autoEndInactiveSessions():**

Khi chạy từ cron, method này được gọi cho TỪNG tenant:

```php
public function autoEndInactiveSessions()
{
    // Get tenant connection (set by cron command)
    $tenantConnection = HelperTenant::getCurrentTenantConnection();
    
    $lock = Cache::lock('auto_end_sessions_' . $tenantConnection, 300);
    // ^ Add tenant to lock key!
    
    if (!$lock->get()) {
        Log::info('Auto-end job skipped: another process is running', [
            'tenant' => $tenantConnection
        ]);
        return 0;
    }
    
    try {
        $timeoutThreshold = now()->subMinutes(self::HEARTBEAT_TIMEOUT_MINUTES);
        $count = 0;

        // ⭐ Use tenant connection for query
        StudentStudySession::on($tenantConnection)
            ->where('status', StudentStudySession::STATUS_ACTIVE)
            ->where(function($query) use ($timeoutThreshold) {
                $query->where('last_heartbeat_at', '<', $timeoutThreshold)
                      ->orWhereNull('last_heartbeat_at');
            })
            ->chunkById(500, function($sessions) use (&$count, $tenantConnection) {
                DB::connection($tenantConnection)->beginTransaction();
                
                try {
                    foreach ($sessions as $session) {
                        // Set connection for session model
                        $session->setConnection($tenantConnection);
                        $session->refresh();
                        
                        if ($session->status !== StudentStudySession::STATUS_ACTIVE) {
                            continue;
                        }
                        
                        $endTime = $session->last_heartbeat_at 
                            ? Carbon::parse($session->last_heartbeat_at)
                            : Carbon::parse($session->session_start_at)
                                ->addMinutes(self::HEARTBEAT_INTERVAL_MINUTES);
                        
                        $this->endSessionWithTime($session, $endTime, 
                            StudentStudySession::STATUS_AUTO_ENDED, $tenantConnection);
                        
                        $count++;
                    }
                    
                    DB::connection($tenantConnection)->commit();
                } catch (\Exception $e) {
                    DB::connection($tenantConnection)->rollBack();
                    Log::error('Error in chunk processing', [
                        'tenant' => $tenantConnection,
                        'error' => $e->getMessage()
                    ]);
                }
            });

        Log::info("Auto ended sessions", [
            'tenant' => $tenantConnection,
            'count' => $count
        ]);

        return $count;

    } finally {
        $lock->release();
    }
}
```

---

## 🎯 Implementation Checklist

### **Phase 1: Helper (✅ Done)**
- [x] Add `getActiveTenantCodes()` to `HelperTenant`
- [x] Add `getActiveTenantConnections()` to `HelperTenant`
- [ ] Update tenant codes list in `getActiveTenantCodes()`

### **Phase 2: Service**
- [ ] Add `$tenantConnection` param to `endSessionWithTime()`
- [ ] Update `startSession()` with `->on($tenantConnection)`
- [ ] Update `continueSession()` with `->on($tenantConnection)`
- [ ] Update `heartbeat()` with `->on($tenantConnection)`
- [ ] Update `submitSession()` with `->on($tenantConnection)`
- [ ] Update `stopSession()` with `->on($tenantConnection)`
- [ ] Update `autoEndInactiveSessions()` - most complex!
- [ ] Update `getStudentStatistics()` with `->on($tenantConnection)`
- [ ] Update `getActiveSession()` with `->on($tenantConnection)`
- [ ] Update `checkExistingSession()` with `->on($tenantConnection)`
- [ ] Update `updateDailyStudyTime()` with `->on($tenantConnection)`
- [ ] Update `updateHistoryStudyTime()` with `->on($tenantConnection)`
- [ ] Update `updateContestStudyTime()` with `->on($tenantConnection)`

### **Phase 3: Cron Command**
- [ ] Update `AutoEndInactiveSessions` to loop tenants
- [ ] Add error handling per tenant
- [ ] Add logging per tenant

### **Phase 4: Controller** (No change needed)
- Controller gets `$tenantConnection` from session automatically
- Service uses `getCurrentTenantConnection()` which reads from session
- No changes needed!

---

## ⚠️ Important Notes

1. **Session context:** Web requests có `tenant_connection` trong session
2. **Cron context:** Cron command phải SET `tenant_connection` cho từng tenant
3. **Lock keys:** Phải unique per tenant (`auto_end_sessions_{tenant}`)
4. **Logging:** Phải include tenant info trong logs
5. **Transactions:** Dùng `DB::connection($tenantConnection)->beginTransaction()`

---

## 🧪 Testing

### **Test 1: Web request (single tenant)**
```bash
# Login as tenant 'hocmai'
curl -X POST /api/heat-user/study-session/start \
  -H "X-Tenant: hocmai" \
  -d '{"id_history": 200, ...}'

# Should use connection 'lms_tenant_hocmai'
```

### **Test 2: Cron job (all tenants)**
```bash
php artisan sessions:auto-end

# Should output:
# Processing tenant: hocmai
#   ✓ Tenant hocmai: 3 sessions ended
# Processing tenant: icc
#   ✓ Tenant icc: 2 sessions ended
# Total: 5 sessions ended across all tenants
```

---

## 📄 Files to Create/Update

1. ✅ `app/Helpers/HelperTenant.php` - Add methods (Done)
2. 🔧 `app/Services/HeatUser/StudySessionService.php` - Major update needed
3. 🔧 `app/Console/Commands/AutoEndInactiveSessions.php` - Update to loop tenants
4. ✅ `docs/HEAT_USER_MULTI_TENANT_UPDATE.md` - This file (Done)

---

## 🚀 Next Steps

1. **Update tenant codes** in `HelperTenant::getActiveTenantCodes()`
2. **Update Service** file theo pattern trên (do file lớn, cần cẩn thận)
3. **Update Cron command** để loop tenants
4. **Test** với từng tenant
5. **Deploy** và monitor logs

---

**Quan trọng:** Backup code trước khi update!

