# Hướng Dẫn: Kéo Kết Quả User Từ LMS Cũ Về LCMS Mới

## Tổng quan

Luồng này thực hiện **toàn bộ quá trình migration dữ liệu học viên** từ hệ thống LMS cũ (Moodle)
về LCMS mới, bao gồm: mapping activity, import danh sách enrol, clean data, sync kết quả quiz,
enrol user và đồng bộ tiến trình hoàn thành.

> **Môi trường:**
> - **Staging LMS** (`lmsnew.hocmai.net`) — dùng cho B1–B7
> - **LCMS Prod** (`lcms.icanwork.vn`) — dùng cho B8–B11
>
> Thay `x-tenant-code` phù hợp với tenant đang thao tác (ví dụ: `ICC`).

---

## Tóm Tắt Luồng

```
[B0]  Chuẩn bị file CSV mapping activity (LMS cũ ↔ LCMS mới)
        ↓
[B1]  Import mapping activity vào staging DB
        ↓
[B2]  Export danh sách enrol từ DB Moodle cũ (SQL)
        ↓
[B3]  Import danh sách enrol vào DB CMS (staging)
        ↓
[B4]  Clean user thừa (cleanup theo CSV)
        ↓
[B5]  Sync trạng thái completion activity (staging → DB staging)
        ↓
[B6a] Sync user_quiz_grade lần 1 (staging)
        ↓
[B6b] Clean quiz grade data (lần 2)
        ↓
[B7]  Export bảng staging → upload AWS
        ↓
[B8]  Import quiz grade vào LCMS Prod
        ↓
[B9]  Enrol user vào course (LCMS Prod)
        ↓
[B10] Sync activity completion (LCMS Prod)
        ↓
[B11] Kiểm tra trạng thái enrol
```

---

## Chi Tiết Các Bước

### Bước 0 — Chuẩn bị file CSV mapping activity (LMS cũ ↔ LCMS mới)

Trước khi import, cần xuất danh sách activity từ **cả hai hệ thống**, sau đó **ghép thủ công** thành 1 file CSV mapping.

#### 0.1 — Lấy danh sách activity của khóa học từ **LCMS mới** (DB CMS)

Query trên DB CMS (`lms_hocmai`). Thay `3449` bằng `id` của course trong bảng `api_moodle`:

```sql
-- Lấy toàn bộ activity (quiz, resource, url) kèm section
SELECT
    sec.moodle_id   AS 'Section ID',
    sec.moodle_name AS 'Section Name',
    q.id            AS 'Activity ID',
    q.moodle_name   AS 'Activity Name'
FROM api_moodle q
INNER JOIN api_moodle sec ON q.parent_id = sec.id
WHERE q.moodle_type IN ('quiz', 'resource', 'url')
  AND sec.moodle_type = 'section'
  AND sec.parent_id = 3449   -- ← Thay bằng id course trong api_moodle
ORDER BY sec.id, q.id;
```

> **Cột `Activity ID`** ở đây là `id` (primary key) của bảng `api_moodle` — đây sẽ là giá trị điền vào cột `mapping_cms_id` trong file mapping cuối.

---

#### 0.2 — Lấy danh sách activity của khóa học từ **LMS Moodle cũ** (DB Moodle)

Query trên DB Moodle cũ. Thay **prefix bảng** và **course ID** cho đúng:

```sql
SELECT
    cm.id    AS cmid,
    m.name   AS module_type,
    CASE
        WHEN m.name = 'quiz'     THEN (SELECT name FROM omo_quiz     WHERE id = cm.instance)
        WHEN m.name = 'resource' THEN (SELECT name FROM omo_resource WHERE id = cm.instance)
        WHEN m.name = 'url'      THEN (SELECT name FROM omo_url      WHERE id = cm.instance)
    END      AS activity_name,
    cs.id    AS section_id,
    cs.section AS section_number,
    IFNULL(cs.name, CONCAT('Topic ', cs.section)) AS section_name,
    cm.visible AS visible
FROM omo_course_modules cm
JOIN omo_modules m  ON cm.module  = m.id
JOIN omo_course_sections cs ON cm.section = cs.id
WHERE cm.course = 180          -- ← Thay bằng course ID trong Moodle cũ
  AND m.name IN ('quiz', 'resource', 'url')
  AND cm.visible = 1            -- Chỉ lấy activity đang hiển thị
ORDER BY cs.section, cm.id;
```

> **Lưu ý prefix:** Thay `omo_` bằng prefix thực tế của DB Moodle (ví dụ `spk_`, `mdl_`, `ec_`...).

---

#### 0.3 — Ghép thành file CSV mapping

Sau khi có kết quả từ cả 2 query, **ghép thủ công** thành file CSV với format sau:

https://docs.google.com/spreadsheets/d/11ID995yCGal7ig8f77WP6SL57VzP8Z6pcWmhxFAp9Js/edit?gid=218142675#gid=218142675

sau đó đổi tên cột theo format bên dưới

```
cmid,module_type,activity_name,section_id,section_number,section_name,visible,mapping_cms_id
```

| Cột | Lấy từ | Mô tả |
|---|---|---|
| `cmid` | LMS cũ | `cm.id` |
| `module_type` | LMS cũ | `m.name` (quiz / resource / url) |
| `activity_name` | LMS cũ | Tên activity |
| `section_id` | LMS cũ | `cs.id` |
| `section_number` | LMS cũ | `cs.section` |
| `section_name` | LMS cũ | Tên section |
| `visible` | LMS cũ | `cm.visible` (1/0) |
| `mapping_cms_id` | **LCMS mới** | `moodle_id` của activity tương ứng trên LCMS |

> **Lưu ý mapping:** Một activity LMS cũ có thể map với **một** hoặc **nhiều** activity LCMS mới (cột `mapping_cms_id` có thể chứa nhiều id phân tách bằng dấu phẩy nếu cần).

---

### Bước 1 — Import mapping activity vào staging DB

Sau khi có file CSV đã mapping đầy đủ ở Bước 0, import vào bảng `activity_info_moodle_speakup`:

```bash
curl --location 'https://lmsnew.hocmai.net/api/speakup/import-activity-info-csv' \
--header 'x-tenant-code: ICC' \
--form 'course_id="200"' \
--form 'file=@"/path/to/activity-mapping.csv"'
```

---

### Bước 2 — Export danh sách enrol từ DB Moodle cũ

Chạy SQL sau trên **DB Moodle của course cần export**. Lưu ý thay đúng **prefix bảng** và **course ID**:

```sql
SELECT DISTINCT
  u.id,
  u.username,
  u.firstname,
  u.lastname,
  u.email,
  u.phone1,
  u.phone2,
  u.city,
  u.country,
  u.lastaccess,
  u.timecreated,
  u.timemodified,
  r.id         AS role_id,
  r.shortname  AS role_shortname,
  r.name       AS role_name

FROM spk_user u

JOIN spk_user_enrolments ue ON ue.userid = u.id
JOIN spk_enrol e
    ON e.id = ue.enrolid
   AND e.courseid = 68          -- ← Thay course ID tương ứng

JOIN spk_context ctx
    ON ctx.contextlevel = 50
   AND ctx.instanceid = e.courseid

JOIN spk_role_assignments ra
    ON ra.userid = u.id
   AND ra.contextid = ctx.id

JOIN spk_role r ON r.id = ra.roleid

WHERE u.deleted = 0
  AND u.suspended = 0
  AND EXISTS (
        SELECT 1
        FROM spk_role_assignments ra2
        JOIN spk_role r2 ON r2.id = ra2.roleid
        WHERE ra2.userid = u.id
          AND ra2.contextid = ctx.id
          AND r2.shortname = 'student'   -- Chỉ lấy role student
  )

ORDER BY u.lastname, u.firstname;
```

> **Lưu ý:** Thay prefix `spk_` bằng prefix thực tế của DB Moodle (ví dụ `omo_`, `mdl_`...).
> Sau đó **export kết quả dưới dạng JSON** trên MySQL Workbench hoặc công cụ tương tự.

---

### Bước 3 — Import danh sách enrol vào DB CMS (staging)

Import file JSON vừa export vào bảng `users_enrollment_moodle_speakup` trên staging:

```bash
curl --location 'https://lmsnew.hocmai.net/api/speakup/import-users-enrollment-csv' \
--header 'x-tenant-code: ICC' \
--header 'content-type: application/json' \
--form 'course_id="45"' \
--form 'file=@"/path/to/users-45.json"'
```

---

### Bước 4 — Clean user thừa theo CSV

Xóa các user không có trong danh sách CSV và các bảng liên quan (`activity_completion`, `user_quiz_grade`).

#### Trường hợp có Zeus ID (dùng `cleanup-data-by-csv`)

CSV header: `id zeus,lms,user_email` *(đọc code để biết chính xác)*

```bash
curl --location 'https://lmsnew.hocmai.net/api/speakup/cleanup-data-by-csv' \
--header 'x-tenant-code: ICC' \
--form 'csv_file=@"/path/to/posData.csv"'
```

#### Trường hợp KHÔNG có Zeus ID (dùng `update-user-info-by-csv-v2`)

CSV header: `lms_user` *(cột username — đọc code để biết đầy đủ)*

```bash
curl --location 'https://lmsnew.hocmai.net/api/speakup/update-user-info-by-csv-v2' \
--header 'x-tenant-code: ICC' \
--form 'csv_file=@"/path/to/mapping_lms.csv"'
```

---

### Bước 5 — Sync trạng thái completion activity (staging)

Update bảng `activity_completion_speakup` dựa trên data từ Moodle cũ:

```bash
curl --location 'https://lmsnew.hocmai.net/api/speakup/sync-activity-completion-exec' \
--header 'x-tenant-code: ICC' \
--header 'Content-Type: application/json' \
--data '{
    "course_id": 68
}'
```

---

### Bước 6a — Sync user_quiz_grade lần 1 (staging)

Sync kết quả quiz vào bảng `user_quiz_grade` hoặc `english_classroom_user_quiz_grade`.

#### Trường hợp 1 activity LMS cũ → 1 activity LCMS mới → dùng `user_quiz_grade`

```bash
curl --location 'https://lmsnew.hocmai.net/api/user-quiz-grade/sync/exec' \
--header 'Content-Type: application/json' \
--data '{
    "tenant_db_name": "lms_tenant_icc"
}'
```

#### Trường hợp nhiều activity LMS cũ → 1 activity LCMS mới → dùng `english_classroom_user_quiz_grade`

```bash
curl --location 'https://lmsnew.hocmai.net/api/english-classroom/sync/exec-english-classroom' \
--header 'Content-Type: application/json' \
--data '{
    "tenant_db_name": "lms_tenant_icc"
}'
```

---

### Bước 6b — Clean quiz grade data (lần 2)

Sau khi sync xong quiz grade lần 1, chạy thêm lượt clean để loại bỏ data thừa.

#### Với `user_quiz_grade` (Speakup) → chạy lại API ở Bước 4

#### Với `english_classroom_user_quiz_grade` → dùng API sau

CSV header: `Final_user (fx),email,username,user_first_name (fx),user_last_name (fx)` *(đọc code để biết đầy đủ)*

```bash
curl --location 'https://lmsnew.hocmai.net/api/english-classroom/clean-user-info-by-csv' \
--header 'x-tenant-code: ICC' \
--form 'csv_file=@"/path/to/mapping_lms.csv"'
```

---

### Bước 7 — Export bảng staging → upload AWS

Sau khi các bước trên hoàn thành thành công, **export toàn bộ các bảng sau** từ staging DB rồi upload lên AWS để chuẩn bị import vào LCMS Prod:

| Bảng | Mục đích |
|---|---|
| `users_enrollment_moodle_speakup_{courseId}` | Danh sách enrol đã được clean |
| `activity_completion_speakup_{courseId}` | Tiến trình hoàn thành activity |
| `speakup_sync_log_{courseId}` | Log đồng bộ |
| `activity_info_moodle_speakup_{courseId}` | Mapping activity LMS cũ ↔ LCMS mới |

---

### Bước 8 — Import quiz grade vào LCMS Prod

Import file CSV quiz grade từ staging vào DB LCMS Production.

#### Với `user_quiz_grade`

CSV header: `user_moodle_id,username,email,score,spk_course_id,spk_quiz_id,spk_cmid,quiz_name,status`

```bash
curl --location 'https://lcms.icanwork.vn/api/speakup/import-user-quiz-grade' \
--header 'x-tenant-code: ICC' \
--form 'csv_file=@"/path/to/user_quiz_grade.csv"'
```

#### Với `english_classroom_user_quiz_grade`

CSV header: `user_moodle_id,username,email,score,spk_course_id,spk_quiz_id,spk_cmid,quiz_name,status`

```bash
curl --location 'https://lcms.icanwork.vn/api/english-classroom/import-user-quiz-grade-csv' \
--header 'x-tenant-code: ICC' \
--form 'csv_file=@"/path/to/english_classroom_user_quiz_grade.csv"'
```

---

### Bước 9 — Enrol user vào course (LCMS Prod)

Tiến hành enrol toàn bộ user vào course trên LCMS Prod. Chạy **một lần cho mỗi course**.

#### Speakup courses

```bash
curl --location 'https://lcms.icanwork.vn/api/speakup/enroll-users-from-speakup-exec' \
--header 'x-tenant-code: ICC' \
--header 'Content-Type: application/json' \
--data '{
    "course_id": 45
}'
```

#### English Classroom courses

```bash
curl --location 'https://lcms.icanwork.vn/api/english-classroom/enroll-users-from-english-classroom-exec' \
--header 'x-tenant-code: ICC' \
--header 'Content-Type: application/json' \
--data '{
    "course_id": 180
}'
```

> API này chạy nền (`background exec`). Kiểm tra log tại `storage/logs/english_classroom_enroll_sync/` hoặc `speakup_enroll_sync/`.

---

### Bước 10 — Sync activity completion (LCMS Prod)

Sau khi enrol xong, đồng bộ trạng thái hoàn thành activity từ bảng `activity_completion_speakup` sang LCMS.

#### Speakup courses

```bash
curl --location 'https://lcms.icanwork.vn/api/speakup/sync-completion-from-students-exec' \
--header 'x-tenant-code: ICC' \
--header 'Content-Type: application/json' \
--data '{
    "course_id": 47
}'
```

#### English Classroom courses

```bash
curl --location 'https://lcms.icanwork.vn/api/english-classroom/sync-completion-from-students-exec' \
--header 'x-tenant-code: ICC' \
--header 'Content-Type: application/json' \
--data '{
    "course_id": 164
}'
```

---

### Bước 11 — Kiểm tra trạng thái enrol

Sau khi các bước trên hoàn thành, kiểm tra báo cáo enrol để verify:

```bash
curl --location 'https://lcms.icanwork.vn/api/speakup/get-enrollment-report' \
--header 'x-tenant-code: ICC' \
--header 'Content-Type: application/json' \
--data '{
    "course_id": 68
}'
```

---

## Lưu Ý Quan Trọng

- **Thứ tự bước phải đúng** — không được bỏ qua hoặc đảo ngược.
- **B1–B7 chạy trên staging** (`lmsnew.hocmai.net`), **B8–B11 chạy trên LCMS Prod** (`lcms.icanwork.vn`).
- Bước 9 (enrol) và Bước 10 (sync completion) chạy nền — cần chờ xong mới tiếp tục.
- Nếu **1 activity LCMS được map với nhiều activity LMS cũ**, bắt buộc dùng bảng `english_classroom_user_quiz_grade` thay vì `user_quiz_grade`.
- Mỗi lần chạy API enrol/sync nên chạy **từng `course_id` một**, không chạy song song nhiều course để tránh deadlock.
- Tất cả curl command trong tài liệu dùng tenant `ICC` làm ví dụ — thay `x-tenant-code` phù hợp thực tế.

---

## Phụ Lục — SQL Tham Khảo

### Lấy danh sách section của khóa học (LCMS mới)

> **Lưu ý:** Query này **không thuộc flow migration** ở trên. Dùng để lấy danh sách section cho mục đích **mapping bài tập phía Zeus**.

```sql
SELECT
    sec.moodle_id   AS 'Section Moodle ID',
    sec.moodle_name AS 'Section Name'
FROM api_moodle sec
WHERE sec.moodle_type = 'section'
  AND sec.parent_id = 3449   -- ← Thay bằng id course trong api_moodle
ORDER BY sec.id;
```
