# API Sync Activity Completion By Score

## Tổng quan

Hai API này được sử dụng để đồng bộ trạng thái hoàn thành (completion state) của các activity trong Moodle dựa trên điểm số từ bảng `UserQuizGrade` và `StudentExamHistory`.

## Danh sách API

1. **syncActivityCompletionByScore** - API xử lý trực tiếp (Direct Single)
2. **syncActivityCompletionByScoreExec** - API xử lý nền (Background Exec)

---

## 1. API Direct Single: `syncActivityCompletionByScore`

### Endpoint
```
POST /api/speakup/sync-activity-completion-by-score
```

### Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `course_id` | integer | Yes | ID của course (38, 39, 45, 46, 47, 68) |
| `cmid` | integer | No | Course Module ID để filter activity cụ thể (dùng để test) |
| `user_moodle_id` | integer | No | user_moodle_id_old để filter student cụ thể (dùng để test) |

### Request Headers
- `x-tenant-code`: Mã tenant (required)

### Request Body Examples

**Test với 1 activity và 1 user:**
```json
{
    "course_id": 38,
    "cmid": 12345,
    "user_moodle_id": 17206
}
```

**Test với 1 activity (tất cả users):**
```json
{
    "course_id": 38,
    "cmid": 12345
}
```

**Xử lý tất cả (batch mode):**
```json
{
    "course_id": 38
}
```

### Response Example

```json
{
    "success": true,
    "status": "completed",
    "message": "Process completed",
    "course_id": 38,
    "course_api_id": 1559,
    "mode": "single",
    "cmid": 12345,
    "user_moodle_id_old": 17206,
    "summary": {
        "total_students": 1,
        "total_activities": 1,
        "processed": 1,
        "success": 1,
        "errors": 0,
        "skipped": 0,
        "bulk_api_calls": 1,
        "bulk_activities": 1,
        "bulk_success": 1,
        "bulk_failed": 0
    }
}
```

---

## 2. API Background Exec: `syncActivityCompletionByScoreExec`

### Endpoint
```
POST /api/speakup/sync-activity-completion-by-score-exec
```

### Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `course_id` | integer | Yes | ID của course (38, 39, 45, 46, 47, 68) |

### Request Headers
- `x-tenant-code`: Mã tenant (required)

### Request Body Example

```json
{
    "course_id": 38
}
```

### Response Example

```json
{
    "success": true,
    "message": "Background process started",
    "course_id": 38,
    "course_api_id": 1559,
    "script_path": "/path/to/temp_sync_activity_completion_by_score_xxx.php"
}
```

**Lưu ý:** API này chạy nền, không trả về kết quả chi tiết ngay lập tức. Kết quả được ghi vào log file.

---

## Logic xử lý chi tiết

### Bước 1: Lấy Course Mapping

- Dựa vào `course_id` truyền vào, hệ thống sẽ lấy `courseApiId` từ mapping `$courseApiMapping`
- Ví dụ: `course_id = 38` → `courseApiId = 1559`

### Bước 2: Lấy danh sách Activities

- Truy vấn bảng `activity_info_moodle_speakup_{courseid}` 
- Lọc các activity có `mapping_cms_id` không null và không rỗng
- `mapping_cms_id` là ID của bảng `api_moodle` (dùng để map với quiz_id)

**Nếu có `cmid` trong request:**
- Chỉ lấy activity có `cmid` tương ứng

**Query:**
```sql
SELECT * FROM activity_info_moodle_speakup_{courseid}
WHERE mapping_cms_id IS NOT NULL 
  AND mapping_cms_id != ''
  [AND cmid = {cmid} -- nếu có filter]
```

### Bước 3: Lấy danh sách Students

- Truy vấn bảng `students` với điều kiện:
  - `user_moodle_id_old` không null
  - `user_moodle_id_old` != ''

**Nếu có `user_moodle_id` trong request:**
- Chỉ lấy student có `user_moodle_id_old` tương ứng

**Query:**
```sql
SELECT * FROM students
WHERE user_moodle_id_old IS NOT NULL 
  AND user_moodle_id_old != ''
  [AND user_moodle_id_old = {user_moodle_id} -- nếu có filter]
```

### Bước 4: Lấy moodle_user_id từ Users

Với mỗi student:
- Lấy `user_id` từ `students.user_id`
- Truy vấn bảng `users` với `id = user_id`
- Lấy `moodle_user_id` từ `users.moodle_user_id`

**Nếu không có `user_id` hoặc `moodle_user_id`:**
- Skip student này

### Bước 5: Xử lý từng cặp Student + Activity

Với mỗi cặp (student, activity):

#### 5.1. Lấy thông tin Activity từ api_moodle

- Lấy `mapping_cms_id` từ activity
- Truy vấn `api_moodle` với `id = mapping_cms_id`
- Lấy `moodle_id` (đây là `cmid` để update completion trong Moodle)
- Kiểm tra `moodle_type` không phải là 'course', 'category', 'section'

#### 5.2. Kiểm tra điểm số và xác định completion state

**Rule 1: Kiểm tra UserQuizGrade trước**

```php
$userQuizGrade = UserQuizGrade::where([
    'user_moodle_id' => user_moodle_id_old,
    'quiz_id' => mapping_cms_id,
    'spk_course_id' => course_id
])->first();
```

**Nếu có data trong UserQuizGrade:**
- Lấy `score` từ `UserQuizGrade.score`
- Nếu `score >= 5` → `completionstate = 1` (ĐẠT)
- Nếu `score < 5` → `completionstate = 0` (CHƯA ĐẠT)
- `scoreSource = 'UserQuizGrade'`

**Nếu không có data trong UserQuizGrade:**

**Rule 2: Kiểm tra StudentExamHistory**

```php
// Get record with highest lms_score if multiple records exist
$examHistory = StudentExamHistory::where([
    'student_id' => student.id,
    'quiz_id' => mapping_cms_id
])
->orderBy('lms_score', 'desc')  // Sort by score descending (highest first)
->orderBy('created_at', 'desc')  // Secondary sort by created_at if scores are equal
->first();
```

**Nếu có data trong StudentExamHistory:**
- Lấy `lms_score` từ `StudentExamHistory.lms_score` (lấy điểm cao nhất nếu có nhiều records)
- Nếu `lms_score >= 5` → `completionstate = 1` (ĐẠT)
- Nếu `lms_score < 5` → `completionstate = 0` (CHƯA ĐẠT)
- `scoreSource = 'StudentExamHistory'`

**Nếu không có data trong cả 2 bảng:**
- `completionstate = 0` (CHƯA ĐẠT)
- `score = null`
- `scoreSource = null`

#### 5.3. Thu thập dữ liệu để bulk update

- Nhóm các activity completion theo `moodle_user_id`
- Mỗi user sẽ có một mảng các activities cần update
- Format:
```php
$bulkCompletionUpdates[$moodleUserId] = [
    'email' => $email,
    'tenant_user_id' => $tenantUserId,
    'user_moodle_id_old' => $userMoodleIdOld,
    'activities' => [
        [
            'cmid' => $targetCmid,
            'completionstate' => $completionState,
            'forcecompletion' => 1,
            'spk_cmid' => $activityCmid,
            'activity_name' => $activityName,
            'mapping_cms_id' => $mappingCmsId,
            'score' => $score,
            'score_source' => $scoreSource,
        ],
        // ... more activities
    ],
];
```

### Bước 6: Thực hiện Bulk Update Completion

- Với mỗi user trong `$bulkCompletionUpdates`:
  - Chia activities thành các chunk (50 activities/chunk) để tránh vượt quá `max_input_vars`
  - Gọi API `Common::local_custom_service_bulk_update_activity_completion()`
  - API này sẽ update completion cho nhiều activities cùng lúc

**API Call:**
```php
Common::local_custom_service_bulk_update_activity_completion(
    $moodleUserId,
    [
        ['cmid' => 123, 'completionstate' => 1, 'forcecompletion' => 1],
        ['cmid' => 456, 'completionstate' => 0, 'forcecompletion' => 1],
        // ... more activities
    ]
);
```

---

## Sơ đồ luồng xử lý

```
┌─────────────────────────────────────────────────────────────┐
│ 1. Lấy Course Mapping (course_id → courseApiId)            │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ 2. Lấy Activities từ activity_info_moodle_speakup         │
│    - Filter: mapping_cms_id IS NOT NULL                    │
│    - Filter: cmid (nếu có)                                 │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ 3. Lấy Students từ bảng students                          │
│    - Filter: user_moodle_id_old IS NOT NULL                │
│    - Filter: user_moodle_id_old (nếu có)                  │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ 4. Với mỗi Student:                                        │
│    - Lấy user_id → users → moodle_user_id                  │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ 5. Với mỗi cặp (Student, Activity):                       │
│                                                             │
│    ┌─────────────────────────────────────────┐            │
│    │ 5.1. Lấy mapping_cms_id từ activity    │            │
│    │     → api_moodle → moodle_id (cmid)    │            │
│    └─────────────────────────────────────────┘            │
│                        ↓                                   │
│    ┌─────────────────────────────────────────┐            │
│    │ 5.2. Kiểm tra điểm số:                 │            │
│    │                                          │            │
│    │  Có UserQuizGrade?                      │            │
│    │  ├─ YES → score >= 5? → completionstate │            │
│    │  └─ NO → Có StudentExamHistory?        │            │
│    │         ├─ YES → lms_score >= 5?        │            │
│    │         └─ NO → completionstate = 0     │            │
│    └─────────────────────────────────────────┘            │
│                        ↓                                   │
│    ┌─────────────────────────────────────────┐            │
│    │ 5.3. Thu thập vào bulkCompletionUpdates │            │
│    └─────────────────────────────────────────┘            │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ 6. Bulk Update Completion                                 │
│    - Chia thành chunks (50 activities/chunk)              │
│    - Gọi Common::local_custom_service_bulk_update_        │
│      activity_completion() cho mỗi user                    │
└─────────────────────────────────────────────────────────────┘
```

---

## Bảng dữ liệu liên quan

### 1. activity_info_moodle_speakup_{courseid}

| Field | Description |
|-------|-------------|
| `cmid` | Course Module ID trong Speakup Moodle |
| `mapping_cms_id` | ID của bảng `api_moodle` (dùng làm quiz_id) |
| `activity_name` | Tên activity |

### 2. students

| Field | Description |
|-------|-------------|
| `id` | Student ID |
| `user_id` | ID của bảng `users` |
| `user_moodle_id_old` | Moodle User ID cũ từ Speakup |

### 3. users

| Field | Description |
|-------|-------------|
| `id` | User ID |
| `moodle_user_id` | Moodle User ID hiện tại |
| `email` | Email |

### 4. api_moodle

| Field | Description |
|-------|-------------|
| `id` | API Moodle ID (mapping_cms_id) |
| `moodle_id` | Moodle ID (cmid để update completion) |
| `moodle_type` | Loại: 'quiz', 'assign', 'course', etc. |

### 5. UserQuizGrade

| Field | Description |
|-------|-------------|
| `user_moodle_id` | user_moodle_id_old |
| `quiz_id` | mapping_cms_id |
| `spk_course_id` | course_id |
| `score` | Điểm số |

### 6. StudentExamHistory

| Field | Description |
|-------|-------------|
| `student_id` | Student ID |
| `quiz_id` | mapping_cms_id |
| `lms_score` | Điểm số từ LMS |

---

## Quy tắc xác định Completion State

### Rule 1: Không có lịch sử làm bài

**Điều kiện:**
- Không có data trong `StudentExamHistory`
- VÀ không có data trong `UserQuizGrade`

**Kết quả:**
- `completionstate = 0` (CHƯA ĐẠT)

### Rule 2: Có điểm số

**Trường hợp 2.1: Có data trong UserQuizGrade**

- Lấy `score` từ `UserQuizGrade.score`
- Nếu `score >= 5` → `completionstate = 1` (ĐẠT)
- Nếu `score < 5` → `completionstate = 0` (CHƯA ĐẠT)

**Trường hợp 2.2: Không có UserQuizGrade nhưng có StudentExamHistory**

- Lấy `lms_score` từ `StudentExamHistory.lms_score` (lấy điểm cao nhất nếu có nhiều records)
- Query được sắp xếp theo `lms_score DESC` để lấy record có điểm cao nhất
- Nếu `lms_score >= 5` → `completionstate = 1` (ĐẠT)
- Nếu `lms_score < 5` → `completionstate = 0` (CHƯA ĐẠT)

---

## Logging

### Log Files

Log được ghi vào:
```
storage/logs/speakup_enroll_sync/{year}/{month}/{date}_course_{course_id}.log
```

### Log Table

Log được ghi vào bảng:
```
speakup_sync_log_{course_id}
```

### Log Levels

- **INFO**: Thông tin quá trình xử lý
- **ERROR**: Lỗi xảy ra

---

## Error Handling

### Các trường hợp bỏ qua (Skip)

1. Student không có `user_id`
2. User không có `moodle_user_id`
3. Activity không có `mapping_cms_id`
4. `api_moodle` không tồn tại hoặc là 'course', 'category', 'section'

### Các trường hợp lỗi (Error)

1. Exception khi xử lý student
2. Exception khi gọi API bulk update
3. Fatal error trong quá trình xử lý

---

## Performance Optimization

1. **Bulk Updates**: Nhóm các activity completion theo user để gọi API ít lần hơn
2. **Chunking**: Chia activities thành chunks (50/chunk) để tránh vượt quá `max_input_vars`
3. **Background Processing**: API Exec chạy nền để không block request

---

## Testing

### Test với 1 activity và 1 user

```bash
curl -X POST https://api.example.com/api/speakup/sync-activity-completion-by-score \
  -H "Content-Type: application/json" \
  -H "x-tenant-code: tenant_code" \
  -d '{
    "course_id": 38,
    "cmid": 12345,
    "user_moodle_id": 17206
  }'
```

### Test với tất cả (batch mode)

```bash
curl -X POST https://api.example.com/api/speakup/sync-activity-completion-by-score \
  -H "Content-Type: application/json" \
  -H "x-tenant-code: tenant_code" \
  -d '{
    "course_id": 38
  }'
```

---

## Lưu ý quan trọng

1. **Điểm số tối thiểu**: Điểm >= 5 mới được coi là ĐẠT (completionstate = 1)
2. **Ưu tiên UserQuizGrade**: Nếu có data trong cả 2 bảng, ưu tiên lấy từ `UserQuizGrade`
3. **Mapping**: `mapping_cms_id` từ activity phải map với `quiz_id` trong `UserQuizGrade` và `StudentExamHistory`
4. **Background Exec**: API Exec chạy nền, kết quả chỉ có trong log file

---

## Tài liệu liên quan

- `Common::local_custom_service_bulk_update_activity_completion()` - API update completion
- `syncCompletionFromStudents()` - API sync completion từ students (tham khảo logic)
- `syncCompletionFromStudentsExec()` - API sync completion từ students (background)

