diff --git a/.env b/.env new file mode 100644 index 00000000..b0a7a2bd --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +# 递交日期 -> 受理日期 超期(月) +VITE_STARTUP_SUBMIT_ACCEPT_TIMEOUT_MONTHS=1 +# 受理日期 -> 批准日期 超期(月) +VITE_STARTUP_ACCEPT_APPROVAL_TIMEOUT_MONTHS=1 diff --git a/.gitignore b/.gitignore index 1ee5c0d6..a70d0106 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,6 @@ Thumbs.db *.swp *.swo -# Environment/config -.env -.env.* -!.env.example -.envrc # Python build artifacts __pycache__/ @@ -46,14 +41,6 @@ pyrightconfig.json .pyre/ .ruff_cache/ -# Virtual environments -venv/ -.venv/ -env/ -ENV/ -.conda/ -.virtualenv/ - # Node/Vite frontend frontend/node_modules/ frontend/dist/ diff --git a/backend/alembic/versions/20250218_01_add_site_id_to_startup_tables.py b/backend/alembic/versions/20250218_01_add_site_id_to_startup_tables.py new file mode 100644 index 00000000..8777ee9a --- /dev/null +++ b/backend/alembic/versions/20250218_01_add_site_id_to_startup_tables.py @@ -0,0 +1,114 @@ +"""Add site_id to startup feasibility and ethics. + +Revision ID: 20250218_01 +Revises: None +Create Date: 2025-02-18 +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy import inspect +from sqlalchemy.dialects import postgresql + +revision = "20250218_01" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + bind = op.get_bind() + inspector = inspect(bind) + + feasibility_columns = {col["name"] for col in inspector.get_columns("startup_feasibility")} + if "site_id" not in feasibility_columns: + op.add_column("startup_feasibility", sa.Column("site_id", postgresql.UUID(as_uuid=True), nullable=True)) + feasibility_fks = {fk["name"] for fk in inspector.get_foreign_keys("startup_feasibility")} + if "fk_startup_feasibility_site_id" not in feasibility_fks: + op.create_foreign_key( + "fk_startup_feasibility_site_id", + "startup_feasibility", + "sites", + ["site_id"], + ["id"], + ondelete="RESTRICT", + ) + + ethics_columns = {col["name"] for col in inspector.get_columns("startup_ethics")} + if "site_id" not in ethics_columns: + op.add_column("startup_ethics", sa.Column("site_id", postgresql.UUID(as_uuid=True), nullable=True)) + ethics_fks = {fk["name"] for fk in inspector.get_foreign_keys("startup_ethics")} + if "fk_startup_ethics_site_id" not in ethics_fks: + op.create_foreign_key( + "fk_startup_ethics_site_id", + "startup_ethics", + "sites", + ["site_id"], + ["id"], + ondelete="RESTRICT", + ) + + # Best-effort backfill for demo data where codes include BJ/SH. + op.execute( + """ + UPDATE startup_feasibility + SET site_id = ( + SELECT id FROM sites + WHERE name ILIKE '%北京%' OR city ILIKE '%北京%' + LIMIT 1 + ) + WHERE site_id IS NULL AND project_no ILIKE '%-BJ-%' + """ + ) + op.execute( + """ + UPDATE startup_feasibility + SET site_id = ( + SELECT id FROM sites + WHERE name ILIKE '%上海%' OR city ILIKE '%上海%' + LIMIT 1 + ) + WHERE site_id IS NULL AND project_no ILIKE '%-SH-%' + """ + ) + op.execute( + """ + UPDATE startup_ethics + SET site_id = ( + SELECT id FROM sites + WHERE name ILIKE '%北京%' OR city ILIKE '%北京%' + LIMIT 1 + ) + WHERE site_id IS NULL AND approval_no ILIKE '%-BJ-%' + """ + ) + op.execute( + """ + UPDATE startup_ethics + SET site_id = ( + SELECT id FROM sites + WHERE name ILIKE '%上海%' OR city ILIKE '%上海%' + LIMIT 1 + ) + WHERE site_id IS NULL AND approval_no ILIKE '%-SH-%' + """ + ) + + +def downgrade() -> None: + bind = op.get_bind() + inspector = inspect(bind) + + feasibility_fks = {fk["name"] for fk in inspector.get_foreign_keys("startup_feasibility")} + if "fk_startup_feasibility_site_id" in feasibility_fks: + op.drop_constraint("fk_startup_feasibility_site_id", "startup_feasibility", type_="foreignkey") + feasibility_columns = {col["name"] for col in inspector.get_columns("startup_feasibility")} + if "site_id" in feasibility_columns: + op.drop_column("startup_feasibility", "site_id") + + ethics_fks = {fk["name"] for fk in inspector.get_foreign_keys("startup_ethics")} + if "fk_startup_ethics_site_id" in ethics_fks: + op.drop_constraint("fk_startup_ethics_site_id", "startup_ethics", type_="foreignkey") + ethics_columns = {col["name"] for col in inspector.get_columns("startup_ethics")} + if "site_id" in ethics_columns: + op.drop_column("startup_ethics", "site_id") diff --git a/backend/app/crud/startup.py b/backend/app/crud/startup.py index 6c9ce892..8b5d8f4b 100644 --- a/backend/app/crud/startup.py +++ b/backend/app/crud/startup.py @@ -28,6 +28,7 @@ async def create_feasibility( ) -> StartupFeasibility: record = StartupFeasibility( study_id=study_id, + site_id=record_in.site_id, submit_date=record_in.submit_date, accept_date=record_in.accept_date, approved_date=record_in.approved_date, @@ -86,6 +87,7 @@ async def create_ethics( ) -> StartupEthics: record = StartupEthics( study_id=study_id, + site_id=record_in.site_id, submit_date=record_in.submit_date, accept_date=record_in.accept_date, meeting_date=record_in.meeting_date, diff --git a/backend/app/models/startup_ethics.py b/backend/app/models/startup_ethics.py index 4eb01f92..bf737d8e 100644 --- a/backend/app/models/startup_ethics.py +++ b/backend/app/models/startup_ethics.py @@ -13,6 +13,7 @@ class StartupEthics(Base): id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False) + site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True) submit_date: Mapped[date | None] = mapped_column(Date, nullable=True) accept_date: Mapped[date | None] = mapped_column(Date, nullable=True) meeting_date: Mapped[date | None] = mapped_column(Date, nullable=True) diff --git a/backend/app/models/startup_feasibility.py b/backend/app/models/startup_feasibility.py index 75a80652..861a56a0 100644 --- a/backend/app/models/startup_feasibility.py +++ b/backend/app/models/startup_feasibility.py @@ -13,6 +13,7 @@ class StartupFeasibility(Base): id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False) + site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True) submit_date: Mapped[date | None] = mapped_column(Date, nullable=True) accept_date: Mapped[date | None] = mapped_column(Date, nullable=True) approved_date: Mapped[date | None] = mapped_column(Date, nullable=True) diff --git a/backend/app/schemas/startup.py b/backend/app/schemas/startup.py index a2d5b046..e1d67729 100644 --- a/backend/app/schemas/startup.py +++ b/backend/app/schemas/startup.py @@ -6,6 +6,7 @@ from pydantic import BaseModel, ConfigDict class StartupFeasibilityCreate(BaseModel): + site_id: Optional[uuid.UUID] = None submit_date: Optional[date] = None accept_date: Optional[date] = None approved_date: Optional[date] = None @@ -13,6 +14,7 @@ class StartupFeasibilityCreate(BaseModel): class StartupFeasibilityUpdate(BaseModel): + site_id: Optional[uuid.UUID] = None submit_date: Optional[date] = None accept_date: Optional[date] = None approved_date: Optional[date] = None @@ -22,6 +24,7 @@ class StartupFeasibilityUpdate(BaseModel): class StartupFeasibilityRead(BaseModel): id: uuid.UUID study_id: uuid.UUID + site_id: Optional[uuid.UUID] submit_date: Optional[date] accept_date: Optional[date] approved_date: Optional[date] @@ -34,6 +37,7 @@ class StartupFeasibilityRead(BaseModel): class StartupEthicsCreate(BaseModel): + site_id: Optional[uuid.UUID] = None submit_date: Optional[date] = None accept_date: Optional[date] = None meeting_date: Optional[date] = None @@ -42,6 +46,7 @@ class StartupEthicsCreate(BaseModel): class StartupEthicsUpdate(BaseModel): + site_id: Optional[uuid.UUID] = None submit_date: Optional[date] = None accept_date: Optional[date] = None meeting_date: Optional[date] = None @@ -52,6 +57,7 @@ class StartupEthicsUpdate(BaseModel): class StartupEthicsRead(BaseModel): id: uuid.UUID study_id: uuid.UUID + site_id: Optional[uuid.UUID] submit_date: Optional[date] accept_date: Optional[date] meeting_date: Optional[date] diff --git a/database/init.sql b/database/init.sql index c7e5a5fc..f4f26947 100644 --- a/database/init.sql +++ b/database/init.sql @@ -328,6 +328,7 @@ CREATE TABLE IF NOT EXISTS public.drug_shipments ( 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, @@ -336,12 +337,14 @@ CREATE TABLE IF NOT EXISTS public.startup_feasibility ( 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, @@ -351,6 +354,7 @@ CREATE TABLE IF NOT EXISTS public.startup_ethics ( 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 ); @@ -623,19 +627,35 @@ INSERT INTO public.drug_shipments ( ON CONFLICT (id) DO NOTHING; INSERT INTO public.startup_feasibility ( - id, study_id, submit_date, accept_date, approved_date, project_no, created_by, created_at, updated_at + 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'), '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'), '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') + ('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, submit_date, accept_date, meeting_date, approved_date, approval_no, created_by, created_at, updated_at + 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'), '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'), '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') + ('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, kickoff_date, attendees, created_by, created_at, updated_at ) VALUES ( diff --git a/frontend/src/views/ia/StartupFeasibilityEthics.vue b/frontend/src/views/ia/StartupFeasibilityEthics.vue index bd05fa0a..ec35c52e 100644 --- a/frontend/src/views/ia/StartupFeasibilityEthics.vue +++ b/frontend/src/views/ia/StartupFeasibilityEthics.vue @@ -1,64 +1,122 @@ diff --git a/frontend/src/views/startup/EthicsDetail.vue b/frontend/src/views/startup/EthicsDetail.vue index d5b3e6aa..063b22db 100644 --- a/frontend/src/views/startup/EthicsDetail.vue +++ b/frontend/src/views/startup/EthicsDetail.vue @@ -1,43 +1,59 @@ diff --git a/frontend/src/views/startup/EthicsForm.vue b/frontend/src/views/startup/EthicsForm.vue index 2a4dcffb..ceaacdef 100644 --- a/frontend/src/views/startup/EthicsForm.vue +++ b/frontend/src/views/startup/EthicsForm.vue @@ -1,43 +1,75 @@