From d7409c5fa48303b33aeb729a85390556f1a0689f Mon Sep 17 00:00:00 2001 From: Aleksey Razorvin <> Date: Sat, 21 Mar 2026 14:00:55 +0500 Subject: [PATCH] docs: mark Sprint 3 complete, add step 010 Co-Authored-By: Claude Sonnet 4.6 --- DOC/СПРИНТЫ.md | 24 +++++-- DOC/ШАГИ/ШАГ_2026-03-21_010.md | 98 +++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 DOC/ШАГИ/ШАГ_2026-03-21_010.md diff --git a/DOC/СПРИНТЫ.md b/DOC/СПРИНТЫ.md index c78bf5d..5f13980 100644 --- a/DOC/СПРИНТЫ.md +++ b/DOC/СПРИНТЫ.md @@ -74,21 +74,31 @@ --- -## Спринт 3 — Редактирование теста + версионность +## Спринт 3 — Редактирование теста + версионность ✅ **Результат:** Тест можно редактировать. Если тест уже проходили — создаётся новая версия, старая сохраняется для истории. +**Статус:** Завершён и протестирован вручную в браузере. ### Backend -- [ ] Миграция `003`: добавить поле `parent_id` в таблицу `tests` (ссылка на предыдущую версию) -- [ ] `PUT /api/tests/{id}` — редактировать тест: +- [x] Миграция `003`: добавить поле `parent_id` в таблицу `tests` +- [x] `PUT /api/tests/{id}` — редактировать тест: - Нет попыток → обновить на месте - Есть попытки → создать новый тест (`version + 1`, `parent_id = id`), вернуть `{test, is_new_version: true}` -- [ ] `GET /api/tests` — показывать только последние версии (у которых нет «потомка») +- [x] `GET /api/tests` — показывать только активные версии (`is_active = True`) +- [x] `GET /api/tests/{id}/versions` — цепочка всех версий теста +- [x] `POST /api/tests/{id}/activate` — сделать версию активной (деактивирует остальные в цепочке) ### Frontend -- [ ] Активировать кнопку «Редактировать» на странице `/tests/:id/edit` -- [ ] Форма редактирования теста с предзаполненными данными -- [ ] При сохранении с созданием новой версии — редирект на новый тест + уведомление +- [x] Страница `/tests/:id/edit` разделена на режим просмотра и режим редактирования +- [x] Форма редактирования с предзаполненными данными (общий компонент `TestForm`) +- [x] При сохранении с новой версией — редирект + уведомление «Создана новая версия v2» +- [x] Кнопка «← К просмотру теста» в форме редактирования +- [x] Секция «История версий»: таблица с версиями, статусом, датой, кнопкой «Сделать активной» +- [x] Активная версия — единственная видимая в списке тестов + +### Баги, найденные и исправленные при тестировании +- [x] `ForeignKeyViolationError` при сохранении — bulk `DELETE questions` не каскадирует на `answers` → исправлено: сначала удаляем `answers`, потом `questions` +- [x] Обе версии показывались «Активными» при создании до введения логики деактивации → исправлено: кнопка «Сделать активной» в шапке и в строке таблицы --- diff --git a/DOC/ШАГИ/ШАГ_2026-03-21_010.md b/DOC/ШАГИ/ШАГ_2026-03-21_010.md new file mode 100644 index 0000000..851fb8f --- /dev/null +++ b/DOC/ШАГИ/ШАГ_2026-03-21_010.md @@ -0,0 +1,98 @@ +# ШАГ 010 — Спринт 3: Редактирование теста + версионность + +**Дата:** 2026-03-21 +**Контекст:** Мастер-класс по разработке системы тестирования сотрудников клиники. + +--- + +## Запрос + +> запускаем спринт 3 + +--- + +## Реализовано + +Полноценное редактирование тестов с автоматическим версионированием. + +--- + +## Новые файлы + +``` +backend/alembic/versions/003_test_versioning.py ← добавляет parent_id в tests +frontend/src/components/TestForm/index.tsx ← переиспользуемая форма теста +``` + +## Изменённые файлы + +``` +backend/app/models/test.py ← добавлено поле parent_id +backend/app/schemas/test.py ← TestOut.parent_id, TestUpdateResponse +backend/app/api/tests.py ← PUT + /versions + /activate +frontend/src/api/tests.ts ← update, versions, activate +frontend/src/pages/TestCreate/ ← упрощён через TestForm +frontend/src/pages/TestEdit/ ← полноценный просмотр + редактирование +``` + +--- + +## API эндпоинты (новые) + +| Метод | URL | Описание | +|-------|-----|----------| +| PUT | `/api/tests/{id}` | Редактировать тест | +| GET | `/api/tests/{id}/versions` | Цепочка всех версий | +| POST | `/api/tests/{id}/activate` | Сделать версию активной | + +--- + +## Схема БД (изменено) + +``` +tests + + parent_id → tests.id ← ссылка на предыдущую версию (NULL у первой) +``` + +--- + +## Логика версионирования + +| Условие при сохранении | Результат | +|------------------------|-----------| +| Попыток нет | Редактируем на месте, версия не меняется | +| Есть хотя бы одна попытка | Новый тест: `version+1`, `parent_id=old.id`, `is_active=True`; старый: `is_active=False` | + +**Список тестов** показывает только `is_active=True` версии. +**История версий** отображается на странице `/tests/:id/edit` — таблица всех версий цепочки. + +--- + +## UX редактирования + +- Страница `/tests/:id/edit` работает в двух режимах: просмотр автора → нажать «Редактировать» → форма редактирования +- Кнопка «← К просмотру теста» в шапке формы +- При создании новой версии: редирект на `/tests/:newId/edit` + уведомление +- В таблице «История версий»: статус (Активная/Неактивная), кнопка «Сделать активной» для любой версии + +--- + +## Баги, найденные и исправленные при тестировании + +| # | Симптом | Причина | Исправление | +|---|---------|---------|-------------| +| 1 | `ForeignKeyViolationError` при сохранении теста | `DELETE FROM questions` bulk-запросом не каскадирует на `answers` — в схеме нет `ON DELETE CASCADE` | Сначала удаляем `answers` по `question_id IN (...)`, затем `questions` | +| 2 | Обе версии показывались «Активными» | Тест был создан до введения логики деактивации родителя | Добавлена кнопка «Сделать активной» в шапке и в строке таблицы для любой версии | + +> **Для джуниора:** `cascade="all, delete-orphan"` в SQLAlchemy работает только при удалении через ORM-объекты (`session.delete(obj)`). При bulk-delete через `db.execute(delete(Model)...)` ORM-каскад не срабатывает — нужно вручную удалять дочерние записи в правильном порядке. + +--- + +## Следующие шаги + +- [x] Спринт 1: Инфраструктура + Создание тестов +- [x] Спринт 2: Прохождение теста + результат +- [x] Спринт 3: Редактирование + версионность +- [ ] Спринт 4: Трекер результатов +- [ ] Спринт 5: Авторизация и роли +- [ ] Спринт 6: Уведомления в MAX