# Phân Hệ Quản Lý Danh Sách Học Viên Đến Lịch Thi (Web Exam Schedule)
## 03. Sơ Đồ Chức Năng & Thiết Kế Chi Tiết (Function Diagram & Design)

---

### 1. Kiến Trúc Tổng Thể Phân Hệ (System Component Architecture)

```mermaid
graph TD
    subgraph Client ["Client Layer (Browser)"]
        UI["Blade View: exam-schedule.index"]
        ModalCreate["Modal: Tạo / Đổi Lịch Thi"]
        ModalExtend["Modal: Gia Hạn Lịch Thi"]
        JS["JS Controller: exam-schedule.js"]
    end

    subgraph MiddlewareLayer ["Middleware & Security"]
        AuthMid["auth (Xác thực đăng nhập)"]
        PermMid["check.permission (Kiểm tra quyền Route)"]
        TenantMid["tenant.access:web (Định tuyến Tenant Connection)"]
    end

    subgraph ControllerLayer ["Application Layer"]
        ESC["ExamScheduleController"]
        ExportJob["ExportExamScheduleJob (Queue Background)"]
    end

    subgraph ServiceHelperLayer ["Service & Helper Layer"]
        MoodleSvc["ApiMoodleService (Cây danh mục & Khóa học)"]
        TenantHelper["HelperTenant (Tenant Connection Resolver)"]
        RemindHelper["Helper::sendMocktestRemindingManualWithLog"]
    end

    subgraph StorageLayer ["Data & Cache Storage (Per Tenant)"]
        DB[(MySQL Database - Multi-Tenant)]
        RedisCache[(Redis Cache - Export Progress)]
        DiskStorage[(Public Disk Storage - CSV / ZIP Exports)]
    end

    subgraph ExternalServices ["External Systems"]
        ZeusAPI["Zeus / ICL Learning Management System"]
    end

    UI --> JS
    ModalCreate --> JS
    ModalExtend --> JS

    JS -->|HTTP Requests| MiddlewareLayer
    MiddlewareLayer --> ESC

    ESC --> TenantHelper
    ESC --> MoodleSvc
    ESC --> RemindHelper
    ESC -->|Dispatch| ExportJob
    ESC -->|Query / Insert / Update| DB

    ExportJob -->|Read Chunks & Format CSV| DB
    ExportJob -->|Update Progress| RedisCache
    ExportJob -->|Save ZIP| DiskStorage

    RemindHelper -->|Send HTTP RFC3339 Payload| ZeusAPI
    RemindHelper -->|Log Status| DB
```

---

### 2. Sơ Đồ Chức Năng Chi Tiết (Function Flowcharts)

#### 2.1. Chức năng `getExamSchedule` (Lấy dữ liệu danh sách)

```mermaid
flowchart TD
    A[Nhận Request GET /exam-schedule/exam-schedule] --> B[Lấy Tenant Connection hiện tại]
    B --> C[Gọi buildExamScheduleQuery áp dụng tất cả Filters]
    C --> D[Thực hiện paginate perPage]
    D --> E[Lấy Category Map qua ApiMoodleService::getCategoryMap]
    E --> F[Lặp qua từng dòng kết quả để Format Data]
    F --> G[Xác định Root Category để lấy Tên Sản Phẩm chuẩn]
    G --> H[Kiểm tra quyền: can_delete, can_update, can_extend]
    H --> I[Trả về HTTP 200 JSON cho Frontend DataTable]
```

---

#### 2.2. Chức năng `storeExamSchedule` (Tạo mới lịch thi)

```mermaid
flowchart TD
    Start([POST /store-exam-schedule]) --> Val{Validate Inputs: student_ids, exam_ids, requirement_type, start_date, deadline}
    Val -- Lỗi --> ErrVal[Trả về HTTP 422 JSON Validation Error]
    Val -- Hợp lệ --> QueryMoodle[Nội suy cây Moodle: Section -> Course -> Category]
    QueryMoodle --> QueryStudents[Lấy danh sách Students kèm zeus_id]
    
    QueryStudents --> LoopStudents[Vòng lặp từng Student ID]
    LoopStudents --> LoopSections[Vòng lặp từng Section ID]
    
    LoopSections --> CheckScore[Lấy latest student_score để giữ nguyên trạng thái nếu đã có điểm]
    CheckScore --> InsertSchedule[INSERT student_exam_schedules status = 1]
    InsertSchedule --> InsertHistory[INSERT student_exam_schedule_histories action = 'create']
    InsertHistory --> CheckRetake{requirement_type == 1?}
    
    CheckRetake -- Có --> IncAllowance[Tăng reset_allowance trong user_quiz_attempts]
    CheckRetake -- Không --> CheckZeus
    IncAllowance --> CheckZeus{Student có zeus_id?}
    
    CheckZeus -- Có --> PushZeus[sendMocktestRemindingManualWithLog sang Zeus]
    CheckZeus -- Không --> NextItem
    PushZeus --> NextItem[Tiếp tục phần tử kế tiếp]
    
    NextItem --> EndLoop{Hết danh sách?}
    EndLoop -- Chưa --> LoopStudents
    EndLoop -- Rồi --> RespSuccess[HTTP 200: Tạo lịch thi hàng loạt thành công!]
```

---

#### 2.3. Chức năng `updateExamSchedule` (Đổi lịch thi)

```mermaid
flowchart TD
    Start([POST /update-exam-schedule]) --> Val{Validate: student_ids[], course_id, section_id, start_date, deadline}
    Val -- Lỗi --> Err422[HTTP 422 Lỗi dữ liệu đầu vào]
    Val -- Hợp lệ --> QueryOld[Query các bản ghi StudentExamSchedule cũ]
    QueryOld --> CheckFound{Có bản ghi nào không?}
    CheckFound -- Không --> Err404[HTTP 404 Không tìm thấy lịch thi gốc]
    CheckFound -- Có --> BeginTx[Bắt đầu Database Transaction]
    
    BeginTx --> Loop[Lặp qua từng Student ID]
    Loop --> CheckTime{new_start_date > deadline cũ?}
    CheckTime -- Không --> Rollback400[Rollback & Trả về HTTP 400 Báo lỗi mốc thời gian]
    CheckTime -- Có --> CloseOld[UPDATE lịch thi cũ: status = 0]
    CloseOld --> CreateNew[INSERT lịch thi mới: status = 1, reset trạng thái thi về 0]
    CreateNew --> AddHist[INSERT history: action = 'update', ghi old -> new]
    AddHist --> NextSt[Sang Student kế tiếp]
    
    NextSt --> IsDone{Hết danh sách?}
    IsDone -- Chưa --> Loop
    IsDone -- Rồi --> CommitTx[Commit Database Transaction]
    CommitTx --> CheckRetake{requirement_type == 1?}
    CheckRetake -- Có --> Retake[Gọi processRetakeAllowance: Tăng reset_allowance]
    CheckRetake -- Không --> RespOk[HTTP 200 Đổi lịch thi thành công]
    Retake --> RespOk
```

---

#### 2.4. Chức năng `extendExamSchedule` (Gia hạn hạn nộp)

```mermaid
flowchart TD
    Start([POST /extend-exam-schedule]) --> Val{Validate: student_ids[], course_id, section_id, new_deadline}
    Val -- Lỗi --> Err422[HTTP 422 Lỗi dữ liệu]
    Val -- Hợp lệ --> GetSchedules[Query StudentExamSchedule cũ]
    GetSchedules --> GetMoodle[Lấy thông tin Section & Course từ api_moodle]
    GetMoodle --> GetStudents[Lấy thông tin sinh viên kèm zeus_id]
    
    GetStudents --> BeginTx[Bắt đầu DB Transaction]
    BeginTx --> Loop[Lặp qua từng Student]
    Loop --> CheckDeadline{new_deadline >= deadline cũ?}
    CheckDeadline -- Không --> Rollback400[Rollback & HTTP 400 Hạn mới không được nhỏ hơn hạn cũ]
    CheckDeadline -- Có --> AddHist[INSERT history: action = 'extend']
    AddHist --> UpdateDeadline[UPDATE trực tiếp: deadline = new_deadline, status = 1]
    UpdateDeadline --> PushZeus[Gửi payload hạn nộp mới sang Zeus qua sendMocktestRemindingManualWithLog]
    PushZeus --> Next[Sang học viên kế tiếp]
    
    Next --> DoneLoop{Hết danh sách?}
    DoneLoop -- Chưa --> Loop
    DoneLoop -- Rồi --> CommitTx[Commit DB Transaction]
    CommitTx --> RespOk[HTTP 200 Gia hạn lịch thi thành công]
```

---

#### 2.5. Chức năng `deleteExamSchedule` (Xóa lịch thi an toàn)

```mermaid
flowchart TD
    Start([POST /delete-exam-schedule]) --> CheckAuth{Auth User Email trong Whitelist?}
    CheckAuth -- Không --> Err403[HTTP 403 / 401 Từ chối truy cập]
    CheckAuth -- Có --> FindRecord[Tìm bản ghi StudentExamSchedule theo schedule_id]
    FindRecord --> CheckExist{Tồn tại?}
    CheckExist -- Không --> Err404[HTTP 404 Không tìm thấy]
    CheckExist -- Có --> GetMeta[Lấy zeus_id, moodleCourseId, moodleSectionId]
    
    GetMeta --> BeginTx[Bắt đầu DB Transaction]
    BeginTx --> Step1[1. DELETE zeus_session_completions theo course_id, section_id, zeus_id]
    Step1 --> Step2[2. DELETE student_exam_schedule_histories theo schedule_id]
    Step2 --> Step3[3. DELETE student_exam_schedule_icl_logs theo student_exam_schedule_id]
    Step3 --> Step4[4. DELETE bản ghi gốc student_exam_schedules]
    Step4 --> CommitTx[Commit DB Transaction]
    CommitTx --> RespOk[HTTP 200 Xóa lịch thi thành công]
```

---

#### 2.6. Chức năng `export` & `ExportExamScheduleJob` (Xuất dữ liệu Async)

```mermaid
flowchart TD
    subgraph WebRequest ["Request Xử Lý Nhanh"]
        A[POST /export kèm bộ lọc] --> B[Count tổng số bản ghi & tính số batch 1000/batch]
        B --> C[Sinh export_id UUID]
        C --> D[Lưu Cache export_progress_{exportId} status = processing]
        D --> E[Dispatch ExportExamScheduleJob vào Queue]
        E --> F[Trả về ngay HTTP 200 kèm export_id]
    end

    subgraph QueueWorker ["Queue Background Worker"]
        E -.-> G[ExportExamScheduleJob::handle]
        G --> H[Tạo thư mục exports/exam_schedules/export_id trên Disk]
        H --> LoopBatch[Vòng lặp từng Batch từ 0 đến totalBatches - 1]
        LoopBatch --> WriteCSV[Query 1000 bản ghi, thêm UTF-8 BOM, ghi file CSV part]
        WriteCSV --> UpdCache[Cập nhật Cache: exported_batches++]
        UpdCache --> NextBatch{Hết batch?}
        NextBatch -- Chưa --> LoopBatch
        NextBatch -- Rồi --> ZipFiles[Nén tất cả các file CSV part thành exam_schedules_export.zip]
        ZipFiles --> CleanCSV[Xóa các file CSV tạm]
        CleanCSV --> CompleteCache[Cập nhật Cache: status = completed, file_url]
    end

    subgraph Polling ["Frontend Polling"]
        F -.-> PollLoop[FE gọi GET /export-progress/export_id mỗi 1.5s]
        PollLoop --> CheckStatus{status == completed?}
        CheckStatus -- Chưa --> PollLoop
        CheckStatus -- Rồi --> TriggerDownload[Tự động kích hoạt GET /download/export_id]
    end
```

---

### 3. Đặc Tả API Chi Tiết (API Endpoints Specification)

#### 3.1. `GET /exam-schedule/exam-schedule` (Lấy danh sách lịch thi)
- **Route Name**: `exam-schedule.getExamSchedule`
- **Middleware**: `auth`, `check.permission`, `tenant.access:web`
- **Request Parameters (Query)**:
  | Tham số | Kiểu | Bắt buộc | Mô tả |
  | :--- | :---: | :---: | :--- |
  | `page` | Integer | Không | Trang hiện tại (Mặc định: 1) |
  | `per_page` | Integer | Không | Số bản ghi mỗi trang (Mặc định: 10) |
  | `start_date` | String | Không | Lọc mở bài từ ngày (Format: `d-m-Y`) |
  | `end_date` | String | Không | Lọc mở bài đến ngày (Format: `d-m-Y`) |
  | `close_start_date`| String | Không | Lọc đóng bài từ ngày (Format: `d-m-Y`) |
  | `close_end_date` | String | Không | Lọc đóng bài đến ngày (Format: `d-m-Y`) |
  | `phone` | String | Không | Tìm theo SĐT học viên |
  | `email` | String | Không | Tìm theo Email học viên |
  | `student_code[]` | Array[Int]| Không | Mảng ID học viên |
  | `class_code[]` | Array[Str]| Không | Mảng mã lớp |
  | `product[]` | Array[Int]| Không | Mảng Category ID sản phẩm |
  | `course[]` | Array[Int]| Không | Mảng Course ID |
  | `exam[]` | Array[Int]| Không | Mảng Section ID |
  | `requirement` | Integer | Không | 0: Đúng lộ trình, 1: Thi lại, 2: Thi sớm |
  | `exam_status` | Integer | Không | 0: Chờ nộp, 1: Đã nộp, 2: Không nộp |
  | `point_status` | Integer | Không | 0: Đang xử lý, 1: Hoàn tất, 2: Giao chấm lại, 3: Đã hủy |
  | `is_vip` | Integer | Không | 1: VIP, 0: Thường |
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "can_delete": true,
      "can_update": true,
      "can_extend": true,
      "data": {
          "current_page": 1,
          "last_page": 5,
          "per_page": 10,
          "total": 48,
          "data": [
              {
                  "id": 1024,
                  "studentId": 55,
                  "studentName": "Nguyễn Văn A",
                  "studentEmail": "nguyenvana@gmail.com",
                  "studentCode": "HV2026_001",
                  "studentAvatar": "https://...",
                  "product": "IELTS Master",
                  "course": "IELTS Intensive 6.5+",
                  "courseId": 120,
                  "sectionId": 450,
                  "examId": 450,
                  "exam": "Mocktest 01 - Full 4 Skills",
                  "requirement": 1,
                  "examStatus": 1,
                  "submitStatus": 0,
                  "timeOpen": "2026-10-01 08:00:00",
                  "timeClose": "2026-10-10 23:59:59",
                  "zeusTimeOpen": "2026-10-01 08:00:00",
                  "zeusTimeClose": "2026-10-08 23:59:59",
                  "status": 1
              }
          ]
      }
  }
  ```

---

#### 3.2. `POST /exam-schedule/store-exam-schedule` (Tạo lịch thi)
- **Route Name**: `exam-schedule.storeExamSchedule`
- **Request Body (JSON / Form-data)**:
  ```json
  {
      "student_ids": [55, 56],
      "exam_ids": [450, 451],
      "requirement_type": 1,
      "start_date": "2026-10-15 08:00:00",
      "deadline": "2026-10-20 23:59:59"
  }
  ```
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "message": "Tạo lịch thi hàng loạt thành công!"
  }
  ```

---

#### 3.3. `POST /exam-schedule/update-exam-schedule` (Đổi lịch thi)
- **Route Name**: `exam-schedule.updateExamSchedule`
- **Request Body**:
  ```json
  {
      "student_ids": [55],
      "course_id": 120,
      "section_id": 450,
      "requirement_type": 1,
      "start_date": "2026-10-25 08:00:00",
      "deadline": "2026-10-30 23:59:59"
  }
  ```
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "message": "Đổi lịch thi hàng loạt thành công!"
  }
  ```
- **Response Error (400 Bad Request)**:
  ```json
  {
      "success": false,
      "message": "Ngày bắt đầu phải lớn hơn thời gian đóng của lịch thi hiện tại (10/10/2026 23:59)"
  }
  ```

---

#### 3.4. `POST /exam-schedule/extend-exam-schedule` (Gia hạn lịch thi)
- **Route Name**: `exam-schedule.extendExamSchedule`
- **Request Body**:
  ```json
  {
      "student_ids": [55],
      "course_id": 120,
      "section_id": 450,
      "new_deadline": "2026-11-05 23:59:59"
  }
  ```
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "message": "Gia hạn lịch thi thành công!"
  }
  ```

---

#### 3.5. `POST /exam-schedule/delete-exam-schedule` (Xóa lịch thi)
- **Route Name**: `exam-schedule.deleteExamSchedule`
- **Request Body**:
  ```json
  {
      "schedule_id": 1024
  }
  ```
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "message": "Xóa lịch thi thành công!"
  }
  ```

---

#### 3.6. `POST /exam-schedule/export` (Khởi tạo xuất báo cáo)
- **Route Name**: `exam-schedule.export`
- **Request Body**: Nhận toàn bộ parameters lọc tương tự `getExamSchedule`.
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "export_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
  }
  ```

---

#### 3.7. `GET /exam-schedule/export-progress/{export_id}` (Kiểm tra tiến độ xuất)
- **Route Name**: `exam-schedule.export-progress`
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "data": {
          "status": "completed",
          "total_batches": 5,
          "exported_batches": 5,
          "file_url": "https://lms.hocmai.vn/exam-schedule/download/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
      }
  }
  ```

---

#### 3.8. `GET /exam-schedule/download/{export_id}` (Tải file ZIP kết quả)
- **Route Name**: `exam-schedule.download`
- **Output**: Binary Stream File nén `exam_schedules_export.zip`.
