# 🔍 HEAT USER STUDY SESSION - GIẢI THÍCH LUỒNG VÀ CÁC BẢNG

## 📋 TỔNG QUAN

Hệ thống **Heat User Study Session** tracking thời gian học sinh làm bài tập với các bảng aggregation để lưu trữ thống kê theo ngày, theo phần (id_history), và theo bài thi (id_history_contest).

---

## 🗄️ CÁC BẢNG VÀ CHỨC NĂNG

### 1. **`student_study_sessions`** (Bảng chính - Session log)

**Mục đích:** Lưu từng session học tập của học sinh.

**Các trường quan trọng:**
- `student_id`: ID học sinh
- `id_history_contest`: ID bài thi (exam)
- `id_history`: ID phần con (exam part)
- `id_bai_kiem_tra`: ID bài kiểm tra
- `session_start_at`: Thời gian bắt đầu
- `session_end_at`: Thời gian kết thúc
- `last_heartbeat_at`: Thời gian heartbeat cuối cùng
- `duration_seconds`: Thời gian thực tế (giây)
- `adjusted_duration_seconds`: Thời gian sau khi áp dụng rule min (5 phút)
- `status`: `active`, `auto_ended`, `paused`, `completed`, `stopped`

**Khi nào được tạo/cập nhật:**
- ✅ **Tạo mới:** Khi học sinh click `start` → API `POST /api/heat-user/study-session/start`
- ✅ **Cập nhật:** 
  - Khi học sinh click `submit` → API `POST /api/heat-user/study-session/submit`
  - Khi học sinh click `stop` → API `POST /api/heat-user/study-session/stop`
  - Khi học sinh click `heartbeat` → API `POST /api/heat-user/study-session/heartbeat`
  - **Khi cron job chạy** → `sessions:auto-end` (mỗi 5 phút)

---

### 2. **`student_daily_study_time`** (Aggregation theo ngày)

**Mục đích:** Tổng hợp thời gian học tập của học sinh theo từng ngày.

**Các trường:**
- `student_id`: ID học sinh
- `study_date`: Ngày học (DATE)
- `total_minutes`: Tổng số phút học trong ngày
- `session_count`: Số lượng session trong ngày

**Khi nào được cập nhật:**
- ✅ **Khi session kết thúc** → Method `endSessionWithTime()` → `distributeDailyTime()` → `updateDailyStudyTime()`
- ✅ **Được gọi từ:**
  - `submitSession()` → Khi học sinh submit
  - `stopSession()` → Khi học sinh stop
  - `autoEndInactiveSessions()` → Khi cron job auto-end session

**Logic phân bổ thời gian:**
- Nếu session kéo dài qua nhiều ngày (ví dụ: 23h59 → 1h), thời gian sẽ được phân bổ đều cho các ngày.

**Ví dụ:**
```php
// Session: 2025-12-05 23:00 → 2025-12-06 01:00 (2 giờ = 120 phút)
// → 2025-12-05: 60 phút
// → 2025-12-06: 60 phút
```

---

### 3. **`student_history_study_time`** (Aggregation theo id_history)

**Mục đích:** Tổng hợp thời gian học tập cho từng phần (id_history) của bài thi.

**Các trường:**
- `student_id`: ID học sinh
- `id_history`: ID phần (exam part)
- `id_history_contest`: ID bài thi (exam)
- `id_bai_kiem_tra`: ID bài kiểm tra
- `total_minutes`: Tổng số phút học cho phần này
- `session_count`: Số lượng session
- `is_completed`: Đã hoàn thành chưa
- `first_started_at`: Lần đầu bắt đầu
- `last_activity_at`: Hoạt động cuối cùng

**Khi nào được cập nhật:**
- ✅ **Khi session kết thúc** → Method `endSessionWithTime()` → `updateHistoryStudyTime()`
- ✅ **Được gọi từ:**
  - `submitSession()` → Khi học sinh submit
  - `stopSession()` → Khi học sinh stop
  - `autoEndInactiveSessions()` → Khi cron job auto-end session

---

### 4. **`student_contest_study_time`** (Aggregation theo id_history_contest)

**Mục đích:** Tổng hợp thời gian học tập cho toàn bộ bài thi (id_history_contest).

**Các trường:**
- `student_id`: ID học sinh
- `id_history_contest`: ID bài thi (exam)
- `total_minutes`: Tổng số phút học cho bài thi
- `parts_completed`: Số phần đã hoàn thành
- `total_parts`: Tổng số phần
- `is_fully_completed`: Đã hoàn thành hết tất cả phần chưa
- `first_started_at`: Lần đầu bắt đầu
- `last_activity_at`: Hoạt động cuối cùng

**Khi nào được cập nhật:**
- ✅ **Khi session kết thúc** → Method `endSessionWithTime()` → `updateContestStudyTime()`
- ✅ **Được gọi từ:**
  - `submitSession()` → Khi học sinh submit
  - `stopSession()` → Khi học sinh stop
  - `autoEndInactiveSessions()` → Khi cron job auto-end session

---

## 🔄 LUỒNG HOẠT ĐỘNG

### **Luồng 1: Học sinh Start → Submit (Trong cùng ngày)**

```
1. FE: Click "Start"
   ↓
2. API: POST /api/heat-user/study-session/start
   ↓
3. Service: startSession()
   → Tạo record trong `student_study_sessions` (status = 'active')
   ↓
4. FE: Làm bài (mỗi 5 phút gọi heartbeat)
   ↓
5. API: POST /api/heat-user/study-session/heartbeat
   → Cập nhật `last_heartbeat_at` trong `student_study_sessions`
   ↓
6. FE: Click "Submit"
   ↓
7. API: POST /api/heat-user/study-session/submit
   ↓
8. Service: submitSession()
   → endSessionWithTime()
   → distributeDailyTime() → updateDailyStudyTime() ✅
   → updateHistoryStudyTime() ✅
   → updateContestStudyTime() ✅
   ↓
9. Kết quả:
   - `student_study_sessions`: status = 'completed'
   - `student_daily_study_time`: total_minutes += thời gian học
   - `student_history_study_time`: total_minutes += thời gian học
   - `student_contest_study_time`: total_minutes += thời gian học
```

---

### **Luồng 2: Học sinh Start → Đóng trình duyệt → Cron Job Auto-End**

```
1. FE: Click "Start"
   ↓
2. API: POST /api/heat-user/study-session/start
   → Tạo record trong `student_study_sessions` (status = 'active')
   ↓
3. FE: Đóng trình duyệt (không gọi heartbeat nữa)
   ↓
4. Cron Job: sessions:auto-end (chạy mỗi 5 phút)
   ↓
5. Command: AutoEndInactiveSessions@handle()
   → Loop qua tất cả tenants
   → Gọi service->autoEndInactiveSessions($tenantConnection)
   ↓
6. Service: autoEndInactiveSessions()
   → Query sessions có last_heartbeat_at < 10 phút trước
   → Với mỗi session: endSessionWithTime()
   → distributeDailyTime() → updateDailyStudyTime() ✅
   → updateHistoryStudyTime() ✅
   → updateContestStudyTime() ✅
   ↓
7. Kết quả:
   - `student_study_sessions`: status = 'auto_ended'
   - `student_daily_study_time`: total_minutes += thời gian học
   - `student_history_study_time`: total_minutes += thời gian học
   - `student_contest_study_time`: total_minutes += thời gian học
```

---

### **Luồng 3: Học sinh Start → Continue → Submit (Nhiều ngày)**

```
1. FE: Click "Start" (Ngày 1)
   → Tạo session (status = 'active')
   ↓
2. FE: Đóng trình duyệt
   ↓
3. Cron Job: Auto-end session (status = 'auto_ended')
   → updateDailyStudyTime() cho Ngày 1 ✅
   ↓
4. FE: Click "Continue" (Ngày 2)
   → continueSession()
   → Tạo session mới (status = 'active')
   ↓
5. FE: Click "Submit"
   → submitSession()
   → updateDailyStudyTime() cho Ngày 2 ✅
   → updateHistoryStudyTime() (tổng hợp cả 2 ngày) ✅
   → updateContestStudyTime() (tổng hợp cả 2 ngày) ✅
```

---

## ⚠️ VẤN ĐỀ HIỆN TẠI

### **Vấn đề 1: Method `autoEndInactiveSessions()` không nhận `$tenantConnection`**

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

**Vấn đề:**
```php
public function autoEndInactiveSessions()  // ❌ Không có $tenantConnection
{
    // Query không chỉ định connection
    StudentStudySession::where('status', ...)  // ❌ Query default connection
        ->chunkById(500, function($sessions) {
            // ...
        });
}
```

**Hậu quả:**
- Query từ **default connection** thay vì từng tenant connection
- Không update được các bảng aggregation cho đúng tenant
- `student_daily_study_time` không được update

**Giải pháp:**
- Method cần nhận `$tenantConnection` parameter
- Query với `StudentStudySession::on($tenantConnection)`

---

### **Vấn đề 2: Command gọi service không đúng**

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

**Vấn đề:**
```php
$endedCount = $service->autoEndInactiveSessions();  // ❌ Không truyền $tenantConnection
```

**Giải pháp:**
- Command đã loop qua tenants và set session, nhưng service method không dùng connection đó
- Cần sửa method `autoEndInactiveSessions()` để nhận `$tenantConnection`

---

## ✅ CÁCH KIỂM TRA

### **1. Kiểm tra Cron Job có chạy không:**

```bash
# Xem log cron
tail -f storage/logs/laravel.log | grep "auto-end"

# Hoặc chạy thủ công
php artisan sessions:auto-end
```

### **2. Kiểm tra bảng `student_daily_study_time`:**

```sql
-- Xem dữ liệu
SELECT * FROM student_daily_study_time 
WHERE student_id = YOUR_STUDENT_ID 
ORDER BY study_date DESC 
LIMIT 10;

-- Đếm số records
SELECT COUNT(*) FROM student_daily_study_time;
```

### **3. Kiểm tra sessions đã được auto-end chưa:**

```sql
-- Xem sessions auto-ended
SELECT * FROM student_study_sessions 
WHERE status = 'auto_ended' 
ORDER BY session_end_at DESC 
LIMIT 10;
```

---

## 🔧 CẦN SỬA

1. ✅ **Sửa method `autoEndInactiveSessions()`** để nhận `$tenantConnection`
2. ✅ **Sửa query** trong method để dùng `StudentStudySession::on($tenantConnection)`
3. ✅ **Sửa command** để truyền `$tenantConnection` vào service method

---

## 📊 TÓM TẮT CÁC BẢNG ĐƯỢC UPDATE

| Bảng | Khi nào update | Method gọi |
|------|----------------|------------|
| `student_study_sessions` | Start, Submit, Stop, Heartbeat, Auto-end | `startSession()`, `submitSession()`, `stopSession()`, `heartbeat()`, `autoEndInactiveSessions()` |
| `student_daily_study_time` | **Khi session kết thúc** | `endSessionWithTime()` → `distributeDailyTime()` → `updateDailyStudyTime()` |
| `student_history_study_time` | **Khi session kết thúc** | `endSessionWithTime()` → `updateHistoryStudyTime()` |
| `student_contest_study_time` | **Khi session kết thúc** | `endSessionWithTime()` → `updateContestStudyTime()` |

**Lưu ý:** Các bảng aggregation (`student_daily_study_time`, `student_history_study_time`, `student_contest_study_time`) **CHỈ được update khi session kết thúc** (submit, stop, hoặc auto-end).

