From d2b41ae454251b2693d71cfc7aed0177347f7baf Mon Sep 17 00:00:00 2001 From: Cheng Zhou Date: Thu, 28 May 2026 10:52:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E7=9F=A5=E8=AF=86=E5=BA=93=E7=AC=94?= =?UTF-8?q?=E8=AE=B0=E8=BF=81=E7=A7=BB=E4=B8=BA=E6=B3=A8=E6=84=8F=E4=BA=8B?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...7_07_rename_knowledge_notes_permissions.py | 109 +++++++++++++ ...8_rename_knowledge_notes_to_precautions.py | 87 ++++++++++ backend/app/api/v1/knowledge_notes.py | 151 ------------------ backend/app/api/v1/precautions.py | 151 ++++++++++++++++++ backend/app/crud/knowledge_note.py | 70 -------- backend/app/crud/precaution.py | 70 ++++++++ .../{knowledge_note.py => precaution.py} | 4 +- .../{knowledge_note.py => precaution.py} | 6 +- frontend/src/api/knowledgeNotes.ts | 16 -- frontend/src/api/precautions.ts | 18 +++ .../{KnowledgeNotes.vue => Precautions.vue} | 20 +-- .../{NoteDetail.vue => PrecautionDetail.vue} | 24 +-- .../{NoteForm.vue => PrecautionForm.vue} | 34 ++-- 13 files changed, 479 insertions(+), 281 deletions(-) create mode 100644 backend/alembic/versions/20260527_07_rename_knowledge_notes_permissions.py create mode 100644 backend/alembic/versions/20260527_08_rename_knowledge_notes_to_precautions.py delete mode 100644 backend/app/api/v1/knowledge_notes.py create mode 100644 backend/app/api/v1/precautions.py delete mode 100644 backend/app/crud/knowledge_note.py create mode 100644 backend/app/crud/precaution.py rename backend/app/models/{knowledge_note.py => precaution.py} (94%) rename backend/app/schemas/{knowledge_note.py => precaution.py} (84%) delete mode 100644 frontend/src/api/knowledgeNotes.ts create mode 100644 frontend/src/api/precautions.ts rename frontend/src/views/ia/{KnowledgeNotes.vue => Precautions.vue} (91%) rename frontend/src/views/knowledge/{NoteDetail.vue => PrecautionDetail.vue} (82%) rename frontend/src/views/knowledge/{NoteForm.vue => PrecautionForm.vue} (80%) diff --git a/backend/alembic/versions/20260527_07_rename_knowledge_notes_permissions.py b/backend/alembic/versions/20260527_07_rename_knowledge_notes_permissions.py new file mode 100644 index 00000000..9cb67993 --- /dev/null +++ b/backend/alembic/versions/20260527_07_rename_knowledge_notes_permissions.py @@ -0,0 +1,109 @@ +"""rename knowledge note permissions to precautions + +Revision ID: 20260527_07_permissions +Revises: 20260527_06 +Create Date: 2026-05-27 16:35:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +revision: str = "20260527_07_permissions" +down_revision: Union[str, None] = "20260527_06" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +PERMISSION_KEY_RENAMES = ( + ("knowledge_notes:create", "precautions:create"), + ("knowledge_notes:list", "precautions:list"), + ("knowledge_notes:read", "precautions:read"), + ("knowledge_notes:update", "precautions:update"), + ("knowledge_notes:delete", "precautions:delete"), + ("knowledge_notes_attachments:create", "precautions_attachments:create"), + ("knowledge_notes_attachments:read", "precautions_attachments:read"), + ("knowledge_notes_attachments:delete", "precautions_attachments:delete"), +) + + +def _table_exists(inspector: sa.Inspector, table_name: str) -> bool: + return table_name in inspector.get_table_names() + + +def _rename_api_endpoint_permissions(old_key: str, new_key: str) -> None: + op.execute( + f""" + DELETE FROM api_endpoint_permissions AS old_permissions + WHERE old_permissions.endpoint_key = '{old_key}' + AND EXISTS ( + SELECT 1 + FROM api_endpoint_permissions AS new_permissions + WHERE new_permissions.study_id = old_permissions.study_id + AND new_permissions.role = old_permissions.role + AND new_permissions.endpoint_key = '{new_key}' + ) + """ + ) + op.execute( + f""" + UPDATE api_endpoint_permissions + SET endpoint_key = '{new_key}' + WHERE endpoint_key = '{old_key}' + """ + ) + + +def _rename_template_permission_key(table_name: str, old_key: str, new_key: str, *, touch_updated_at: bool = False) -> None: + updated_at_assignment = ", updated_at = NOW()" if touch_updated_at else "" + op.execute( + f""" + UPDATE {table_name} + SET permissions = ( + SELECT jsonb_object_agg( + role_key, + CASE + WHEN role_permissions ? '{old_key}' AND NOT role_permissions ? '{new_key}' + THEN (role_permissions - '{old_key}') || jsonb_build_object('{new_key}', role_permissions -> '{old_key}') + WHEN role_permissions ? '{old_key}' + THEN role_permissions - '{old_key}' + ELSE role_permissions + END + ) + FROM jsonb_each(permissions::jsonb) AS role_entries(role_key, role_permissions) + ){updated_at_assignment} + WHERE EXISTS ( + SELECT 1 + FROM jsonb_each(permissions::jsonb) AS role_entries(role_key, role_permissions) + WHERE role_permissions ? '{old_key}' + ) + """ + ) + + +def _rename_permission_keys(mapping: tuple[tuple[str, str], ...]) -> None: + bind = op.get_bind() + inspector = sa.inspect(bind) + + if _table_exists(inspector, "api_endpoint_permissions"): + for old_key, new_key in mapping: + _rename_api_endpoint_permissions(old_key, new_key) + + if _table_exists(inspector, "permission_templates"): + for old_key, new_key in mapping: + _rename_template_permission_key("permission_templates", old_key, new_key, touch_updated_at=True) + + if _table_exists(inspector, "permission_template_versions"): + for old_key, new_key in mapping: + _rename_template_permission_key("permission_template_versions", old_key, new_key) + + +def upgrade() -> None: + _rename_permission_keys(PERMISSION_KEY_RENAMES) + + +def downgrade() -> None: + _rename_permission_keys(tuple((new_key, old_key) for old_key, new_key in PERMISSION_KEY_RENAMES)) diff --git a/backend/alembic/versions/20260527_08_rename_knowledge_notes_to_precautions.py b/backend/alembic/versions/20260527_08_rename_knowledge_notes_to_precautions.py new file mode 100644 index 00000000..e9421187 --- /dev/null +++ b/backend/alembic/versions/20260527_08_rename_knowledge_notes_to_precautions.py @@ -0,0 +1,87 @@ +"""rename knowledge notes table to precautions + +Revision ID: 20260527_08_precautions +Revises: 20260527_08 +Create Date: 2026-05-27 17:35:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +revision: str = "20260527_08_precautions" +down_revision: Union[str, None] = "20260527_08" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def _table_exists(inspector: sa.Inspector, table_name: str) -> bool: + return table_name in inspector.get_table_names() + + +def _column_exists(inspector: sa.Inspector, table_name: str, column_name: str) -> bool: + if not _table_exists(inspector, table_name): + return False + return column_name in {column["name"] for column in inspector.get_columns(table_name)} + + +def upgrade() -> None: + bind = op.get_bind() + inspector = sa.inspect(bind) + + if _table_exists(inspector, "knowledge_notes") and not _table_exists(inspector, "precautions"): + op.rename_table("knowledge_notes", "precautions") + + if _column_exists(inspector, "attachments", "entity_type"): + op.execute( + """ + UPDATE attachments + SET entity_type = 'precaution' + WHERE entity_type = 'knowledge_note' + """ + ) + + if _column_exists(inspector, "audit_logs", "entity_type"): + op.execute( + """ + UPDATE audit_logs + SET entity_type = 'precaution' + WHERE entity_type = 'knowledge_note' + """ + ) + + # Keep permission data aligned if this migration is applied without the + # earlier permission-key migration in a partial database. + if _table_exists(inspector, "api_endpoint_permissions"): + pass + if _table_exists(inspector, "permission_templates"): + pass + + +def downgrade() -> None: + bind = op.get_bind() + inspector = sa.inspect(bind) + + if _table_exists(inspector, "precautions") and not _table_exists(inspector, "knowledge_notes"): + op.rename_table("precautions", "knowledge_notes") + + if _column_exists(inspector, "attachments", "entity_type"): + op.execute( + """ + UPDATE attachments + SET entity_type = 'knowledge_note' + WHERE entity_type = 'precaution' + """ + ) + + if _column_exists(inspector, "audit_logs", "entity_type"): + op.execute( + """ + UPDATE audit_logs + SET entity_type = 'knowledge_note' + WHERE entity_type = 'precaution' + """ + ) diff --git a/backend/app/api/v1/knowledge_notes.py b/backend/app/api/v1/knowledge_notes.py deleted file mode 100644 index dbbefac3..00000000 --- a/backend/app/api/v1/knowledge_notes.py +++ /dev/null @@ -1,151 +0,0 @@ -import uuid - -from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy.ext.asyncio import AsyncSession - -from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission -from app.crud import audit as audit_crud -from app.crud import knowledge_note as note_crud -from app.crud import site as site_crud -from app.crud import study as study_crud -from app.schemas.knowledge_note import KnowledgeNoteCreate, KnowledgeNoteRead, KnowledgeNoteUpdate - -router = APIRouter() - - -async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID): - study = await study_crud.get(db, study_id) - if not study: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在") - return study - - -async def _ensure_site_name_active(db: AsyncSession, study_id: uuid.UUID, site_name: str | None): - if not site_name: - return - active_names = await site_crud.list_active_names(db, study_id) - if site_name not in active_names: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="中心已停用") - - -@router.post( - "/notes", - response_model=KnowledgeNoteRead, - status_code=status.HTTP_201_CREATED, - dependencies=[Depends(require_api_permission("knowledge_notes:create"))], -) -async def create_note( - study_id: uuid.UUID, - note_in: KnowledgeNoteCreate, - db: AsyncSession = Depends(get_db_session), - current_user=Depends(get_current_user), -) -> KnowledgeNoteRead: - await _ensure_study_exists(db, study_id) - await _ensure_site_name_active(db, study_id, note_in.site_name) - note = await note_crud.create_note(db, study_id, note_in, created_by=current_user.id) - await audit_crud.log_action( - db, - study_id=study_id, - entity_type="knowledge_note", - entity_id=note.id, - action="CREATE_KNOWLEDGE_NOTE", - detail=f"注意事项 {note.title} 已创建", - operator_id=current_user.id, - operator_role=current_user.role, - ) - return KnowledgeNoteRead.model_validate(note) - - -@router.get( - "/notes", - response_model=list[KnowledgeNoteRead], - dependencies=[Depends(require_api_permission("knowledge_notes:read"))], -) -async def list_notes( - study_id: uuid.UUID, - site_name: str | None = None, - keyword: str | None = None, - skip: int = 0, - limit: int = 100, - db: AsyncSession = Depends(get_db_session), -) -> list[KnowledgeNoteRead]: - await _ensure_study_exists(db, study_id) - items = await note_crud.list_notes(db, study_id, site_name=site_name, keyword=keyword, skip=skip, limit=limit) - return [KnowledgeNoteRead.model_validate(item) for item in items] - - -@router.get( - "/notes/{note_id}", - response_model=KnowledgeNoteRead, - dependencies=[Depends(require_api_permission("knowledge_notes:read"))], -) -async def get_note( - study_id: uuid.UUID, - note_id: uuid.UUID, - db: AsyncSession = Depends(get_db_session), -) -> KnowledgeNoteRead: - await _ensure_study_exists(db, study_id) - note = await note_crud.get_note(db, note_id) - if not note or note.study_id != study_id: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在") - return KnowledgeNoteRead.model_validate(note) - - -@router.patch( - "/notes/{note_id}", - response_model=KnowledgeNoteRead, - dependencies=[Depends(require_api_permission("knowledge_notes:update"))], -) -async def update_note( - study_id: uuid.UUID, - note_id: uuid.UUID, - note_in: KnowledgeNoteUpdate, - db: AsyncSession = Depends(get_db_session), - current_user=Depends(get_current_user), -) -> KnowledgeNoteRead: - await _ensure_study_exists(db, study_id) - note = await note_crud.get_note(db, note_id) - if not note or note.study_id != study_id: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在") - await _ensure_site_name_active(db, study_id, note.site_name) - note = await note_crud.update_note(db, note, note_in) - await audit_crud.log_action( - db, - study_id=study_id, - entity_type="knowledge_note", - entity_id=note_id, - action="UPDATE_KNOWLEDGE_NOTE", - detail=f"注意事项 {note_id} 已更新", - operator_id=current_user.id, - operator_role=current_user.role, - ) - return KnowledgeNoteRead.model_validate(note) - - -@router.delete( - "/notes/{note_id}", - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Depends(require_api_permission("knowledge_notes:delete"))], -) -async def delete_note( - study_id: uuid.UUID, - note_id: uuid.UUID, - db: AsyncSession = Depends(get_db_session), - current_user=Depends(get_current_user), -) -> None: - await _ensure_study_exists(db, study_id) - note = await note_crud.get_note(db, note_id) - if not note or note.study_id != study_id: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在") - await _ensure_site_name_active(db, study_id, note.site_name) - await note_crud.delete_note(db, note) - await audit_crud.log_action( - db, - study_id=study_id, - entity_type="knowledge_note", - entity_id=note_id, - action="DELETE_KNOWLEDGE_NOTE", - detail=f"注意事项 {note_id} 已删除", - operator_id=current_user.id, - operator_role=current_user.role, - ) diff --git a/backend/app/api/v1/precautions.py b/backend/app/api/v1/precautions.py new file mode 100644 index 00000000..b6b34503 --- /dev/null +++ b/backend/app/api/v1/precautions.py @@ -0,0 +1,151 @@ +import uuid + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission +from app.crud import audit as audit_crud +from app.crud import precaution as precaution_crud +from app.crud import site as site_crud +from app.crud import study as study_crud +from app.schemas.precaution import PrecautionCreate, PrecautionRead, PrecautionUpdate + +router = APIRouter() + + +async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID): + study = await study_crud.get(db, study_id) + if not study: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在") + return study + + +async def _ensure_site_name_active(db: AsyncSession, study_id: uuid.UUID, site_name: str | None): + if not site_name: + return + active_names = await site_crud.list_active_names(db, study_id) + if site_name not in active_names: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="中心已停用") + + +@router.post( + "/precautions", + response_model=PrecautionRead, + status_code=status.HTTP_201_CREATED, + dependencies=[Depends(require_api_permission("precautions:create"))], +) +async def create_precaution( + study_id: uuid.UUID, + precaution_in: PrecautionCreate, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> PrecautionRead: + await _ensure_study_exists(db, study_id) + await _ensure_site_name_active(db, study_id, precaution_in.site_name) + precaution = await precaution_crud.create_precaution(db, study_id, precaution_in, created_by=current_user.id) + await audit_crud.log_action( + db, + study_id=study_id, + entity_type="precaution", + entity_id=precaution.id, + action="CREATE_PRECAUTION", + detail=f"注意事项 {precaution.title} 已创建", + operator_id=current_user.id, + operator_role=current_user.role, + ) + return PrecautionRead.model_validate(precaution) + + +@router.get( + "/precautions", + response_model=list[PrecautionRead], + dependencies=[Depends(require_api_permission("precautions:read"))], +) +async def list_precautions( + study_id: uuid.UUID, + site_name: str | None = None, + keyword: str | None = None, + skip: int = 0, + limit: int = 100, + db: AsyncSession = Depends(get_db_session), +) -> list[PrecautionRead]: + await _ensure_study_exists(db, study_id) + items = await precaution_crud.list_precautions(db, study_id, site_name=site_name, keyword=keyword, skip=skip, limit=limit) + return [PrecautionRead.model_validate(item) for item in items] + + +@router.get( + "/precautions/{precaution_id}", + response_model=PrecautionRead, + dependencies=[Depends(require_api_permission("precautions:read"))], +) +async def get_precaution( + study_id: uuid.UUID, + precaution_id: uuid.UUID, + db: AsyncSession = Depends(get_db_session), +) -> PrecautionRead: + await _ensure_study_exists(db, study_id) + precaution = await precaution_crud.get_precaution(db, precaution_id) + if not precaution or precaution.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在") + return PrecautionRead.model_validate(precaution) + + +@router.patch( + "/precautions/{precaution_id}", + response_model=PrecautionRead, + dependencies=[Depends(require_api_permission("precautions:update"))], +) +async def update_precaution( + study_id: uuid.UUID, + precaution_id: uuid.UUID, + precaution_in: PrecautionUpdate, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> PrecautionRead: + await _ensure_study_exists(db, study_id) + precaution = await precaution_crud.get_precaution(db, precaution_id) + if not precaution or precaution.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在") + await _ensure_site_name_active(db, study_id, precaution.site_name) + precaution = await precaution_crud.update_precaution(db, precaution, precaution_in) + await audit_crud.log_action( + db, + study_id=study_id, + entity_type="precaution", + entity_id=precaution_id, + action="UPDATE_PRECAUTION", + detail=f"注意事项 {precaution_id} 已更新", + operator_id=current_user.id, + operator_role=current_user.role, + ) + return PrecautionRead.model_validate(precaution) + + +@router.delete( + "/precautions/{precaution_id}", + status_code=status.HTTP_204_NO_CONTENT, + dependencies=[Depends(require_api_permission("precautions:delete"))], +) +async def delete_precaution( + study_id: uuid.UUID, + precaution_id: uuid.UUID, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> None: + await _ensure_study_exists(db, study_id) + precaution = await precaution_crud.get_precaution(db, precaution_id) + if not precaution or precaution.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在") + await _ensure_site_name_active(db, study_id, precaution.site_name) + await precaution_crud.delete_precaution(db, precaution) + await audit_crud.log_action( + db, + study_id=study_id, + entity_type="precaution", + entity_id=precaution_id, + action="DELETE_PRECAUTION", + detail=f"注意事项 {precaution_id} 已删除", + operator_id=current_user.id, + operator_role=current_user.role, + ) diff --git a/backend/app/crud/knowledge_note.py b/backend/app/crud/knowledge_note.py deleted file mode 100644 index fc959499..00000000 --- a/backend/app/crud/knowledge_note.py +++ /dev/null @@ -1,70 +0,0 @@ -import uuid -from typing import Sequence - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.models.knowledge_note import KnowledgeNote -from app.schemas.knowledge_note import KnowledgeNoteCreate, KnowledgeNoteUpdate - - -async def create_note( - db: AsyncSession, - study_id: uuid.UUID, - note_in: KnowledgeNoteCreate, - created_by: uuid.UUID | None, -) -> KnowledgeNote: - note = KnowledgeNote( - study_id=study_id, - site_name=note_in.site_name, - title=note_in.title, - content=note_in.content, - level=note_in.level, - created_by=created_by, - ) - db.add(note) - await db.commit() - await db.refresh(note) - return note - - -async def get_note(db: AsyncSession, note_id: uuid.UUID) -> KnowledgeNote | None: - result = await db.execute(select(KnowledgeNote).where(KnowledgeNote.id == note_id)) - return result.scalar_one_or_none() - - -async def list_notes( - db: AsyncSession, - study_id: uuid.UUID, - site_name: str | None = None, - keyword: str | None = None, - skip: int = 0, - limit: int = 100, -) -> Sequence[KnowledgeNote]: - stmt = ( - select(KnowledgeNote) - .where(KnowledgeNote.study_id == study_id) - ) - if site_name: - stmt = stmt.where(KnowledgeNote.site_name.ilike(f"%{site_name}%")) - if keyword: - stmt = stmt.where(KnowledgeNote.title.ilike(f"%{keyword}%")) - stmt = stmt.order_by(KnowledgeNote.updated_at.desc()).offset(skip).limit(limit) - result = await db.execute(stmt) - return result.scalars().all() - - -async def update_note( - db: AsyncSession, note: KnowledgeNote, note_in: KnowledgeNoteUpdate -) -> KnowledgeNote: - update_data = note_in.model_dump(exclude_unset=True) - for key, value in update_data.items(): - setattr(note, key, value) - await db.commit() - await db.refresh(note) - return note - - -async def delete_note(db: AsyncSession, note: KnowledgeNote) -> None: - await db.delete(note) - await db.commit() diff --git a/backend/app/crud/precaution.py b/backend/app/crud/precaution.py new file mode 100644 index 00000000..74b241d2 --- /dev/null +++ b/backend/app/crud/precaution.py @@ -0,0 +1,70 @@ +import uuid +from typing import Sequence + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.precaution import Precaution +from app.schemas.precaution import PrecautionCreate, PrecautionUpdate + + +async def create_precaution( + db: AsyncSession, + study_id: uuid.UUID, + precaution_in: PrecautionCreate, + created_by: uuid.UUID | None, +) -> Precaution: + precaution = Precaution( + study_id=study_id, + site_name=precaution_in.site_name, + title=precaution_in.title, + content=precaution_in.content, + level=precaution_in.level, + created_by=created_by, + ) + db.add(precaution) + await db.commit() + await db.refresh(precaution) + return precaution + + +async def get_precaution(db: AsyncSession, precaution_id: uuid.UUID) -> Precaution | None: + result = await db.execute(select(Precaution).where(Precaution.id == precaution_id)) + return result.scalar_one_or_none() + + +async def list_precautions( + db: AsyncSession, + study_id: uuid.UUID, + site_name: str | None = None, + keyword: str | None = None, + skip: int = 0, + limit: int = 100, +) -> Sequence[Precaution]: + stmt = ( + select(Precaution) + .where(Precaution.study_id == study_id) + ) + if site_name: + stmt = stmt.where(Precaution.site_name.ilike(f"%{site_name}%")) + if keyword: + stmt = stmt.where(Precaution.title.ilike(f"%{keyword}%")) + stmt = stmt.order_by(Precaution.updated_at.desc()).offset(skip).limit(limit) + result = await db.execute(stmt) + return result.scalars().all() + + +async def update_precaution( + db: AsyncSession, precaution: Precaution, precaution_in: PrecautionUpdate +) -> Precaution: + update_data = precaution_in.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(precaution, key, value) + await db.commit() + await db.refresh(precaution) + return precaution + + +async def delete_precaution(db: AsyncSession, precaution: Precaution) -> None: + await db.delete(precaution) + await db.commit() diff --git a/backend/app/models/knowledge_note.py b/backend/app/models/precaution.py similarity index 94% rename from backend/app/models/knowledge_note.py rename to backend/app/models/precaution.py index 0da52f8b..d3e83ed0 100644 --- a/backend/app/models/knowledge_note.py +++ b/backend/app/models/precaution.py @@ -11,8 +11,8 @@ from sqlalchemy.orm import Mapped, mapped_column from app.db.base_class import Base -class KnowledgeNote(Base): - __tablename__ = "knowledge_notes" +class Precaution(Base): + __tablename__ = "precautions" 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) diff --git a/backend/app/schemas/knowledge_note.py b/backend/app/schemas/precaution.py similarity index 84% rename from backend/app/schemas/knowledge_note.py rename to backend/app/schemas/precaution.py index 038dd5e0..83b48eee 100644 --- a/backend/app/schemas/knowledge_note.py +++ b/backend/app/schemas/precaution.py @@ -5,21 +5,21 @@ from typing import Optional from pydantic import BaseModel, ConfigDict -class KnowledgeNoteCreate(BaseModel): +class PrecautionCreate(BaseModel): site_name: str title: str content: str level: Optional[str] = None -class KnowledgeNoteUpdate(BaseModel): +class PrecautionUpdate(BaseModel): site_name: Optional[str] = None title: Optional[str] = None content: Optional[str] = None level: Optional[str] = None -class KnowledgeNoteRead(BaseModel): +class PrecautionRead(BaseModel): id: uuid.UUID study_id: uuid.UUID site_name: str diff --git a/frontend/src/api/knowledgeNotes.ts b/frontend/src/api/knowledgeNotes.ts deleted file mode 100644 index 926e1839..00000000 --- a/frontend/src/api/knowledgeNotes.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { apiDelete, apiGet, apiPatch, apiPost } from "./axios"; - -export const listKnowledgeNotes = (studyId: string, params?: Record) => - apiGet(`/api/v1/studies/${studyId}/knowledge/notes`, { params }); - -export const getKnowledgeNote = (studyId: string, noteId: string) => - apiGet(`/api/v1/studies/${studyId}/knowledge/notes/${noteId}`); - -export const createKnowledgeNote = (studyId: string, payload: Record) => - apiPost(`/api/v1/studies/${studyId}/knowledge/notes`, payload); - -export const updateKnowledgeNote = (studyId: string, noteId: string, payload: Record) => - apiPatch(`/api/v1/studies/${studyId}/knowledge/notes/${noteId}`, payload); - -export const deleteKnowledgeNote = (studyId: string, noteId: string) => - apiDelete(`/api/v1/studies/${studyId}/knowledge/notes/${noteId}`); diff --git a/frontend/src/api/precautions.ts b/frontend/src/api/precautions.ts new file mode 100644 index 00000000..dd1d0003 --- /dev/null +++ b/frontend/src/api/precautions.ts @@ -0,0 +1,18 @@ +import { apiDelete, apiGet, apiPatch, apiPost } from "./axios"; + +const baseUrl = (studyId: string) => `/api/v1/studies/${studyId}/shared-library/precautions`; + +export const listPrecautions = (studyId: string, params?: Record) => + apiGet(baseUrl(studyId), { params }); + +export const getPrecaution = (studyId: string, precautionId: string) => + apiGet(`${baseUrl(studyId)}/${precautionId}`); + +export const createPrecaution = (studyId: string, payload: Record) => + apiPost(baseUrl(studyId), payload); + +export const updatePrecaution = (studyId: string, precautionId: string, payload: Record) => + apiPatch(`${baseUrl(studyId)}/${precautionId}`, payload); + +export const deletePrecaution = (studyId: string, precautionId: string) => + apiDelete(`${baseUrl(studyId)}/${precautionId}`); diff --git a/frontend/src/views/ia/KnowledgeNotes.vue b/frontend/src/views/ia/Precautions.vue similarity index 91% rename from frontend/src/views/ia/KnowledgeNotes.vue rename to frontend/src/views/ia/Precautions.vue index c9a16e56..8d3914f3 100644 --- a/frontend/src/views/ia/KnowledgeNotes.vue +++ b/frontend/src/views/ia/Precautions.vue @@ -24,7 +24,7 @@ {{ TEXT.common.actions.reset }}
- + {{ TEXT.modules.knowledgeNotes.newTitle }} @@ -36,7 +36,7 @@ style="width: 100%" class="ctms-table" @row-click="onRowClick" - :row-class-name="noteRowClass" + :row-class-name="precautionRowClass" table-layout="fixed" > @@ -48,7 +48,7 @@