init.sql小bug修复-20260204

This commit is contained in:
Cheng Zhou
2026-02-04 16:17:51 +08:00
parent 737f84bf54
commit 0693f09b1d
4 changed files with 97 additions and 207 deletions
+41 -196
View File
@@ -45,6 +45,7 @@ CREATE TABLE IF NOT EXISTS public.studies (
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_interval_days integer,
visit_total integer,
visit_window_start_offset integer,
@@ -63,6 +64,7 @@ CREATE TABLE IF NOT EXISTS public.sites (
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
);
@@ -514,6 +516,7 @@ CREATE INDEX IF NOT EXISTS ix_drug_shipments_center_id ON public.drug_shipments
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);
@@ -571,10 +574,11 @@ INSERT INTO public.studies (
created_by = EXCLUDED.created_by;
INSERT INTO public.sites (
id, study_id, name, city, pi_name, contact, is_active, created_at
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', true, '2025-01-06 09:00:00+00'),
('cccccccc-cccc-cccc-cccc-cccccccccccc', (SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'), '上海瑞金医院', '上海', '王主任', '021-12345678', true, '2025-01-06 09:10:00+00')
('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 (
@@ -659,16 +663,39 @@ SET site_id = (SELECT id FROM public.sites WHERE name ILIKE '%上海%' OR city I
WHERE site_id IS NULL AND approval_no ILIKE '%-SH-%';
INSERT INTO public.kickoff_meetings (
id, study_id, kickoff_date, attendees, created_by, created_at, updated_at
) VALUES (
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'
) ON CONFLICT (id) DO NOTHING;
),
(
'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
@@ -802,20 +829,6 @@ EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$
BEGIN
CREATE TYPE public.workflow_status AS ENUM ('PENDING', 'APPROVED', 'REJECTED', 'CANCELED');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$
BEGIN
CREATE TYPE public.workflow_action_type AS ENUM ('SUBMIT', 'APPROVE', 'REJECT');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$
BEGIN
CREATE TYPE public.distribution_target_type AS ENUM ('SITE', 'ROLE', 'USER');
@@ -857,31 +870,6 @@ CREATE TABLE IF NOT EXISTS public.documents (
CONSTRAINT fk_documents_owner_id FOREIGN KEY (owner_id) REFERENCES public.users(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS public.workflow_templates (
id uuid PRIMARY KEY,
trial_id uuid,
name character varying(200) NOT NULL,
description text,
is_active boolean NOT NULL DEFAULT true,
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_workflow_templates_trial_id FOREIGN KEY (trial_id) REFERENCES public.studies(id) ON DELETE RESTRICT,
CONSTRAINT fk_workflow_templates_created_by FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS public.workflow_nodes (
id uuid PRIMARY KEY,
template_id uuid NOT NULL,
node_order integer NOT NULL,
role character varying(50) NOT NULL,
name character varying(100),
is_required boolean NOT NULL DEFAULT true,
created_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT uq_workflow_nodes_order UNIQUE (template_id, node_order),
CONSTRAINT fk_workflow_nodes_template_id FOREIGN KEY (template_id) REFERENCES public.workflow_templates(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS public.document_versions (
id uuid PRIMARY KEY,
document_id uuid NOT NULL,
@@ -919,37 +907,6 @@ EXCEPTION
WHEN duplicate_object THEN null;
END $$;
CREATE TABLE IF NOT EXISTS public.version_workflows (
id uuid PRIMARY KEY,
document_id uuid NOT NULL,
version_id uuid NOT NULL,
template_id uuid NOT NULL,
status public.workflow_status NOT NULL DEFAULT 'PENDING',
current_node integer,
submitted_by uuid,
submitted_at timestamp with time zone,
completed_at timestamp with time zone,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT uq_version_workflow_version UNIQUE (version_id),
CONSTRAINT fk_version_workflows_document_id FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE CASCADE,
CONSTRAINT fk_version_workflows_version_id FOREIGN KEY (version_id) REFERENCES public.document_versions(id) ON DELETE CASCADE,
CONSTRAINT fk_version_workflows_template_id FOREIGN KEY (template_id) REFERENCES public.workflow_templates(id) ON DELETE RESTRICT,
CONSTRAINT fk_version_workflows_submitted_by FOREIGN KEY (submitted_by) REFERENCES public.users(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS public.workflow_actions (
id uuid PRIMARY KEY,
workflow_id uuid NOT NULL,
node_order integer,
action public.workflow_action_type NOT NULL,
actor_id uuid,
comment text,
acted_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT fk_workflow_actions_workflow_id FOREIGN KEY (workflow_id) REFERENCES public.version_workflows(id) ON DELETE CASCADE,
CONSTRAINT fk_workflow_actions_actor_id FOREIGN KEY (actor_id) REFERENCES public.users(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS public.distributions (
id uuid PRIMARY KEY,
document_id uuid NOT NULL,
@@ -988,93 +945,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS ix_document_versions_doc_no ON public.document
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 UNIQUE INDEX IF NOT EXISTS uq_document_pending_workflow ON public.version_workflows (document_id)
WHERE status IN ('PENDING', '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.workflow_templates (
id, trial_id, name, description, is_active, created_by, created_at, updated_at
) VALUES
(
'11111111-aaaa-bbbb-cccc-111111111111',
(SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'),
'标准审批(PM 单签)',
'PM 单节点审批,适用于一般文件',
true,
(SELECT id FROM public.users WHERE email = 'pm@example.com'),
'2025-01-07 08:00:00+00',
'2025-01-07 08:00:00+00'
),
(
'11111111-aaaa-bbbb-cccc-111111111112',
(SELECT id FROM public.studies WHERE code = 'DEMO-CTMS'),
'严格审批(CRA→PM→QA',
'三节点审批,适用于关键文件',
true,
(SELECT id FROM public.users WHERE email = 'pm@example.com'),
'2025-01-07 08:20:00+00',
'2025-01-07 08:20:00+00'
)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
description = EXCLUDED.description,
is_active = EXCLUDED.is_active,
trial_id = EXCLUDED.trial_id,
created_by = EXCLUDED.created_by,
updated_at = EXCLUDED.updated_at;
INSERT INTO public.workflow_nodes (
id, template_id, node_order, role, name, is_required, created_at
) VALUES
(
'22222222-aaaa-bbbb-cccc-222222222222',
'11111111-aaaa-bbbb-cccc-111111111111',
1,
'PM',
'项目经理审批',
true,
'2025-01-07 08:10:00+00'
),
(
'22222222-aaaa-bbbb-cccc-222222222223',
'11111111-aaaa-bbbb-cccc-111111111112',
1,
'CRA',
'CRA 初审',
true,
'2025-01-07 08:30:00+00'
),
(
'22222222-aaaa-bbbb-cccc-222222222224',
'11111111-aaaa-bbbb-cccc-111111111112',
2,
'PM',
'项目经理复审',
true,
'2025-01-07 08:40:00+00'
),
(
'22222222-aaaa-bbbb-cccc-222222222225',
'11111111-aaaa-bbbb-cccc-111111111112',
3,
'QA',
'QA 终审',
true,
'2025-01-07 08:50:00+00'
)
ON CONFLICT (id) DO UPDATE
SET template_id = EXCLUDED.template_id,
node_order = EXCLUDED.node_order,
role = EXCLUDED.role,
name = EXCLUDED.name,
is_required = EXCLUDED.is_required;
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
@@ -1223,47 +1109,6 @@ SET current_effective_version_id = '88888888-aaaa-bbbb-cccc-888888888888',
WHERE id = '55555555-aaaa-bbbb-cccc-555555555555'
AND current_effective_version_id IS NULL;
INSERT INTO public.version_workflows (
id, document_id, version_id, template_id, status, current_node, submitted_by, submitted_at, completed_at, created_at, updated_at
) VALUES
(
'88888888-aaaa-bbbb-cccc-888888888888',
'33333333-aaaa-bbbb-cccc-333333333333',
'66666666-aaaa-bbbb-cccc-666666666666',
'11111111-aaaa-bbbb-cccc-111111111111',
'APPROVED',
1,
(SELECT id FROM public.users WHERE email = 'pm@example.com'),
'2025-01-12 10:00:00+00',
'2025-01-12 11:00:00+00',
'2025-01-12 10:00:00+00',
'2025-01-12 11:00:00+00'
)
ON CONFLICT (id) DO NOTHING;
INSERT INTO public.workflow_actions (
id, workflow_id, node_order, action, actor_id, comment, acted_at
) VALUES
(
'99999999-aaaa-bbbb-cccc-999999999999',
'88888888-aaaa-bbbb-cccc-888888888888',
1,
'SUBMIT',
(SELECT id FROM public.users WHERE email = 'pm@example.com'),
'提交审批',
'2025-01-12 10:00:00+00'
),
(
'aaaa0000-bbbb-cccc-dddd-111111111111',
'88888888-aaaa-bbbb-cccc-888888888888',
1,
'APPROVE',
(SELECT id FROM public.users WHERE email = 'pm@example.com'),
'批准通过',
'2025-01-12 11:00:00+00'
)
ON CONFLICT (id) DO NOTHING;
INSERT INTO public.distributions (
id, document_id, version_id, target_type, target_id, status, due_at, closed_at, created_by, created_at
) VALUES
+19 -2
View File
@@ -476,16 +476,33 @@ router.beforeEach(async (to, _from, next) => {
}
}
const token = getToken();
const isAdmin = auth.user?.role === "ADMIN";
if (token && !studyStore.currentStudy) {
if (isAdmin) {
await studyStore.ensureDefaultActiveStudy();
} else {
await studyStore.ensureDefaultStudy();
}
}
if (!to.meta.public && !token) {
next({ path: "/login" });
return;
}
if (isAdmin && (to.path === "/" || to.path === "/workbench")) {
next({ path: studyStore.currentStudy ? "/project/overview" : "/admin/users" });
return;
}
if ((to.path === "/login" || to.path === "/register") && token) {
if (!auth.forceLogin) {
next({ path: studyStore.currentStudy ? "/project/overview" : "/workbench" });
next({
path: isAdmin
? studyStore.currentStudy
? "/project/overview"
: "/admin/users"
: studyStore.currentStudy
? "/project/overview"
: "/workbench",
});
return;
}
}
@@ -502,7 +519,7 @@ router.beforeEach(async (to, _from, next) => {
}
}
if (to.meta.requiresStudy && !studyStore.currentStudy) {
next({ path: "/workbench" });
next({ path: isAdmin ? "/admin/users" : "/workbench" });
return;
}
next();
+17
View File
@@ -63,6 +63,22 @@ export const useStudyStore = defineStore("study", () => {
return null;
};
const ensureDefaultActiveStudy = async () => {
if (currentStudy.value) return currentStudy.value;
try {
const { data } = await fetchStudies();
const items = (data as any).items || [];
const active = items.find((study: Study) => study.status === "ACTIVE" && !study.is_locked);
if (active) {
setCurrentStudy(active as Study);
return active as Study;
}
} catch {
return null;
}
return null;
};
const clearCurrentStudy = () => {
currentStudy.value = null;
currentStudyRole.value = null;
@@ -106,6 +122,7 @@ export const useStudyStore = defineStore("study", () => {
setViewContext,
loadCurrentStudy,
ensureDefaultStudy,
ensureDefaultActiveStudy,
clearCurrentStudy,
};
});
+11
View File
@@ -115,6 +115,7 @@ import { reactive, ref, onMounted, onUnmounted } from "vue";
import { useRouter } from "vue-router";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { useAuthStore } from "../store/auth";
import { useStudyStore } from "../store/study";
import { getCachedCredential, setCachedCredential, clearCachedCredential } from "../utils/auth";
import { TEXT, requiredMessage } from "../locales";
@@ -215,7 +216,17 @@ const onSubmit = async () => {
} else {
clearCachedCredential();
}
if (auth.user?.role === "ADMIN") {
const studyStore = useStudyStore();
await studyStore.ensureDefaultActiveStudy();
if (studyStore.currentStudy) {
router.push("/project/overview");
} else {
router.push("/admin/users");
}
} else {
router.push("/");
}
} catch (error: any) {
const detail = error?.response?.data?.detail || error?.response?.data?.message;
ElMessage.error(detail || TEXT.modules.auth.authFailed);