115 lines
4.2 KiB
SQL
115 lines
4.2 KiB
SQL
-- User staff profiles (1:1 with users) — HR / verification workflow
|
|
-- Apply: docker exec -i initiative-postgres psql -U initiative -d initiatives < be0/migrations/010_user_staff_profiles.sql
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE profile_verification_status AS ENUM ('draft', 'pending', 'verified', 'rejected');
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
CREATE TABLE IF NOT EXISTS academic_titles (
|
|
code TEXT PRIMARY KEY,
|
|
label_vi TEXT NOT NULL,
|
|
label_en TEXT NOT NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
active BOOLEAN NOT NULL DEFAULT TRUE
|
|
);
|
|
|
|
INSERT INTO academic_titles (code, label_vi, label_en, sort_order) VALUES
|
|
('professor', 'Giáo sư', 'Professor', 10),
|
|
('associate_professor', 'Phó Giáo sư', 'Associate Professor', 20),
|
|
('doctor_sc', 'Tiến sĩ', 'Doctor of Science', 30),
|
|
('bsckii', 'BSCKII', 'Specialist level II', 35),
|
|
('bscki', 'BSCKI', 'Specialist level I', 36),
|
|
('master', 'Thạc sĩ', 'Master', 40),
|
|
('doctor_md', 'Bác sĩ', 'Physician', 45),
|
|
('pharmacist', 'Dược sĩ', 'Pharmacist', 46),
|
|
('bachelor', 'Cử nhân', 'Bachelor', 50),
|
|
('other', 'Khác (ghi rõ)', 'Other (specify)', 100)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
CREATE TABLE IF NOT EXISTS user_staff_profiles (
|
|
user_id UUID PRIMARY KEY
|
|
REFERENCES users(id) ON DELETE CASCADE,
|
|
|
|
employee_id TEXT,
|
|
academic_title_code TEXT REFERENCES academic_titles(code),
|
|
academic_title_other TEXT,
|
|
unit_name_freetext TEXT,
|
|
job_title TEXT,
|
|
|
|
profile_verification_status profile_verification_status
|
|
NOT NULL DEFAULT 'draft',
|
|
verification_submitted_at TIMESTAMPTZ,
|
|
verified_at TIMESTAMPTZ,
|
|
verified_by_user_id UUID REFERENCES users(id),
|
|
rejection_reason TEXT,
|
|
|
|
version INTEGER NOT NULL DEFAULT 1,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
CONSTRAINT employee_id_shape
|
|
CHECK (employee_id IS NULL OR employee_id ~ '^[A-Z0-9-]{3,32}$'),
|
|
|
|
CONSTRAINT academic_title_other_invariant CHECK (
|
|
CASE
|
|
WHEN academic_title_code IS NULL THEN academic_title_other IS NULL
|
|
WHEN academic_title_code = 'other' THEN
|
|
academic_title_other IS NOT NULL AND length(trim(academic_title_other)) > 0
|
|
ELSE academic_title_other IS NULL
|
|
END
|
|
),
|
|
|
|
CONSTRAINT verified_requires_metadata CHECK (
|
|
profile_verification_status <> 'verified'
|
|
OR (verified_at IS NOT NULL AND verified_by_user_id IS NOT NULL)
|
|
),
|
|
|
|
CONSTRAINT rejected_requires_reason CHECK (
|
|
profile_verification_status <> 'rejected'
|
|
OR (rejection_reason IS NOT NULL AND length(trim(rejection_reason)) > 0)
|
|
),
|
|
|
|
CONSTRAINT non_terminal_clears_verification CHECK (
|
|
profile_verification_status NOT IN ('draft', 'pending')
|
|
OR (verified_at IS NULL AND verified_by_user_id IS NULL)
|
|
),
|
|
|
|
CONSTRAINT rejected_clears_verification_metadata CHECK (
|
|
profile_verification_status <> 'rejected'
|
|
OR (verified_at IS NULL AND verified_by_user_id IS NULL)
|
|
),
|
|
|
|
CONSTRAINT verified_clears_rejection CHECK (
|
|
profile_verification_status <> 'verified'
|
|
OR rejection_reason IS NULL
|
|
),
|
|
|
|
CONSTRAINT job_title_length CHECK (
|
|
job_title IS NULL OR length(job_title) <= 120
|
|
)
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_usp_employee_id_unique
|
|
ON user_staff_profiles (employee_id)
|
|
WHERE employee_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_usp_pending_queue
|
|
ON user_staff_profiles (verification_submitted_at)
|
|
WHERE profile_verification_status = 'pending';
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_usp_verifier_activity
|
|
ON user_staff_profiles (verified_by_user_id, verified_at DESC)
|
|
WHERE verified_by_user_id IS NOT NULL;
|
|
|
|
-- Backfill one row per existing user (draft, NULL fields)
|
|
INSERT INTO user_staff_profiles (user_id, profile_verification_status)
|
|
SELECT u.id, 'draft'::profile_verification_status
|
|
FROM users u
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM user_staff_profiles p WHERE p.user_id = u.id
|
|
);
|
|
|
|
COMMENT ON TABLE user_staff_profiles IS
|
|
'Institutional staff profile and verification state; scalars only — no MinIO.';
|