# Phân Hệ Quản Lý Bài Làm Của Học Viên & Chấm Bài Thi
## 03. Sơ Đồ Chức Năng & Thiết Kế API/Job (Function Diagram & Design)

---

### 1. Kiến Trúc Thành Phần Phân Hệ (System Architecture)

```mermaid
graph TD
    subgraph Client ["Client Presentation Layer (Browser)"]
        IndexView["Blade View: exam-history.index-v2"]
        ModalContainer["Modal Core: exam-detail-modal.js"]
        ModalDetail["Modal View: exam-detail-modal.detail.js"]
        ModalEdit["Modal Grading: exam-detail-modal.edit.js"]
        ModalAssign["Modal Gán Giáo Viên / QA"]
    end

    subgraph RouterMiddleware ["Routing & Middleware Layer"]
        WebRoutes["routes/web.php (prefix: exam-histories & student-exam-histories)"]
        ApiRoutes["routes/api.php"]
        AuthMid["auth & check.permission"]
        TenantMid["tenant.access:web"]
    end

    subgraph Controllers ["Application Controllers"]
        EHC["ExamHistoryController"]
        SEHC["StudentExamHistoryController"]
        ExportJob["ExportExamHistoryJob (Queue Worker)"]
    end

    subgraph ServiceLayer ["Core Services & Calculation Engines"]
        SEHService["StudentExamHistoryService"]
        RoundMixSvc["RoundMixOverallService"]
        ScoringScale["ScoringScaleFactory"]
        IclScoreSvc["IclScoreService"]
        ContestSvc["ContestType19 -> ContestType36"]
    end

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

    IndexView --> RouterMiddleware
    ModalContainer --> RouterMiddleware
    ModalDetail --> RouterMiddleware
    ModalEdit --> RouterMiddleware
    ModalAssign --> RouterMiddleware

    RouterMiddleware --> EHC
    RouterMiddleware --> SEHC

    EHC --> SEHService
    EHC -->|Dispatch| ExportJob
    EHC --> DB

    SEHC --> SEHService
    SEHC --> RoundMixSvc
    SEHC --> ScoringScale
    SEHC --> ContestSvc
    SEHC --> IclScoreSvc
    SEHC --> DB

    ExportJob --> DB
    ExportJob --> RedisCache
    ExportJob --> DiskStorage
```

---

### 2. Sơ Đồ Luồng Xử Lý Chức Năng (Function Flowcharts)

#### 2.1. Hàm `getExamHistories` (ExamHistoryController)

```mermaid
flowchart TD
    Start([GET /exam-histories/list]) --> GetTenant[Lấy Tenant Connection hiện tại]
    GetTenant --> BuildQuery[buildExamHistoryQueryV2: Query bảng student_score ss]
    BuildQuery --> ApplyFilters[Áp dụng các điều kiện lọc: Date, Phone, Email, TomSelect, Trạng thái, VIP, Cheating]
    ApplyFilters --> Paginate[Thực thi paginate perPage theo ss.id DESC]
    
    Paginate --> ExtractIDs[Trích xuất danh sách: studentScoreIds, studentIds, quizIds]
    ExtractIDs --> QueryTimeEnd[Query timeEnd: MAX created_at từ student_exam_histories theo scoreId]
    ExtractIDs --> QueryMoodle[Lấy cây danh mục Moodle Category -> Course -> Section -> Quiz qua Cache]
    ExtractIDs --> QueryCheating[Lấy báo cáo gian lận cheating_reports nếu có cờ]
    
    QueryTimeEnd --> MergeData[Ghép nối dữ liệu: Điểm L/R/W/S, Điểm Overall, Tên Sản phẩm, Tên Khóa học, Trạng thái Gán GV]
    QueryMoodle --> MergeData
    QueryCheating --> MergeData
    
    MergeData --> OutputJSON[Trả về HTTP 200 JSON cho DataTable Frontend]
```

---

#### 2.2. Hàm `getTeacherAssignedOverview` (StudentExamHistoryController)

```mermaid
flowchart TD
    Start([GET /student-exam-histories/ID/teacher-assigned-overview]) --> AuthCheck{Người dùng đăng nhập?}
    AuthCheck -- Không --> Err401[HTTP 401 Unauthorized]
    AuthCheck -- Có --> GetScore[Tìm StudentScore theo student_score_id]
    
    GetScore --> CheckFound{Tồn tại bài thi?}
    CheckFound -- Không --> Err404[HTTP 404 Không tìm thấy bài thi]
    CheckFound -- Có --> CheckRole{Vai trò người dùng?}
    
    CheckRole -->|Super Admin / Operator| FullAccess[Gán can_grade = true, mở full các kỹ năng]
    CheckRole -->|Giáo viên| CheckAssign[Truy vấn teacher_assigned_histories & student_exam_history_teachers]
    
    CheckAssign --> IsAssigned{Có được phân công Part nào không?}
    IsAssigned -- Không --> Deny[Gán can_grade = false, trả về thông báo không có quyền]
    IsAssigned -- Có --> Allow[Gán can_grade = true, chỉ mở các Part được phân công]
    
    FullAccess --> BuildSidebar[Dựng dữ liệu Sidebar học viên & Danh sách Part cần chấm]
    Allow --> BuildSidebar
    
    BuildSidebar --> ReturnSuccess[HTTP 200 JSON {success: true, data: {student, parts, can_grade}}]
```

---

#### 2.3. Hàm `updateDataRescored` (Lưu Kết Quả Chấm Bài - StudentExamHistoryController)

```mermaid
flowchart TD
    Start([POST /api/student-exam-histories/update-data-rescored]) --> Val{Validate: student_score_id, skill, criteria_scores, comments}
    Val -- Lỗi --> Err422[HTTP 422 Dữ liệu không hợp lệ]
    Val -- Hợp lệ --> BeginTx[Bắt đầu Database Transaction]
    
    BeginTx --> UpdateHistory[Cập nhật student_exam_histories: Điểm kỹ năng & externalGradingDetail JSON]
    UpdateHistory --> SaveAuditLog[INSERT student_score_grading_histories: Snapshot điểm cũ/mới, Rubric, Grader ID]
    SaveAuditLog --> SaveTeacherLog[INSERT teacher_grading_logs: Ghi nhận nhật ký chấm của GV]
    
    SaveTeacherLog --> CheckAllGraded{Tất cả các kỹ năng tự luận W/S đã hoàn thành chấm?}
    CheckAllGraded -- Chưa --> UpdatePartial[UPDATE student_score: Giữ score_processing_status = 0]
    CheckAllGraded -- Rồi --> RecalculateOverall[Kích hoạt RoundMixOverallService / ScoringScale: Tính lại Overall Band Score]
    
    RecalculateOverall --> UpdateComplete[UPDATE student_score: overall_score = Điểm mới, score_processing_status = 1]
    UpdatePartial --> CommitTx[Commit DB Transaction]
    UpdateComplete --> CommitTx
    
    CommitTx --> CheckSync{score_processing_status == 1?}
    CheckSync -- Có --> SyncICL[Gọi IclScoreService::syncScoreToIcl đồng bộ điểm sang Zeus]
    CheckSync -- Không --> RespOk[HTTP 200 JSON {success: true, overall_score}]
    SyncICL --> RespOk
```

---

#### 2.4. Hàm `assignRandomQaForRegrading` (Gán QA Phúc Khảo)

```mermaid
flowchart TD
    Start([POST /student-exam-histories/assign-random-qa]) --> Val{Validate: student_score_id, skill}
    Val -- Lỗi --> Err422[HTTP 422 Lỗi dữ liệu]
    Val -- Hợp lệ --> GetQAs[Lấy danh sách các tài khoản có Role QA/Kiểm định chất lượng trong Tenant]
    
    GetQAs --> SelectQA[Chọn ngẫu nhiên 1 QA (loại trừ giáo viên đã chấm lần 1)]
    SelectQA --> BeginTx[Bắt đầu DB Transaction]
    
    BeginTx --> AssignQA[Gán quyền chấm cho QA trong student_exam_history_teachers]
    AssignQA --> UpdateScoreStatus[Cập nhật student_score: score_processing_status = 2 Giao chấm lại]
    UpdateScoreStatus --> SaveAssignLog[Ghi log phân công vào teacher_assigned_histories]
    
    BeginTx --> CommitTx[Commit Transaction]
    CommitTx --> RespSuccess[HTTP 200: Gán QA phúc khảo thành công!]
```

---

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

#### 3.1. `GET /exam-histories/list` (Lấy danh sách lịch sử bài làm)
- **Route Name**: `exam-histories.getExamHistories`
- **Middleware**: `auth`, `check.permission`, `tenant.access:web`
- **Request Parameters**:
  | Tham số | Kiểu | Mô tả |
  | :--- | :---: | :--- |
  | `page` | Integer | Số trang hiện tại (Mặc định: 1) |
  | `perpage` | Integer | Số dòng trên 1 trang (Mặc định: 10) |
  | `start_date` / `end_date` | String | Lọc theo khoảng ngày thi nộp bài (`d-m-Y`) |
  | `phone` / `email` | String | Tìm kiếm theo số điện thoại hoặc email học viên |
  | `student_code[]` | Array[Int] | Danh sách ID học viên |
  | `class_code[]` | Array[Str] | Danh sách mã lớp học |
  | `product[]` / `course[]` / `exam[]` | Array[Int] | Mảng ID Sản phẩm, Khóa học, Bài thi |
  | `exam_status` | Integer | 0: Chờ nộp, 1: Đã nộp, 2: Không nộp |
  | `point_status` | Integer | 0: Đang xử lý, 1: Hoàn tất, 2: Giao chấm lại, 3: Đã hủy |
  | `teacher_status` | Integer | 0: Chưa gán, 1: Đã gán, 2: Gán 1 phần |
  | `is_vip` | Integer | 1: VIP, 0: Thường |
  | `cheating_status` | Integer | 1: Có nghi vấn gian lận, 0: Bình thường |
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "data": {
          "current_page": 1,
          "last_page": 10,
          "total": 95,
          "data": [
              {
                  "id": 5001,
                  "student_id": 108,
                  "student_name": "Trần Thị B",
                  "student_email": "tranthib@gmail.com",
                  "student_code": "HV_2026_09",
                  "product_name": "IELTS Master",
                  "course_name": "IELTS Intensive 6.5+",
                  "exam_name": "Mocktest 02 - Full Test",
                  "listening_score": 7.0,
                  "reading_score": 6.5,
                  "writing_score": 6.0,
                  "speaking_score": 6.5,
                  "overall_score": 6.5,
                  "exam_status": 1,
                  "submission_status": 0,
                  "score_processing_status": 1,
                  "teacher_assigned_status": 1,
                  "cheating_status": 0,
                  "time_end": "2026-10-05 15:30:00"
              }
          ]
      }
  }
  ```

---

#### 3.2. `GET /student-exam-histories/{student_score_id}/teacher-assigned-overview`
- **Route Name**: `student-exam-histories.getTeacherAssignedOverview`
- **Mô tả**: Lấy thông tin bài thi dành riêng cho giao diện Chấm bài của Giáo viên.
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "data": {
          "can_grade": true,
          "student": {
              "id": 108,
              "name": "Trần Thị B",
              "email": "tranthib@gmail.com",
              "code": "HV_2026_09",
              "avatar": "https://..."
          },
          "exam": {
              "student_score_id": 5001,
              "title": "Mocktest 02 - Full Test",
              "overall_score": 6.5,
              "is_completed_rescored": true
          },
          "parts": [
              {
                  "idHistory": 1205,
                  "skill": 15,
                  "skill_name": "Writing",
                  "can_edit": true,
                  "score": 6.0,
                  "evaluation_status": "confirmed"
              },
              {
                  "idHistory": 1206,
                  "skill": 16,
                  "skill_name": "Speaking",
                  "can_edit": true,
                  "score": 6.5,
                  "evaluation_status": "confirmed"
              }
          ]
      }
  }
  ```

---

#### 3.3. `POST /student-exam-histories/assign-teacher`
- **Route Name**: `student-exam-histories.assignTeacher`
- **Request Body (JSON)**:
  ```json
  {
      "student_score_id": 5001,
      "teacher_id": 45,
      "part_number": 3,
      "skill": 15
  }
  ```
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "message": "Phân công giáo viên chấm bài thành công!"
  }
  ```

---

#### 3.4. `POST /api/student-exam-histories/update-data-rescored`
- **Route Name**: `student-exam-histories.updateDataRescored`
- **Request Body (JSON)**:
  ```json
  {
      "student_score_id": 5001,
      "idHistory": 1205,
      "skill": 15,
      "score": 6.5,
      "criteria": {
          "TR": 6.5,
          "CC": 6.5,
          "LR": 6.0,
          "GRA": 7.0
      },
      "comment": "Bài viết phát triển ý tốt, ngữ pháp đa dạng.",
      "audio_comment": "https://storage.lms.hocmai.vn/audio/comment_1205.mp3"
  }
  ```
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "message": "Lưu kết quả chấm thi thành công!",
      "overall_score": 6.5
  }
  ```

---

#### 3.5. `POST /exam-histories/export` (Xuất báo cáo kết quả thi)
- **Route Name**: `exam-histories.export`
- **Response Success (200 OK)**:
  ```json
  {
      "success": true,
      "export_id": "7c8e9b1d-2f3a-4e5c-8b9a-1d2e3f4a5b6c"
  }
  ```
