-- ============================================================ --
HDH-BOS Test Cases Database Schema v3 (Dynamic Modules) --
Modules: quản lý động qua bảng tc_modules -- Run in: Supabase SQL
Editor --
============================================================ -- ──
1. ENUM types (status & priority) ────────────────────── DO $$
BEGIN CREATE TYPE tc_status AS ENUM ('todo','pass','fail','skip');
EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN
CREATE TYPE tc_priority AS ENUM ('Cao','Trung bình','Thấp');
EXCEPTION WHEN duplicate_object THEN NULL; END $$; -- NOTE:
tc_module ENUM đã bỏ — module nay là TEXT tham chiếu
tc_modules.key -- ── 2. Bảng tc_modules (quản lý module động)
─────────────── CREATE TABLE IF NOT EXISTS tc_modules ( key TEXT
PRIMARY KEY, display_name TEXT NOT NULL, sort_order INT NOT NULL
DEFAULT 0 ); -- ── 3. Bảng test_cases
───────────────────────────────────── CREATE TABLE IF NOT EXISTS
test_cases ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, tc_id
TEXT NOT NULL UNIQUE, module TEXT NOT NULL REFERENCES
tc_modules(key) ON UPDATE CASCADE, category TEXT NOT NULL, name
TEXT NOT NULL, description TEXT, precondition TEXT, steps TEXT,
expected TEXT, role TEXT NOT NULL DEFAULT 'All', priority TEXT NOT
NULL DEFAULT 'Trung bình', status TEXT NOT NULL DEFAULT 'todo',
dev_status TEXT NOT NULL DEFAULT 'todo',
error_note TEXT, tester_name TEXT, tested_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ
DEFAULT NOW(), CONSTRAINT chk_status CHECK (status IN
('todo','pass','fail','skip')), CONSTRAINT chk_priority CHECK
(priority IN ('Cao','Trung bình','Thấp')) -- Không có chk_module —
module được validate qua FOREIGN KEY → tc_modules ); -- ── 4. Bảng
test_case_images (ảnh đính kèm lỗi) ─────────── CREATE TABLE IF
NOT EXISTS test_case_images ( id UUID DEFAULT gen_random_uuid()
PRIMARY KEY, tc_id TEXT NOT NULL REFERENCES test_cases(tc_id) ON
DELETE CASCADE, file_name TEXT NOT NULL, file_data TEXT NOT NULL,
-- base64 data URL (data:image/png;base64,…) file_size INT, --
bytes created_at TIMESTAMPTZ DEFAULT NOW() ); -- ── 5. Indexes
───────────────────────────────────────────── CREATE INDEX IF NOT
EXISTS idx_tc_module ON test_cases(module); CREATE INDEX IF NOT
EXISTS idx_tc_status ON test_cases(status); CREATE INDEX IF NOT
EXISTS idx_tc_priority ON test_cases(priority); CREATE INDEX IF
NOT EXISTS idx_tc_role ON test_cases(role); CREATE INDEX IF NOT
EXISTS idx_tc_updated ON test_cases(updated_at DESC); CREATE INDEX
IF NOT EXISTS idx_img_tcid ON test_case_images(tc_id); -- ── 6.
Trigger: auto-update updated_at ──────────────────── CREATE OR
REPLACE FUNCTION update_updated_at() RETURNS TRIGGER LANGUAGE
plpgsql AS $$ BEGIN NEW.updated_at = NOW(); IF NEW.status <>
OLD.status THEN NEW.tested_at = NOW(); END IF; RETURN NEW; END;
$$; DROP TRIGGER IF EXISTS trg_tc_updated ON test_cases; CREATE
TRIGGER trg_tc_updated BEFORE UPDATE ON test_cases FOR EACH ROW
EXECUTE FUNCTION update_updated_at(); -- ── 7. RLS (bật khi cần
bảo mật theo user) ──────────────── -- ALTER TABLE test_cases
ENABLE ROW LEVEL SECURITY; -- ALTER TABLE test_case_images ENABLE
ROW LEVEL SECURITY; -- CREATE POLICY "anon_all" ON test_cases FOR
ALL USING (true) WITH CHECK (true); -- CREATE POLICY "anon_all" ON
test_case_images FOR ALL USING (true) WITH CHECK (true); -- ── 8.
View: thống kê theo module ───────────────────────── CREATE OR
REPLACE VIEW v_test_summary AS SELECT module, COUNT(*) AS total,
COUNT(*) FILTER (WHERE status = 'pass') AS passed, COUNT(*) FILTER
(WHERE status = 'fail') AS failed, COUNT(*) FILTER (WHERE status =
'skip') AS skipped, COUNT(*) FILTER (WHERE status = 'todo') AS
todo, ROUND( COUNT(*) FILTER (WHERE status = 'pass') * 100.0 /
NULLIF(COUNT(*), 0), 1 ) AS pass_rate, MAX(updated_at) AS
last_updated FROM test_cases GROUP BY module ORDER BY module; --
── 9. View: danh sách TC đang lỗi ──────────────────────── CREATE
OR REPLACE VIEW v_failed_tests AS SELECT tc_id, module, category,
name, role, priority, error_note, tester_name, tested_at FROM
test_cases WHERE status = 'fail' ORDER BY tested_at DESC NULLS
LAST; -- ── 10. Hàm tiện ích: reset trạng thái theo module
──────── CREATE OR REPLACE FUNCTION reset_module_status(p_module
TEXT) RETURNS INT LANGUAGE plpgsql AS $$ DECLARE updated_count
INT; BEGIN UPDATE test_cases SET status = 'todo', error_note =
NULL, tested_at = NULL WHERE module = p_module; GET DIAGNOSTICS
updated_count = ROW_COUNT; RETURN updated_count; END; $$; -- Dùng:
SELECT reset_module_status('project'); -- ── 11. Seed tc_modules
────────────────────────────────────── -- Điền dữ liệu module mặc
định (có thể sửa trước khi chạy) INSERT INTO tc_modules (key,
display_name, sort_order) VALUES ('units', 'Danh sách đơn vị', 1),
('positions', 'Danh sách vị trí', 2), ('staffs', 'Danh sách nhân
sự', 3), ('orgchart', 'Sơ đồ tổ chức', 4), ('details', 'Hồ sơ nhân
sự', 5), ('task_mgmt', 'Quản lý công việc', 6), ('project', 'Danh
mục dự án', 7) ON CONFLICT (key) DO UPDATE SET display_name =
EXCLUDED.display_name, sort_order = EXCLUDED.sort_order; -- ── 12.
Seed test_cases ────────────────────────────────────── -- Dùng nút
[📥 Import từ Excel] hoặc [📋 Copy SQL đầy đủ (kèm dữ liệu)]