Skip to content

Mô hình dữ liệu đào tạo ​

Là Training Manager, bạn dựng và vận hành cấu trúc đào tạo của trường. Trang này trình bày 6 thực thể cốt lõi và mối quan hệ giữa chúng — đủ để bạn quyết định cái gì nên tạo trước, cái gì gắn vào đâu, hệ quả khi đổi/xoá là gì.

Trang này khác gì với các trang chuyên sâu?

  • Trang này là bản đồ tổng quan — hiểu cấu trúc trước khi đi sâu.
  • Các trang sau (Programs, Batches, Students, Enrollments...) đi vào chi tiết thao tác trong từng thực thể.
  • Đọc trang này một lần khi mới bắt đầu; quay lại khi gặp trường hợp lạ (vd: "vì sao Roadmap không gắn vào Student?").

Sơ đồ tổng quan ​

                  ┌────────────────────────┐
                  │  Competency Framework  │
                  │  (chuẩn năng lực)      │
                  └───────────┬────────────┘
                              │ (gắn nullable)
                  ┌───────────┴──────────────────┐
                  ▼                              ▼
            ┌──────────┐    program_course   ┌────────┐
            │ Program  │◀──── (n:n, có order)─┤ Course │  ◀── enrollments ── User
            └────┬─────┘                     └────┬───┘                       ▲
                 │ (1:n)                          │                           │
                 ▼                                │                           │
            ┌──────────┐    batch_enrollments    │                            │
            │  Batch   │◀────(user × course)─────┤                            │
            │ (LỚP)    │                                                      │
            └────┬─────┘                                                      │
                 │ batch_user (n:n, có status)                                │
                 │ batch_trainers (n:n, có role)                              │
                 ▼                                                            │
              [Học viên trong lớp]──────────────────────────────────────[User]
                                                                              │
                                                            ┌────── (1:1) ────┤
                                                            ▼                 │
                                                       ┌─────────┐            │
                                                       │ Student │            │
                                                       └────┬────┘            │
                                                            │                 │
                                                            │ batch_transfers │
                                                            ▼                 │
                                                      [Log chuyển lớp]        │
                                                                              │
                                       ┌──── (user_id) ──────────────────────┘
                                       ▼
                              ┌──────────────────┐
                              │ Learning Roadmap │ ◀── targets ── Competency
                              │ (template /       │                 (Level 4 + mức)
                              │  personal)        │
                              └──────────────────┘

Phân loại: core vs optional ​

Thực thểModuleBắt buộc với mọi tenant?
CourseCourseManagement✅ Core — luôn có. Là spine (xương sống) của hệ thống.
ProgramTrainingManagement❌ Optional — chỉ tồn tại khi tenant bật module này.
BatchTrainingManagement❌ Optional.
StudentTrainingManagement❌ Optional.
Competency FrameworkCompetencyFramework❌ Optional.
Learning RoadmapLearningRoadmap❌ Optional. Là spine cho Personalization — nếu bật, các module cá nhân hoá khác sẽ xây trên nó.

Nếu Training Manager tắt module TrainingManagement?

Hệ thống vẫn chạy — học viên vẫn enroll trực tiếp vào Course (kiểu marketplace self-paced). Nhưng Programs/Batches/Students/Transfers/Sessions sẽ biến mất khỏi UI. Đây là chế độ "chỉ marketplace, không đào tạo chính quy".

Diễn giải chi tiết ​

Course — đơn vị nội dung học (spine) ​

Khoá học cụ thể với modules → lessons → activities. Bạn không tạo Course (Course Manager soạn nội dung). Vai trò của bạn: gắn Course đã có vào Program theo thứ tự đào tạo.

  • Quan hệ then chốt: Course ↔ User qua bảng enrollments. Mỗi row = "User X đang/đã học Course Y". Bảng này là spine — tồn tại độc lập với mọi thứ TrainingManagement.
  • Hệ quả: một User có thể enroll Course mà không thuộc Batch/Program nào (vd: tự mua trên marketplace). Họ vẫn học bình thường.

Program — bộ Course có thứ tự ​

Chương trình đào tạo chính quy, vd: "Lập trình viên Fullstack 6 tháng". Một Program gom 1+ Course với thứ tự (order trên pivot).

  • Course ↔ Program là quan hệ n:n qua bảng program_course — một Course có thể nằm trong nhiều Program, một Program có nhiều Course.
  • Program ↔ Competency Framework là belongsTo nullable — Program có thể đặt 1 framework làm chuẩn đầu ra, hoặc không.
  • Program đứng một mình không có học viên — học viên chỉ kết nối vào Program gián tiếp qua Batch.

Sao Program không enroll trực tiếp được?

Vì một Program có thể chạy nhiều cohort khác nhau (K1, K2, K3...) với trainer / thời gian / lịch học khác nhau. Cohort = Batch. Học viên phải thuộc 1 cohort cụ thể, nên enroll vào Batch, không vào Program.

Batch — cohort thực thi một Program ​

Lớp đào tạo cụ thể: "Fullstack K7 — 03/2026 – 09/2026". Mỗi Batch:

Thuộc tínhQuan hệ
program_idbelongsTo Program (cố định sau khi tạo)
Học viênbelongsToMany User qua batch_user (pivot có status: active/suspended/transferred_out/dropped/completed, joined_at, exited_at, exit_reason)
TrainerbelongsToMany User qua batch_trainers (pivot có role: Lead/Assistant/Mentor)
Ghi danh theo khoáhasMany BatchEnrollment (user × course × batch) — sinh tự động khi attach User vào Batch
Buổi họchasMany BatchSession
Lộ trình mặc địnhbelongsTo Roadmap (template) — gợi ý cho học viên chưa có roadmap riêng

Không đổi program_id sau khi tạo Batch

UI khoá field này. Đổi Program = phá vỡ tất cả BatchEnrollment đang trỏ tới các Course của Program cũ. Nếu thực sự cần đổi: tạo Batch mới + chuyển học viên qua Chuyển lớp.

Student — hồ sơ "đã nhập học của trường" ​

Đây là chỗ dễ nhầm nhất. Phải phân biệt 3 thứ độc lập:

Là gìQuản lý bởi
UserTài khoản đăng nhập (users table)Tenant Admin
Role student (Spatie)Quyền truy cập Learnspace, làm bàiTenant Admin
Student recordHồ sơ học viên trường: student_code, status (Active/Graduated/Withdrawn), admitted_at, graduated_at, withdrawn_atTraining Manager
  • Quan hệ User ↔ Student là 1:1 qua FK students.user_id (unique).
  • Student record được tạo tự động khi attach User vào Batch lần đầu (listener EnsureStudentForBatchMember) — bạn không phải làm gì.
  • Sau khi có Student record, bạn quản lý lifecycle qua Quản lý học viên.

Student status ≠ Batch status

Một Student Active có thể đang dropped ở Batch hiện tại — họ vẫn là học viên trường, chỉ rời lớp đó. Khi học viên nghỉ hẳn → đổi Student status sang Withdrawn thủ công (không tự lan từ BatchUser).

Hai quan hệ Student → Batch (chủ đích, không phải dư thừa) ​

ĐườngQua bảngFKDùng để
Hiện tạibatch_useruser_id (của User, không của Student)Liệt kê các lớp Student đang/đã ở (qua user_id)
Audit logbatch_transfersstudent_idLog mọi lần chuyển lớp với source_batch_id, dest_batch_id, transferred_by, transferred_at, reason

Lý do tách: batch_user là quan hệ "ai trong lớp" — cần áp dụng cho cả User chưa có Student record (vd: marketplace user đột nhiên được attach vào Batch). batch_transfers là log nghiệp vụ riêng cho học viên chính quy, nên gắn student_id.

Competency Framework — chuẩn năng lực ​

Cây 4 tầng Level 1 → Level 2 → Level 3 → Level 4 (leaf) + tập proficiency levels (mức thành thạo: Nhận biết / Hiểu / Vận dụng / Thành thạo).

Quan hệ chính:

Quan hệBảngÝ nghĩa
Framework hasMany Level1..4(cây)Cấu trúc cây năng lực
Program belongsTo Frameworkprograms.competency_framework_id (nullable)Chuẩn đầu ra của Program
Course belongsToMany Frameworkcourse_competency_frameworkKhoá học đóng góp vào framework nào
Roadmap belongsTo Frameworkroadmaps.competency_framework_id (nullable)Lộ trình hướng tới framework nào
Roadmap belongsToMany Level4roadmap_competency_goals (pivot có target_proficiency_level_id)Mục tiêu: đạt năng lực X ở mức Y

Tắt CompetencyFramework có sao không?

Hệ thống vẫn chạy. Mất các tính năng: gap analysis, Bản đồ năng lực lớp (trainer), gợi ý khoá học theo năng lực thiếu của Roadmap. Programs/Batches/Course vẫn hoạt động bình thường.

Learning Roadmap — lộ trình học ​

Roadmap = "muốn đạt các năng lực X, Y, Z ở các mức tương ứng" → engine tạo ra danh sách Course nên học theo thứ tự (milestones + items).

Hai loại:

Loạiuser_idDùng để
TemplatenullLộ trình mẫu — Course Manager soạn, gắn vào Batch làm gợi ý mặc định
Personalcó user_idLộ trình cụ thể của một học viên — sinh từ template hoặc học viên tự lập

Quan hệ chính:

  • Roadmap belongsTo User (nullable; có khi personal).
  • Roadmap belongsTo Roadmap qua template_id — clone từ template nào.
  • Roadmap belongsTo CompetencyFramework (nullable).
  • Roadmap belongsToMany CompetencyLevel4 qua roadmap_competency_goals.

Roadmap gắn User, không gắn Student

Vì lộ trình học đi theo người, không theo trạng thái nhập học. Nếu một User rời trường (Student Withdrawn) rồi quay lại trường khác (Student record mới ở tenant khác), Roadmap personal của họ vẫn còn nguyên. Đây là quyết định cố ý.

Quan hệ then chốt & hệ quả vận hành ​

User × Course ↔ Enrollment (spine, độc lập mọi thứ khác) ​

  • Mỗi lần học viên học một Course (dù qua Batch hay không), có 1 row enrollments.
  • Hệ quả khi chuyển lớp: vì tiến độ gắn vào (user_id, course_id), học viên chuyển từ Batch A → Batch B (chung Program hoặc có Course chung) giữ nguyên tiến độ cũ. Không phải import lại.

Batch × User ↔ BatchUser (pivot có lifecycle) ​

  • Pivot có 5 trạng thái: active, suspended, transferred_out, dropped, completed.
  • Khi attach User vào Batch lần đầu:
    1. Tạo row batch_user với status active.
    2. Tự sinh Student record (nếu chưa có).
    3. Tự sinh các BatchEnrollment cho từng Course thuộc Program của Batch.
    4. Tự sinh Enrollment cho từng Course đó (qua sync).

Batch × Course ↔ BatchEnrollment (cầu nối 3 chiều) ​

  • Mỗi row = "User X học Course Y trong Batch Z". Là cầu nối giữa Batch và spine Enrollment.
  • Khi User rời Batch (dropped), BatchEnrollment đóng nhưng Enrollment không xoá — tiến độ vẫn còn.

Batch × Trainer ↔ batch_trainers (n:n, có role) ​

  • Pivot có role (Lead / Assistant / Mentor) và notes.
  • Quyền truy cập Trainer Panel: mọi User có row trong batch_trainers đều thấy lớp đó trong /trainer, kể cả khi không có role trainer trong Spatie. Đây là chủ đích — phân quyền dựa trên membership lớp, không dựa role toàn cục.

Student → BatchTransfer (audit-only) ​

  • Mỗi lần chuyển lớp = 1 row log không xoá.
  • Là dữ liệu quan sát, không phải state — state hiện tại đọc từ batch_user.

Roadmap → User chứ không phải Roadmap → Student ​

  • Roadmap đi theo người, không theo tư cách "học viên trường".
  • Bạn cấu hình roadmap template mặc định cho Batch — khi User vào lớp, nếu chưa có roadmap personal nào, dùng template làm gợi ý.

Quyết định thiết kế thường gây thắc mắc ​

"Vì sao tách User vs Student?"

Multi-tenant. Một User (nguyenvana@gmail.com) có thể là học viên ở trường A, trợ giảng ở trường B, và chỉ đăng ký một khoá lẻ ở trường C. Mỗi trường (tenant) có Student record riêng với mã + lifecycle riêng. User là danh tính toàn hệ thống; Student là tư cách trong từng trường.

"Vì sao Course là spine còn Program/Batch là optional?"

Vì có tenant kiểu marketplace (mua khoá lẻ, học tự do) và tenant kiểu đào tạo chính quy (Program + Batch). Course là chung của cả hai → spine. Program/Batch chỉ tồn tại ở kiểu thứ hai → optional.

"Vì sao có 2 đường Student → Batch (qua user_id và qua student_id)?"

  • batch_user phải nhận cả User chưa có Student record (vd: trainer cũng có thể được attach để tham gia học bù) → dùng user_id.
  • batch_transfers là log nghiệp vụ riêng cho học viên chính quy → dùng student_id để query log lịch sử nhanh và rõ.

"Tôi xoá Student có xoá User không? Ngược lại?"

  • Xoá Student: không động vào User. Người đó vẫn đăng nhập, vẫn có tiến độ học cũ.
  • Xoá User: do cascadeOnDelete trên FK → Student record bị xoá theo. Cẩn thận với thao tác này (thường Tenant Admin làm trên UserManagement, không phải Training Manager).

FAQ ​

Tôi tạo Batch trước hay Program trước? Program trước. Một Program có thể có nhiều Batch (mỗi cohort 1 Batch). Khi mở cohort mới, bạn chọn Program đã có → tạo Batch.

Tôi muốn 1 Course có ở 2 Program khác nhau, có được không? Được. program_course là n:n. Khoá học cơ bản như "Tư duy lập trình" có thể nằm ở cả Program "Fullstack" và "AI Engineer".

Tôi gắn Framework cho Program — học viên ở Batch tự được tính năng lực? Có, gián tiếp. Khi học viên hoàn thành Course (đã gắn Level 4 năng lực), năng lực tương ứng được tích luỹ trên User. Trainer dùng Bản đồ năng lực lớp để xem.

Tôi muốn tạo roadmap mẫu cho Batch — phải làm gì? Course Manager soạn Roadmap template (gắn vào CompetencyFramework, chọn các Level 4 mục tiêu). Sau đó bạn vào sửa Batch → field Lộ trình mặc định → chọn template đó.

Một User vừa là học viên vừa là trainer được không? Được. Một row trong batch_user (status active) + một row trong batch_trainers (role nào đó). Người này vào Learnspace học, vào /trainer để dạy lớp khác.

Student Active nhưng batch_user đều dropped — vậy là gì? Học viên trường nhưng hiện không ở lớp nào. Có thể đang chờ phân lớp mới, hoặc đang nghỉ giữa kỳ. Tự bạn đánh giá → có thể chuyển Student sang Withdrawn nếu nghỉ hẳn, hoặc giữ Active nếu chờ.

Bước tiếp theo ​

TrangKhi nào đọc
Chương trình đào tạoTạo Program, gắn Course với thứ tự, gắn Framework
Lớp đào tạoTạo Batch, attach trainer + học viên, đặt roadmap mặc định
Quản lý học viênLifecycle Student (admit/graduate/withdraw), mã học viên
Ghi danh & vòng đời ghi danh5-state lifecycle của BatchUser
Chuyển lớp (Batch Transfer)Quy trình atomic + audit batch_transfers

LMS Documentation