Files
ctms/docs/plans/2026-05-27-etmf-implementation.md
2026-06-23 09:21:57 +08:00

8.2 KiB

eTMF Module Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Build an eTMF module that combines a compliant TMF directory tree with reusable document version management.

Architecture: Add eTMF directory nodes as metadata, then mount existing documents records onto those nodes with etmf_node_id. Keep file storage, versions, permissions, downloads, distribution, and audit behavior in the existing document module.

Tech Stack: FastAPI, SQLAlchemy async ORM, Alembic, PostgreSQL, Vue 3, Element Plus, Vitest, Pytest.


Implementation Notes

The repository instruction says not to plan or execute git commits unless explicitly requested. This plan intentionally omits commit steps.

Follow TDD for each backend behavior before implementation. Keep the first release small: no e-signature, no workflow redesign, no template versioning, no bulk import.

Task 1: Backend eTMF Node Model

Files:

  • Create: backend/app/models/etmf.py
  • Create: backend/app/schemas/etmf.py
  • Modify: backend/app/db/base.py
  • Create: backend/alembic/versions/20260527_07_add_etmf_nodes.py
  • Test: backend/tests/test_etmf_nodes.py

Step 1: Write failing model/import test

Add a test that imports EtmfNode through app.db.base metadata and verifies the table name is registered.

Run:

cd backend
pytest tests/test_etmf_nodes.py -q

Expected: fail because app.models.etmf does not exist.

Step 2: Add model

Create EtmfNode with these columns:

  • id
  • study_id
  • parent_id
  • code
  • name
  • description
  • scope_type
  • required
  • expected_doc_type
  • sort_order
  • is_active
  • created_at
  • updated_at

Use existing enum values from document scope where practical. Keep node-specific logic out of the model.

Step 3: Add schemas

Create request and response schemas:

  • EtmfNodeCreate
  • EtmfNodeUpdate
  • EtmfNodeRead
  • EtmfTreeNode
  • EtmfNodeStatusSummary

Step 4: Add migration

Create etmf_nodes with:

  • Foreign key to studies.id
  • Self foreign key parent_id
  • Unique constraint on (study_id, parent_id, code)
  • Indexes on study_id, parent_id, scope_type, is_active

Step 5: Verify

Run:

cd backend
pytest tests/test_etmf_nodes.py -q

Expected: pass.

Files:

  • Modify: backend/app/models/document.py
  • Modify: backend/app/schemas/document.py
  • Modify: backend/app/crud/document.py
  • Modify: backend/app/services/document_service.py
  • Modify: frontend/src/types/documents.ts
  • Modify: backend/alembic/versions/20260527_07_add_etmf_nodes.py
  • Test: backend/tests/test_etmf_nodes.py

Step 1: Write failing service test

Test that creating a document with etmf_node_id persists the relationship and list/detail responses include it.

Expected: fail because schema and model do not include etmf_node_id.

Step 2: Add model column

Add nullable Document.etmf_node_id with foreign key to etmf_nodes.id.

Step 3: Add schema fields

Add etmf_node_id to:

  • DocumentCreate
  • DocumentUpdate
  • DocumentSummary
  • DocumentDetail

Step 4: Validate node scope

In document_service.create_document, if etmf_node_id is provided:

  • Verify node exists.
  • Verify node belongs to the same study.
  • If node scope is SITE, require site_id.
  • If node scope is GLOBAL, normalize site_id to null unless the product decision changes.

Step 5: Verify

Run:

cd backend
pytest tests/test_etmf_nodes.py -q

Expected: pass.

Task 3: eTMF Service And Tree API

Files:

  • Create: backend/app/crud/etmf.py
  • Create: backend/app/services/etmf_service.py
  • Create: backend/app/api/v1/etmf.py
  • Modify: backend/app/api/v1/router.py
  • Test: backend/tests/test_etmf_api.py

Step 1: Write failing API tests

Cover:

  • GET /api/v1/etmf/tree?study_id=...
  • Required empty node returns MISSING.
  • Node with document but no effective version returns UPLOADED.
  • Node with current effective version returns EFFECTIVE.
  • Site-scoped node can return per-site status.

Expected: fail because API does not exist.

Step 2: Implement CRUD

Add small CRUD functions:

  • get_node
  • list_nodes_by_study
  • create_node
  • update_node

Avoid broad generic repository abstractions.

Step 3: Implement service

Add tree assembly and status calculation:

  • Build parent-child tree in memory.
  • Query documents for target study and group by etmf_node_id.
  • Calculate node status from required flag and document effective version presence.
  • Keep status calculation deterministic and isolated for unit tests.

Step 4: Implement routes

Add:

  • GET /tree
  • GET /nodes/{node_id}/documents
  • POST /nodes
  • PATCH /nodes/{node_id}
  • POST /nodes/{node_id}/documents

Use existing project permission checks through the document permission model.

Step 5: Verify

Run:

cd backend
pytest tests/test_etmf_api.py -q

Expected: pass.

Task 4: Frontend API And Types

Files:

  • Create: frontend/src/types/etmf.ts
  • Create: frontend/src/api/etmf.ts
  • Test: frontend/src/api/etmf.test.ts

Step 1: Write failing API test

Test that each frontend API helper calls the expected URL and HTTP method.

Expected: fail because src/api/etmf.ts does not exist.

Step 2: Add types

Add types matching backend schemas:

  • EtmfNodeStatus
  • EtmfTreeNode
  • EtmfNodeCreatePayload
  • EtmfNodeUpdatePayload

Step 3: Add API helpers

Add:

  • fetchEtmfTree
  • fetchEtmfNodeDocuments
  • createEtmfNode
  • updateEtmfNode
  • createEtmfDocument

Step 4: Verify

Run:

cd frontend
npm test -- src/api/etmf.test.ts

Expected: pass.

Task 5: eTMF Page

Files:

  • Replace: frontend/src/views/ia/EtmfPlaceholder.vue
  • Modify: frontend/src/locales/zh-CN.ts

Step 1: Write failing page test

Test that /etmf page:

  • Renders tree panel.
  • Loads eTMF tree for current study.
  • Selecting a node loads its documents.
  • Shows missing/effective status labels.

Expected: fail because the page is still a placeholder.

Step 2: Build layout

Use existing CTMS shell classes:

  • Top action bar.
  • Left tree panel.
  • Main document table.
  • Right node detail panel.

Do not introduce a marketing-style page or nested card layout.

Step 3: Add interactions

Implement:

  • Center filter.
  • Node selection.
  • Refresh.
  • Create node dialog.
  • Create document dialog that posts to eTMF node document API.

Reuse existing display helpers and document enums.

Step 4: Verify

Run:

cd frontend
npm test -- src/api/etmf.test.ts

Expected: pass.

Task 6: Route And Permission Regression

Files:

  • Modify: frontend/src/utils/projectRoutePermissions.ts
  • Modify: frontend/src/utils/projectRoutePermissions.test.ts
  • Modify: backend/tests/test_api_permissions.py

Step 1: Write failing regression tests

Ensure:

  • /etmf still requires documents:read.
  • eTMF write endpoints are covered by document permissions.
  • Existing document permission tests remain stable.

Step 2: Implement minimal permission wiring

Keep first-stage eTMF permissions mapped to existing document operation keys.

Step 3: Verify

Run:

cd backend
pytest tests/test_api_permissions.py -q
cd ../frontend
npm test -- src/utils/projectRoutePermissions.test.ts

Expected: pass.

Task 7: End-To-End Verification

Files:

  • No source changes expected unless verification finds a bug.

Step 1: Run backend focused tests

cd backend
pytest tests/test_etmf_nodes.py tests/test_etmf_api.py tests/test_api_permissions.py -q

Expected: pass.

Step 2: Run frontend focused tests

cd frontend
npm test -- src/api/etmf.test.ts src/utils/projectRoutePermissions.test.ts

Expected: pass.

Step 3: Run UI contract check if available

cd frontend
npm run verify-ui-contract

Expected: pass.

Step 4: Manual browser check

Start the dev server and open /etmf with an active study:

cd frontend
npm run dev

Expected:

  • eTMF page is not blank.
  • Directory tree is visible.
  • Selecting a node updates the document list.
  • Center filter does not break layout.
  • Text does not overlap at desktop and mobile widths.