sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
-- 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.';
|
||||
Reference in New Issue
Block a user