Refactor exam information workflow

This commit is contained in:
2026-07-20 10:07:14 +08:00 Unverified
parent d4e085b6c5
commit 7094e9b916
22 changed files with 3233 additions and 2793 deletions
+741
View File
@@ -0,0 +1,741 @@
export const sqliteSchema = `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS schema_metadata (
id INTEGER PRIMARY KEY CHECK (id = 1),
schema_version INTEGER NOT NULL DEFAULT 1,
app_version INTEGER NOT NULL DEFAULT 1,
self_registration_enabled INTEGER NOT NULL DEFAULT 0 CHECK (self_registration_enabled IN (0, 1)),
created_at TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS organization (
id INTEGER PRIMARY KEY CHECK (id = 1),
name TEXT NOT NULL,
code TEXT NOT NULL,
phone TEXT NOT NULL,
address TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS schools (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
code TEXT NOT NULL UNIQUE,
address TEXT,
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))
) STRICT;
CREATE TABLE IF NOT EXISTS school_classes (
id TEXT PRIMARY KEY,
school_id TEXT NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
name TEXT NOT NULL,
grade TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),
UNIQUE (school_id, name)
) STRICT;
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
candidate_number TEXT UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('admin', 'candidate')),
admin_level TEXT CHECK (admin_level IN ('super', 'school', 'class')),
school_id TEXT REFERENCES schools(id) ON DELETE SET NULL,
class_id TEXT REFERENCES school_classes(id) ON DELETE SET NULL,
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),
must_change_password INTEGER NOT NULL DEFAULT 0 CHECK (must_change_password IN (0, 1)),
display_name TEXT NOT NULL,
created_at TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS candidate_profiles (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
gender TEXT,
id_number TEXT NOT NULL UNIQUE,
phone TEXT NOT NULL,
email TEXT,
school TEXT,
grade TEXT,
school_id TEXT REFERENCES schools(id) ON DELETE SET NULL,
class_id TEXT REFERENCES school_classes(id) ON DELETE SET NULL,
address TEXT,
emergency_contact TEXT,
emergency_phone TEXT,
native_place TEXT,
birth_date TEXT,
ethnicity TEXT,
postal_code TEXT,
guardian_name TEXT,
guardian_phone TEXT,
profile_completed INTEGER NOT NULL DEFAULT 0 CHECK (profile_completed IN (0, 1)),
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'rejected')),
review_note TEXT,
reviewed_at TEXT,
reviewer_id TEXT REFERENCES users(id) ON DELETE SET NULL,
updated_at TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS notices (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
summary TEXT NOT NULL,
content TEXT NOT NULL,
category TEXT NOT NULL,
pinned INTEGER NOT NULL DEFAULT 0 CHECK (pinned IN (0, 1)),
status TEXT NOT NULL CHECK (status IN ('draft', 'published')),
publish_at TEXT,
created_at TEXT,
author TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS exams (
id TEXT PRIMARY KEY,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT NOT NULL,
registration_start TEXT NOT NULL,
registration_end TEXT NOT NULL,
exam_start TEXT NOT NULL,
exam_end TEXT NOT NULL,
admit_download_start TEXT NOT NULL,
admit_download_end TEXT NOT NULL,
location TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('draft', 'published', 'closed')),
created_at TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS exam_subjects (
id TEXT PRIMARY KEY,
exam_id TEXT NOT NULL REFERENCES exams(id) ON DELETE CASCADE,
name TEXT NOT NULL,
subject_date TEXT NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
fee REAL NOT NULL DEFAULT 0,
position INTEGER NOT NULL,
UNIQUE (exam_id, position)
) STRICT;
CREATE TABLE IF NOT EXISTS registrations (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
exam_id TEXT NOT NULL REFERENCES exams(id) ON DELETE CASCADE,
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'rejected')),
payment_status TEXT NOT NULL CHECK (payment_status IN ('unpaid', 'paid', 'refunded')),
created_at TEXT NOT NULL,
reviewed_at TEXT,
review_note TEXT,
registration_number TEXT,
number_rule_id TEXT,
UNIQUE (user_id, exam_id)
) STRICT;
CREATE TABLE IF NOT EXISTS registration_subjects (
registration_id TEXT NOT NULL REFERENCES registrations(id) ON DELETE CASCADE,
subject_id TEXT NOT NULL REFERENCES exam_subjects(id) ON DELETE CASCADE,
PRIMARY KEY (registration_id, subject_id)
) STRICT;
CREATE TABLE IF NOT EXISTS admit_cards (
registration_id TEXT PRIMARY KEY REFERENCES registrations(id) ON DELETE CASCADE,
card_number TEXT NOT NULL UNIQUE,
test_center TEXT NOT NULL,
room TEXT NOT NULL,
seat TEXT NOT NULL,
generated_at TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS results (
id TEXT PRIMARY KEY,
registration_id TEXT NOT NULL REFERENCES registrations(id) ON DELETE CASCADE,
subject_id TEXT NOT NULL REFERENCES exam_subjects(id) ON DELETE CASCADE,
score REAL NOT NULL CHECK (score >= 0 AND score <= 150),
grade TEXT NOT NULL,
published INTEGER NOT NULL DEFAULT 0 CHECK (published IN (0, 1)),
updated_at TEXT,
published_at TEXT,
UNIQUE (registration_id, subject_id)
) STRICT;
CREATE TABLE IF NOT EXISTS test_centers (
id TEXT PRIMARY KEY,
school_id TEXT NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
address TEXT NOT NULL,
contact TEXT,
manager_name TEXT,
manager_phone TEXT,
emergency_phone TEXT,
gate_open_time TEXT,
transport TEXT,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
notes TEXT,
rooms TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE (school_id, name)
) STRICT;
CREATE TABLE IF NOT EXISTS test_rooms (
id TEXT PRIMARY KEY,
center_id TEXT NOT NULL REFERENCES test_centers(id) ON DELETE CASCADE,
code TEXT NOT NULL,
name TEXT NOT NULL,
building TEXT NOT NULL,
floor TEXT,
capacity INTEGER NOT NULL CHECK (capacity > 0),
seat_plan TEXT,
seat_start INTEGER NOT NULL DEFAULT 1 CHECK (seat_start > 0),
seat_end INTEGER NOT NULL CHECK (seat_end >= seat_start),
room_type TEXT NOT NULL CHECK (room_type IN ('standard', 'computer', 'accessible', 'spare')),
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
notes TEXT,
UNIQUE (center_id, code)
) STRICT;
CREATE TABLE IF NOT EXISTS center_change_requests (
id TEXT PRIMARY KEY,
center_id TEXT REFERENCES test_centers(id) ON DELETE SET NULL,
school_id TEXT NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
request_type TEXT NOT NULL CHECK (request_type IN ('create', 'update')),
code TEXT NOT NULL,
name TEXT NOT NULL,
address TEXT NOT NULL,
contact TEXT,
manager_name TEXT,
manager_phone TEXT,
emergency_phone TEXT,
gate_open_time TEXT,
transport TEXT,
center_status TEXT NOT NULL CHECK (center_status IN ('active', 'inactive')),
notes TEXT,
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'rejected')),
review_note TEXT,
requested_by TEXT REFERENCES users(id) ON DELETE SET NULL,
created_at TEXT NOT NULL,
reviewed_at TEXT
) STRICT;
CREATE TABLE IF NOT EXISTS center_change_rooms (
id TEXT PRIMARY KEY,
request_id TEXT NOT NULL REFERENCES center_change_requests(id) ON DELETE CASCADE,
room_id TEXT,
code TEXT NOT NULL,
name TEXT NOT NULL,
building TEXT NOT NULL,
floor TEXT,
capacity INTEGER NOT NULL CHECK (capacity > 0),
seat_plan TEXT,
seat_start INTEGER NOT NULL DEFAULT 1,
seat_end INTEGER NOT NULL,
room_type TEXT NOT NULL CHECK (room_type IN ('standard', 'computer', 'accessible', 'spare')),
status TEXT NOT NULL CHECK (status IN ('active', 'inactive')),
notes TEXT,
UNIQUE (request_id, code)
) STRICT;
CREATE TABLE IF NOT EXISTS number_rules (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
separator TEXT NOT NULL DEFAULT '',
active INTEGER NOT NULL DEFAULT 0 CHECK (active IN (0, 1)),
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
updated_at TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS number_rule_segments (
id TEXT PRIMARY KEY,
rule_id TEXT NOT NULL REFERENCES number_rules(id) ON DELETE CASCADE,
position INTEGER NOT NULL,
type TEXT NOT NULL CHECK (type IN ('year', 'school_code', 'gender', 'sequence', 'literal')),
value TEXT,
width INTEGER NOT NULL DEFAULT 0,
UNIQUE (rule_id, position)
) STRICT;
CREATE TABLE IF NOT EXISTS candidate_account_batches (
id TEXT PRIMARY KEY,
school_id TEXT NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
requested_by TEXT REFERENCES users(id) ON DELETE SET NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'rejected')),
review_note TEXT,
created_at TEXT NOT NULL,
reviewed_at TEXT
) STRICT;
CREATE TABLE IF NOT EXISTS candidate_account_batch_items (
id TEXT PRIMARY KEY,
batch_id TEXT NOT NULL REFERENCES candidate_account_batches(id) ON DELETE CASCADE,
class_id TEXT NOT NULL REFERENCES school_classes(id) ON DELETE RESTRICT,
position INTEGER NOT NULL,
candidate_number TEXT UNIQUE,
initial_password TEXT,
user_id TEXT UNIQUE REFERENCES users(id) ON DELETE SET NULL,
created_at TEXT,
UNIQUE (batch_id, position)
) STRICT;
CREATE TABLE IF NOT EXISTS workflow_definitions (
id TEXT PRIMARY KEY,
business_type TEXT NOT NULL CHECK (business_type IN ('profile_change', 'registration_review', 'center_change', 'candidate_account_batch')),
name TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),
updated_by TEXT REFERENCES users(id) ON DELETE SET NULL,
updated_at TEXT NOT NULL,
UNIQUE (business_type, active)
) STRICT;
CREATE TABLE IF NOT EXISTS workflow_steps (
id TEXT PRIMARY KEY,
workflow_id TEXT NOT NULL REFERENCES workflow_definitions(id) ON DELETE CASCADE,
position INTEGER NOT NULL,
name TEXT NOT NULL,
admin_level TEXT NOT NULL CHECK (admin_level IN ('school', 'super')),
UNIQUE (workflow_id, position)
) STRICT;
CREATE TABLE IF NOT EXISTS workflow_instances (
id TEXT PRIMARY KEY,
workflow_id TEXT NOT NULL REFERENCES workflow_definitions(id) ON DELETE RESTRICT,
business_type TEXT NOT NULL,
business_id TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'rejected')),
current_step INTEGER NOT NULL DEFAULT 1,
assignee_id TEXT REFERENCES users(id) ON DELETE SET NULL,
created_at TEXT NOT NULL,
completed_at TEXT
) STRICT;
CREATE TABLE IF NOT EXISTS workflow_actions (
id TEXT PRIMARY KEY,
instance_id TEXT NOT NULL REFERENCES workflow_instances(id) ON DELETE CASCADE,
actor_id TEXT REFERENCES users(id) ON DELETE SET NULL,
action TEXT NOT NULL CHECK (action IN ('submit', 'approve', 'reject', 'transfer', 'return', 'supervise')),
note TEXT,
from_assignee_id TEXT REFERENCES users(id) ON DELETE SET NULL,
to_assignee_id TEXT REFERENCES users(id) ON DELETE SET NULL,
created_at TEXT NOT NULL
) STRICT;
CREATE TABLE IF NOT EXISTS audit_logs (
id TEXT PRIMARY KEY,
actor_id TEXT REFERENCES users(id) ON DELETE SET NULL,
action TEXT NOT NULL,
detail TEXT NOT NULL,
created_at TEXT NOT NULL
) STRICT;
CREATE INDEX IF NOT EXISTS idx_profiles_status ON candidate_profiles(status);
CREATE INDEX IF NOT EXISTS idx_profiles_scope ON candidate_profiles(school_id, class_id, status);
CREATE INDEX IF NOT EXISTS idx_notices_status_publish ON notices(status, publish_at);
CREATE INDEX IF NOT EXISTS idx_exams_status_registration ON exams(status, registration_start, registration_end);
CREATE INDEX IF NOT EXISTS idx_subjects_exam ON exam_subjects(exam_id, position);
CREATE INDEX IF NOT EXISTS idx_registrations_status ON registrations(status);
CREATE INDEX IF NOT EXISTS idx_registrations_exam ON registrations(exam_id);
CREATE INDEX IF NOT EXISTS idx_workflow_inbox ON workflow_instances(status, assignee_id, business_type);
CREATE UNIQUE INDEX IF NOT EXISTS uq_test_centers_code ON test_centers(code);
CREATE INDEX IF NOT EXISTS idx_rooms_center ON test_rooms(center_id, status, code);
CREATE INDEX IF NOT EXISTS idx_center_changes_school ON center_change_requests(school_id, status, created_at);
CREATE INDEX IF NOT EXISTS idx_account_batches_school ON candidate_account_batches(school_id, status, created_at);
CREATE INDEX IF NOT EXISTS idx_account_batch_items ON candidate_account_batch_items(batch_id, class_id, position);
CREATE INDEX IF NOT EXISTS idx_results_registration ON results(registration_id, published);
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_logs(created_at);
`;
export const mysqlSchema = [
`CREATE TABLE IF NOT EXISTS schema_metadata (
id TINYINT UNSIGNED NOT NULL,
schema_version INT UNSIGNED NOT NULL DEFAULT 1,
app_version INT UNSIGNED NOT NULL DEFAULT 1,
self_registration_enabled BOOLEAN NOT NULL DEFAULT FALSE,
created_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT chk_schema_metadata_singleton CHECK (id = 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS organization (
id TINYINT UNSIGNED NOT NULL,
name VARCHAR(120) NOT NULL,
code VARCHAR(60) NOT NULL,
phone VARCHAR(60) NOT NULL,
address VARCHAR(255) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT chk_organization_singleton CHECK (id = 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS schools (
id VARCHAR(64) NOT NULL,
name VARCHAR(160) NOT NULL,
code VARCHAR(40) NOT NULL,
address VARCHAR(255) NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (id),
UNIQUE KEY uq_schools_name (name),
UNIQUE KEY uq_schools_code (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS school_classes (
id VARCHAR(64) NOT NULL,
school_id VARCHAR(64) NOT NULL,
name VARCHAR(100) NOT NULL,
grade VARCHAR(60) NOT NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (id),
UNIQUE KEY uq_classes_school_name (school_id, name),
CONSTRAINT fk_classes_school FOREIGN KEY (school_id) REFERENCES schools(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS users (
id VARCHAR(64) NOT NULL,
username VARCHAR(100) NOT NULL,
candidate_number VARCHAR(120) NULL,
password_hash VARCHAR(255) NOT NULL,
role ENUM('admin', 'candidate') NOT NULL,
admin_level ENUM('super', 'school', 'class') NULL,
school_id VARCHAR(64) NULL,
class_id VARCHAR(64) NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
must_change_password BOOLEAN NOT NULL DEFAULT FALSE,
display_name VARCHAR(100) NOT NULL,
created_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_users_username (username),
UNIQUE KEY uq_users_candidate_number (candidate_number),
KEY idx_users_admin_scope (role, admin_level, school_id, class_id),
CONSTRAINT fk_users_school FOREIGN KEY (school_id) REFERENCES schools(id) ON DELETE SET NULL,
CONSTRAINT fk_users_class FOREIGN KEY (class_id) REFERENCES school_classes(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS candidate_profiles (
id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
name VARCHAR(100) NOT NULL,
gender VARCHAR(20) NULL,
id_number VARCHAR(60) NOT NULL,
phone VARCHAR(60) NOT NULL,
email VARCHAR(160) NULL,
school VARCHAR(160) NULL,
grade VARCHAR(100) NULL,
school_id VARCHAR(64) NULL,
class_id VARCHAR(64) NULL,
address VARCHAR(255) NULL,
emergency_contact VARCHAR(100) NULL,
emergency_phone VARCHAR(60) NULL,
native_place VARCHAR(160) NULL,
birth_date VARCHAR(20) NULL,
ethnicity VARCHAR(60) NULL,
postal_code VARCHAR(20) NULL,
guardian_name VARCHAR(100) NULL,
guardian_phone VARCHAR(60) NULL,
profile_completed BOOLEAN NOT NULL DEFAULT FALSE,
status ENUM('pending', 'approved', 'rejected') NOT NULL,
review_note VARCHAR(500) NULL,
reviewed_at VARCHAR(35) NULL,
reviewer_id VARCHAR(64) NULL,
updated_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_profiles_user (user_id),
UNIQUE KEY uq_profiles_id_number (id_number),
KEY idx_profiles_status (status),
CONSTRAINT fk_profiles_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_profiles_reviewer FOREIGN KEY (reviewer_id) REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT fk_profiles_school FOREIGN KEY (school_id) REFERENCES schools(id) ON DELETE SET NULL,
CONSTRAINT fk_profiles_class FOREIGN KEY (class_id) REFERENCES school_classes(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS notices (
id VARCHAR(64) NOT NULL,
title VARCHAR(240) NOT NULL,
summary VARCHAR(500) NOT NULL,
content TEXT NOT NULL,
category VARCHAR(60) NOT NULL,
pinned BOOLEAN NOT NULL DEFAULT FALSE,
status ENUM('draft', 'published') NOT NULL,
publish_at VARCHAR(35) NULL,
created_at VARCHAR(35) NULL,
author VARCHAR(100) NOT NULL,
PRIMARY KEY (id),
KEY idx_notices_status_publish (status, publish_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS exams (
id VARCHAR(64) NOT NULL,
code VARCHAR(60) NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
registration_start VARCHAR(35) NOT NULL,
registration_end VARCHAR(35) NOT NULL,
exam_start VARCHAR(35) NOT NULL,
exam_end VARCHAR(35) NOT NULL,
admit_download_start VARCHAR(35) NOT NULL,
admit_download_end VARCHAR(35) NOT NULL,
location VARCHAR(200) NOT NULL,
status ENUM('draft', 'published', 'closed') NOT NULL,
created_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_exams_code (code),
KEY idx_exams_status_registration (status, registration_start, registration_end)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS exam_subjects (
id VARCHAR(64) NOT NULL,
exam_id VARCHAR(64) NOT NULL,
name VARCHAR(100) NOT NULL,
subject_date VARCHAR(35) NOT NULL,
start_time VARCHAR(20) NOT NULL,
end_time VARCHAR(20) NOT NULL,
fee DOUBLE NOT NULL DEFAULT 0,
position INT UNSIGNED NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_subjects_exam_position (exam_id, position),
KEY idx_subjects_exam (exam_id, position),
CONSTRAINT fk_subjects_exam FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS registrations (
id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
exam_id VARCHAR(64) NOT NULL,
status ENUM('pending', 'approved', 'rejected') NOT NULL,
payment_status ENUM('unpaid', 'paid', 'refunded') NOT NULL,
created_at VARCHAR(35) NOT NULL,
reviewed_at VARCHAR(35) NULL,
review_note VARCHAR(500) NULL,
registration_number VARCHAR(120) NULL,
number_rule_id VARCHAR(64) NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_registrations_user_exam (user_id, exam_id),
KEY idx_registrations_status (status),
KEY idx_registrations_exam (exam_id),
CONSTRAINT fk_registrations_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_registrations_exam FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS registration_subjects (
registration_id VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NOT NULL,
PRIMARY KEY (registration_id, subject_id),
CONSTRAINT fk_registration_subjects_registration FOREIGN KEY (registration_id) REFERENCES registrations(id) ON DELETE CASCADE,
CONSTRAINT fk_registration_subjects_subject FOREIGN KEY (subject_id) REFERENCES exam_subjects(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS admit_cards (
registration_id VARCHAR(64) NOT NULL,
card_number VARCHAR(100) NOT NULL,
test_center VARCHAR(200) NOT NULL,
room VARCHAR(100) NOT NULL,
seat VARCHAR(30) NOT NULL,
generated_at VARCHAR(35) NOT NULL,
PRIMARY KEY (registration_id),
UNIQUE KEY uq_admit_cards_number (card_number),
CONSTRAINT fk_admit_cards_registration FOREIGN KEY (registration_id) REFERENCES registrations(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS results (
id VARCHAR(64) NOT NULL,
registration_id VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NOT NULL,
score DOUBLE NOT NULL,
grade VARCHAR(20) NOT NULL,
published BOOLEAN NOT NULL DEFAULT FALSE,
updated_at VARCHAR(35) NULL,
published_at VARCHAR(35) NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_results_registration_subject (registration_id, subject_id),
KEY idx_results_registration (registration_id, published),
CONSTRAINT chk_results_score CHECK (score >= 0 AND score <= 150),
CONSTRAINT fk_results_registration FOREIGN KEY (registration_id) REFERENCES registrations(id) ON DELETE CASCADE,
CONSTRAINT fk_results_subject FOREIGN KEY (subject_id) REFERENCES exam_subjects(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS test_centers (
id VARCHAR(64) NOT NULL,
school_id VARCHAR(64) NOT NULL,
code VARCHAR(40) NOT NULL,
name VARCHAR(200) NOT NULL,
address VARCHAR(255) NOT NULL,
contact VARCHAR(100) NULL,
manager_name VARCHAR(100) NULL,
manager_phone VARCHAR(60) NULL,
emergency_phone VARCHAR(60) NULL,
gate_open_time VARCHAR(40) NULL,
transport VARCHAR(500) NULL,
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active',
notes VARCHAR(1000) NULL,
rooms TEXT NOT NULL,
updated_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_centers_code (code),
UNIQUE KEY uq_centers_school_name (school_id, name),
CONSTRAINT fk_centers_school FOREIGN KEY (school_id) REFERENCES schools(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS test_rooms (
id VARCHAR(64) NOT NULL,
center_id VARCHAR(64) NOT NULL,
code VARCHAR(40) NOT NULL,
name VARCHAR(120) NOT NULL,
building VARCHAR(120) NOT NULL,
floor VARCHAR(40) NULL,
capacity INT UNSIGNED NOT NULL,
seat_plan VARCHAR(500) NULL,
seat_start INT UNSIGNED NOT NULL DEFAULT 1,
seat_end INT UNSIGNED NOT NULL,
room_type ENUM('standard', 'computer', 'accessible', 'spare') NOT NULL,
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active',
notes VARCHAR(500) NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_rooms_center_code (center_id, code),
KEY idx_rooms_center (center_id, status, code),
CONSTRAINT fk_rooms_center FOREIGN KEY (center_id) REFERENCES test_centers(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS center_change_requests (
id VARCHAR(64) NOT NULL,
center_id VARCHAR(64) NULL,
school_id VARCHAR(64) NOT NULL,
request_type ENUM('create', 'update') NOT NULL,
code VARCHAR(40) NOT NULL,
name VARCHAR(200) NOT NULL,
address VARCHAR(255) NOT NULL,
contact VARCHAR(100) NULL,
manager_name VARCHAR(100) NULL,
manager_phone VARCHAR(60) NULL,
emergency_phone VARCHAR(60) NULL,
gate_open_time VARCHAR(40) NULL,
transport VARCHAR(500) NULL,
center_status ENUM('active', 'inactive') NOT NULL,
notes VARCHAR(1000) NULL,
status ENUM('pending', 'approved', 'rejected') NOT NULL,
review_note VARCHAR(500) NULL,
requested_by VARCHAR(64) NULL,
created_at VARCHAR(35) NOT NULL,
reviewed_at VARCHAR(35) NULL,
PRIMARY KEY (id),
KEY idx_center_changes_school (school_id, status, created_at),
CONSTRAINT fk_center_change_center FOREIGN KEY (center_id) REFERENCES test_centers(id) ON DELETE SET NULL,
CONSTRAINT fk_center_change_school FOREIGN KEY (school_id) REFERENCES schools(id) ON DELETE CASCADE,
CONSTRAINT fk_center_change_requester FOREIGN KEY (requested_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS center_change_rooms (
id VARCHAR(64) NOT NULL,
request_id VARCHAR(64) NOT NULL,
room_id VARCHAR(64) NULL,
code VARCHAR(40) NOT NULL,
name VARCHAR(120) NOT NULL,
building VARCHAR(120) NOT NULL,
floor VARCHAR(40) NULL,
capacity INT UNSIGNED NOT NULL,
seat_plan VARCHAR(500) NULL,
seat_start INT UNSIGNED NOT NULL DEFAULT 1,
seat_end INT UNSIGNED NOT NULL,
room_type ENUM('standard', 'computer', 'accessible', 'spare') NOT NULL,
status ENUM('active', 'inactive') NOT NULL,
notes VARCHAR(500) NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_center_change_rooms_code (request_id, code),
CONSTRAINT fk_center_change_rooms_request FOREIGN KEY (request_id) REFERENCES center_change_requests(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS number_rules (
id VARCHAR(64) NOT NULL,
name VARCHAR(120) NOT NULL,
separator VARCHAR(10) NOT NULL DEFAULT '',
active BOOLEAN NOT NULL DEFAULT FALSE,
created_by VARCHAR(64) NULL,
updated_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_number_rules_creator FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS number_rule_segments (
id VARCHAR(64) NOT NULL,
rule_id VARCHAR(64) NOT NULL,
position INT UNSIGNED NOT NULL,
type ENUM('year', 'school_code', 'gender', 'sequence', 'literal') NOT NULL,
value VARCHAR(60) NULL,
width INT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uq_rule_segments_position (rule_id, position),
CONSTRAINT fk_rule_segments_rule FOREIGN KEY (rule_id) REFERENCES number_rules(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS candidate_account_batches (
id VARCHAR(64) NOT NULL,
school_id VARCHAR(64) NOT NULL,
requested_by VARCHAR(64) NULL,
status ENUM('pending', 'approved', 'rejected') NOT NULL,
review_note VARCHAR(500) NULL,
created_at VARCHAR(35) NOT NULL,
reviewed_at VARCHAR(35) NULL,
PRIMARY KEY (id),
KEY idx_account_batches_school (school_id, status, created_at),
CONSTRAINT fk_account_batches_school FOREIGN KEY (school_id) REFERENCES schools(id) ON DELETE CASCADE,
CONSTRAINT fk_account_batches_requester FOREIGN KEY (requested_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS candidate_account_batch_items (
id VARCHAR(64) NOT NULL,
batch_id VARCHAR(64) NOT NULL,
class_id VARCHAR(64) NOT NULL,
position INT UNSIGNED NOT NULL,
candidate_number VARCHAR(120) NULL,
initial_password VARCHAR(120) NULL,
user_id VARCHAR(64) NULL,
created_at VARCHAR(35) NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_account_batch_position (batch_id, position),
UNIQUE KEY uq_account_batch_number (candidate_number),
UNIQUE KEY uq_account_batch_user (user_id),
KEY idx_account_batch_items (batch_id, class_id, position),
CONSTRAINT fk_account_batch_items_batch FOREIGN KEY (batch_id) REFERENCES candidate_account_batches(id) ON DELETE CASCADE,
CONSTRAINT fk_account_batch_items_class FOREIGN KEY (class_id) REFERENCES school_classes(id),
CONSTRAINT fk_account_batch_items_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS workflow_definitions (
id VARCHAR(64) NOT NULL,
business_type ENUM('profile_change', 'registration_review', 'center_change', 'candidate_account_batch') NOT NULL,
name VARCHAR(120) NOT NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
updated_by VARCHAR(64) NULL,
updated_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_workflow_type_active (business_type, active),
CONSTRAINT fk_workflow_updater FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS workflow_steps (
id VARCHAR(64) NOT NULL,
workflow_id VARCHAR(64) NOT NULL,
position INT UNSIGNED NOT NULL,
name VARCHAR(120) NOT NULL,
admin_level ENUM('school', 'super') NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uq_workflow_steps_position (workflow_id, position),
CONSTRAINT fk_workflow_steps_definition FOREIGN KEY (workflow_id) REFERENCES workflow_definitions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS workflow_instances (
id VARCHAR(64) NOT NULL,
workflow_id VARCHAR(64) NOT NULL,
business_type VARCHAR(40) NOT NULL,
business_id VARCHAR(64) NOT NULL,
status ENUM('pending', 'approved', 'rejected') NOT NULL,
current_step INT UNSIGNED NOT NULL DEFAULT 1,
assignee_id VARCHAR(64) NULL,
created_at VARCHAR(35) NOT NULL,
completed_at VARCHAR(35) NULL,
PRIMARY KEY (id),
KEY idx_workflow_inbox (status, assignee_id, business_type),
CONSTRAINT fk_workflow_instance_definition FOREIGN KEY (workflow_id) REFERENCES workflow_definitions(id),
CONSTRAINT fk_workflow_instance_assignee FOREIGN KEY (assignee_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS workflow_actions (
id VARCHAR(64) NOT NULL,
instance_id VARCHAR(64) NOT NULL,
actor_id VARCHAR(64) NULL,
action ENUM('submit', 'approve', 'reject', 'transfer', 'return', 'supervise') NOT NULL,
note VARCHAR(500) NULL,
from_assignee_id VARCHAR(64) NULL,
to_assignee_id VARCHAR(64) NULL,
created_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
KEY idx_workflow_actions_instance (instance_id, created_at),
CONSTRAINT fk_workflow_action_instance FOREIGN KEY (instance_id) REFERENCES workflow_instances(id) ON DELETE CASCADE,
CONSTRAINT fk_workflow_action_actor FOREIGN KEY (actor_id) REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT fk_workflow_action_from FOREIGN KEY (from_assignee_id) REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT fk_workflow_action_to FOREIGN KEY (to_assignee_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS audit_logs (
id VARCHAR(64) NOT NULL,
actor_id VARCHAR(64) NULL,
action VARCHAR(100) NOT NULL,
detail VARCHAR(1000) NOT NULL,
created_at VARCHAR(35) NOT NULL,
PRIMARY KEY (id),
KEY idx_audit_created (created_at),
CONSTRAINT fk_audit_actor FOREIGN KEY (actor_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`
];