Tài Liệu Thiết Kế Cơ Sở Dữ Liệu
Module Tuyển Dụng
RECR
1. Tổng Quan
1.1 Mục đích
Tài liệu này định nghĩa thiết kế cơ sở dữ liệu cho module Tuyển Dụng (cơ sở dữ liệu: recr_db).
1.2 Phụ Thuộc Module
| Module | Loại Phụ Thuộc | Mô Tả |
| AUTH | Cung | Xác thực người dùng (tham chiếu bảng auth_users) |
| FILE | Cung | Lưu trữ file hồ sơ của ứng viên (tham chiếu bảng sys_files qua các cột file_id UUID) |
1.3 Yêu Cầu Được Giải Quyết
| Yêu Cầu SRS | Giải Pháp Cơ Sở Dữ Liệu |
| FR-001, FR-015 Đăng Ký Người Dùng | Các bảng recr_users, recr_candidates, recr_employers |
| FR-016, FR-017 Quản Lý Việc Làm | Các bảng recr_jobs, recr_job_requirements |
| FR-004, FR-005 Quản Lý Hồ Sơ Ứng Tuyển | Bảng recr_applications (với resume_file_id khóa) |
| FR-007, FR-037 Quản Lý Hồ Sơ | Bảng recr_candidate_resumes (tích hợp FILE module) |
| FR-038 Liên Kết Hồ Sơ Ứng Tuyển | recr_applications.resume_file_id (tham chiếu sys_files qua FILE module) |
| FR-010 Lên Lịch Phỏng Vấn | Bảng recr_interviews |
| FR-011 Đánh Giá | Bảng recr_evaluations |
| FR-012 Từ Chối Ứng Viên | Bảng recr_applications (cập nhật trạng thái) |
| FR-030 Đánh Giá Việc Làm | Bảng recr_job_reviews |
| FR-031 Đánh Giá Ứng Viên | Bảng recr_candidate_reviews |
| FR-021, FR-022 Gói Đăng Ký | Bảng recr_subscription_plans |
| FR-023, FR-024 Điều Khiển Truy Cập Gói Đăng Ký | Bảng recr_subscriptions |
| FR-039, FR-042, FR-043 Quản Lý Lời Mời | Bảng recr_invitations |
1.4 Sơ Đồ ERD
Hình 1: ERD Module Tuyển Dụng (3 Vai Trò)
2. Thiết Kế Schema
2.1 Chiến Lược Cơ Sở Dữ Liệu
| Khía Cạnh | Quyết Định |
| Cơ sở dữ liệu | recr_db |
| Quy tắc đặt tên bảng | recr_<ten_bang> |
| Khóa chính | BIGSERIAL (tự động) |
| Thời gian | created_at, updated_at với giá trị mặc định NOW() |
| Trường JSON | JSONB cho dữ liệu linh hoạt (kỹ năng, phúc lợi) |
2.2 Bảng
Bảng: recr_users — Bảng Người Dùng Cơ Bản
Lưu ý: Xác thực người dùng (email, mật khẩu, vai trò) được xử lý bởi AUTH module. Bảng này chỉ lưu dữ liệu đặc thù của tuyển dụng và liên kết với auth_users qua user_id.
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
user_id | BIGINT | NOT NULL | - | FK → auth_users.id (UNIQUE) |
avatar_url | VARCHAR(500) | NULL | - | URL ảnh đại diện |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_users (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE REFERENCES auth_users(id),
avatar_url VARCHAR(500),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_candidates — Hồ Sơ Ứng Viên
Lưu ý: Cột resume_url đã ngưng sử dụng. Tham chiếu file hồ sơ được lưu trong recr_candidate_resumes qua file_id (UUID → sys_files.id trong FILE module).
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
user_id | BIGINT | NOT NULL | - | FK → recr_users.id (UNIQUE) |
resume_url | VARCHAR(500) | NULL | - | DEPRECATED — dùng recr_candidate_resumes |
linkedin_url | VARCHAR(300) | NULL | - | LinkedIn profile URL |
skills | JSONB | NULL | '[]' | Danh sách kỹ năng |
experience_years | INT | NULL | 0 | Số năm kinh nghiệm |
current_company | VARCHAR(200) | NULL | - | Công ty hiện tại |
expected_salary | DECIMAL(12,2) | NULL | - | Lương mong muốn (VND) |
source | VARCHAR(20) | NULL | - | Nguồn: website, referral, linkedin, job_board, other |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_candidates (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE REFERENCES recr_users(id),
resume_url VARCHAR(500), -- DEPRECATED
linkedin_url VARCHAR(300),
skills JSONB DEFAULT '[]',
experience_years INT DEFAULT 0,
current_company VARCHAR(200),
expected_salary DECIMAL(12,2),
source VARCHAR(20) CHECK (source IN ('website','referral','linkedin','job_board','other')),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_candidate_resumes — Danh Sách Hồ Sơ Ứng Viên
Mô Tả: Lưu các tham chiếu file hồ sơ ứng viên được quản lý bởi FILE module. Mỗi ứng viên có thể có nhiều hồ sơ. file_id tham chiếu sys_files.id.
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | UUID | NOT NULL | gen_random_uuid() | Khóa chính |
candidate_id | BIGINT | NOT NULL | - | FK → recr_candidates.id (CASCADE) |
file_id | UUID | NOT NULL | - | FK → sys_files.id (FILE module, CASCADE) |
is_primary | BOOLEAN | NOT NULL | FALSE | Hồ sơ chính |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_candidate_resumes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
candidate_id BIGINT NOT NULL REFERENCES recr_candidates(id) ON DELETE CASCADE,
file_id UUID NOT NULL REFERENCES sys_files(id) ON DELETE CASCADE,
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_recr_candidate_resumes_candidate_id ON recr_candidate_resumes(candidate_id);
CREATE INDEX idx_recr_candidate_resumes_file_id ON recr_candidate_resumes(file_id);
CREATE INDEX idx_recr_candidate_resumes_primary ON recr_candidate_resumes(candidate_id, is_primary) WHERE is_primary = TRUE;
Bảng: recr_employers — Hồ Sơ Nhà Tuyển Dụng
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
user_id | BIGINT | NOT NULL | - | FK → recr_users.id (UNIQUE) |
company_name | VARCHAR(200) | NOT NULL | - | Tên công ty |
company_website | VARCHAR(300) | NULL | - | Website công ty |
company_description | TEXT | NULL | - | Mô tả công ty |
industry | VARCHAR(100) | NULL | - | Ngành nghề |
company_size | VARCHAR(20) | NULL | - | Quy mô: 1-10, 11-50, 51-200, 201-500, 501-1000, 1000+ |
logo_url | VARCHAR(500) | NULL | - | URL logo |
is_verified | BOOLEAN | NULL | false | Đã xác minh |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_employers (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE REFERENCES recr_users(id),
company_name VARCHAR(200) NOT NULL,
company_website VARCHAR(300),
company_description TEXT,
industry VARCHAR(100),
company_size VARCHAR(20) CHECK (company_size IN ('1-10','11-50','51-200','201-500','501-1000','1000+')),
logo_url VARCHAR(500),
is_verified BOOLEAN DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_hr_profiles — Hồ Sơ HR
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
user_id | BIGINT | NOT NULL | - | FK → recr_users.id (UNIQUE) |
department | VARCHAR(100) | NULL | - | Phòng ban |
position | VARCHAR(100) | NULL | - | Chức vụ |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_hr_profiles (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE REFERENCES recr_users(id),
department VARCHAR(100),
position VARCHAR(100),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_jobs — Tin Tuyển Dụng
Lưu ý: Cả HR và Nhà Tuyển Dụng đều có thể tạo tin tuyển dụng. Trường created_by ghi nhận ai đã tạo tin.
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
employer_id | BIGINT | NULL | - | FK → recr_employers.id (NULL cho tin HR tạo) |
created_by | BIGINT | NOT NULL | - | FK → recr_users.id |
title | VARCHAR(200) | NOT NULL | - | Tiêu đề tin tuyển dụng |
description | TEXT | NULL | - | Mô tả công việc |
department | VARCHAR(100) | NULL | - | Phòng ban |
location | VARCHAR(200) | NULL | - | Địa điểm làm việc |
salary_min | DECIMAL(12,2) | NULL | - | Lương tối thiểu (VND) |
salary_max | DECIMAL(12,2) | NULL | - | Lương tối đa (VND) |
employment_type | VARCHAR(20) | NOT NULL | - | full_time, part_time, contract, internship |
status | VARCHAR(20) | NOT NULL | 'draft' | draft, pending_approval, open, closed, archived |
approved_by | BIGINT | NULL | - | FK → recr_users.id (người phê duyệt) |
approved_at | TIMESTAMP | NULL | - | Thời gian phê duyệt |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_jobs (
id BIGSERIAL PRIMARY KEY,
employer_id BIGINT REFERENCES recr_employers(id),
created_by BIGINT NOT NULL REFERENCES recr_users(id),
title VARCHAR(200) NOT NULL,
description TEXT,
department VARCHAR(100),
location VARCHAR(200),
salary_min DECIMAL(12,2),
salary_max DECIMAL(12,2),
employment_type VARCHAR(20) NOT NULL CHECK (employment_type IN ('full_time','part_time','contract','internship')),
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','pending_approval','open','closed','archived')),
approved_by BIGINT REFERENCES recr_users(id),
approved_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_applications — Hồ Sơ Ứng Tuyển
Lưu ý: resume_file_id lưu bản sao khóa tại thời điểm nộp — đảm bảo hồ sơ HR xét duyệt là hồ sơ ứng viên đã nộp.
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
job_id | BIGINT | NOT NULL | - | FK → recr_jobs.id |
candidate_id | BIGINT | NOT NULL | - | FK → recr_candidates.id |
resume_file_id | UUID | NULL | - | FK → sys_files.id (FILE module, đóng băng tại nộp) |
cover_letter | TEXT | NULL | - | Thư xin việc |
status | VARCHAR(20) | NOT NULL | 'submitted' | submitted, screening, interview, offer, rejected, hired |
applied_at | TIMESTAMP | NOT NULL | NOW() | Thời gian nộp |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
Ràng buộc: UNIQUE(job_id, candidate_id) — Mỗi ứng viên chỉ nộp 1 lần cho 1 việc làm.
CREATE TABLE recr_applications (
id BIGSERIAL PRIMARY KEY,
job_id BIGINT NOT NULL REFERENCES recr_jobs(id),
candidate_id BIGINT NOT NULL REFERENCES recr_candidates(id),
resume_file_id UUID REFERENCES sys_files(id),
cover_letter TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'submitted' CHECK (status IN ('submitted','screening','interview','offer','rejected','hired')),
applied_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(job_id, candidate_id)
);
Bảng: recr_interviews — Lịch Phỏng Vấn
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
application_id | BIGINT | NOT NULL | - | FK → recr_applications.id |
interviewer_id | BIGINT | NOT NULL | - | FK → recr_users.id |
scheduled_at | TIMESTAMP | NOT NULL | - | Thời gian phỏng vấn |
duration_minutes | INT | NULL | 60 | Thời lượng (phút) |
location | VARCHAR(200) | NULL | - | Địa điểm |
meeting_link | VARCHAR(500) | NULL | - | Link họp trực tuyến |
status | VARCHAR(20) | NOT NULL | 'scheduled' | scheduled, completed, cancelled, no_show |
notes | TEXT | NULL | - | Ghi chú |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
CREATE TABLE recr_interviews (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL REFERENCES recr_applications(id),
interviewer_id BIGINT NOT NULL REFERENCES recr_users(id),
scheduled_at TIMESTAMP NOT NULL,
duration_minutes INT DEFAULT 60,
location VARCHAR(200),
meeting_link VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled','completed','cancelled','no_show')),
notes TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_evaluations — Đánh Giá Phỏng Vấn
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
interview_id | BIGINT | NOT NULL | - | FK → recr_interviews.id |
evaluator_id | BIGINT | NOT NULL | - | FK → recr_users.id |
score | INT | NULL | - | Điểm tổng quát (1-10) |
technical_score | INT | NULL | - | Điểm kỹ thuật (1-10) |
communication_score | INT | NULL | - | Điểm giao tiếp (1-10) |
cultural_fit_score | INT | NULL | - | Điểm phù hợp văn hóa (1-10) |
recommendation | VARCHAR(20) | NULL | - | strong_yes, yes, neutral, no, strong_no |
comments | TEXT | NULL | - | Nhận xét chi tiết |
submitted_at | TIMESTAMP | NOT NULL | NOW() | Thời gian nộp |
CREATE TABLE recr_evaluations (
id BIGSERIAL PRIMARY KEY,
interview_id BIGINT NOT NULL REFERENCES recr_interviews(id),
evaluator_id BIGINT NOT NULL REFERENCES recr_users(id),
score INT CHECK (score >= 1 AND score <= 10),
technical_score INT CHECK (technical_score >= 1 AND technical_score <= 10),
communication_score INT CHECK (communication_score >= 1 AND communication_score <= 10),
cultural_fit_score INT CHECK (cultural_fit_score >= 1 AND cultural_fit_score <= 10),
recommendation VARCHAR(20) CHECK (recommendation IN ('strong_yes','yes','neutral','no','strong_no')),
comments TEXT,
submitted_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_job_requirements — Yêu Cầu Việc Làm
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
job_id | BIGINT | NOT NULL | - | FK → recr_jobs.id |
requirement_type | VARCHAR(20) | NOT NULL | - | skill, experience, education, certification |
description | TEXT | NOT NULL | - | Mô tả yêu cầu |
is_mandatory | BOOLEAN | NULL | true | Bắt buộc hay ưu tiên |
sort_order | INT | NULL | 0 | Thứ tự hiển thị |
CREATE TABLE recr_job_requirements (
id BIGSERIAL PRIMARY KEY,
job_id BIGINT NOT NULL REFERENCES recr_jobs(id),
requirement_type VARCHAR(20) NOT NULL CHECK (requirement_type IN ('skill','experience','education','certification')),
description TEXT NOT NULL,
is_mandatory BOOLEAN DEFAULT true,
sort_order INT DEFAULT 0
);
Bảng: recr_subscription_plans — Gói Đăng Ký
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
name | VARCHAR(50) | NOT NULL | - | Tên định danh: 1_month, 6_months, 1_year (UNIQUE) |
display_name | VARCHAR(100) | NOT NULL | - | Tên hiển thị |
duration_months | INT | NOT NULL | - | Thời hạn (tháng) |
base_price | DECIMAL(12,2) | NOT NULL | - | Giá trước giảm (VND) |
discount_percent | INT | NULL | 0 | % giảm giá (0-100) |
free_months | INT | NULL | 0 | Tháng miễn phí |
final_price | DECIMAL(12,2) | NOT NULL | - | Giá sau giảm (VND) |
is_active | BOOLEAN | NULL | true | Đang hoạt động |
features | JSONB | NULL | '[]' | Tính năng theo vai trò |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_subscription_plans (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
display_name VARCHAR(100) NOT NULL,
duration_months INT NOT NULL,
base_price DECIMAL(12,2) NOT NULL,
discount_percent INT DEFAULT 0 CHECK (discount_percent >= 0 AND discount_percent <= 100),
free_months INT DEFAULT 0,
final_price DECIMAL(12,2) NOT NULL,
is_active BOOLEAN DEFAULT true,
features JSONB DEFAULT '[]',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
INSERT INTO recr_subscription_plans (name, display_name, duration_months, base_price, discount_percent, free_months, final_price, features) VALUES
('1_month', '1 Month', 1, 500000, 0, 0, 500000, '{"candidate": ["priority_application", "profile_boost"], "hr": ["advanced_analytics"], "employer": ["featured_listing"]}'),
('6_months', '6 Months', 6, 3000000, 10, 0, 2700000, '{"candidate": ["priority_application", "profile_boost", "resume_review"], "hr": ["advanced_analytics", "priority_support"], "employer": ["featured_listing", "premium_analytics"]}'),
('1_year', '1 Year', 12, 6000000, 0, 2, 5000000, '{"candidate": ["priority_application", "profile_boost", "resume_review", "career_coaching"], "hr": ["advanced_analytics", "priority_support", "dedicated_account_manager"], "employer": ["featured_listing", "premium_analytics", "dedicated_account_manager"]}');
Bảng: recr_subscriptions — Đăng Ký Người Dùng
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
user_id | BIGINT | NOT NULL | - | FK → recr_users.id |
plan_id | BIGINT | NOT NULL | - | FK → recr_subscription_plans.id |
status | VARCHAR(20) | NOT NULL | 'active' | active, expired, cancelled, pending |
start_date | TIMESTAMP | NOT NULL | - | Ngày bắt đầu |
end_date | TIMESTAMP | NOT NULL | - | Ngày kết thúc |
payment_amount | DECIMAL(12,2) | NOT NULL | - | Số tiền thanh toán (VND) |
payment_method | VARCHAR(50) | NULL | - | credit_card, bank_transfer, e_wallet |
payment_reference | VARCHAR(100) | NULL | - | Mã giao dịch |
auto_renew | BOOLEAN | NULL | false | Tự động gia hạn |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
Ràng buộc: CHECK (end_date > start_date)
CREATE TABLE recr_subscriptions (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES recr_users(id),
plan_id BIGINT NOT NULL REFERENCES recr_subscription_plans(id),
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','expired','cancelled','pending')),
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
payment_amount DECIMAL(12,2) NOT NULL,
payment_method VARCHAR(50),
payment_reference VARCHAR(100),
auto_renew BOOLEAN DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CHECK (end_date > start_date)
);
CREATE INDEX idx_recr_subscriptions_user_id ON recr_subscriptions(user_id);
CREATE INDEX idx_recr_subscriptions_status ON recr_subscriptions(status);
CREATE UNIQUE INDEX idx_recr_subscriptions_active ON recr_subscriptions(user_id) WHERE status = 'active';
Bảng: recr_subscription_history — Lịch Sử Đăng Ký
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
subscription_id | BIGINT | NOT NULL | - | FK → recr_subscriptions.id |
action | VARCHAR(50) | NOT NULL | - | created, renewed, upgraded, cancelled, expired, payment_received |
old_status | VARCHAR(20) | NULL | - | Trạng thái trước |
new_status | VARCHAR(20) | NULL | - | Trạng thái sau |
old_plan_id | BIGINT | NULL | - | Gói trước (FK → recr_subscription_plans.id) |
new_plan_id | BIGINT | NULL | - | Gói sau (FK → recr_subscription_plans.id) |
notes | TEXT | NULL | - | Ghi chú |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian ghi nhận |
CREATE TABLE recr_subscription_history (
id BIGSERIAL PRIMARY KEY,
subscription_id BIGINT NOT NULL REFERENCES recr_subscriptions(id),
action VARCHAR(50) NOT NULL CHECK (action IN ('created','renewed','upgraded','cancelled','expired','payment_received')),
old_status VARCHAR(20),
new_status VARCHAR(20),
old_plan_id BIGINT REFERENCES recr_subscription_plans(id),
new_plan_id BIGINT REFERENCES recr_subscription_plans(id),
notes TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Bảng: recr_job_reviews — Đánh Giá Việc Làm
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
job_id | BIGINT | NOT NULL | - | FK → recr_jobs.id |
user_id | BIGINT | NOT NULL | - | FK → recr_users.id |
overall_rating | INT | NOT NULL | - | Điểm tổng quát (1-5) |
work_life_balance | INT | NULL | - | Cân bằng CV - cuộc sống (1-5) |
compensation | INT | NULL | - | Lương thưởng (1-5) |
career_growth | INT | NULL | - | Phát triển (1-5) |
company_culture | INT | NULL | - | Văn hóa (1-5) |
title | VARCHAR(200) | NOT NULL | - | Tiêu đề đánh giá |
description | TEXT | NULL | - | Nội dung đánh giá |
is_anonymous | BOOLEAN | NULL | false | Ẩn danh |
status | VARCHAR(20) | NOT NULL | 'pending' | pending, approved, rejected |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
Ràng buộc: UNIQUE(job_id, user_id)
CREATE TABLE recr_job_reviews (
id BIGSERIAL PRIMARY KEY,
job_id BIGINT NOT NULL REFERENCES recr_jobs(id),
user_id BIGINT NOT NULL REFERENCES recr_users(id),
overall_rating INT NOT NULL CHECK (overall_rating >= 1 AND overall_rating <= 5),
work_life_balance INT CHECK (work_life_balance >= 1 AND work_life_balance <= 5),
compensation INT CHECK (compensation >= 1 AND compensation <= 5),
career_growth INT CHECK (career_growth >= 1 AND career_growth <= 5),
company_culture INT CHECK (company_culture >= 1 AND company_culture <= 5),
title VARCHAR(200) NOT NULL,
description TEXT,
is_anonymous BOOLEAN DEFAULT false,
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','approved','rejected')),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(job_id, user_id)
);
Bảng: recr_candidate_reviews — Đánh Giá Ứng Viên
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | BIGSERIAL | NOT NULL | auto | Khóa chính |
candidate_id | BIGINT | NOT NULL | - | FK → recr_candidates.id |
interviewer_id | BIGINT | NOT NULL | - | FK → recr_users.id |
interview_id | BIGINT | NOT NULL | - | FK → recr_interviews.id |
technical_skills | INT | NULL | - | Kỹ thuật (1-5) |
communication_skills | INT | NULL | - | Giao tiếp (1-5) |
cultural_fit | INT | NULL | - | Phù hợp văn hóa (1-5) |
problem_solving | INT | NULL | - | Giải quyết vấn đề (1-5) |
team_collaboration | INT | NULL | - | Làm việc nhóm (1-5) |
title | VARCHAR(200) | NOT NULL | - | Tiêu đề đánh giá |
description | TEXT | NULL | - | Nội dung đánh giá |
recommendation | VARCHAR(20) | NULL | - | strong_yes, yes, neutral, no, strong_no |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
Ràng buộc: UNIQUE(interview_id, interviewer_id)
CREATE TABLE recr_candidate_reviews (
id BIGSERIAL PRIMARY KEY,
candidate_id BIGINT NOT NULL REFERENCES recr_candidates(id),
interviewer_id BIGINT NOT NULL REFERENCES recr_users(id),
interview_id BIGINT NOT NULL REFERENCES recr_interviews(id),
technical_skills INT CHECK (technical_skills >= 1 AND technical_skills <= 5),
communication_skills INT CHECK (communication_skills >= 1 AND communication_skills <= 5),
cultural_fit INT CHECK (cultural_fit >= 1 AND cultural_fit <= 5),
problem_solving INT CHECK (problem_solving >= 1 AND problem_solving <= 5),
team_collaboration INT CHECK (team_collaboration >= 1 AND team_collaboration <= 5),
title VARCHAR(200) NOT NULL,
description TEXT,
recommendation VARCHAR(20) CHECK (recommendation IN ('strong_yes','yes','neutral','no','strong_no')),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(interview_id, interviewer_id)
);
Bảng: recr_invitations — Quản Lý Lời Mời
| Cột | Kiểu | Nullable | Default | Mô Tả |
id | UUID | NOT NULL | gen_random_uuid() | Khóa chính |
email | VARCHAR(255) | NOT NULL | - | Email người nhận |
token_hash | VARCHAR(64) | NOT NULL | - | Hash token xác thực (UNIQUE) |
role | VARCHAR(50) | NOT NULL | - | Vai trò được mời |
inviter_id | UUID | NOT NULL | - | FK → auth_users.id (người gửi) |
employer_id | UUID | NULL | - | FK → recr_employers.id |
status | VARCHAR(20) | NOT NULL | 'PENDING' | PENDING, ACCEPTED, EXPIRED, REVOKED |
expired_at | TIMESTAMP | NOT NULL | - | Thời gian hết hạn |
accepted_at | TIMESTAMP | NULL | - | Thời gian chấp nhận |
created_at | TIMESTAMP | NOT NULL | NOW() | Thời gian tạo |
updated_at | TIMESTAMP | NOT NULL | NOW() | Thời gian cập nhật |
CREATE TABLE recr_invitations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL,
token_hash VARCHAR(64) NOT NULL,
role VARCHAR(50) NOT NULL,
inviter_id UUID NOT NULL REFERENCES auth_users(id),
employer_id UUID REFERENCES recr_employers(id),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'ACCEPTED', 'EXPIRED', 'REVOKED')),
expired_at TIMESTAMP NOT NULL,
accepted_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT recr_invitations_token_hash_unique UNIQUE (token_hash)
);
CREATE INDEX idx_recr_invitations_email ON recr_invitations(email);
CREATE INDEX idx_recr_invitations_token_hash ON recr_invitations(token_hash);
CREATE INDEX idx_recr_invitations_status ON recr_invitations(status);
CREATE INDEX idx_recr_invitations_inviter_id ON recr_invitations(inviter_id);
CREATE INDEX idx_recr_invitations_employer_id ON recr_invitations(employer_id);
3. Chỉ Mục (Indexes)
recr_users / recr_candidates
CREATE INDEX idx_recr_users_user_id ON recr_users(user_id);
CREATE INDEX idx_recr_candidates_user_id ON recr_candidates(user_id);
CREATE INDEX idx_recr_candidates_skills ON recr_candidates USING GIN (skills);
recr_employers
CREATE INDEX idx_recr_employers_user_id ON recr_employers(user_id);
CREATE INDEX idx_recr_employers_company ON recr_employers(company_name);
recr_jobs
CREATE INDEX idx_recr_jobs_employer_id ON recr_jobs(employer_id);
CREATE INDEX idx_recr_jobs_created_by ON recr_jobs(created_by);
CREATE INDEX idx_recr_jobs_status ON recr_jobs(status);
CREATE INDEX idx_recr_jobs_location ON recr_jobs(location);
recr_applications
CREATE INDEX idx_recr_applications_job_id ON recr_applications(job_id);
CREATE INDEX idx_recr_applications_candidate_id ON recr_applications(candidate_id);
CREATE INDEX idx_recr_applications_status ON recr_applications(status);
CREATE INDEX idx_recr_applications_resume_file_id ON recr_applications(resume_file_id);
recr_interviews / recr_evaluations
CREATE INDEX idx_recr_interviews_application_id ON recr_interviews(application_id);
CREATE INDEX idx_recr_interviews_interviewer_id ON recr_interviews(interviewer_id);
CREATE INDEX idx_recr_interviews_scheduled_at ON recr_interviews(scheduled_at);
CREATE INDEX idx_recr_evaluations_interview_id ON recr_evaluations(interview_id);
recr_subscription_plans / recr_subscriptions
CREATE INDEX idx_recr_subscription_plans_name ON recr_subscription_plans(name);
CREATE INDEX idx_recr_subscription_plans_active ON recr_subscription_plans(is_active);
CREATE INDEX idx_recr_subscriptions_plan_id ON recr_subscriptions(plan_id);
CREATE INDEX idx_recr_subscriptions_end_date ON recr_subscriptions(end_date);
CREATE INDEX idx_recr_subscriptions_user_status ON recr_subscriptions(user_id, status);
recr_subscription_history
CREATE INDEX idx_recr_subscription_history_subscription_id ON recr_subscription_history(subscription_id);
CREATE INDEX idx_recr_subscription_history_created_at ON recr_subscription_history(created_at);
recr_job_reviews / recr_candidate_reviews
CREATE INDEX idx_recr_job_reviews_job_id ON recr_job_reviews(job_id);
CREATE INDEX idx_recr_job_reviews_user_id ON recr_job_reviews(user_id);
CREATE INDEX idx_recr_job_reviews_status ON recr_job_reviews(status);
CREATE INDEX idx_recr_candidate_reviews_candidate_id ON recr_candidate_reviews(candidate_id);
CREATE INDEX idx_recr_candidate_reviews_interviewer_id ON recr_candidate_reviews(interviewer_id);
CREATE INDEX idx_recr_candidate_reviews_interview_id ON recr_candidate_reviews(interview_id);
recr_invitations
CREATE INDEX idx_recr_invitations_email ON recr_invitations(email);
CREATE INDEX idx_recr_invitations_token_hash ON recr_invitations(token_hash);
CREATE INDEX idx_recr_invitations_status ON recr_invitations(status);
CREATE INDEX idx_recr_invitations_inviter_id ON recr_invitations(inviter_id);
CREATE INDEX idx_recr_invitations_employer_id ON recr_invitations(employer_id);
4. Di Chuyển Dữ Liệu (Migrations)
4.1 Chiến Lược Di Chuyển
| Khía Cạnh | Quyết Định |
| Công cụ | Drizzle ORM (drizzle-kit) |
| Cơ sở dữ liệu | recr_db |
| Hoàn tác | Bắt buộc cho mỗi lần di chuyển |
4.2 Thứ Tự Di Chuyển
| Thứ Tự | Di Chuyển | Mô Tả |
| 1 | 001_create_users.sql | Tạo bảng recr_users |
| 2 | 002_create_candidates.sql | Tạo bảng recr_candidates |
| 3 | 003_create_employers.sql | Tạo bảng recr_employers |
| 4 | 004_create_hr_profiles.sql | Tạo bảng recr_hr_profiles |
| 5 | 005_create_jobs.sql | Tạo recr_jobs và recr_job_requirements |
| 6 | 006_create_applications.sql | Tạo bảng recr_applications |
| 6a | 006a_create_candidate_resumes.sql | Tạo recr_candidate_resumes, thêm resume_file_id |
| 7 | 007_create_interviews.sql | Tạo bảng recr_interviews |
| 8 | 008_create_evaluations.sql | Tạo bảng recr_evaluations |
| 9 | 009_create_subscription_plans.sql | Tạo recr_subscription_plans + seed data |
| 10 | 010_create_subscriptions.sql | Tạo recr_subscriptions + recr_subscription_history |
| 11 | 011_create_job_reviews.sql | Tạo bảng recr_job_reviews |
| 12 | 012_create_candidate_reviews.sql | Tạo bảng recr_candidate_reviews |
| 13 | 013_create_indexes.sql | Tạo tất cả chỉ mục |
| 14 | 014_create_invitations.sql | Tạo bảng recr_invitations |
5. Từ Điển Dữ Liệu
| Bảng | Mô Tả | Giải Quyết | Dòng Dự Kiến (Năm 1) |
| recr_users | Tài khoản người dùng (tất cả vai trò) | FR-001, FR-002, FR-015 | 15,000 |
| recr_candidates | Hồ sơ ứng viên | FR-006, FR-007 | 10,000 |
| recr_candidate_resumes | Danh sách hồ sơ ứng viên (FILE module) | FR-007, FR-037, FR-038 | 20,000 |
| recr_employers | Hồ sơ công ty nhà tuyển dụng | FR-015, FR-033 | 500 |
| recr_hr_profiles | Hồ sơ người dùng HR | FR-032 | 50 |
| recr_jobs | Tin tuyển dụng | FR-016, FR-017, FR-028, FR-029 | 2,000 |
| recr_applications | Hồ sơ ứng tuyển | FR-004, FR-005, FR-038 | 25,000 |
| recr_interviews | Phiên phỏng vấn | FR-010 | 8,000 |
| recr_evaluations | Đánh giá phỏng vấn | FR-011 | 8,000 |
| recr_job_requirements | Yêu cầu việc làm | FR-016, FR-017 | 5,000 |
| recr_subscription_plans | Định nghĩa gói đăng ký | FR-021 | 3 |
| recr_subscriptions | Đăng ký người dùng | FR-022, FR-023, FR-024 | 500 |
| recr_subscription_history | Lịch sử thay đổi đăng ký | FR-026 | 2,000 |
| recr_job_reviews | Đánh giá việc làm từ ứng viên | FR-030 | 5,000 |
| recr_candidate_reviews | Đánh giá ứng viên từ người phỏng vấn | FR-031 | 8,000 |
| recr_invitations | Lời mời với xác thực token | FR-039, FR-042, FR-043 | 5,000 |
6. Sao Lưu & Phục Hồi
Tham khảo chiến lược sao lưu cấp hệ thống. Dữ liệu của module này là một phần của recr_db.
7. Tính Toán Hiệu Suất
| Khía Cạnh | Cấu Hình |
| Kết nối pool | pg-pool với tối đa 20 kết nối |
| Bộ nhớ đệm truy vấn | Redis cho tin tuyển dụng truy cập nhiều |
| Tìm kiếm | Elasticsearch cho tìm kiếm việc làm và ứng viên |
| Chỉ mục JSON | Chỉ mục GIN trên recr_candidates.skills |
| Phân trang | Dựa trên con trỏ cho các kết quả lớn |