<aside> <img src="/icons/checklist_green.svg" alt="/icons/checklist_green.svg" width="40px" />
</aside>
Table products
{
id uuid [pk, note: 'Khóa chung cho mọi “sản phẩm” (Course, Specialization, Bundle…)']
title varchar [not null, note: 'Tiêu đề sản phẩm']
slug varchar [unique, not null, note: 'URL thân thiện cho sản phẩm']
short_description text [not null, note: 'Mô tả ngắn hiển thị danh sách']
description text [note: 'Mô tả chi tiết sản phẩm']
type product_type [not null, note: 'Loại sản phẩm: course | specialization | bundle | ...']
category_id uuid [ref: > categories.id, note: 'Thuộc danh mục nào']
owner uuid [ref: > users.id, note: 'Người tạo (Giảng viên/Admin)']
thumbnail varchar [not null, note: 'Ảnh thumbnail nhỏ']
label product_labels [default: 'new', note: 'Nhãn: new/hot/featured/best_seller']
status product_status [default: 'draft', note: 'Trạng thái: draft/published/archived']
rating_avg float [default: 0, note: "Danh gia trung binh"]
rating_cnt int [default: 0, note: "Danh giá trung binh"]
enrollment_cnt int [default: 0, note: "Số lượt đăng ký"]
created_at timestamp [default: `now()`, note: 'Thời điểm khởi tạo product record']
updated_at timestamp [default: `now()`, note: 'Cập nhật lần cuối']
deleted_at timestamp [null, note: 'Thời điểm xóa (soft-delete)']
indexes {
(type, slug) [unique]
}
}
Table product_related {
product_id uuid [not null, ref: > products.id]
related_id uuid [not null, ref: > products.id]
order int [not null, default: 0]
notes text
}
Table courses [note: 'Bảng chính lưu thông tin các khóa học']
{
product_id uuid [pk, ref: > products.id, note: 'Tham chiếu đến products.id và chứa chi tiết Course']
requirements text [not null, note: 'Yêu cầu tham gia sản phẩm']
learning_outcomes text [not null, note: 'Kết quả đạt được sản phẩm']
preview_video varchar [note: 'Link video preview (nếu có)']
preview_img varchar [not null, note: 'Ảnh preview banner']
difficulty course_difficulty_level [not null, default: 'easy', note: 'Mức độ: easy/medium/hard']
regular_price int [not null, note: 'Giá gốc khóa học (đơn vị nhỏ nhất, ví dụ VND)']
discounted_price int [not null, note: 'Giá ưu đãi khóa học (đơn vị nhỏ nhất, ví dụ VND)']
}
Table product_prequisites [note: 'Liên kết khóa học với các khóa phải hoàn thành trước']
{
product_id uuid [ref: > products.id, note: 'Khóa học chính']
required_id uuid [ref: > products.id, note: 'Khóa học yêu cầu trước']
indexes {
(product_id, required_id) [pk]
}
}
course, specialization, bundleproducts
type = course ⇒ phải tồn tại chính xác 1 bản ghi trong courses.type = specialization ⇒ phải tồn tại chính xác 1 bản ghi trong specializations.type = bundle ⇒ phải tồn tại chính xác 1 bản ghi trong bundles.(type, slug) phải unique trên bảng productstitle :
-- liên tiếp; không cho phép - đầu/cuối-{Date.now().toString(36)})deleted_at dùng để đánh dấu “xóa logic” (không hiển thị, có thể restore).archived dùng để ẩn khóa học khỏi người dùng cuối nhưng vẫn giữ dữ liệu cho báo cáo và có thể publish lại sau.archived chỉ thay đổi status; mục soft-deleted phải dùng command restore để đặt lại deleted_at = null