This commit is contained in:
Ken
2026-01-19 09:33:35 +07:00
parent 374dc12b2d
commit 70838a4bc1
103 changed files with 16929 additions and 2 deletions

19
models/AcademicYear.js Normal file
View File

@@ -0,0 +1,19 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const AcademicYear = sequelize.define('academic_years', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
year_name: { type: DataTypes.STRING(50), allowNull: false },
start_date: { type: DataTypes.DATE, allowNull: false },
end_date: { type: DataTypes.DATE, allowNull: false },
semester_count: { type: DataTypes.INTEGER, defaultValue: 2 },
is_current: { type: DataTypes.BOOLEAN, defaultValue: false },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'academic_years',
timestamps: true,
underscored: true,
});
module.exports = AcademicYear;

22
models/AttendanceDaily.js Normal file
View File

@@ -0,0 +1,22 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const AttendanceDaily = sequelize.define('attendance_daily', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
user_id: { type: DataTypes.UUID, allowNull: false },
school_id: { type: DataTypes.UUID, allowNull: false },
class_id: { type: DataTypes.UUID },
attendance_date: { type: DataTypes.DATE, allowNull: false },
status: { type: DataTypes.ENUM('present', 'absent', 'late', 'excused'), allowNull: false },
check_in_time: { type: DataTypes.TIME },
check_out_time: { type: DataTypes.TIME },
notes: { type: DataTypes.TEXT },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'attendance_daily',
timestamps: true,
underscored: true,
});
module.exports = AttendanceDaily;

90
models/AttendanceLog.js Normal file
View File

@@ -0,0 +1,90 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* AttendanceLogs Model - Dữ liệu thô điểm danh từ QR
* Bảng này cần partition theo thời gian do data lớn
*/
const AttendanceLog = sequelize.define('attendance_logs', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users_auth',
key: 'id',
},
comment: 'ID người dùng (học sinh/giáo viên)',
},
school_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'schools',
key: 'id',
},
comment: 'ID trường',
},
check_time: {
type: DataTypes.DATE,
allowNull: false,
comment: 'Thời gian quét QR',
},
check_type: {
type: DataTypes.ENUM('IN', 'OUT'),
allowNull: false,
comment: 'Loại: Vào hoặc Ra',
},
device_id: {
type: DataTypes.STRING(100),
comment: 'ID thiết bị quét',
},
location: {
type: DataTypes.STRING(200),
comment: 'Vị trí quét (cổng, lớp học)',
},
qr_version: {
type: DataTypes.INTEGER,
comment: 'Version QR code',
},
ip_address: {
type: DataTypes.STRING(45),
comment: 'IP address thiết bị (IPv4/IPv6)',
},
user_agent: {
type: DataTypes.TEXT,
comment: 'User agent',
},
is_valid: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'QR code hợp lệ',
},
notes: {
type: DataTypes.TEXT,
comment: 'Ghi chú',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'attendance_logs',
timestamps: false,
underscored: true,
indexes: [
{ fields: ['user_id', 'check_time'] },
{ fields: ['school_id', 'check_time'] },
{ fields: ['check_time'] },
{ fields: ['device_id'] },
{ fields: ['is_valid'] },
],
comment: 'Bảng log điểm danh thô - Cần partition theo tháng',
});
module.exports = AttendanceLog;

35
models/AuditLog.js Normal file
View File

@@ -0,0 +1,35 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const AuditLog = sequelize.define('audit_logs', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
school_id: { type: DataTypes.UUID },
user_id: { type: DataTypes.UUID },
action: { type: DataTypes.STRING(100), allowNull: false },
entity_type: { type: DataTypes.STRING(100) },
entity_id: { type: DataTypes.UUID },
old_values: { type: DataTypes.JSON },
new_values: { type: DataTypes.JSON },
ip_address: { type: DataTypes.STRING(45) },
user_agent: { type: DataTypes.TEXT },
status: {
type: DataTypes.ENUM('success', 'failed'),
defaultValue: 'success'
},
error_message: { type: DataTypes.TEXT },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'audit_logs',
timestamps: false,
createdAt: 'created_at',
updatedAt: false,
indexes: [
{ fields: ['school_id'] },
{ fields: ['user_id'] },
{ fields: ['action'] },
{ fields: ['entity_type', 'entity_id'] },
{ fields: ['created_at'] },
],
});
module.exports = AuditLog;

69
models/Chapter.js Normal file
View File

@@ -0,0 +1,69 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* Chapter Model - Chương trong giáo trình
* Một Subject có nhiều Chapter
*/
const Chapter = sequelize.define('chapters', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
subject_id: {
type: DataTypes.UUID,
allowNull: false,
comment: 'ID của giáo trình'
},
chapter_number: {
type: DataTypes.INTEGER,
allowNull: false,
comment: 'Số thứ tự chương (1, 2, 3...)'
},
chapter_title: {
type: DataTypes.STRING(200),
allowNull: false,
comment: 'Tiêu đề chương'
},
chapter_description: {
type: DataTypes.TEXT,
comment: 'Mô tả về chương học'
},
duration_minutes: {
type: DataTypes.INTEGER,
comment: 'Thời lượng học (phút)'
},
is_published: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Đã xuất bản chưa'
},
display_order: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Thứ tự hiển thị'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
}, {
tableName: 'chapters',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['subject_id'] },
{ fields: ['subject_id', 'chapter_number'], unique: true },
{ fields: ['display_order'] },
{ fields: ['is_published'] },
],
});
module.exports = Chapter;

22
models/Class.js Normal file
View File

@@ -0,0 +1,22 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Class = sequelize.define('classes', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
class_code: { type: DataTypes.STRING(20), unique: true, allowNull: false },
class_name: { type: DataTypes.STRING(100), allowNull: false },
school_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'schools', key: 'id' } },
academic_year_id: { type: DataTypes.UUID, allowNull: false },
grade_level: { type: DataTypes.INTEGER },
homeroom_teacher_id: { type: DataTypes.UUID },
max_students: { type: DataTypes.INTEGER, defaultValue: 30 },
current_students: { type: DataTypes.INTEGER, defaultValue: 0 },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'classes',
timestamps: true,
underscored: true,
});
module.exports = Class;

22
models/ClassSchedule.js Normal file
View File

@@ -0,0 +1,22 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const ClassSchedule = sequelize.define('class_schedules', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
class_id: { type: DataTypes.UUID, allowNull: false },
subject_id: { type: DataTypes.UUID, allowNull: false },
teacher_id: { type: DataTypes.UUID },
room_id: { type: DataTypes.UUID },
day_of_week: { type: DataTypes.INTEGER, comment: '1=Monday, 7=Sunday' },
period: { type: DataTypes.INTEGER, comment: 'Tiết học' },
start_time: { type: DataTypes.TIME },
end_time: { type: DataTypes.TIME },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'class_schedules',
timestamps: true,
underscored: true,
});
module.exports = ClassSchedule;

101
models/Game.js Normal file
View File

@@ -0,0 +1,101 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* Game Model - Trò chơi giáo dục
* Các game engine/template để render nội dung học tập
* Một game có thể render nhiều lesson khác nhau có cùng type
*/
const Game = sequelize.define('games', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
title: {
type: DataTypes.STRING(200),
allowNull: false,
comment: 'Tên trò chơi'
},
description: {
type: DataTypes.TEXT,
comment: 'Mô tả về trò chơi'
},
url: {
type: DataTypes.TEXT,
allowNull: false,
comment: 'URL của game (HTML5 game, Unity WebGL, etc.)'
},
thumbnail: {
type: DataTypes.TEXT,
comment: 'URL ảnh thumbnail'
},
type: {
type: DataTypes.STRING(50),
allowNull: false,
comment: 'Loại game: counting_quiz, math_game, word_puzzle, etc. - Must match lesson content_json.type'
},
config: {
type: DataTypes.JSON,
comment: 'Cấu hình game: controls, settings, features, etc.'
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'Game có sẵn sàng để sử dụng không'
},
is_premium: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Yêu cầu premium để chơi'
},
min_grade: {
type: DataTypes.INTEGER,
comment: 'Cấp lớp tối thiểu (1-12)'
},
max_grade: {
type: DataTypes.INTEGER,
comment: 'Cấp lớp tối đa (1-12)'
},
difficulty_level: {
type: DataTypes.ENUM('easy', 'medium', 'hard'),
comment: 'Độ khó'
},
play_count: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Số lần đã chơi'
},
rating: {
type: DataTypes.DECIMAL(3, 2),
comment: 'Đánh giá (0.00 - 5.00)'
},
display_order: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Thứ tự hiển thị'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
}, {
tableName: 'games',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['type'] },
{ fields: ['is_active'] },
{ fields: ['is_premium'] },
{ fields: ['display_order'] },
{ fields: ['difficulty_level'] },
],
});
module.exports = Game;

100
models/Grade.js Normal file
View File

@@ -0,0 +1,100 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* Grades Model - Điểm số chi tiết của học sinh
* Bảng lớn nhất trong hệ thống
*/
const Grade = sequelize.define('grades', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
grade_item_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'grade_items',
key: 'id',
},
comment: 'ID cột điểm',
},
student_id: {
type: DataTypes.UUID,
allowNull: false,
comment: 'ID học sinh',
},
class_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'classes',
key: 'id',
},
comment: 'ID lớp học',
},
score: {
type: DataTypes.DECIMAL(5, 2),
comment: 'Điểm số',
validate: {
min: 0,
max: 100,
},
},
grade_level: {
type: DataTypes.STRING(5),
comment: 'Xếp loại (A, B, C, D, F)',
},
comment: {
type: DataTypes.TEXT,
comment: 'Nhận xét của giáo viên',
},
graded_by: {
type: DataTypes.UUID,
comment: 'ID giáo viên chấm điểm',
},
graded_at: {
type: DataTypes.DATE,
comment: 'Thời gian chấm điểm',
},
is_published: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Đã công bố cho học sinh/phụ huynh',
},
published_at: {
type: DataTypes.DATE,
comment: 'Thời gian công bố',
},
is_locked: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Khóa điểm, không cho sửa',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'grades',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['grade_item_id', 'student_id'], unique: true },
{ fields: ['student_id', 'class_id'] },
{ fields: ['class_id'] },
{ fields: ['graded_by'] },
{ fields: ['is_published'] },
{ fields: ['created_at'] },
],
comment: 'Bảng điểm chi tiết - Dữ liệu lớn nhất hệ thống',
});
module.exports = Grade;

19
models/GradeCategory.js Normal file
View File

@@ -0,0 +1,19 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const GradeCategory = sequelize.define('grade_categories', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
category_code: { type: DataTypes.STRING(20), allowNull: false },
category_name: { type: DataTypes.STRING(100), allowNull: false },
subject_id: { type: DataTypes.UUID },
weight: { type: DataTypes.DECIMAL(5, 2), comment: 'Trọng số %' },
description: { type: DataTypes.TEXT },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'grade_categories',
timestamps: true,
underscored: true,
});
module.exports = GradeCategory;

21
models/GradeHistory.js Normal file
View File

@@ -0,0 +1,21 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const GradeHistory = sequelize.define('grade_histories', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
grade_id: { type: DataTypes.UUID, allowNull: false },
student_id: { type: DataTypes.UUID, allowNull: false },
old_score: { type: DataTypes.DECIMAL(5, 2) },
new_score: { type: DataTypes.DECIMAL(5, 2) },
changed_by: { type: DataTypes.UUID },
change_reason: { type: DataTypes.TEXT },
changed_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'grade_histories',
timestamps: true,
underscored: true,
});
module.exports = GradeHistory;

29
models/GradeItem.js Normal file
View File

@@ -0,0 +1,29 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const GradeItem = sequelize.define('grade_items', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
item_name: { type: DataTypes.STRING(200), allowNull: false },
category_id: { type: DataTypes.UUID, allowNull: false },
class_id: { type: DataTypes.UUID, allowNull: false },
max_score: { type: DataTypes.DECIMAL(5, 2), defaultValue: 100 },
exam_date: { type: DataTypes.DATE },
description: { type: DataTypes.TEXT },
is_premium: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung premium' },
is_training: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung đào tạo nhân sự' },
is_public: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung công khai' },
min_subscription_tier: { type: DataTypes.STRING(50), comment: 'Gói tối thiểu' },
min_interaction_time: { type: DataTypes.INTEGER, defaultValue: 0, comment: 'Thời gian tương tác tối thiểu (giây)' },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'grade_items',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['is_premium'] },
{ fields: ['is_training'] },
],
});
module.exports = GradeItem;

20
models/GradeSummary.js Normal file
View File

@@ -0,0 +1,20 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const GradeSummary = sequelize.define('grade_summaries', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
student_id: { type: DataTypes.UUID, allowNull: false },
subject_id: { type: DataTypes.UUID, allowNull: false },
academic_year_id: { type: DataTypes.UUID, allowNull: false },
semester: { type: DataTypes.INTEGER },
average_score: { type: DataTypes.DECIMAL(5, 2) },
final_grade: { type: DataTypes.STRING(5) },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'grade_summaries',
timestamps: true,
underscored: true,
});
module.exports = GradeSummary;

22
models/LeaveRequest.js Normal file
View File

@@ -0,0 +1,22 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const LeaveRequest = sequelize.define('leave_requests', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
student_id: { type: DataTypes.UUID, allowNull: false },
requested_by: { type: DataTypes.UUID, allowNull: false, comment: 'Parent user_id' },
leave_from: { type: DataTypes.DATE, allowNull: false },
leave_to: { type: DataTypes.DATE, allowNull: false },
reason: { type: DataTypes.TEXT, allowNull: false },
status: { type: DataTypes.ENUM('pending', 'approved', 'rejected'), defaultValue: 'pending' },
approved_by: { type: DataTypes.UUID },
approved_at: { type: DataTypes.DATE },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'leave_requests',
timestamps: true,
underscored: true,
});
module.exports = LeaveRequest;

101
models/Lesson.js Normal file
View File

@@ -0,0 +1,101 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* Lesson Model - Bài học trong chương
* Một Chapter có nhiều Lesson
*/
const Lesson = sequelize.define('lessons', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
chapter_id: {
type: DataTypes.UUID,
allowNull: false,
comment: 'ID của chương'
},
lesson_number: {
type: DataTypes.INTEGER,
allowNull: false,
comment: 'Số thứ tự bài học (1, 2, 3...)'
},
lesson_title: {
type: DataTypes.STRING(200),
allowNull: false,
comment: 'Tiêu đề bài học'
},
lesson_type: {
type: DataTypes.ENUM('json_content', 'url_content'),
defaultValue: 'json_content',
comment: 'Loại bài học: json_content (nội dung JSON) hoặc url_content (URL)'
},
lesson_description: {
type: DataTypes.TEXT,
comment: 'Mô tả bài học'
},
// Dạng 1: JSON Content - Nội dung học tập dạng JSON
content_json: {
type: DataTypes.JSON,
comment: 'Nội dung học tập dạng JSON: text, quiz, interactive, assignment, etc.'
},
// Dạng 2: URL Content - Chứa link external
content_url: {
type: DataTypes.STRING(500),
comment: 'URL nội dung: video, audio, document, external link'
},
content_type: {
type: DataTypes.STRING(50),
comment: 'Loại content cho URL: video, audio, pdf, external_link, youtube, etc.'
},
duration_minutes: {
type: DataTypes.INTEGER,
comment: 'Thời lượng (phút)'
},
is_published: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Đã xuất bản'
},
is_free: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Cho phép học thử miễn phí'
},
display_order: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Thứ tự hiển thị'
},
thumbnail_url: {
type: DataTypes.STRING(500),
comment: 'URL ảnh thumbnail'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
}, {
tableName: 'lessons',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['chapter_id'] },
{ fields: ['chapter_id', 'lesson_number'], unique: true },
{ fields: ['display_order'] },
{ fields: ['is_published'] },
{ fields: ['lesson_type'] },
],
});
module.exports = Lesson;

36
models/Message.js Normal file
View File

@@ -0,0 +1,36 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Message = sequelize.define('messages', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
school_id: { type: DataTypes.UUID },
sender_id: { type: DataTypes.UUID, allowNull: false },
receiver_id: { type: DataTypes.UUID },
receiver_role: { type: DataTypes.STRING(50) },
subject: { type: DataTypes.STRING(255) },
content: { type: DataTypes.TEXT, allowNull: false },
message_type: {
type: DataTypes.ENUM('direct', 'broadcast', 'class_message', 'parent_teacher'),
defaultValue: 'direct'
},
parent_message_id: { type: DataTypes.UUID },
attachments: { type: DataTypes.JSON },
is_read: { type: DataTypes.BOOLEAN, defaultValue: false },
read_at: { type: DataTypes.DATE },
is_archived: { type: DataTypes.BOOLEAN, defaultValue: false },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'messages',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['school_id'] },
{ fields: ['sender_id'] },
{ fields: ['receiver_id'] },
{ fields: ['is_read'] },
{ fields: ['parent_message_id'] },
],
});
module.exports = Message;

40
models/Notification.js Normal file
View File

@@ -0,0 +1,40 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Notification = sequelize.define('notifications', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
school_id: { type: DataTypes.UUID },
title: { type: DataTypes.STRING(255), allowNull: false },
content: { type: DataTypes.TEXT },
notification_type: {
type: DataTypes.ENUM('announcement', 'event', 'emergency', 'reminder', 'grade_update', 'attendance_alert'),
defaultValue: 'announcement'
},
priority: {
type: DataTypes.ENUM('low', 'normal', 'high', 'urgent'),
defaultValue: 'normal'
},
target_role: { type: DataTypes.STRING(50) },
target_users: { type: DataTypes.JSON },
scheduled_at: { type: DataTypes.DATE },
sent_at: { type: DataTypes.DATE },
created_by: { type: DataTypes.UUID },
status: {
type: DataTypes.ENUM('draft', 'scheduled', 'sent', 'cancelled'),
defaultValue: 'draft'
},
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'notifications',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['school_id'] },
{ fields: ['notification_type'] },
{ fields: ['status'] },
{ fields: ['scheduled_at'] },
],
});
module.exports = Notification;

33
models/NotificationLog.js Normal file
View File

@@ -0,0 +1,33 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const NotificationLog = sequelize.define('notification_logs', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
notification_id: { type: DataTypes.UUID, allowNull: false },
user_id: { type: DataTypes.UUID, allowNull: false },
sent_at: { type: DataTypes.DATE },
read_at: { type: DataTypes.DATE },
delivery_status: {
type: DataTypes.ENUM('pending', 'sent', 'delivered', 'failed', 'read'),
defaultValue: 'pending'
},
delivery_channel: {
type: DataTypes.ENUM('in_app', 'email', 'sms', 'push'),
defaultValue: 'in_app'
},
error_message: { type: DataTypes.TEXT },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'notification_logs',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['notification_id'] },
{ fields: ['user_id'] },
{ fields: ['delivery_status'] },
{ fields: ['read_at'] },
],
});
module.exports = NotificationLog;

View File

@@ -0,0 +1,95 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* ParentAssignedTask Model - Phụ huynh gán bài tập cho con
*/
const ParentAssignedTask = sequelize.define('parent_assigned_tasks', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
parent_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users_auth',
key: 'id',
},
comment: 'Phụ huynh gán',
},
student_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'student_details',
key: 'id',
},
comment: 'Học sinh được gán',
},
grade_item_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'grade_items',
key: 'id',
},
comment: 'Bài tập được gán',
},
assigned_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: 'Thời gian gán',
},
due_date: {
type: DataTypes.DATEONLY,
comment: 'Hạn hoàn thành',
},
status: {
type: DataTypes.ENUM('pending', 'in_progress', 'completed', 'overdue'),
defaultValue: 'pending',
comment: 'Trạng thái',
},
message: {
type: DataTypes.TEXT,
comment: 'Lời nhắn của phụ huynh',
},
priority: {
type: DataTypes.ENUM('low', 'normal', 'high'),
defaultValue: 'normal',
comment: 'Độ ưu tiên',
},
completion_date: {
type: DataTypes.DATE,
comment: 'Ngày hoàn thành',
},
student_notes: {
type: DataTypes.TEXT,
comment: 'Ghi chú của học sinh khi hoàn thành',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'parent_assigned_tasks',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['parent_id'] },
{ fields: ['student_id'] },
{ fields: ['status'] },
{ fields: ['due_date'] },
],
comment: 'Bảng quản lý bài tập phụ huynh gán cho con',
});
module.exports = ParentAssignedTask;

View File

@@ -0,0 +1,24 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const ParentStudentMap = sequelize.define('parent_student_map', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
parent_id: { type: DataTypes.UUID, allowNull: false },
student_id: { type: DataTypes.UUID, allowNull: false },
relationship: { type: DataTypes.ENUM('father', 'mother', 'guardian', 'other'), allowNull: false },
is_primary_contact: { type: DataTypes.BOOLEAN, defaultValue: false },
can_assign_tasks: { type: DataTypes.BOOLEAN, defaultValue: true, comment: 'Quyền gán bài tập' },
can_view_detailed_grades: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Xem điểm chi tiết' },
can_communicate_teacher: { type: DataTypes.BOOLEAN, defaultValue: true, comment: 'Liên hệ giáo viên' },
permission_level: { type: DataTypes.ENUM('limited', 'standard', 'full'), defaultValue: 'standard', comment: 'Cấp quyền' },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'parent_student_map',
timestamps: false,
underscored: true,
indexes: [
{ fields: ['can_assign_tasks', 'can_view_detailed_grades'] },
],
});
module.exports = ParentStudentMap;

24
models/Permission.js Normal file
View File

@@ -0,0 +1,24 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Permission = sequelize.define('permissions', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
permission_code: { type: DataTypes.STRING(50), unique: true, allowNull: false },
permission_name: { type: DataTypes.STRING(100), allowNull: false },
permission_description: { type: DataTypes.TEXT },
resource: { type: DataTypes.STRING(50), comment: 'Tài nguyên (students, grades, etc.)' },
action: { type: DataTypes.STRING(50), comment: 'Hành động (view, create, edit, delete)' },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'permissions',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['permission_code'], unique: true },
{ fields: ['resource', 'action'] },
],
});
module.exports = Permission;

64
models/Role.js Normal file
View File

@@ -0,0 +1,64 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* Roles Model - Danh sách 9 vai trò trong hệ thống
*/
const Role = sequelize.define('roles', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
role_code: {
type: DataTypes.STRING(50),
unique: true,
allowNull: false,
comment: 'Mã vai trò (system_admin, center_manager, etc.)',
},
role_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'Tên vai trò',
},
role_description: {
type: DataTypes.TEXT,
comment: 'Mô tả vai trò',
},
level: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Cấp độ vai trò (0=cao nhất)',
},
is_system_role: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Vai trò hệ thống không thể xóa',
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'roles',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['role_code'], unique: true },
{ fields: ['level'] },
{ fields: ['is_active'] },
],
comment: 'Bảng quản lý vai trò (9 vai trò)',
});
module.exports = Role;

18
models/RolePermission.js Normal file
View File

@@ -0,0 +1,18 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const RolePermission = sequelize.define('role_permissions', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
role_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'roles', key: 'id' } },
permission_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'permissions', key: 'id' } },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'role_permissions',
timestamps: false,
underscored: true,
indexes: [
{ fields: ['role_id', 'permission_id'], unique: true },
],
});
module.exports = RolePermission;

20
models/Room.js Normal file
View File

@@ -0,0 +1,20 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Room = sequelize.define('rooms', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
room_code: { type: DataTypes.STRING(20), allowNull: false },
room_name: { type: DataTypes.STRING(100), allowNull: false },
school_id: { type: DataTypes.UUID, allowNull: false },
room_type: { type: DataTypes.ENUM('classroom', 'lab', 'library', 'gym', 'other') },
capacity: { type: DataTypes.INTEGER },
floor: { type: DataTypes.INTEGER },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'rooms',
timestamps: true,
underscored: true,
});
module.exports = Room;

83
models/School.js Normal file
View File

@@ -0,0 +1,83 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* Schools Model - Quản lý thông tin 200 trường
*/
const School = sequelize.define('schools', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
school_code: {
type: DataTypes.STRING(20),
unique: false,
allowNull: false,
comment: 'Mã trường duy nhất',
},
school_name: {
type: DataTypes.STRING(200),
allowNull: false,
comment: 'Tên trường',
},
school_type: {
type: DataTypes.ENUM('preschool', 'primary', 'secondary', 'high_school'),
allowNull: false,
comment: 'Loại trường',
},
address: {
type: DataTypes.TEXT,
comment: 'Địa chỉ trường',
},
city: {
type: DataTypes.STRING(100),
comment: 'Thành phố',
},
district: {
type: DataTypes.STRING(100),
comment: 'Quận/Huyện',
},
phone: {
type: DataTypes.STRING(20),
comment: 'Số điện thoại',
},
logo: {
type: DataTypes.TEXT,
allowNull: true,
comment: 'URL logo của trường',
},
config: {
type: DataTypes.JSON,
defaultValue: {},
comment: 'Cấu hình riêng của trường (JSON)',
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'Trạng thái hoạt động',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'schools',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['school_code'], unique: true },
{ fields: ['school_type'] },
{ fields: ['is_active'] },
{ fields: ['city', 'district'] },
],
comment: 'Bảng quản lý thông tin 200 trường học',
});
module.exports = School;

View File

@@ -0,0 +1,92 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* StaffAchievement Model - Lưu trữ chứng chỉ và thành tích đào tạo
*/
const StaffAchievement = sequelize.define('staff_achievements', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
staff_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users_auth',
key: 'id',
},
comment: 'Nhân viên',
},
course_id: {
type: DataTypes.UUID,
references: {
model: 'subjects',
key: 'id',
},
comment: 'Khóa học trong hệ thống',
},
course_name: {
type: DataTypes.STRING(200),
allowNull: false,
comment: 'Tên khóa học',
},
completion_date: {
type: DataTypes.DATEONLY,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: 'Ngày hoàn thành',
},
certificate_url: {
type: DataTypes.STRING(500),
comment: 'URL file chứng chỉ',
},
certificate_code: {
type: DataTypes.STRING(100),
comment: 'Mã chứng chỉ',
},
type: {
type: DataTypes.ENUM('mandatory', 'self_study', 'external'),
allowNull: false,
comment: 'Loại: bắt buộc, tự học, bên ngoài',
},
score: {
type: DataTypes.DECIMAL(5, 2),
comment: 'Điểm số (0-100)',
},
total_hours: {
type: DataTypes.INTEGER,
comment: 'Tổng số giờ học',
},
verified_by: {
type: DataTypes.UUID,
references: {
model: 'users_auth',
key: 'id',
},
comment: 'Admin xác nhận',
},
verified_at: {
type: DataTypes.DATE,
comment: 'Thời gian xác nhận',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'staff_achievements',
timestamps: false,
underscored: true,
indexes: [
{ fields: ['staff_id'] },
{ fields: ['type'] },
{ fields: ['completion_date'] },
{ fields: ['certificate_code'] },
],
comment: 'Bảng lưu trữ chứng chỉ và thành tích đào tạo',
});
module.exports = StaffAchievement;

21
models/StaffContract.js Normal file
View File

@@ -0,0 +1,21 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const StaffContract = sequelize.define('staff_contracts', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
teacher_id: { type: DataTypes.UUID, allowNull: false },
school_id: { type: DataTypes.UUID, allowNull: false },
contract_type: { type: DataTypes.ENUM('full_time', 'part_time', 'contract'), allowNull: false },
start_date: { type: DataTypes.DATE, allowNull: false },
end_date: { type: DataTypes.DATE },
salary: { type: DataTypes.DECIMAL(12, 2) },
status: { type: DataTypes.ENUM('active', 'expired', 'terminated'), defaultValue: 'active' },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'staff_contracts',
timestamps: true,
underscored: true,
});
module.exports = StaffContract;

View File

@@ -0,0 +1,90 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* StaffTrainingAssignment Model - Quản lý phân công đào tạo nhân sự
*/
const StaffTrainingAssignment = sequelize.define('staff_training_assignments', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
staff_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users_auth',
key: 'id',
},
comment: 'Nhân viên được giao',
},
subject_id: {
type: DataTypes.UUID,
references: {
model: 'subjects',
key: 'id',
},
comment: 'Khóa học/môn học bắt buộc',
},
course_name: {
type: DataTypes.STRING(200),
comment: 'Tên khóa học (nếu không có subject_id)',
},
assigned_by: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users_auth',
key: 'id',
},
comment: 'Admin giao nhiệm vụ',
},
assigned_date: {
type: DataTypes.DATEONLY,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: 'Ngày giao',
},
deadline: {
type: DataTypes.DATEONLY,
comment: 'Hạn hoàn thành',
},
status: {
type: DataTypes.ENUM('pending', 'in_progress', 'completed', 'overdue'),
defaultValue: 'pending',
comment: 'Trạng thái',
},
priority: {
type: DataTypes.ENUM('low', 'normal', 'high', 'urgent'),
defaultValue: 'normal',
comment: 'Độ ưu tiên',
},
notes: {
type: DataTypes.TEXT,
comment: 'Ghi chú',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'staff_training_assignments',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['staff_id'] },
{ fields: ['status'] },
{ fields: ['deadline'] },
{ fields: ['assigned_by'] },
],
comment: 'Bảng quản lý phân công đào tạo nhân sự',
});
module.exports = StaffTrainingAssignment;

24
models/StudentDetail.js Normal file
View File

@@ -0,0 +1,24 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const StudentDetail = sequelize.define('student_details', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
user_id: { type: DataTypes.UUID, unique: true, allowNull: false, references: { model: 'user_profiles', key: 'user_id' } },
student_code: { type: DataTypes.STRING(50), unique: true, allowNull: false },
enrollment_date: { type: DataTypes.DATE },
current_class_id: { type: DataTypes.UUID, references: { model: 'classes', key: 'id' } },
status: { type: DataTypes.ENUM('active', 'on_leave', 'graduated', 'dropped'), defaultValue: 'active' },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'student_details',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['user_id'], unique: true },
{ fields: ['student_code'], unique: true },
{ fields: ['current_class_id'] },
],
});
module.exports = StudentDetail;

29
models/Subject.js Normal file
View File

@@ -0,0 +1,29 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Subject = sequelize.define('subjects', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
subject_code: { type: DataTypes.STRING(20), unique: true, allowNull: false },
subject_name: { type: DataTypes.STRING(100), allowNull: false },
subject_name_en: { type: DataTypes.STRING(100) },
description: { type: DataTypes.TEXT },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
is_premium: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung premium' },
is_training: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung đào tạo nhân sự' },
is_public: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung tự học công khai' },
required_role: { type: DataTypes.STRING(50), comment: 'Role yêu cầu' },
min_subscription_tier: { type: DataTypes.STRING(50), comment: 'Gói tối thiểu: basic, premium, vip' },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'subjects',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['is_premium'] },
{ fields: ['is_training'] },
{ fields: ['is_public'] },
],
});
module.exports = Subject;

View File

@@ -0,0 +1,69 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* SubscriptionPlan Model - Định nghĩa các gói thẻ tháng
*/
const SubscriptionPlan = sequelize.define('subscription_plans', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: 'Tên gói: VIP Học sinh, Premium Giáo viên, etc.',
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
comment: 'Giá tiền (VND)',
},
duration_days: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 30,
comment: 'Thời hạn gói (ngày)',
},
target_role: {
type: DataTypes.STRING(50),
allowNull: false,
comment: 'Role được phép mua: student, parent, teacher',
},
description: {
type: DataTypes.TEXT,
comment: 'Mô tả gói',
},
features: {
type: DataTypes.JSON,
comment: 'Danh sách tính năng: ["detailed_reports", "priority_support"]',
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'Trạng thái kích hoạt',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'subscription_plans',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['target_role'] },
{ fields: ['is_active'] },
{ fields: ['price'] },
],
comment: 'Bảng định nghĩa các gói thẻ tháng',
});
module.exports = SubscriptionPlan;

31
models/TeacherDetail.js Normal file
View File

@@ -0,0 +1,31 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const TeacherDetail = sequelize.define('teacher_details', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
user_id: { type: DataTypes.UUID, unique: true, allowNull: false, references: { model: 'user_profiles', key: 'user_id' } },
teacher_code: { type: DataTypes.STRING(50), unique: true, allowNull: false },
teacher_type: { type: DataTypes.ENUM('foreign', 'assistant', 'homeroom', 'specialist'), allowNull: false },
qualification: { type: DataTypes.STRING(200) },
specialization: { type: DataTypes.STRING(200) },
hire_date: { type: DataTypes.DATE },
status: { type: DataTypes.ENUM('active', 'on_leave', 'resigned'), defaultValue: 'active' },
skill_tags: { type: DataTypes.JSON, comment: 'Array of skills: [{"name": "Python", "level": "expert"}]' },
certifications: { type: DataTypes.JSON, comment: 'Array of certificates' },
training_hours: { type: DataTypes.INTEGER, defaultValue: 0, comment: 'Tổng giờ đào tạo' },
last_training_date: { type: DataTypes.DATEONLY, comment: 'Ngày đào tạo gần nhất' },
training_score_avg: { type: DataTypes.DECIMAL(5, 2), comment: 'Điểm TB các khóa học' },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'teacher_details',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['user_id'], unique: true },
{ fields: ['teacher_code'], unique: true },
{ fields: ['teacher_type'] },
],
});
module.exports = TeacherDetail;

27
models/UserAssignment.js Normal file
View File

@@ -0,0 +1,27 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const UserAssignment = sequelize.define('user_assignments', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
user_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'users_auth', key: 'id' } },
role_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'roles', key: 'id' } },
school_id: { type: DataTypes.UUID, references: { model: 'schools', key: 'id' } },
class_id: { type: DataTypes.UUID, references: { model: 'classes', key: 'id' } },
is_primary: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Vai trò chính' },
valid_from: { type: DataTypes.DATE },
valid_until: { type: DataTypes.DATE },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
}, {
tableName: 'user_assignments',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['user_id', 'role_id', 'school_id'] },
{ fields: ['school_id'] },
{ fields: ['is_active'] },
],
});
module.exports = UserAssignment;

79
models/UserProfile.js Normal file
View File

@@ -0,0 +1,79 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const UserProfile = sequelize.define('user_profiles', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
user_id: {
type: DataTypes.UUID,
unique: true,
allowNull: false,
references: {
model: 'users_auth',
key: 'id',
},
},
full_name: {
type: DataTypes.STRING(100),
allowNull: false,
},
first_name: {
type: DataTypes.STRING(50),
},
last_name: {
type: DataTypes.STRING(50),
},
date_of_birth: {
type: DataTypes.DATE,
},
gender: {
type: DataTypes.ENUM('male', 'female', 'other'),
},
phone: {
type: DataTypes.STRING(20),
},
avatar_url: {
type: DataTypes.STRING(500),
},
address: {
type: DataTypes.TEXT,
},
school_id: {
type: DataTypes.UUID,
},
city: {
type: DataTypes.STRING(100),
},
district: {
type: DataTypes.STRING(100),
},
etc: {
type: DataTypes.JSON,
defaultValue: {},
comment: 'Thông tin bổ sung (JSONB)',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'user_profiles',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['user_id'], unique: true },
{ fields: ['full_name'] },
{ fields: ['phone'] },
],
});
module.exports = UserProfile;

View File

@@ -0,0 +1,87 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* UserSubscription Model - Quản lý subscription của từng user
*/
const UserSubscription = sequelize.define('user_subscriptions', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users_auth',
key: 'id',
},
comment: 'User mua gói',
},
plan_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'subscription_plans',
key: 'id',
},
comment: 'Gói đã mua',
},
start_date: {
type: DataTypes.DATEONLY,
allowNull: false,
comment: 'Ngày bắt đầu',
},
end_date: {
type: DataTypes.DATEONLY,
allowNull: false,
comment: 'Ngày kết thúc',
},
status: {
type: DataTypes.ENUM('active', 'expired', 'cancelled'),
defaultValue: 'active',
allowNull: false,
comment: 'Trạng thái subscription',
},
transaction_id: {
type: DataTypes.STRING(100),
comment: 'Mã giao dịch để đối soát',
},
payment_method: {
type: DataTypes.STRING(50),
comment: 'Phương thức thanh toán: momo, vnpay, banking',
},
payment_amount: {
type: DataTypes.DECIMAL(10, 2),
comment: 'Số tiền đã thanh toán',
},
auto_renew: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Tự động gia hạn',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'user_subscriptions',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['user_id'] },
{ fields: ['status'] },
{ fields: ['end_date'] },
{ fields: ['transaction_id'] },
],
comment: 'Bảng quản lý subscription của user',
});
module.exports = UserSubscription;

112
models/UsersAuth.js Normal file
View File

@@ -0,0 +1,112 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* UsersAuth Model - Thông tin đăng nhập người dùng
*/
const UsersAuth = sequelize.define('users_auth', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
username: {
type: DataTypes.STRING(50),
unique: true,
allowNull: false,
comment: 'Tên đăng nhập duy nhất',
},
email: {
type: DataTypes.STRING(100),
unique: true,
allowNull: false,
validate: {
isEmail: true,
},
comment: 'Email đăng nhập',
},
password_hash: {
type: DataTypes.STRING(255),
allowNull: false,
comment: 'Mật khẩu đã hash',
},
salt: {
type: DataTypes.STRING(255),
allowNull: false,
comment: 'Salt cho mật khẩu',
},
qr_version: {
type: DataTypes.INTEGER,
defaultValue: 1,
comment: 'Phiên bản mã QR cho điểm danh',
},
qr_secret: {
type: DataTypes.STRING(255),
comment: 'Secret key cho QR code',
},
current_session_id: {
type: DataTypes.STRING(255),
comment: 'ID phiên đăng nhập hiện tại',
},
last_login: {
type: DataTypes.DATE,
comment: 'Lần đăng nhập cuối',
},
last_login_ip: {
type: DataTypes.STRING(50),
comment: 'IP đăng nhập cuối',
},
login_count: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Số lần đăng nhập',
},
login_attempts: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Số lần thử đăng nhập',
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'Trạng thái tài khoản',
},
is_locked: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Tài khoản bị khóa',
},
failed_login_attempts: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Số lần đăng nhập thất bại',
},
locked_until: {
type: DataTypes.DATE,
comment: 'Khóa tài khoản đến',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'users_auth',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['username'], unique: true },
{ fields: ['email'], unique: true },
{ fields: ['is_active'] },
{ fields: ['qr_version'] },
{ fields: ['current_session_id'] },
],
comment: 'Bảng quản lý thông tin đăng nhập người dùng',
});
module.exports = UsersAuth;

271
models/index.js Normal file
View File

@@ -0,0 +1,271 @@
const { sequelize } = require('../config/database');
/**
* Import all models
*/
// Group 1: System & Authorization
const School = require('./School');
const UsersAuth = require('./UsersAuth');
const Role = require('./Role');
const Permission = require('./Permission');
const RolePermission = require('./RolePermission');
const UserAssignment = require('./UserAssignment');
// Group 2: User Profiles
const UserProfile = require('./UserProfile');
const StudentDetail = require('./StudentDetail');
const TeacherDetail = require('./TeacherDetail');
const ParentStudentMap = require('./ParentStudentMap');
const StaffContract = require('./StaffContract');
// Group 3: Academic Structure
const AcademicYear = require('./AcademicYear');
const Subject = require('./Subject');
const Class = require('./Class');
const ClassSchedule = require('./ClassSchedule');
const Room = require('./Room');
// Group 3.1: Learning Content (NEW)
const Chapter = require('./Chapter');
const Lesson = require('./Lesson');
const Game = require('./Game');
// Group 4: Attendance
const AttendanceLog = require('./AttendanceLog');
const AttendanceDaily = require('./AttendanceDaily');
const LeaveRequest = require('./LeaveRequest');
// Group 5: Gradebook
const GradeCategory = require('./GradeCategory');
const GradeItem = require('./GradeItem');
const Grade = require('./Grade');
const GradeSummary = require('./GradeSummary');
const GradeHistory = require('./GradeHistory');
// Group 6: Notifications
const Notification = require('./Notification');
const NotificationLog = require('./NotificationLog');
const Message = require('./Message');
const AuditLog = require('./AuditLog');
// Group 7: Subscription & Training
const SubscriptionPlan = require('./SubscriptionPlan');
const UserSubscription = require('./UserSubscription');
const StaffTrainingAssignment = require('./StaffTrainingAssignment');
const StaffAchievement = require('./StaffAchievement');
const ParentAssignedTask = require('./ParentAssignedTask');
/**
* Define relationships between models
*/
const setupRelationships = () => {
// UsersAuth relationships
UsersAuth.hasOne(UserProfile, { foreignKey: 'user_id', as: 'profile' });
UsersAuth.hasMany(UserAssignment, { foreignKey: 'user_id', as: 'assignments' });
// UserProfile relationships
UserProfile.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'auth' });
UserProfile.belongsTo(School, { foreignKey: 'school_id', as: 'school' });
UserProfile.hasOne(StudentDetail, { foreignKey: 'user_id', as: 'studentDetail' });
UserProfile.hasOne(TeacherDetail, { foreignKey: 'user_id', as: 'teacherDetail' });
// School relationships
School.hasMany(Class, { foreignKey: 'school_id', as: 'classes' });
School.hasMany(Room, { foreignKey: 'school_id', as: 'rooms' });
School.hasMany(UserAssignment, { foreignKey: 'school_id', as: 'assignments' });
School.hasMany(AttendanceLog, { foreignKey: 'school_id', as: 'attendanceLogs' });
// Role & Permission relationships
Role.belongsToMany(Permission, {
through: RolePermission,
foreignKey: 'role_id',
otherKey: 'permission_id',
as: 'permissions'
});
Permission.belongsToMany(Role, {
through: RolePermission,
foreignKey: 'permission_id',
otherKey: 'role_id',
as: 'roles'
});
// UserAssignment relationships
UserAssignment.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
UserAssignment.belongsTo(Role, { foreignKey: 'role_id', as: 'role' });
UserAssignment.belongsTo(School, { foreignKey: 'school_id', as: 'school' });
UserAssignment.belongsTo(Class, { foreignKey: 'class_id', as: 'class' });
// StudentDetail relationships
StudentDetail.belongsTo(UserProfile, {
foreignKey: 'user_id',
targetKey: 'user_id',
as: 'profile'
});
StudentDetail.belongsTo(Class, { foreignKey: 'current_class_id', as: 'currentClass' });
StudentDetail.belongsToMany(UserProfile, {
through: ParentStudentMap,
foreignKey: 'student_id',
otherKey: 'parent_id',
as: 'parents'
});
// TeacherDetail relationships
TeacherDetail.belongsTo(UserProfile, { foreignKey: 'user_id', as: 'profile' });
TeacherDetail.hasMany(StaffContract, { foreignKey: 'teacher_id', as: 'contracts' });
// Class relationships
Class.belongsTo(School, { foreignKey: 'school_id', as: 'school' });
Class.belongsTo(AcademicYear, { foreignKey: 'academic_year_id', as: 'academicYear' });
Class.belongsTo(TeacherDetail, { foreignKey: 'homeroom_teacher_id', as: 'homeroomTeacher' });
Class.hasMany(StudentDetail, { foreignKey: 'current_class_id', as: 'students' });
Class.hasMany(ClassSchedule, { foreignKey: 'class_id', as: 'schedules' });
Class.hasMany(Grade, { foreignKey: 'class_id', as: 'grades' });
// ClassSchedule relationships
ClassSchedule.belongsTo(Class, { foreignKey: 'class_id', as: 'class' });
ClassSchedule.belongsTo(Subject, { foreignKey: 'subject_id', as: 'subject' });
ClassSchedule.belongsTo(Room, { foreignKey: 'room_id', as: 'room' });
ClassSchedule.belongsTo(TeacherDetail, { foreignKey: 'teacher_id', as: 'teacher' });
// Learning Content relationships (NEW)
// Subject -> Chapter (1:N)
Subject.hasMany(Chapter, { foreignKey: 'subject_id', as: 'chapters' });
Chapter.belongsTo(Subject, { foreignKey: 'subject_id', as: 'subject' });
// Chapter -> Lesson (1:N)
Chapter.hasMany(Lesson, { foreignKey: 'chapter_id', as: 'lessons' });
Lesson.belongsTo(Chapter, { foreignKey: 'chapter_id', as: 'chapter' });
// Attendance relationships
AttendanceLog.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
AttendanceLog.belongsTo(School, { foreignKey: 'school_id', as: 'school' });
AttendanceDaily.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
AttendanceDaily.belongsTo(School, { foreignKey: 'school_id', as: 'school' });
AttendanceDaily.belongsTo(Class, { foreignKey: 'class_id', as: 'class' });
LeaveRequest.belongsTo(StudentDetail, { foreignKey: 'student_id', as: 'student' });
LeaveRequest.belongsTo(UserProfile, { foreignKey: 'requested_by', as: 'requester' });
LeaveRequest.belongsTo(TeacherDetail, { foreignKey: 'approved_by', as: 'approver' });
// Grade relationships
GradeCategory.belongsTo(Subject, { foreignKey: 'subject_id', as: 'subject' });
GradeCategory.hasMany(GradeItem, { foreignKey: 'category_id', as: 'items' });
GradeItem.belongsTo(GradeCategory, { foreignKey: 'category_id', as: 'category' });
GradeItem.belongsTo(Class, { foreignKey: 'class_id', as: 'class' });
GradeItem.hasMany(Grade, { foreignKey: 'grade_item_id', as: 'grades' });
Grade.belongsTo(GradeItem, { foreignKey: 'grade_item_id', as: 'gradeItem' });
Grade.belongsTo(StudentDetail, { foreignKey: 'student_id', as: 'student' });
Grade.belongsTo(Class, { foreignKey: 'class_id', as: 'class' });
Grade.belongsTo(TeacherDetail, { foreignKey: 'graded_by', as: 'gradedBy' });
Grade.hasMany(GradeHistory, { foreignKey: 'grade_id', as: 'history' });
GradeSummary.belongsTo(StudentDetail, { foreignKey: 'student_id', as: 'student' });
GradeSummary.belongsTo(Subject, { foreignKey: 'subject_id', as: 'subject' });
GradeSummary.belongsTo(AcademicYear, { foreignKey: 'academic_year_id', as: 'academicYear' });
// Notification relationships
Notification.hasMany(NotificationLog, { foreignKey: 'notification_id', as: 'logs' });
NotificationLog.belongsTo(Notification, { foreignKey: 'notification_id', as: 'notification' });
NotificationLog.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
Message.belongsTo(UserProfile, { foreignKey: 'sender_id', as: 'sender' });
Message.belongsTo(UserProfile, { foreignKey: 'recipient_id', as: 'recipient' });
// Subscription relationships
SubscriptionPlan.hasMany(UserSubscription, { foreignKey: 'plan_id', as: 'subscriptions' });
UserSubscription.belongsTo(SubscriptionPlan, { foreignKey: 'plan_id', as: 'plan' });
UserSubscription.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
// Training relationships
StaffTrainingAssignment.belongsTo(UsersAuth, { foreignKey: 'staff_id', as: 'staff' });
StaffTrainingAssignment.belongsTo(UsersAuth, { foreignKey: 'assigned_by', as: 'assigner' });
StaffTrainingAssignment.belongsTo(Subject, { foreignKey: 'subject_id', as: 'subject' });
StaffAchievement.belongsTo(UsersAuth, { foreignKey: 'staff_id', as: 'staff' });
StaffAchievement.belongsTo(Subject, { foreignKey: 'course_id', as: 'course' });
StaffAchievement.belongsTo(UsersAuth, { foreignKey: 'verified_by', as: 'verifier' });
// Parent assigned task relationships
ParentAssignedTask.belongsTo(UsersAuth, { foreignKey: 'parent_id', as: 'parent' });
ParentAssignedTask.belongsTo(StudentDetail, { foreignKey: 'student_id', as: 'student' });
ParentAssignedTask.belongsTo(GradeItem, { foreignKey: 'grade_item_id', as: 'gradeItem' });
console.log('✅ Model relationships configured');
};
/**
* Sync database (only in development)
*/
const syncDatabase = async (options = {}) => {
try {
await sequelize.sync(options);
console.log('✅ Database synchronized');
} catch (error) {
console.error('❌ Database sync failed:', error.message);
throw error;
}
};
/**
* Export all models and utilities
*/
module.exports = {
sequelize,
setupRelationships,
syncDatabase,
// Group 1: System & Authorization
School,
UsersAuth,
Role,
Permission,
RolePermission,
UserAssignment,
// Group 2: User Profiles
UserProfile,
StudentDetail,
TeacherDetail,
ParentStudentMap,
StaffContract,
// Group 3: Academic Structure
AcademicYear,
Subject,
Class,
ClassSchedule,
Room,
// Group 3.1: Learning Content (NEW)
Chapter,
Lesson,
Game,
// Group 4: Attendance
AttendanceLog,
AttendanceDaily,
LeaveRequest,
// Group 5: Gradebook
GradeCategory,
GradeItem,
Grade,
GradeSummary,
GradeHistory,
// Group 6: Notifications
Notification,
NotificationLog,
Message,
AuditLog,
// Group 7: Subscription & Training
SubscriptionPlan,
UserSubscription,
StaffTrainingAssignment,
StaffAchievement,
ParentAssignedTask,
};