# OMO Center — Tài liệu chức năng liên kết khóa học Zeus & LMS

**Tài liệu liên quan:** [Database schema (ER)](omo-center-schema-db.md)

## 1. Tổng quan

**OMO Center** là cơ chế liên kết **khóa học nguồn (Khóa học A)** với **khóa học đích** trên LMS, phục vụ mô hình OMO:

- Học sinh học các **buổi/session** trên Zeus, tương ứng với **section** trong Khóa học A.
- Sau khi hoàn thành đủ điều kiện ở Khóa học A, học sinh mới được **mở section / làm bài kiểm tra** trong Khóa học đích.
- **Đề kiểm tra** trong Khóa học đích **khác nhau** tùy theo **điểm trung bình** học sinh đạt được ở các section nguồn.

---

## 2. Khái niệm chính

| Khái niệm | Mô tả |
|-----------|--------|
| **Khóa học A (nguồn)** | Khóa học chứa các section tương ứng buổi học trên Zeus. Học sinh làm bài tập / quiz tại đây. |
| **Khóa học đích** | Khóa học được gán cho học sinh với mục đích kiểm tra. Section trong khóa này chỉ mở khi đủ điều kiện từ Khóa học A. |
| **Section nguồn** | Section thuộc Khóa học A, được cấu hình làm điều kiện cho 1 section đích. |
| **Section đích** | Section thuộc Khóa học đích, chứa các activity / đề kiểm tra EMS. |
| **Giao bài (assignment)** | Zeus ghi nhận học sinh đã tham gia buổi học → LMS lưu vào `zeus_session_completions`. |
| **Điểm section nguồn** | Điểm cao nhất học sinh đạt được trong section nguồn (từ `student_score` + `user_quiz_grade`). |

### Mối quan hệ Zeus ↔ LMS

```
Zeus (buổi học / session)
        │
        ▼  API ZeusSessionCompletion
LMS: zeus_session_completions
        │
        ▼  map theo course_id + section_id
Khóa học A → Section 1, Section 2, Section 3, ...
        │
        ▼  tính điểm TB + chọn đề
Khóa học đích → Section kiểm tra
```

---

## 3. Luồng nghiệp vụ tổng thể

Sơ đồ: [luong-nghiep-vu-tong-the.png](luong-nghiep-vu-tong-the.png)

```mermaid
flowchart TD
    A[Học sinh tham gia buổi học trên Zeus] --> B[Zeus gọi API session-completion]
    B --> C[LMS lưu zeus_session_completions]
    C --> D[Học sinh làm bài ở section Khóa học A]
    D --> E[LMS ghi điểm quiz vào student_score / user_quiz_grade]
    E --> F{Học sinh vào section Khóa học đích?}
    F -->|Có giao bài + đủ điều kiện| G[Hiển thị section đích]
    F -->|Chưa giao bài| H[Ẩn / chặn — chưa được giao session]
    G --> I[FE gọi API availability/check]
    I --> J{Điểm TB section nguồn >= ngưỡng?}
    J -->|Không| K[Chặn làm bài — thông báo điểm chưa đạt]
    J -->|Có| L[Chọn đề EMS theo khoảng điểm]
    L --> M[Mở bài kiểm tra phù hợp cho học sinh]
```

---

## 4. Điều kiện hiển thị section trong Khóa học đích

### 4.1. Zeus lưu thông tin hoàn thành session

Khi buổi học trên Zeus kết thúc, Zeus gọi API:

- **API:** `POST /api/zeus/session-completion`
- **Tài liệu:** [ZeusSessionCompletion — API Doc](https://lmsnew.hocmai.net/apidoc/#api-ApiZeus-ZeusSessionCompletion)

**Tham số chính:**

| Tham số | Ý nghĩa |
|---------|---------|
| `zeus_user_id` | ID user trên Zeus |
| `tenant_key` | Mã tenant |
| `course_id` | Moodle ID khóa học A |
| `section_id` | Moodle ID section tương ứng buổi học |
| `completion_time` | Thời gian kết thúc buổi học |
| `start_date` | (tuỳ chọn) Thời gian bắt đầu — dùng tính deadline |

**Kết quả lưu vào bảng:** `zeus_session_completions`

| Trường | Ý nghĩa |
|--------|---------|
| `course_id` | Moodle ID khóa học |
| `section_id` | Moodle ID section |
| `zeus_id` | ID user Zeus |
| `student_id` | ID học sinh LMS |
| `completion_state` | `0` = chưa hoàn thành activity, `1` = đã hoàn thành |
| `completion_time` | Thời gian Zeus báo kết thúc session |

> **Lưu ý:** Bản ghi `zeus_session_completions` chứng minh học sinh **đã được giao buổi học / session** tương ứng section Khóa học A. Đây là điều kiện tiên quyết trước khi xét điểm và mở bài kiểm tra.

### 4.2. Khi nào section đích được hiển thị cho học sinh?

Ở màn hình khóa học đích, mỗi section có các cờ:

| Cờ | Nguồn | Ý nghĩa |
|----|-------|---------|
| `has_availability` | `relationship_models` (type=`section`) | Section này có rule điều kiện mở |
| `is_unlocked` | `StudentExamSchedule` + deadline Zeus | Section đã đến thời gian được phép làm bài |
| `open_date` / `close_date` | Lịch thi / deadline từ Zeus | Khung thời gian mở section |

**Tóm lại:** Section đích chỉ hiển thị / mở cho học sinh khi:

1. Học sinh **đã được Zeus giao session** (có record trong `zeus_session_completions` cho các section nguồn liên quan).
2. Section đích **đến thời gian mở** (`is_unlocked = true`).
3. Section đích **được cấu hình rule** trong hệ thống availability.

---

## 5. Điều kiện làm activity trong section Khóa học đích

Khi học sinh **click vào section đích** có `has_availability = true`, Frontend gọi:

- **API:** `POST /api/availability/check`
- **Body:** `{ "destination_section_moodle_id": <moodle_id section đích>, "student_id": <id học sinh> }`
- **Service xử lý:** `SectionAvailabilityService::checkAndOpenEms()`

### 5.1. Các bước kiểm tra

| Bước | Kiểm tra | Kết quả nếu fail |
|------|----------|------------------|
| 1 | Học sinh đã từng pass section này chưa? (`student_section_availability`) | Nếu đã pass → mở lại bài cũ |
| 2 | Section đích có rule cấu hình không? (`relationship_models`) | Không chặn — báo chưa cấu hình |
| 3 | Học sinh đã được **giao bài** ở **tất cả section nguồn** chưa? (`zeus_session_completions`) | **Chặn** — `reason: not_assigned` |
| 4 | Học sinh đã **làm bài** ở tất cả section nguồn chưa? (có điểm) | **Chặn** — `reason: not_attempted` |
| 5 | **Điểm trung bình** section nguồn >= `min_avg_score`? | **Chặn** — `reason: score_too_low` |
| 6 | Tìm được đề EMS phù hợp với điểm TB? | Báo lỗi — `reason: no_ems_matched` |
| 7 | Pass tất cả → lưu `student_section_availability` | **Mở bài** — `available: true` |

### 5.2. Công thức tính điểm trung bình

```
Điểm mỗi section nguồn = MAX(điểm cao nhất các quiz trong section)
                        = MAX(student_score.overrall_score, user_quiz_grade.score)

Điểm trung bình (avg_score) = SUM(điểm các section nguồn) / SỐ section nguồn được config
```

**Ví dụ cấu hình:**

- Section đích `Kiểm tra Buổi 3` yêu cầu hoàn thành:
  - Section nguồn `Buổi 1` (Khóa A)
  - Section nguồn `Buổi 2` (Khóa A)
- `min_avg_score = 7.0`
- Học sinh đạt: Buổi 1 = 8.0, Buổi 2 = 6.5 → avg = 7.25 → **đạt**
- Học sinh đạt: Buổi 1 = 6.0, Buổi 2 = 7.0 → avg = 6.5 → **không đạt**

---

## 6. Đề kiểm tra khác nhau theo điểm trung bình

Trong section đích có thể cấu hình **nhiều đề EMS** với **khoảng điểm** khác nhau trên bảng `api_moodle_ems`:

| Trường | Ý nghĩa |
|--------|---------|
| `score_above` | Điểm TB tối thiểu (bao gồm) |
| `score_below` | Điểm TB tối đa (không bao gồm) |

**Điều kiện chọn đề:**

```
score_above <= avg_score < score_below
```

### Ví dụ

| Đề EMS | score_above | score_below | Đối tượng |
|--------|-------------|-------------|-----------|
| Đề cơ bản | 0 | 5 | Học sinh yếu (avg < 5) |
| Đề trung bình | 5 | 8 | Học sinh khá (5 ≤ avg < 8) |
| Đề nâng cao | 8 | 10 | Học sinh giỏi (avg ≥ 8) |

> Cùng 1 section đích, nhưng mỗi học sinh có thể nhận **đề kiểm tra khác nhau** tùy `avg_score` tính từ Khóa học A.

---

## 7. Cấu hình Admin

Cấu hình thực hiện trên CMS (Product Detail) hoặc qua API Admin.

### 7.1. Liên kết khóa học (Course Link)

Liên kết **1:1** giữa Khóa học A và Khóa học đích.

- **Bảng:** `relationship_models` (`type = 'course'`)
- **API tạo:** `POST /api/admin/availability/course-links`

```json
{
  "source_course_moodle_id": 213,
  "destination_course_moodle_id": 350
}
```

### 7.2. Rule section (Section Rules)

Cấu hình section nguồn + ngưỡng điểm cho section đích.

- **Bảng:** `relationship_models` (`type = 'section'`)
- **API tạo:** `POST /api/admin/availability/section-rules`

```json
{
  "destination_section_moodle_id": 1786,
  "source_section_moodle_id": "1201,1202,1203",
  "min_avg_score": 7.0
}
```

| Trường | Ý nghĩa |
|--------|---------|
| `destination_section_moodle_id` | Section đích (Khóa học đích) |
| `source_section_moodle_id` | Danh sách section nguồn (Khóa A), phân cách bằng dấu phẩy |
| `min_avg_score` | Điểm trung bình tối thiểu để mở bài |

### 7.3. Cấu hình khoảng điểm cho đề EMS

- **API:** `PATCH /api/admin/ems-score-range/{apiMoodleEmsId}`
- Gán `score_above` / `score_below` cho từng quiz EMS trong section đích.

### 7.4. Mô phỏng (Simulate)

Admin có thể test cấu hình mà không mở bài thật:

- **API:** `POST /api/admin/availability/simulate`

---

## 8. API liên quan

| API | Vai trò | Gọi bởi |
|-----|---------|---------|
| `POST /api/zeus/session-completion` | Zeus lưu hoàn thành session | Zeus |
| `POST /api/availability/check` | Kiểm tra & mở bài kiểm tra | LMS Frontend |
| `POST /api/admin/availability/course-links` | Liên kết khóa A ↔ khóa đích | Admin CMS |
| `POST /api/admin/availability/section-rules` | Cấu hình rule section | Admin CMS |
| `PATCH /api/admin/ems-score-range/{id}` | Cấu hình khoảng điểm đề EMS | Admin CMS |
| `POST /api/admin/availability/simulate` | Mô phỏng kết quả mở bài | Admin CMS |

**Tài liệu API Zeus Session Completion:**  
https://lmsnew.hocmai.net/apidoc/#api-ApiZeus-ZeusSessionCompletion

---

## 9. Bảng dữ liệu

Sơ đồ ER, quy ước PK/FK và nhóm bảng theo luồng nghiệp vụ xem tại:

**[omo-center-schema-db.md](omo-center-schema-db.md)**

Các bảng chính: `api_moodle`, `students`, `zeus_session_completions`, `relationship_models`, `student_section_availability`, `api_moodle_ems`, `student_score`, `user_quiz_grade`, `student_exam_schedules`, `student_exam_schedule_histories`.

---

## 10. Luồng xử lý chi tiết — `checkAndOpenEms`

Sơ đồ: [luong-chi-tiet.png](luong-chi-tiet.png)

**Hàm:** `SectionAvailabilityService::checkAndOpenEms(destinationSectionMoodleId, studentId, tenantConnection)`

```mermaid
sequenceDiagram
    participant FE as LMS Frontend
    participant API as /api/availability/check
    participant SVC as SectionAvailabilityService
    participant ZSC as zeus_session_completions
    participant DB as student_score / user_quiz_grade
    participant EMS as api_moodle_ems

    FE->>API: destination_section_moodle_id + student_id
    API->>SVC: checkAndOpenEms()
    SVC->>ZSC: Kiểm tra đã giao bài section nguồn?
    alt Chưa giao bài
        SVC-->>FE: block_access=true, reason=not_assigned
    else Đã giao bài
        SVC->>DB: Lấy điểm từng section nguồn
        alt Chưa làm đủ bài
            SVC-->>FE: block_access=true, reason=not_attempted
        else Đã có điểm
            SVC->>SVC: Tính avg_score
            alt avg_score < min_avg_score
                SVC-->>FE: block_access=true, reason=score_too_low
            else Đạt ngưỡng
                SVC->>EMS: resolveTargetEms(avg_score)
                EMS-->>SVC: Đề EMS phù hợp
                SVC->>SVC: Lưu student_section_availability
                SVC-->>FE: available=true, ems_opened
            end
        end
    end
```

---

## 11. Kịch bản ví dụ end-to-end

### Bối cảnh

- **Khóa học A** (`course_id: 213`): 4 section = 4 buổi học Zeus
- **Khóa học đích** (`course_id: 350`): section `Kiểm tra giữa kỳ`
- Rule: cần hoàn thành **Buổi 1 + Buổi 2** (2 section nguồn), `min_avg_score = 6.0`

### Diễn biến

1. Học sinh học Buổi 1 trên Zeus → Zeus gọi `session-completion` → LMS tạo record `zeus_session_completions`.
2. Học sinh làm quiz Buổi 1 trên LMS → điểm `8.0`.
3. Học sinh học Buổi 2 → Zeus giao bài → học sinh làm quiz → điểm `7.0`.
4. `avg_score = (8.0 + 7.0) / 2 = 7.5` → đạt `min_avg_score = 6.0`.
5. Học sinh vào section `Kiểm tra giữa kỳ` → FE gọi `availability/check`.
6. Hệ thống chọn đề EMS có `score_above=5, score_below=8` → mở **Đề trung bình**.

---

## 12. Lưu ý triển khai

1. **`zeus_session_completions`** dùng để kiểm tra **đã giao bài**, không dùng làm điểm số.
2. **Điểm số** lấy từ `student_score` và `user_quiz_grade`, không từ Zeus completion.
3. Một section đích có thể yêu cầu **2 hoặc nhiều section nguồn** — tất cả phải được giao bài và có điểm.
4. Sau khi pass lần đầu, trạng thái lưu tại `student_section_availability` — lần sau vào lại không cần tính lại.
5. `completion_state` trong `zeus_session_completions` được cron `zeus:sync-session-completion-state` cập nhật khi học sinh hoàn thành 100% activity trong section.

---

## 13. Tài liệu & code tham chiếu

| Thành phần | File |
|------------|------|
| Database schema (ER) | `docs/omo_center/omo-center-schema-db.md` |
| Service chính | `app/Services/SectionAvailabilityService.php` |
| API Zeus session | `app/Http/Controllers/ApiZeusController.php` |
| API availability | `app/Http/Controllers/SectionAvailabilityController.php` |
| Course data + has_availability | `app/Http/Controllers/ApiEmsController.php` |
| Model completion | `app/Models/ZeusSessionCompletion.php` |
| Model rule | `app/Models/RelationshipModel.php` |
| UI cấu hình Admin | `public/js/product_detail/product_detail-v2.js` |
