-- Idempotent schema for CTMS (derived from current SQLAlchemy models) DO $$ BEGIN CREATE TYPE public.user_role AS ENUM ('ADMIN', 'PM', 'CRA', 'PV', 'IMP', 'QA'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN ALTER TYPE public.user_role ADD VALUE IF NOT EXISTS 'QA'; EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE public.user_status AS ENUM ('PENDING', 'ACTIVE', 'REJECTED', 'DISABLED'); EXCEPTION WHEN duplicate_object THEN null; END $$; CREATE TABLE IF NOT EXISTS public.users ( id uuid PRIMARY KEY, email character varying(255) NOT NULL, password_hash character varying(255) NOT NULL, full_name character varying(255) NOT NULL, role public.user_role NOT NULL, clinical_department character varying(255) NOT NULL, status public.user_status NOT NULL DEFAULT 'PENDING', created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), approved_by uuid, approved_at timestamp with time zone, avatar_url character varying(500), CONSTRAINT uq_users_email UNIQUE (email) ); CREATE TABLE IF NOT EXISTS public.studies ( id uuid PRIMARY KEY, code character varying(50) NOT NULL, name character varying(200) NOT NULL, sponsor character varying(200), protocol_no character varying(100), phase character varying(50), status character varying(20) NOT NULL DEFAULT 'DRAFT', is_locked boolean NOT NULL DEFAULT false, visit_schedule jsonb NOT NULL DEFAULT '[]'::jsonb, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_studies_code UNIQUE (code), CONSTRAINT fk_studies_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.sites ( id uuid PRIMARY KEY, study_id uuid NOT NULL, name character varying(200) NOT NULL, city character varying(100), pi_name character varying(100), contact character varying(100), is_active boolean NOT NULL DEFAULT true, enrollment_target integer, created_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_sites_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.study_members ( id uuid PRIMARY KEY, study_id uuid NOT NULL, user_id uuid NOT NULL, role_in_study character varying(20) NOT NULL, is_active boolean NOT NULL DEFAULT true, added_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_study_member UNIQUE (study_id, user_id), CONSTRAINT fk_study_members_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_study_members_user_id FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.milestones ( id uuid PRIMARY KEY, study_id uuid NOT NULL, type character varying(50) NOT NULL, name character varying(200) NOT NULL, planned_date date, adjusted_start_date date, adjusted_end_date date, actual_start_date date, actual_end_date date, actual_date date, status character varying(20) NOT NULL DEFAULT 'NOT_STARTED', site_id uuid, owner_id uuid, notes text, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_milestones_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_milestones_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE RESTRICT, CONSTRAINT fk_milestones_owner_id FOREIGN KEY (owner_id) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.subjects ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_id uuid NOT NULL, subject_no character varying(50) NOT NULL, status character varying(20) NOT NULL DEFAULT 'SCREENING', screening_date date, consent_date date, enrollment_date date, baseline_date date, completion_date date, drop_reason text, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_subject_study_no UNIQUE (study_id, subject_no), CONSTRAINT fk_subjects_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_subjects_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.subject_histories ( id uuid PRIMARY KEY, study_id uuid NOT NULL, subject_id uuid NOT NULL, record_date date, content text NOT NULL, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_subject_histories_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_subject_histories_subject_id FOREIGN KEY (subject_id) REFERENCES public.subjects(id) ON DELETE RESTRICT, CONSTRAINT fk_subject_histories_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.subject_pds ( id uuid PRIMARY KEY, study_id uuid NOT NULL, subject_id uuid NOT NULL, pd_no character varying(64) NOT NULL, pd_type character varying(100) NOT NULL, pd_level character varying(20) NOT NULL DEFAULT 'GENERAL', approval_status character varying(20) NOT NULL DEFAULT 'DRAFT', description text, status character varying(20) NOT NULL DEFAULT 'OPEN', created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_subject_pds_study_pd_no UNIQUE (study_id, pd_no), CONSTRAINT fk_subject_pds_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_subject_pds_subject_id FOREIGN KEY (subject_id) REFERENCES public.subjects(id) ON DELETE RESTRICT, CONSTRAINT fk_subject_pds_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.visits ( id uuid PRIMARY KEY, study_id uuid NOT NULL, subject_id uuid NOT NULL, visit_code character varying(50) NOT NULL, planned_date date, actual_date date, status character varying(20) NOT NULL DEFAULT 'PLANNED', window_start date, window_end date, notes text, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_visits_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_visits_subject_id FOREIGN KEY (subject_id) REFERENCES public.subjects(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.adverse_events ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_id uuid, subject_id uuid, visit_id uuid, term character varying(255) NOT NULL, onset_date date, resolution_date date, seriousness character varying(50) NOT NULL, severity character varying(50) NOT NULL, causality character varying(50), action_taken text, outcome character varying(50), reported_to_sponsor boolean NOT NULL DEFAULT false, is_sae boolean NOT NULL DEFAULT false, is_susar boolean NOT NULL DEFAULT false, report_due_date date, status character varying(20) NOT NULL DEFAULT 'NEW', description text, created_by uuid NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_adverse_events_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_adverse_events_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE RESTRICT, CONSTRAINT fk_adverse_events_subject_id FOREIGN KEY (subject_id) REFERENCES public.subjects(id) ON DELETE RESTRICT, CONSTRAINT fk_adverse_events_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.finance_contracts ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_name character varying(255) NOT NULL, contract_no character varying(100) NOT NULL, signed_date date, amount numeric(12, 2) NOT NULL, currency character varying(10) NOT NULL DEFAULT 'CNY', remark text, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_finance_contracts_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_finance_contracts_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.contract_fees ( id uuid PRIMARY KEY, project_id uuid NOT NULL, center_id uuid NOT NULL, contract_amount numeric(12, 2) NOT NULL, contract_cases integer NOT NULL, actual_cases integer, settlement_amount numeric(12, 2), final_payment_amount numeric(12, 2), created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_contract_fees_project_center UNIQUE (project_id, center_id), CONSTRAINT fk_contract_fees_project_id FOREIGN KEY (project_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_contract_fees_center_id FOREIGN KEY (center_id) REFERENCES public.sites(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.contract_fee_payments ( id uuid PRIMARY KEY, contract_fee_id uuid NOT NULL, seq integer NOT NULL, amount numeric(12, 2) NOT NULL, paid_date date, verified_date date, is_paid boolean NOT NULL DEFAULT false, is_verified boolean NOT NULL DEFAULT false, remark text, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_contract_fee_payments_contract_fee_id FOREIGN KEY (contract_fee_id) REFERENCES public.contract_fees(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS public.fee_attachments ( id uuid PRIMARY KEY, entity_type character varying(50) NOT NULL, entity_id uuid NOT NULL, file_type character varying(30) NOT NULL, filename character varying(255) NOT NULL, mime_type character varying(100), size integer NOT NULL, storage_key character varying(500), url text, uploaded_by uuid NOT NULL, uploaded_at timestamp with time zone NOT NULL DEFAULT now(), is_deleted boolean NOT NULL DEFAULT false, CONSTRAINT fk_fee_attachments_uploaded_by FOREIGN KEY (uploaded_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.finance_items ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_id uuid, subject_id uuid, visit_id uuid, category character varying(50) NOT NULL, title character varying(255) NOT NULL, description text, currency character varying(10) NOT NULL DEFAULT 'CNY', amount numeric(12, 2) NOT NULL, occur_date date NOT NULL, status character varying(20) NOT NULL DEFAULT 'DRAFT', submitted_at timestamp with time zone, approved_at timestamp with time zone, rejected_at timestamp with time zone, paid_at timestamp with time zone, approver_id uuid, payer_id uuid, reject_reason text, created_by uuid NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_finance_items_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_finance_items_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE RESTRICT, CONSTRAINT fk_finance_items_subject_id FOREIGN KEY (subject_id) REFERENCES public.subjects(id) ON DELETE RESTRICT, CONSTRAINT fk_finance_items_approver_id FOREIGN KEY (approver_id) REFERENCES public.users(id) ON DELETE RESTRICT, CONSTRAINT fk_finance_items_payer_id FOREIGN KEY (payer_id) REFERENCES public.users(id) ON DELETE RESTRICT, CONSTRAINT fk_finance_items_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.drug_shipments ( id uuid PRIMARY KEY, study_id uuid NOT NULL, direction character varying(20) NOT NULL, center_id uuid, site_name character varying(255) NOT NULL, ship_date date, receive_date date, quantity integer, batch_no character varying(100), carrier character varying(100), tracking_no character varying(100), status character varying(30) NOT NULL, remark text, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_drug_shipments_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_drug_shipments_center_id FOREIGN KEY (center_id) REFERENCES public.sites(id) ON DELETE RESTRICT, CONSTRAINT fk_drug_shipments_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.material_equipments ( id uuid PRIMARY KEY, study_id uuid NOT NULL, name character varying(255) NOT NULL, spec_model character varying(255) NOT NULL, unit character varying(50), brand character varying(100) NOT NULL, origin character varying(255), production_permit_file_name character varying(255), tech_index_file_name character varying(255), need_calibration boolean NOT NULL DEFAULT false, calibration_cycle_days integer, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_material_equipments_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_material_equipments_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.startup_feasibility ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_id uuid, submit_date date, accept_date date, approved_date date, project_no character varying(100), created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_startup_feasibility_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_startup_feasibility_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE RESTRICT, CONSTRAINT fk_startup_feasibility_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.startup_ethics ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_id uuid, submit_date date, accept_date date, meeting_date date, approved_date date, approval_no character varying(100), created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_startup_ethics_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_startup_ethics_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE RESTRICT, CONSTRAINT fk_startup_ethics_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.kickoff_meetings ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_id uuid, kickoff_date date, attendees json, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_kickoff_meetings_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_kickoff_meetings_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE RESTRICT, CONSTRAINT fk_kickoff_meetings_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.training_authorizations ( id uuid PRIMARY KEY, study_id uuid NOT NULL, name character varying(100) NOT NULL, role character varying(100), site_name character varying(255), trained boolean NOT NULL DEFAULT false, authorized boolean NOT NULL DEFAULT false, trained_date date, authorized_date date, remark text, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_training_authorizations_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_training_authorizations_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.knowledge_notes ( id uuid PRIMARY KEY, study_id uuid NOT NULL, site_name character varying(255) NOT NULL, title character varying(255) NOT NULL, content text NOT NULL, level character varying(50), created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_knowledge_notes_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_knowledge_notes_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.faq_categories ( id uuid PRIMARY KEY, study_id uuid, name character varying(100) NOT NULL, description text, sort_order integer NOT NULL DEFAULT 0, is_active boolean NOT NULL DEFAULT true, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_faq_category_name UNIQUE (study_id, name), CONSTRAINT fk_faq_categories_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.faq_items ( id uuid PRIMARY KEY, study_id uuid, category_id uuid NOT NULL, best_reply_id uuid, status character varying(20) NOT NULL DEFAULT 'PENDING', resolved_by_confirm boolean NOT NULL DEFAULT false, question text NOT NULL, answer text NOT NULL, keywords character varying(255), version integer NOT NULL DEFAULT 1, is_active boolean NOT NULL DEFAULT true, created_by uuid NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_faq_items_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_faq_items_category_id FOREIGN KEY (category_id) REFERENCES public.faq_categories(id) ON DELETE RESTRICT, CONSTRAINT fk_faq_items_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.faq_replies ( id uuid PRIMARY KEY, faq_id uuid NOT NULL, study_id uuid, content text NOT NULL, created_by uuid NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT now(), quote_reply_id uuid, is_deleted boolean NOT NULL DEFAULT false, CONSTRAINT fk_faq_replies_faq_id FOREIGN KEY (faq_id) REFERENCES public.faq_items(id) ON DELETE RESTRICT, CONSTRAINT fk_faq_replies_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_faq_replies_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE RESTRICT, CONSTRAINT fk_faq_replies_quote_reply_id FOREIGN KEY (quote_reply_id) REFERENCES public.faq_replies(id) ON DELETE RESTRICT ); DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'fk_faq_items_best_reply_id' ) THEN ALTER TABLE public.faq_items ADD CONSTRAINT fk_faq_items_best_reply_id FOREIGN KEY (best_reply_id) REFERENCES public.faq_replies(id) ON DELETE SET NULL; END IF; END $$; CREATE TABLE IF NOT EXISTS public.attachments ( id uuid PRIMARY KEY, study_id uuid NOT NULL, entity_type character varying(50) NOT NULL, entity_id uuid NOT NULL, filename character varying(255) NOT NULL, file_path character varying(500) NOT NULL, file_size integer NOT NULL, content_type character varying(100), uploaded_by uuid NOT NULL, uploaded_at timestamp with time zone NOT NULL DEFAULT now(), is_deleted boolean NOT NULL DEFAULT false, CONSTRAINT fk_attachments_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_attachments_uploaded_by FOREIGN KEY (uploaded_by) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS public.audit_logs ( id uuid PRIMARY KEY, study_id uuid, entity_type character varying(50) NOT NULL, entity_id uuid, action character varying(50) NOT NULL, detail text, operator_id uuid NOT NULL, operator_role character varying(50) NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_audit_logs_study_id FOREIGN KEY (study_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_audit_logs_operator_id FOREIGN KEY (operator_id) REFERENCES public.users(id) ON DELETE RESTRICT ); CREATE INDEX IF NOT EXISTS ix_sites_study_id ON public.sites (study_id); CREATE INDEX IF NOT EXISTS ix_study_members_study_id ON public.study_members (study_id); CREATE INDEX IF NOT EXISTS ix_milestones_study_id ON public.milestones (study_id); CREATE INDEX IF NOT EXISTS ix_subjects_study_id ON public.subjects (study_id); CREATE INDEX IF NOT EXISTS ix_subjects_site_id ON public.subjects (site_id); CREATE INDEX IF NOT EXISTS ix_subject_histories_study_id ON public.subject_histories (study_id); CREATE INDEX IF NOT EXISTS ix_subject_histories_subject_id ON public.subject_histories (subject_id); CREATE INDEX IF NOT EXISTS ix_visits_subject_id ON public.visits (subject_id); CREATE INDEX IF NOT EXISTS ix_adverse_events_study_id ON public.adverse_events (study_id); CREATE INDEX IF NOT EXISTS ix_adverse_events_site_id ON public.adverse_events (site_id); CREATE INDEX IF NOT EXISTS ix_adverse_events_subject_id ON public.adverse_events (subject_id); CREATE INDEX IF NOT EXISTS ix_finance_contracts_study_id ON public.finance_contracts (study_id); CREATE INDEX IF NOT EXISTS ix_finance_items_study_id ON public.finance_items (study_id); CREATE INDEX IF NOT EXISTS ix_finance_items_site_id ON public.finance_items (site_id); CREATE INDEX IF NOT EXISTS ix_finance_items_subject_id ON public.finance_items (subject_id); CREATE INDEX IF NOT EXISTS ix_drug_shipments_study_id ON public.drug_shipments (study_id); CREATE INDEX IF NOT EXISTS ix_drug_shipments_center_id ON public.drug_shipments (center_id); CREATE INDEX IF NOT EXISTS ix_material_equipments_study_id ON public.material_equipments (study_id); CREATE INDEX IF NOT EXISTS ix_startup_feasibility_study_id ON public.startup_feasibility (study_id); CREATE INDEX IF NOT EXISTS ix_startup_ethics_study_id ON public.startup_ethics (study_id); CREATE INDEX IF NOT EXISTS ix_kickoff_meetings_study_id ON public.kickoff_meetings (study_id); CREATE INDEX IF NOT EXISTS ix_kickoff_meetings_site_id ON public.kickoff_meetings (site_id); CREATE INDEX IF NOT EXISTS ix_training_authorizations_study_id ON public.training_authorizations (study_id); CREATE INDEX IF NOT EXISTS ix_knowledge_notes_study_id ON public.knowledge_notes (study_id); CREATE INDEX IF NOT EXISTS ix_faq_categories_study_id ON public.faq_categories (study_id); CREATE INDEX IF NOT EXISTS ix_faq_items_study_id ON public.faq_items (study_id); CREATE INDEX IF NOT EXISTS ix_faq_items_category_id ON public.faq_items (category_id); CREATE INDEX IF NOT EXISTS ix_faq_replies_faq_id ON public.faq_replies (faq_id); CREATE INDEX IF NOT EXISTS ix_faq_replies_study_id ON public.faq_replies (study_id); -- ========================================= -- DEMO SEED DATA (idempotent) -- ========================================= INSERT INTO public.users ( id, email, password_hash, full_name, role, clinical_department, status, created_at, updated_at ) VALUES ('11111111-1111-1111-1111-111111111111', 'admin@example.com', '$2y$12$FMfyBt7YfZWeh/x8WMEIA.S6VUTIMryk2NxWHyOOuJvd6YrDwHAdu', 'System Admin', 'ADMIN', 'SYSTEM', 'ACTIVE', '2025-01-05 08:00:00+00', '2025-01-05 08:00:00+00'), ('22222222-2222-2222-2222-222222222222', 'pm@example.com', '$2y$12$P9UDKu48ru84uVrlquqhXufmw/CfMEzQosC0X8eT2EmY/PgM/03j2', '项目经理', 'PM', 'PMO', 'ACTIVE', '2025-01-05 08:10:00+00', '2025-01-05 08:10:00+00'), ('33333333-3333-3333-3333-333333333333', 'cra@example.com', '$2y$12$D/.ZeDrUByPZFPnAl9MnKuzKn2G4ctKQ9IAS/CqS2sfMdE/5NaKAK', 'CRA 用户', 'CRA', 'CRA', 'ACTIVE', '2025-01-05 08:20:00+00', '2025-01-05 08:20:00+00'), ('44444444-4444-4444-4444-444444444444', 'qa@example.com', '$2y$12$P9UDKu48ru84uVrlquqhXufmw/CfMEzQosC0X8eT2EmY/PgM/03j2', 'QA 用户', 'QA', 'QA', 'ACTIVE', '2025-01-05 08:30:00+00', '2025-01-05 08:30:00+00'), ('55555555-5555-5555-5555-555555555555', 'pv@example.com', '$2y$12$P9UDKu48ru84uVrlquqhXufmw/CfMEzQosC0X8eT2EmY/PgM/03j2', 'PV 用户', 'PV', 'PV', 'ACTIVE', '2025-01-05 08:40:00+00', '2025-01-05 08:40:00+00'), ('66666666-6666-6666-6666-666666666666', 'imp@example.com', '$2y$12$P9UDKu48ru84uVrlquqhXufmw/CfMEzQosC0X8eT2EmY/PgM/03j2', '药品管理员', 'IMP', 'IMP', 'ACTIVE', '2025-01-05 08:50:00+00', '2025-01-05 08:50:00+00') ON CONFLICT (email) DO UPDATE SET password_hash = EXCLUDED.password_hash, full_name = EXCLUDED.full_name, role = EXCLUDED.role, clinical_department = EXCLUDED.clinical_department, status = EXCLUDED.status, updated_at = EXCLUDED.updated_at; INSERT INTO public.studies ( id, code, name, sponsor, protocol_no, phase, status, visit_schedule, created_by, created_at ) VALUES ( 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'DEMO-CTMS', '示例项目:糖尿病新药', 'Demo Pharma', 'DP-001', 'Phase II', 'ACTIVE', '[ {"visit_code": "基线访视", "baseline_offset_days": 0, "window_before_days": 0, "window_after_days": 0}, {"visit_code": "V1", "baseline_offset_days": 7, "window_before_days": 2, "window_after_days": 2}, {"visit_code": "V2", "baseline_offset_days": 15, "window_before_days": 2, "window_after_days": 2}, {"visit_code": "V3", "baseline_offset_days": 23, "window_before_days": 3, "window_after_days": 3} ]'::jsonb, (SELECT id FROM public.users WHERE email = 'admin@example.com'), '2025-01-06 08:00:00+00' ) ON CONFLICT (code) DO UPDATE SET name = EXCLUDED.name, sponsor = EXCLUDED.sponsor, protocol_no = EXCLUDED.protocol_no, phase = EXCLUDED.phase, status = EXCLUDED.status, visit_schedule = EXCLUDED.visit_schedule, created_by = EXCLUDED.created_by; INSERT INTO public.sites ( id, study_id, name, city, pi_name, contact, enrollment_target, is_active, created_at ) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '北京协和医院', '北京', '李主任', '010-12345678', 80, true, '2025-01-06 09:00:00+00'), ('cccccccc-cccc-cccc-cccc-cccccccccccc', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '上海瑞金医院', '上海', '王主任', '021-12345678', 60, true, '2025-01-06 09:10:00+00'), ('dddddddd-cccc-bbbb-aaaa-eeeeeeeeeeee', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '示例中心 3', '北京', '赵主任', '010-12340000', 40, true, '2025-01-06 09:20:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.study_members ( id, study_id, user_id, role_in_study, is_active, added_at ) VALUES ('dddddddd-dddd-dddd-dddd-dddddddddddd', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), (SELECT id FROM public.users WHERE email = 'admin@example.com'), 'ADMIN', true, '2025-01-06 10:00:00+00'), ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'PM', true, '2025-01-06 10:05:00+00'), ('ffffffff-ffff-ffff-ffff-ffffffffffff', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), (SELECT id FROM public.users WHERE email = 'cra@example.com'), 'CRA', true, '2025-01-06 10:10:00+00'), ('11111111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), (SELECT id FROM public.users WHERE email = 'qa@example.com'), 'QA', true, '2025-01-06 10:15:00+00'), ('22222222-3333-4444-5555-666666666666', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), (SELECT id FROM public.users WHERE email = 'pv@example.com'), 'PV', true, '2025-01-06 10:20:00+00'), ('33333333-4444-5555-6666-777777777777', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), (SELECT id FROM public.users WHERE email = 'imp@example.com'), 'IMP', true, '2025-01-06 10:25:00+00') ON CONFLICT (study_id, user_id) DO UPDATE SET role_in_study = EXCLUDED.role_in_study, is_active = EXCLUDED.is_active; INSERT INTO public.milestones ( id, study_id, type, name, planned_date, actual_date, status, site_id, owner_id, notes, created_at, updated_at ) VALUES ('10101010-aaaa-bbbb-cccc-111111111111', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'SIV', '启动会', '2025-01-10', '2025-01-12', 'DONE', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '现场启动完成', '2025-01-06 12:00:00+00', '2025-01-12 10:00:00+00'), ('20202020-aaaa-bbbb-cccc-222222222222', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'LPI', '首例入组', '2025-02-01', NULL, 'NOT_STARTED', 'cccccccc-cccc-cccc-cccc-cccccccccccc', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '待完成', '2025-01-06 12:10:00+00', '2025-01-06 12:10:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.finance_contracts ( id, study_id, site_name, contract_no, signed_date, amount, currency, remark, created_by, created_at, updated_at ) VALUES ('77777777-aaaa-bbbb-cccc-111111111111', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '北京协和医院', 'CON-001', '2025-01-08', 1200000.00, 'CNY', '首批合同签署', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-08 09:00:00+00', '2025-01-08 09:00:00+00'), ('77777777-aaaa-bbbb-cccc-222222222222', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '上海瑞金医院', 'CON-002', '2025-01-10', 980000.00, 'CNY', '分中心合同签署', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-10 09:00:00+00', '2025-01-10 09:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.finance_items ( id, study_id, site_id, subject_id, visit_id, category, title, description, currency, amount, occur_date, status, submitted_at, approved_at, rejected_at, paid_at, approver_id, payer_id, reject_reason, created_by, created_at, updated_at ) VALUES ('99999999-aaaa-bbbb-cccc-111111111111', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', NULL, NULL, 'SITE_FEE', '启动费', '中心启动费用', 'CNY', 30000.00, '2025-01-12', 'PAID', '2025-01-13 08:00:00+00', '2025-01-13 10:00:00+00', NULL, '2025-01-15 09:00:00+00', (SELECT id FROM public.users WHERE email = 'pm@example.com'), (SELECT id FROM public.users WHERE email = 'admin@example.com'), NULL, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 09:00:00+00', '2025-01-15 09:00:00+00'), ('99999999-aaaa-bbbb-cccc-222222222222', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'cccccccc-cccc-cccc-cccc-cccccccccccc', NULL, NULL, 'TRAVEL', '监查差旅', 'CRA 监查差旅报销', 'CNY', 4800.00, '2025-01-20', 'APPROVED', '2025-01-21 08:00:00+00', '2025-01-21 10:00:00+00', NULL, NULL, (SELECT id FROM public.users WHERE email = 'pm@example.com'), NULL, NULL, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-20 10:00:00+00', '2025-01-21 10:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.drug_shipments ( id, study_id, direction, center_id, site_name, ship_date, receive_date, quantity, batch_no, carrier, tracking_no, status, remark, created_by, created_at, updated_at ) VALUES ('aaaa1111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '寄送', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '北京协和医院', '2025-01-12', NULL, 200, 'DP-001', '顺丰', 'SF100001', '运输中', '首批寄送:DP-001 片剂 50mg × 200 盒', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-12 11:00:00+00', '2025-01-12 11:00:00+00'), ('bbbb1111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '寄送', 'cccccccc-cccc-cccc-cccc-cccccccccccc', '上海瑞金医院', '2025-01-13', '2025-01-14', 150, 'DP-001', '京东物流', 'JD200002', '已签收', '分中心补货:DP-001 片剂 50mg × 150 盒', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-13 11:00:00+00', '2025-01-14 09:00:00+00'), ('cccc1111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '回收', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '北京协和医院', '2025-02-05', '2025-02-06', NULL, 'DP-001', '顺丰', 'SF100003', '已回收', '首批回收:空药盒及剩余药品', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-02-05 14:00:00+00', '2025-02-06 09:00:00+00'), ('dddd1111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '回收', 'cccccccc-cccc-cccc-cccc-cccccccccccc', '上海瑞金医院', '2025-02-08', NULL, NULL, 'DP-001', '德邦', 'DB300004', '运输中', '回收途中:冷链箱回收', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-02-08 16:00:00+00', '2025-02-08 16:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.startup_feasibility ( id, study_id, site_id, submit_date, accept_date, approved_date, project_no, created_by, created_at, updated_at ) VALUES ('eeee1111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '2025-01-02', '2025-01-05', '2025-01-10', 'FEA-BJ-001', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-10 10:00:00+00', '2025-01-10 10:00:00+00'), ('ffff1111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'cccccccc-cccc-cccc-cccc-cccccccccccc', '2025-01-03', '2025-01-06', '2025-01-12', 'FEA-SH-002', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 10:00:00+00', '2025-01-12 10:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.startup_ethics ( id, study_id, site_id, submit_date, accept_date, meeting_date, approved_date, approval_no, created_by, created_at, updated_at ) VALUES ('1111aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '2025-01-04', '2025-01-07', '2025-01-15', '2025-01-20', 'ETH-BJ-001', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-20 11:00:00+00', '2025-01-20 11:00:00+00'), ('2222aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'cccccccc-cccc-cccc-cccc-cccccccccccc', '2025-01-05', '2025-01-08', '2025-01-16', '2025-01-22', 'ETH-SH-002', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-22 11:00:00+00', '2025-01-22 11:00:00+00') ON CONFLICT (id) DO NOTHING; UPDATE public.startup_feasibility SET site_id = (SELECT id FROM public.sites WHERE name ILIKE '%北京%' OR city ILIKE '%北京%' LIMIT 1) WHERE site_id IS NULL AND project_no ILIKE '%-BJ-%'; UPDATE public.startup_feasibility SET site_id = (SELECT id FROM public.sites WHERE name ILIKE '%上海%' OR city ILIKE '%上海%' LIMIT 1) WHERE site_id IS NULL AND project_no ILIKE '%-SH-%'; UPDATE public.startup_ethics SET site_id = (SELECT id FROM public.sites WHERE name ILIKE '%北京%' OR city ILIKE '%北京%' LIMIT 1) WHERE site_id IS NULL AND approval_no ILIKE '%-BJ-%'; UPDATE public.startup_ethics SET site_id = (SELECT id FROM public.sites WHERE name ILIKE '%上海%' OR city ILIKE '%上海%' LIMIT 1) WHERE site_id IS NULL AND approval_no ILIKE '%-SH-%'; INSERT INTO public.kickoff_meetings ( id, study_id, site_id, kickoff_date, attendees, created_by, created_at, updated_at ) VALUES ( '3333aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '2025-01-12', '["张三","李四","王五"]'::json, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 13:00:00+00', '2025-01-12 13:00:00+00' ), ( '3333aaaa-2222-3333-4444-666666666666', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'cccccccc-cccc-cccc-cccc-cccccccccccc', '2025-01-14', '["赵六","钱七","孙八"]'::json, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-14 13:00:00+00', '2025-01-14 13:00:00+00' ), ( '3333aaaa-2222-3333-4444-777777777777', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'dddddddd-cccc-bbbb-aaaa-eeeeeeeeeeee', '2025-01-16', '["周九","吴十"]'::json, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-16 13:00:00+00', '2025-01-16 13:00:00+00' ) ON CONFLICT (id) DO NOTHING; INSERT INTO public.training_authorizations ( id, study_id, name, role, site_name, trained, authorized, trained_date, authorized_date, remark, created_by, created_at, updated_at ) VALUES ('4444bbbb-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '赵敏', '研究医生', '北京协和医院', true, false, '2025-01-12', NULL, '待授权', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 15:00:00+00', '2025-01-12 15:00:00+00'), ('5555bbbb-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '李雷', 'CRC', '上海瑞金医院', false, true, NULL, '2025-01-18', '先授权后补训', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-18 15:00:00+00', '2025-01-18 15:00:00+00'), ('6666bbbb-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '韩梅梅', '药房管理员', '北京协和医院', true, true, '2025-01-10', '2025-01-12', '已完成', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 16:00:00+00', '2025-01-12 16:00:00+00'), ('7777bbbb-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '王强', 'CRA', '上海瑞金医院', false, false, NULL, NULL, '待安排', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-20 16:00:00+00', '2025-01-20 16:00:00+00'), ('8888bbbb-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '刘晨', '研究护士', '北京协和医院', true, false, '2025-01-14', NULL, '仅完成培训', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-14 16:00:00+00', '2025-01-14 16:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.subjects ( id, study_id, site_id, subject_no, status, screening_date, consent_date, enrollment_date, baseline_date, completion_date, drop_reason, created_at, updated_at ) VALUES ('11111111-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'SUBJ-001', 'ENROLLED', '2025-01-05', '2025-01-06', '2025-01-10', '2025-01-10', NULL, NULL, '2025-01-10 10:00:00+00', '2025-01-10 10:00:00+00'), ('22222222-3333-4444-5555-666666666666', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'SUBJ-002', 'SCREENING', '2025-01-12', '2025-01-13', NULL, NULL, NULL, NULL, '2025-01-12 10:00:00+00', '2025-01-12 10:00:00+00'), ('33333333-4444-5555-6666-777777777777', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'SUBJ-003', 'COMPLETED', '2024-12-20', '2024-12-21', '2024-12-28', '2024-12-28', '2025-02-05', NULL, '2025-02-05 10:00:00+00', '2025-02-05 10:00:00+00') ON CONFLICT (study_id, subject_no) DO UPDATE SET site_id = EXCLUDED.site_id, status = EXCLUDED.status, screening_date = EXCLUDED.screening_date, consent_date = EXCLUDED.consent_date, enrollment_date = EXCLUDED.enrollment_date, baseline_date = EXCLUDED.baseline_date, completion_date = EXCLUDED.completion_date, drop_reason = EXCLUDED.drop_reason, updated_at = EXCLUDED.updated_at; INSERT INTO public.subject_pds ( id, study_id, subject_id, pd_no, pd_type, pd_level, approval_status, description, status, created_by, created_at, updated_at ) VALUES ( '77777777-aaaa-bbbb-cccc-111111111111', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '11111111-2222-3333-4444-555555555555', 'PD0001', '知情同意', 'MAJOR', 'DRAFT', '知情记录不完整,缺少签署时间记录。', 'OPEN', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-11 10:00:00+00', '2025-01-11 10:00:00+00' ), ( '77777777-aaaa-bbbb-cccc-222222222222', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '11111111-2222-3333-4444-555555555555', 'PD0002', '排除标准', 'CRITICAL', 'APPROVED', '排除标准判定延期,已补充说明并审批通过。', 'OPEN', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-18 10:00:00+00', '2025-01-19 09:00:00+00' ), ( '77777777-aaaa-bbbb-cccc-333333333333', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '33333333-4444-5555-6666-777777777777', 'PD0003', '访视流程', 'GENERAL', 'SUBMITTED', '访视窗口外补录,已提交审批。', 'CLOSED', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-02-06 10:00:00+00', '2025-02-06 10:00:00+00' ) ON CONFLICT (study_id, pd_no) DO UPDATE SET subject_id = EXCLUDED.subject_id, pd_type = EXCLUDED.pd_type, pd_level = EXCLUDED.pd_level, approval_status = EXCLUDED.approval_status, description = EXCLUDED.description, status = EXCLUDED.status, created_by = EXCLUDED.created_by, updated_at = EXCLUDED.updated_at; INSERT INTO public.subject_histories ( id, study_id, subject_id, record_date, content, created_by, created_at, updated_at ) VALUES ('55555555-aaaa-bbbb-cccc-111111111111', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '11111111-2222-3333-4444-555555555555', '2025-01-08', '既往有高血压病史,长期服药控制。', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-08 11:00:00+00', '2025-01-08 11:00:00+00'), ('55555555-aaaa-bbbb-cccc-222222222222', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '22222222-3333-4444-5555-666666666666', '2025-01-13', '糖耐量异常,需进一步评估。', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-13 11:00:00+00', '2025-01-13 11:00:00+00'), ('55555555-aaaa-bbbb-cccc-333333333333', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '33333333-4444-5555-6666-777777777777', '2025-01-02', '既往病史稳定,未见严重合并症。', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-02 11:00:00+00', '2025-01-02 11:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.visits ( id, study_id, subject_id, visit_code, planned_date, actual_date, status, window_start, window_end, notes, created_at, updated_at ) VALUES ('44444444-aaaa-bbbb-cccc-111111111111', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '11111111-2222-3333-4444-555555555555', 'V1', '2025-01-09', '2025-01-10', 'DONE', '2025-01-07', '2025-01-12', '按期完成', '2025-01-10 12:00:00+00', '2025-01-10 12:00:00+00'), ('44444444-aaaa-bbbb-cccc-222222222222', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '11111111-2222-3333-4444-555555555555', 'FU1', '2025-02-09', NULL, 'PLANNED', '2025-02-07', '2025-02-12', NULL, '2025-01-20 12:00:00+00', '2025-01-20 12:00:00+00'), ('44444444-aaaa-bbbb-cccc-333333333333', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '11111111-2222-3333-4444-555555555555', 'FU2', '2025-03-11', NULL, 'PLANNED', '2025-03-09', '2025-03-14', NULL, '2025-02-01 12:00:00+00', '2025-02-01 12:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.adverse_events ( id, study_id, site_id, subject_id, visit_id, term, onset_date, resolution_date, seriousness, severity, causality, action_taken, outcome, reported_to_sponsor, report_due_date, status, description, created_by, created_at, updated_at ) VALUES ('66666666-aaaa-bbbb-cccc-111111111111', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-2222-3333-4444-555555555555', '44444444-aaaa-bbbb-cccc-111111111111', '轻微头痛', '2025-01-10', '2025-01-11', 'NON_SERIOUS', 'MILD', '可能相关', '休息观察', '恢复', true, '2025-01-20', 'CLOSED', '参与者轻微头痛,次日缓解。', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-10 13:00:00+00', '2025-01-11 09:00:00+00'), ('66666666-aaaa-bbbb-cccc-222222222222', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'cccccccc-cccc-cccc-cccc-cccccccccccc', '22222222-3333-4444-5555-666666666666', NULL, '血糖升高', '2025-01-18', NULL, 'SERIOUS', 'SEVERE', '相关', '调整用药', '未恢复', false, '2025-01-28', 'NEW', '筛选期血糖异常。', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-18 13:00:00+00', '2025-01-18 13:00:00+00'), ('66666666-aaaa-bbbb-cccc-333333333333', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-2222-3333-4444-555555555555', NULL, '轻度皮疹', '2025-02-10', NULL, 'NON_SERIOUS', 'MODERATE', '待评估', NULL, '未恢复', false, '2025-02-20', 'FOLLOW_UP', '出现皮疹,持续观察。', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-02-10 13:00:00+00', '2025-02-10 13:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.knowledge_notes ( id, study_id, site_name, title, content, level, created_by, created_at, updated_at ) VALUES ('4444aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '北京协和医院', '样本采集注意', '采血需在早晨空腹完成,样本 2 小时内送达。', '高', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 18:00:00+00', '2025-01-12 18:00:00+00'), ('5555aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '上海瑞金医院', '访视资料上传', '访视完成后 24 小时内完成资料上传与核对。', '中', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-15 18:00:00+00', '2025-01-15 18:00:00+00'), ('6666aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '北京协和医院', '冷链运输提醒', '冷链记录必须随药品一起封存。', '低', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-18 18:00:00+00', '2025-01-18 18:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.faq_categories ( id, study_id, name, description, sort_order, is_active, created_at, updated_at ) VALUES ('7777aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '访视相关', '访视常见问题', 1, true, '2025-01-10 08:00:00+00', '2025-01-10 08:00:00+00'), ('8888aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '用药相关', '用药与回收问题', 2, true, '2025-01-10 08:10:00+00', '2025-01-10 08:10:00+00') ON CONFLICT (study_id, name) DO UPDATE SET description = EXCLUDED.description, sort_order = EXCLUDED.sort_order, is_active = EXCLUDED.is_active, updated_at = EXCLUDED.updated_at; INSERT INTO public.faq_items ( id, study_id, category_id, best_reply_id, status, resolved_by_confirm, question, answer, keywords, version, is_active, created_by, created_at, updated_at ) VALUES ('9999aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '7777aaaa-2222-3333-4444-555555555555', NULL, 'PENDING', false, 'V1 访视需要携带什么?', '携带知情同意书、病历及用药记录本。', '访视,携带物品', 1, true, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-10 09:00:00+00', '2025-01-10 09:00:00+00'), ('aaaa9999-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '7777aaaa-2222-3333-4444-555555555555', NULL, 'PENDING', false, '访视延迟是否需要重新排期?', '若延迟超过窗口期需重新安排并备注原因。', '访视,窗口期', 1, true, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-11 09:00:00+00', '2025-01-11 09:00:00+00'), ('bbbb9999-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '8888aaaa-2222-3333-4444-555555555555', NULL, 'PENDING', false, '药品回收需要哪些材料?', '需提供回收清单、交接单及冷链记录。', '回收,交接单', 1, true, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 09:00:00+00', '2025-01-12 09:00:00+00'), ('cccc9999-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '8888aaaa-2222-3333-4444-555555555555', NULL, 'PENDING', false, '药品签收后如何登记?', '签收后在药品流向中登记并上传快递单。', '药品,签收', 1, true, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-13 09:00:00+00', '2025-01-13 09:00:00+00'), ('dddd9999-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '7777aaaa-2222-3333-4444-555555555555', NULL, 'PENDING', false, '随访资料上传时间要求?', '访视结束 24 小时内上传。', '随访,资料上传', 1, true, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-14 09:00:00+00', '2025-01-14 09:00:00+00') ON CONFLICT (id) DO NOTHING; INSERT INTO public.faq_replies ( id, faq_id, study_id, content, created_by, created_at, quote_reply_id, is_deleted ) VALUES ('eeee9999-2222-3333-4444-555555555555', '9999aaaa-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '如有特殊情况请提前沟通 PM。', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-10 10:00:00+00', NULL, false), ('ffff9999-2222-3333-4444-555555555555', 'bbbb9999-2222-3333-4444-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '回收材料可在下载中心获取模板。', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 10:00:00+00', NULL, false) ON CONFLICT (id) DO NOTHING; UPDATE public.faq_items SET best_reply_id = 'eeee9999-2222-3333-4444-555555555555' WHERE id = '9999aaaa-2222-3333-4444-555555555555' AND best_reply_id IS NULL; UPDATE public.faq_items SET best_reply_id = 'ffff9999-2222-3333-4444-555555555555' WHERE id = 'bbbb9999-2222-3333-4444-555555555555' AND best_reply_id IS NULL; INSERT INTO public.attachments ( id, study_id, entity_type, entity_id, filename, file_path, file_size, content_type, uploaded_by, uploaded_at, is_deleted ) VALUES ('aaaa0000-1111-2222-3333-444455556666', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'finance_contract', '77777777-aaaa-bbbb-cccc-111111111111', 'contract_beijing.pdf', '/code/app/uploads/study_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/finance_contract_77777777-aaaa-bbbb-cccc-111111111111/contract_beijing.pdf', 1024, 'application/pdf', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-08 10:00:00+00', false), ('bbbb0000-1111-2222-3333-444455556666', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'drug_shipment', 'aaaa1111-2222-3333-4444-555555555555', 'shipment_beijing.pdf', '/code/app/uploads/study_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/drug_shipment_aaaa1111-2222-3333-4444-555555555555/shipment_beijing.pdf', 2048, 'application/pdf', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-12 12:00:00+00', false), ('cccc0000-1111-2222-3333-444455556666', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'startup_ethics', '1111aaaa-2222-3333-4444-555555555555', 'ethics_beijing.pdf', '/code/app/uploads/study_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/startup_ethics_1111aaaa-2222-3333-4444-555555555555/ethics_beijing.pdf', 1536, 'application/pdf', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-20 12:00:00+00', false), ('dddd0000-1111-2222-3333-444455556666', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'knowledge_note', '4444aaaa-2222-3333-4444-555555555555', 'note_sample.txt', '/code/app/uploads/study_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/knowledge_note_4444aaaa-2222-3333-4444-555555555555/note_sample.txt', 512, 'text/plain', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 19:00:00+00', false) ON CONFLICT (id) DO NOTHING; -- ========================================= -- Document version management module -- ========================================= DO $$ BEGIN CREATE TYPE public.document_scope_type AS ENUM ('GLOBAL', 'SITE', 'DERIVED'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE public.document_status AS ENUM ('DRAFT', 'ACTIVE', 'ARCHIVED'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE public.document_version_status AS ENUM ( 'DRAFT', 'SUBMITTED', 'APPROVED', 'EFFECTIVE', 'SUPERSEDED', 'ARCHIVED', 'WITHDRAWN' ); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE public.distribution_target_type AS ENUM ('SITE', 'ROLE', 'USER'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE public.distribution_status AS ENUM ('ACTIVE', 'CLOSED'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE public.acknowledgement_type AS ENUM ('RECEIVED', 'READ', 'TRAINED'); EXCEPTION WHEN duplicate_object THEN null; END $$; CREATE TABLE IF NOT EXISTS public.documents ( id uuid PRIMARY KEY, trial_id uuid NOT NULL, site_id uuid, doc_no character varying(50) NOT NULL, doc_type character varying(50) NOT NULL, title character varying(200) NOT NULL, scope_type public.document_scope_type NOT NULL DEFAULT 'GLOBAL', owner_id uuid, status public.document_status NOT NULL DEFAULT 'ACTIVE', current_effective_version_id uuid, description text, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_documents_trial_doc_no UNIQUE (trial_id, doc_no), CONSTRAINT fk_documents_trial_id FOREIGN KEY (trial_id) REFERENCES public.studies(id) ON DELETE RESTRICT, CONSTRAINT fk_documents_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE SET NULL, CONSTRAINT fk_documents_owner_id FOREIGN KEY (owner_id) REFERENCES public.users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS public.document_versions ( id uuid PRIMARY KEY, document_id uuid NOT NULL, version_no character varying(50) NOT NULL, parent_version_id uuid, derived_from_version_id uuid, status public.document_version_status NOT NULL DEFAULT 'DRAFT', effective_at timestamp with time zone, superseded_at timestamp with time zone, file_uri character varying(500) NOT NULL, file_hash character varying(128) NOT NULL, file_size bigint NOT NULL, mime_type character varying(100), change_summary text, created_by uuid, submitted_at timestamp with time zone, approved_at timestamp with time zone, withdrawn_at timestamp with time zone, created_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_document_versions_doc_no UNIQUE (document_id, version_no), CONSTRAINT fk_document_versions_document_id FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE CASCADE, CONSTRAINT fk_document_versions_parent_id FOREIGN KEY (parent_version_id) REFERENCES public.document_versions(id) ON DELETE SET NULL, CONSTRAINT fk_document_versions_derived_id FOREIGN KEY (derived_from_version_id) REFERENCES public.document_versions(id) ON DELETE SET NULL, CONSTRAINT fk_document_versions_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE SET NULL ); DO $$ BEGIN ALTER TABLE public.documents ADD CONSTRAINT fk_documents_current_effective_version_id FOREIGN KEY (current_effective_version_id) REFERENCES public.document_versions(id) ON DELETE SET NULL; EXCEPTION WHEN duplicate_object THEN null; END $$; CREATE TABLE IF NOT EXISTS public.distributions ( id uuid PRIMARY KEY, document_id uuid NOT NULL, version_id uuid NOT NULL, target_type public.distribution_target_type NOT NULL, target_id character varying(50) NOT NULL, status public.distribution_status NOT NULL DEFAULT 'ACTIVE', due_at timestamp with time zone, closed_at timestamp with time zone, created_by uuid, created_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT fk_distributions_document_id FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE CASCADE, CONSTRAINT fk_distributions_version_id FOREIGN KEY (version_id) REFERENCES public.document_versions(id) ON DELETE CASCADE, CONSTRAINT fk_distributions_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS public.acknowledgements ( id uuid PRIMARY KEY, distribution_id uuid NOT NULL, user_id uuid NOT NULL, site_id uuid, ack_type public.acknowledgement_type NOT NULL, due_at timestamp with time zone, acked_at timestamp with time zone, note text, created_at timestamp with time zone NOT NULL DEFAULT now(), CONSTRAINT uq_ack_distribution_user_type UNIQUE (distribution_id, user_id, ack_type), CONSTRAINT fk_acknowledgements_distribution_id FOREIGN KEY (distribution_id) REFERENCES public.distributions(id) ON DELETE CASCADE, CONSTRAINT fk_acknowledgements_user_id FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE RESTRICT, CONSTRAINT fk_acknowledgements_site_id FOREIGN KEY (site_id) REFERENCES public.sites(id) ON DELETE SET NULL ); CREATE UNIQUE INDEX IF NOT EXISTS ix_documents_trial_doc_no ON public.documents (trial_id, doc_no); CREATE INDEX IF NOT EXISTS ix_documents_site_id ON public.documents (site_id); CREATE UNIQUE INDEX IF NOT EXISTS ix_document_versions_doc_no ON public.document_versions (document_id, version_no); CREATE INDEX IF NOT EXISTS ix_document_versions_status ON public.document_versions (document_id, status); CREATE UNIQUE INDEX IF NOT EXISTS uq_document_versions_pending ON public.document_versions (document_id) WHERE status IN ('SUBMITTED', 'APPROVED'); CREATE INDEX IF NOT EXISTS ix_distributions_version_target ON public.distributions (version_id, target_type, target_id); CREATE INDEX IF NOT EXISTS ix_acknowledgements_distribution_type ON public.acknowledgements (distribution_id, ack_type); CREATE INDEX IF NOT EXISTS ix_audit_logs_entity_at ON public.audit_logs (entity_type, entity_id, created_at); DO $$ BEGIN ALTER TABLE public.kickoff_meetings ADD CONSTRAINT uq_kickoff_meetings_study_site UNIQUE (study_id, site_id); EXCEPTION WHEN duplicate_object THEN null; END $$; -- ========================================= -- Demo data for document version management -- ========================================= INSERT INTO public.documents ( id, trial_id, site_id, doc_no, doc_type, title, scope_type, owner_id, status, current_effective_version_id, description, created_at, updated_at ) VALUES ( '33333333-aaaa-bbbb-cccc-333333333333', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), NULL, 'SOP-001', 'SOP', '中心文件管理 SOP', 'GLOBAL', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'ACTIVE', NULL, 'Demo SOP document', '2025-01-08 09:00:00+00', '2025-01-12 09:00:00+00' ), ( '44444444-aaaa-bbbb-cccc-444444444444', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), NULL, 'TRN-101', 'Training', '中心培训材料', 'GLOBAL', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'ACTIVE', NULL, 'Training deck', '2025-01-08 10:00:00+00', '2025-01-10 10:00:00+00' ), ( '55555555-aaaa-bbbb-cccc-555555555555', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), (SELECT id FROM public.sites WHERE name = '北京协和医院' AND study_id = (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS')), 'SITE-SOP-001', 'SOP', '北京协和中心 SOP', 'SITE', (SELECT id FROM public.users WHERE email = 'cra@example.com'), 'ACTIVE', NULL, 'Beijing site SOP', '2025-01-09 09:00:00+00', '2025-01-12 09:00:00+00' ) ON CONFLICT (trial_id, doc_no) DO NOTHING; INSERT INTO public.document_versions ( id, document_id, version_no, parent_version_id, derived_from_version_id, status, effective_at, superseded_at, file_uri, file_hash, file_size, mime_type, change_summary, created_by, submitted_at, approved_at, withdrawn_at, created_at ) VALUES ( '55555555-aaaa-bbbb-cccc-555555555555', '33333333-aaaa-bbbb-cccc-333333333333', '1.0', NULL, NULL, 'EFFECTIVE', '2025-01-10 09:00:00+00', NULL, '/code/app/uploads/documents/demo/SOP-001-v1.pdf', 'b7c52b35f0b8e4a3c6d5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', 2048, 'application/pdf', '首次发布', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-09 09:00:00+00', '2025-01-10 08:30:00+00', NULL, '2025-01-08 09:30:00+00' ), ( '66666666-aaaa-bbbb-cccc-666666666666', '33333333-aaaa-bbbb-cccc-333333333333', '1.1', '55555555-aaaa-bbbb-cccc-555555555555', NULL, 'APPROVED', NULL, NULL, '/code/app/uploads/documents/demo/SOP-001-v1.1.pdf', 'c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', 3072, 'application/pdf', '小幅更新', (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 10:00:00+00', '2025-01-12 11:00:00+00', NULL, '2025-01-12 09:30:00+00' ), ( '77777777-aaaa-bbbb-cccc-777777777777', '44444444-aaaa-bbbb-cccc-444444444444', '1.0', NULL, NULL, 'DRAFT', NULL, NULL, '/code/app/uploads/documents/demo/TRN-101-v1.pdf', 'd2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1', 4096, 'application/pdf', '培训材料草稿', (SELECT id FROM public.users WHERE email = 'cra@example.com'), NULL, NULL, NULL, '2025-01-10 10:30:00+00' ), ( '88888888-aaaa-bbbb-cccc-888888888888', '55555555-aaaa-bbbb-cccc-555555555555', '1.0', NULL, NULL, 'EFFECTIVE', '2025-01-10 09:00:00+00', NULL, '/code/app/uploads/documents/demo/SITE-SOP-001-v1.pdf', 'a3b2c1d0e9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2', 2048, 'application/pdf', '分中心 SOP 首版', (SELECT id FROM public.users WHERE email = 'cra@example.com'), '2025-01-09 09:30:00+00', '2025-01-10 08:30:00+00', NULL, '2025-01-09 09:00:00+00' ) ON CONFLICT (document_id, version_no) DO NOTHING; UPDATE public.documents SET current_effective_version_id = '55555555-aaaa-bbbb-cccc-555555555555', updated_at = '2025-01-12 09:00:00+00' WHERE id = '33333333-aaaa-bbbb-cccc-333333333333' AND current_effective_version_id IS NULL; UPDATE public.documents SET current_effective_version_id = '88888888-aaaa-bbbb-cccc-888888888888', updated_at = '2025-01-10 09:10:00+00' WHERE id = '55555555-aaaa-bbbb-cccc-555555555555' AND current_effective_version_id IS NULL; INSERT INTO public.distributions ( id, document_id, version_id, target_type, target_id, status, due_at, closed_at, created_by, created_at ) VALUES ( 'bbbb0000-1111-2222-3333-444444444444', '33333333-aaaa-bbbb-cccc-333333333333', '55555555-aaaa-bbbb-cccc-555555555555', 'SITE', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'ACTIVE', '2025-01-20 18:00:00+00', NULL, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 12:00:00+00' ), ( 'cccc0000-1111-2222-3333-555555555555', '33333333-aaaa-bbbb-cccc-333333333333', '55555555-aaaa-bbbb-cccc-555555555555', 'SITE', 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'ACTIVE', '2025-01-20 18:00:00+00', NULL, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 12:05:00+00' ), ( 'dddd0000-1111-2222-3333-666666666666', '33333333-aaaa-bbbb-cccc-333333333333', '55555555-aaaa-bbbb-cccc-555555555555', 'USER', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'ACTIVE', '2025-01-20 18:00:00+00', NULL, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 12:10:00+00' ), ( 'eeee0000-1111-2222-3333-777777777777', '33333333-aaaa-bbbb-cccc-333333333333', '55555555-aaaa-bbbb-cccc-555555555555', 'USER', (SELECT id FROM public.users WHERE email = 'cra@example.com'), 'ACTIVE', '2025-01-20 18:00:00+00', NULL, (SELECT id FROM public.users WHERE email = 'pm@example.com'), '2025-01-12 12:15:00+00' ) ON CONFLICT (id) DO NOTHING; INSERT INTO public.acknowledgements ( id, distribution_id, user_id, site_id, ack_type, due_at, acked_at, note, created_at ) VALUES ( 'ffff0000-1111-2222-3333-888888888888', 'dddd0000-1111-2222-3333-666666666666', (SELECT id FROM public.users WHERE email = 'pm@example.com'), NULL, 'RECEIVED', '2025-01-20 18:00:00+00', '2025-01-12 13:00:00+00', '已收到', '2025-01-12 13:00:00+00' ), ( '11110000-2222-3333-4444-999999999999', 'eeee0000-1111-2222-3333-777777777777', (SELECT id FROM public.users WHERE email = 'cra@example.com'), NULL, 'READ', '2025-01-20 18:00:00+00', '2025-01-12 14:00:00+00', '已阅读', '2025-01-12 14:00:00+00' ) ON CONFLICT (id) DO NOTHING; INSERT INTO public.audit_logs ( id, study_id, entity_type, entity_id, action, detail, operator_id, operator_role, created_at ) VALUES ( '22220000-3333-4444-5555-aaaaaaaaaaaa', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'DOCUMENT', '33333333-aaaa-bbbb-cccc-333333333333', 'DOCUMENT_CREATED', '{"before":null,"after":{"doc_no":"SOP-001","status":"ACTIVE"}}', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'PM', '2025-01-08 09:00:00+00' ), ( '33330000-4444-5555-6666-bbbbbbbbbbbb', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'DOCUMENT_VERSION', '55555555-aaaa-bbbb-cccc-555555555555', 'VERSION_EFFECTIVE', '{"before":{"status":"APPROVED"},"after":{"status":"EFFECTIVE"}}', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'PM', '2025-01-10 09:00:00+00' ), ( '44440000-5555-6666-7777-cccccccccccc', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'DISTRIBUTION', 'bbbb0000-1111-2222-3333-444444444444', 'DISTRIBUTION_CREATED', '{"version_id":"55555555-aaaa-bbbb-cccc-555555555555","target_id":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"}', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'PM', '2025-01-12 12:00:00+00' ), ( '55550000-6666-7777-8888-dddddddddddd', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), 'ACKNOWLEDGEMENT', 'ffff0000-1111-2222-3333-888888888888', 'ACK_CREATED', '{"distribution_id":"dddd0000-1111-2222-3333-666666666666","ack_type":"RECEIVED"}', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'PM', '2025-01-12 13:00:00+00' ) ON CONFLICT (id) DO NOTHING; -- Demo transition: v1.1 effective, v1.0 superseded UPDATE public.document_versions SET status = 'SUPERSEDED', superseded_at = '2025-01-15 09:00:00+00' WHERE id = '55555555-aaaa-bbbb-cccc-555555555555' AND status = 'EFFECTIVE'; UPDATE public.document_versions SET status = 'EFFECTIVE', effective_at = '2025-01-15 09:00:00+00' WHERE id = '66666666-aaaa-bbbb-cccc-666666666666' AND status = 'APPROVED'; UPDATE public.documents SET current_effective_version_id = '66666666-aaaa-bbbb-cccc-666666666666', updated_at = '2025-01-15 09:00:00+00' WHERE id = '33333333-aaaa-bbbb-cccc-333333333333'; -- Lightweight demo: one extra draft document INSERT INTO public.documents ( id, trial_id, site_id, doc_no, doc_type, title, scope_type, owner_id, status, current_effective_version_id, description, created_at, updated_at ) VALUES ( '99999999-eeee-ffff-aaaa-000000000001', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), NULL, 'QRG-001', 'Guidance', '研究快速指引', 'GLOBAL', (SELECT id FROM public.users WHERE email = 'pm@example.com'), 'ACTIVE', NULL, 'Quick reference guide (draft)', '2025-01-16 09:00:00+00', '2025-01-16 09:00:00+00' ) ON CONFLICT (trial_id, doc_no) DO NOTHING; INSERT INTO public.document_versions ( id, document_id, version_no, parent_version_id, derived_from_version_id, status, effective_at, superseded_at, file_uri, file_hash, file_size, mime_type, change_summary, created_by, submitted_at, approved_at, withdrawn_at, created_at ) VALUES ( '99999999-eeee-ffff-aaaa-000000000002', '99999999-eeee-ffff-aaaa-000000000001', '0.1', NULL, NULL, 'DRAFT', NULL, NULL, '/code/app/uploads/documents/demo/QRG-001-v0.1.pdf', 'a1b2c3d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef0', 1536, 'application/pdf', '初稿', (SELECT id FROM public.users WHERE email = 'pm@example.com'), NULL, NULL, NULL, '2025-01-16 09:10:00+00' ) ON CONFLICT (document_id, version_no) DO NOTHING;