docs: добавление документации по рефакторингу
- Добавлен отчет о рефакторинге хранилища медиафайлов - Добавлено сравнение веток feature/django-filer-documents и develop - Добавлен аудит frontend документов - Добавлен отчет об очистке types, cabinets и dashboard
This commit is contained in:
parent
e689ee7e5a
commit
a662642864
245
docs/completed/MEDIA_STORAGE_REFACTORING.md
Normal file
245
docs/completed/MEDIA_STORAGE_REFACTORING.md
Normal file
@ -0,0 +1,245 @@
|
||||
# Рефакторинг структуры хранения медиа-файлов
|
||||
|
||||
**Дата**: 5 марта 2026
|
||||
**Статус**: ✅ Завершено
|
||||
|
||||
## 🎯 Цели
|
||||
|
||||
Переход от хеш-структуры django-filer к читаемой структуре с организацией по датам.
|
||||
|
||||
## 📁 Было (deprecated)
|
||||
|
||||
```
|
||||
media/filer_public/
|
||||
├── 07/28/07286222-01c2-415c-ad2f-fc0ee3eb13f0/ ← Нечитаемая хеш-структура
|
||||
├── 2a/cd/2acdaf8f-b428-42d6-aaf6-b9b63353e836/
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Проблемы старой структуры:**
|
||||
- ❌ Невозможно найти файл вручную
|
||||
- ❌ Сложно организовать архивацию
|
||||
- ❌ Нет ясности по периодам хранения
|
||||
- ❌ Усложняет бэкапы по датам
|
||||
|
||||
## 📁 Стало (new)
|
||||
|
||||
```
|
||||
media/
|
||||
├── documents/
|
||||
│ ├── public/2026/03/05/ ← Публичные документы по дате
|
||||
│ ├── private/2026/03/05/ ← Приватные документы по дате
|
||||
│ ├── public_thumbnails/ ← Превью публичных документов
|
||||
│ └── private_thumbnails/ ← Превью приватных документов
|
||||
├── avatars/ ← Аватарки пользователей
|
||||
├── chat_attachments/2026/03/05/ ← Вложения мессенджера
|
||||
└── temp/ ← Временные файлы
|
||||
```
|
||||
|
||||
**Преимущества новой структуры:**
|
||||
- ✅ Читаемые пути: `documents/public/2026/03/05/contract.pdf`
|
||||
- ✅ Простая архивация: перемещение старых папок (например, `2024/`)
|
||||
- ✅ Удобные бэкапы: легко выбрать период
|
||||
- ✅ Естественная организация: по дате загрузки
|
||||
|
||||
## 🔧 Изменения в коде
|
||||
|
||||
### 1. Django settings.py
|
||||
|
||||
**Было:**
|
||||
```python
|
||||
FILER_STORAGES = {
|
||||
'public': {
|
||||
'UPLOAD_TO': 'filer.utils.generate_filename.randomized',
|
||||
'UPLOAD_TO_PREFIX': '%Y/%m', # Не работало правильно
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```python
|
||||
FILER_STORAGES = {
|
||||
'public': {
|
||||
'main': {
|
||||
'ENGINE': 'filer.storage.PublicFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/public/'),
|
||||
'base_url': '/media/documents/public/',
|
||||
},
|
||||
'UPLOAD_TO': 'filer.utils.generate_filename.by_date', # ← Ключевое изменение
|
||||
},
|
||||
'thumbnails': {
|
||||
'ENGINE': 'filer.storage.PublicFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/public_thumbnails/'),
|
||||
'base_url': '/media/documents/public_thumbnails/',
|
||||
},
|
||||
},
|
||||
},
|
||||
'private': {
|
||||
'main': {
|
||||
'ENGINE': 'filer.storage.PrivateFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/private/'),
|
||||
'base_url': '/smedia/documents/private/',
|
||||
},
|
||||
'UPLOAD_TO': 'filer.utils.generate_filename.by_date', # ← Ключевое изменение
|
||||
},
|
||||
'thumbnails': {
|
||||
'ENGINE': 'filer.storage.PrivateFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/private_thumbnails/'),
|
||||
'base_url': '/smedia/documents/private_thumbnails/',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Ключевые изменения:**
|
||||
- `randomized` → `by_date` - функция генерации путей
|
||||
- Организация: `YYYY/MM/DD/filename.ext`
|
||||
- Раздельные пути для public/private
|
||||
|
||||
### 2. URL конфигурация (urls.py)
|
||||
|
||||
**Было:**
|
||||
```python
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```python
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
```
|
||||
|
||||
**Причина:** В production статика раздается через Nginx, не Django.
|
||||
|
||||
### 3. .gitignore
|
||||
|
||||
**Добавлено:**
|
||||
```gitignore
|
||||
backend/media/*
|
||||
!backend/media/.gitkeep
|
||||
!backend/media/*/
|
||||
!backend/media/*/.gitkeep
|
||||
!backend/media/*/*/
|
||||
!backend/media/*/*/.gitkeep
|
||||
```
|
||||
|
||||
**Результат:** Git отслеживает структуру папок (через .gitkeep), но игнорирует загруженные файлы.
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### Тест создания файла
|
||||
|
||||
```python
|
||||
from django.core.files.base import ContentFile
|
||||
from filer.models import File
|
||||
|
||||
test_content = ContentFile(b"Test document content", name="test_document.txt")
|
||||
test_file = File.objects.create(
|
||||
original_filename="test_document.txt",
|
||||
file=test_content
|
||||
)
|
||||
|
||||
print(test_file.file.path)
|
||||
# Результат: /backend/media/documents/private/2026/03/05/test_document.txt
|
||||
print(test_file.file.url)
|
||||
# Результат: /smedia/documents/private/2026/03/05/test_document.txt
|
||||
```
|
||||
|
||||
### Результаты
|
||||
|
||||
✅ **Путь корректный**: `documents/private/2026/03/05/test_document.txt`
|
||||
✅ **Дата в пути**: `2026/03/05` (YYYY/MM/DD)
|
||||
✅ **URL правильный**: `/smedia/documents/private/2026/03/05/test_document.txt`
|
||||
✅ **Django check**: 0 ошибок
|
||||
|
||||
## 📋 Management команда
|
||||
|
||||
Создана команда для миграции старых файлов:
|
||||
|
||||
```bash
|
||||
python manage.py migrate_documents_storage --dry-run # Предпросмотр
|
||||
python manage.py migrate_documents_storage # Выполнить миграцию
|
||||
```
|
||||
|
||||
**Статус миграции:**
|
||||
- Проверено: 34 записи в БД
|
||||
- Физически файлов: 0 (старые файлы уже удалены)
|
||||
- Миграция не требуется
|
||||
|
||||
**Файл:** `backend/documents/management/commands/migrate_documents_storage.py`
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
Создана документация со структурой и инструкциями:
|
||||
- **Файл:** `backend/media/README.md`
|
||||
- **Разделы:**
|
||||
- Структура директорий
|
||||
- Настройка FILER_STORAGES
|
||||
- Инструкции по миграции
|
||||
- Безопасность (public vs private)
|
||||
- Конфигурация Nginx для production
|
||||
|
||||
## 🧹 Очистка
|
||||
|
||||
Удалены устаревшие структуры:
|
||||
```bash
|
||||
rm -rf backend/media/filer_public/ # Старая хеш-структура
|
||||
rm -rf backend/media/documents/private/%Y/ # Тестовые папки
|
||||
```
|
||||
|
||||
**До очистки:**
|
||||
- 47 директорий
|
||||
- 10 тестовых файлов в filer_public/
|
||||
|
||||
**После очистки:**
|
||||
- 14 директорий (чистая структура)
|
||||
- .gitkeep файлы во всех пустых папках
|
||||
|
||||
## 🎯 Итоги
|
||||
|
||||
### ✅ Выполнено
|
||||
|
||||
1. ✅ Настроена структура `documents/public/` и `documents/private/`
|
||||
2. ✅ Изменен генератор путей на `by_date` (YYYY/MM/DD)
|
||||
3. ✅ Обновлена конфигурация Django (settings.py)
|
||||
4. ✅ Исправлен URL routing для production
|
||||
5. ✅ Настроен .gitignore для media/
|
||||
6. ✅ Создана management команда миграции
|
||||
7. ✅ Написана документация (README.md)
|
||||
8. ✅ Протестировано создание файлов
|
||||
9. ✅ Удалены старые структуры
|
||||
10. ✅ Созданы .gitkeep файлы
|
||||
|
||||
### 📊 Метрики
|
||||
|
||||
- **Файлов изменено:** 4 (`settings.py`, `urls.py`, `.gitignore`, `README.md`)
|
||||
- **Файлов создано:** 2 (management команда + отчет)
|
||||
- **Директорий очищено:** 33 (из 47 осталось 14)
|
||||
- **Ошибок Django check:** 0
|
||||
- **TypeScript ошибок:** 0 (не затронуто)
|
||||
|
||||
### 🚀 Готовность к production
|
||||
|
||||
- ✅ Nginx конфигурация описана в README.md
|
||||
- ✅ URL routing работает корректно
|
||||
- ✅ Безопасность (public/private разделение)
|
||||
- ✅ Структура масштабируемая
|
||||
- ✅ Бэкапы легко организовать по датам
|
||||
|
||||
## 📌 Следующие шаги (опционально)
|
||||
|
||||
1. **Настройка Nginx** - для раздачи static/media в production
|
||||
2. **Автоочистка** - удаление старых файлов (например, старше 1 года)
|
||||
3. **S3 интеграция** - для облачного хранения (если требуется масштабирование)
|
||||
4. **Мониторинг** - отслеживание размера media/ директории
|
||||
|
||||
## 🔗 Связанные документы
|
||||
|
||||
- [backend/media/README.md](../../backend/media/README.md) - Руководство по структуре
|
||||
- `backend/documents/management/commands/migrate_documents_storage.py` - Команда миграции
|
||||
- `backend/eusrr_backend/settings.py` - Конфигурация FILER_STORAGES
|
||||
862
docs/reports/DOCUMENTS_BRANCH_COMPARISON.md
Normal file
862
docs/reports/DOCUMENTS_BRANCH_COMPARISON.md
Normal file
@ -0,0 +1,862 @@
|
||||
# Сравнение реализаций документов: develop vs feature/django-filer-documents
|
||||
|
||||
## Краткое резюме
|
||||
|
||||
**TL;DR:** Feature ветка в **3 раза сложнее** develop по всем метрикам:
|
||||
- 8 моделей против 2
|
||||
- 2613 строк тестов против 888
|
||||
- FSM workflow с 7 состояниями против простого флага
|
||||
- django-filer + django-fsm + сложные django-rules против базового DRF
|
||||
|
||||
**Вопрос:** Нужна ли эта сложность для use case "публикация регламентов + отслеживание ознакомлений"?
|
||||
|
||||
---
|
||||
|
||||
## Сравнительная таблица
|
||||
|
||||
| Аспект | **develop** (базовая) | **feature** (сложная) |
|
||||
|--------|----------------------|----------------------|
|
||||
| **Модели** | 2 | 8 |
|
||||
| **Строк кода моделей** | ~100 | ~600+ |
|
||||
| **FSM workflow** | ❌ НЕТ | ✅ 7 состояний |
|
||||
| **django-filer** | ❌ НЕТ | ✅ Полная интеграция |
|
||||
| **django-rules** | ⚠️ Шаблон (не работает) | ✅ 6+ predicates |
|
||||
| **Файлов тестов** | 1 | 5 |
|
||||
| **Тестовых методов** | 37 | 86 |
|
||||
| **Строк тестов** | 888 | 2613 |
|
||||
| **Complexity ratio** | **1x** | **3x** |
|
||||
|
||||
---
|
||||
|
||||
## 1. Модели данных
|
||||
|
||||
### develop: 2 простые модели
|
||||
|
||||
```python
|
||||
# backend/documents/models.py (develop)
|
||||
|
||||
class Document(models.Model):
|
||||
"""Документ для рассылки сотрудникам"""
|
||||
title = models.CharField(max_length=255)
|
||||
file = models.FileField(upload_to='documents/%Y/%m/%d/')
|
||||
description = models.TextField(blank=True)
|
||||
uploaded_by = models.ForeignKey(User, ...)
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
# Targeting
|
||||
sent_to_all = models.BooleanField(default=True)
|
||||
departments = models.ManyToManyField('employees.Department', ...)
|
||||
recipients = models.ManyToManyField(User, ...)
|
||||
|
||||
class DocumentAcknowledgement(models.Model):
|
||||
"""Факт ознакомления с документом"""
|
||||
document = models.ForeignKey(Document, ...)
|
||||
user = models.ForeignKey(User, ...)
|
||||
acknowledged_at = models.DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Философия:** Простота. Загрузил → разослал → отследил ознакомление.
|
||||
|
||||
---
|
||||
|
||||
### feature: 8 сложных моделей
|
||||
|
||||
```python
|
||||
# backend/documents/models.py (feature)
|
||||
|
||||
1. Document (FSM-модель)
|
||||
- FilerFileField (django-filer интеграция)
|
||||
- FSMField: status (7 состояний)
|
||||
- 6 FSM переходов с декораторами
|
||||
- Cabinet (папки), DocumentType, tags
|
||||
- related_documents (M2M к самой себе)
|
||||
- cabinet_path property + методы navigation
|
||||
|
||||
2. DocumentAcknowledgement
|
||||
- document, user, acknowledged_at
|
||||
|
||||
3. DocumentType
|
||||
- name, description, icon
|
||||
- is_active, requires_approval, max_file_size, allowed_extensions
|
||||
- Templates support (title_template, description_template)
|
||||
|
||||
4. DocumentMetadata
|
||||
- document, created_by
|
||||
- metadata_fields (JSONField)
|
||||
- Customizable per-document metadata
|
||||
|
||||
5. DocumentTag
|
||||
- name, color, description
|
||||
- Tagging system
|
||||
|
||||
6. Cabinet
|
||||
- name, description, parent (self-referential)
|
||||
- icon, color, can_manage_documents predicate
|
||||
- Tree structure (папки в папках)
|
||||
|
||||
7. DocumentAuditLog
|
||||
- document, user, action, timestamp
|
||||
- old_values, new_values (JSONField)
|
||||
- Audit trail для всех изменений
|
||||
|
||||
8. DocumentComment
|
||||
- document, user, comment, created_at
|
||||
- parent (nested comments)
|
||||
- is_internal flag
|
||||
```
|
||||
|
||||
**Философия:** Полнофункциональная система документооборота с workflow.
|
||||
|
||||
---
|
||||
|
||||
## 2. FSM Workflow
|
||||
|
||||
### develop: Нет workflow
|
||||
|
||||
Документ либо создан, либо нет. Всё.
|
||||
|
||||
```python
|
||||
# Document создан → сразу доступен получателям
|
||||
document = Document.objects.create(
|
||||
title="Регламент",
|
||||
file=file,
|
||||
sent_to_all=True
|
||||
)
|
||||
# Готово! Пользователи могут read + acknowledge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### feature: 7 состояний FSM
|
||||
|
||||
```python
|
||||
# backend/documents/models.py (feature)
|
||||
|
||||
class DocumentStatus(models.TextChoices):
|
||||
DRAFT = "draft", "Черновик"
|
||||
IN_REVIEW = "in_review", "На проверке"
|
||||
APPROVED = "approved", "Одобрен"
|
||||
PUBLISHED = "published", "Опубликован"
|
||||
REJECTED = "rejected", "Отклонён"
|
||||
ARCHIVED = "archived", "В архиве"
|
||||
UNARCHIVED = "unarchived", "Восстановлен из архива"
|
||||
|
||||
# 6 FSM transitions:
|
||||
@fsm_log_by
|
||||
@transition(field=status, source='draft', target='in_review',
|
||||
permission=lambda instance, user: user.has_perm(...))
|
||||
def submit_for_review(self, by=None):
|
||||
"""Черновик → На проверке"""
|
||||
...
|
||||
|
||||
@transition(field=status, source='in_review', target='approved', ...)
|
||||
def approve(self, by=None):
|
||||
"""На проверке → Одобрен"""
|
||||
...
|
||||
|
||||
@transition(field=status, source='in_review', target='rejected', ...)
|
||||
def reject(self, by=None):
|
||||
"""На проверке → Отклонён"""
|
||||
...
|
||||
|
||||
@transition(field=status, source=['approved', 'rejected'], target='published', ...)
|
||||
def publish(self, by=None):
|
||||
"""Одобрен/Отклонён → Опубликован"""
|
||||
...
|
||||
|
||||
@transition(field=status, source=['published', 'approved'], target='archived', ...)
|
||||
def archive(self, by=None):
|
||||
"""Опубликован/Одобрен → В архиве"""
|
||||
...
|
||||
|
||||
@transition(field=status, source='archived', target='draft', ...)
|
||||
def unarchive(self, by=None):
|
||||
"""Архив → Черновик"""
|
||||
...
|
||||
```
|
||||
|
||||
**Проблема:** Для use case "публикация регламентов + ознакомление" это избыточно.
|
||||
|
||||
**Вопросы:**
|
||||
- Зачем состояние IN_REVIEW для регламента?
|
||||
- Кто должен approve регламенты?
|
||||
- Почему rejected документ можно publish?
|
||||
- Зачем unarchive возвращает в draft, а не в published?
|
||||
|
||||
---
|
||||
|
||||
## 3. django-rules Permissions
|
||||
|
||||
### develop: Шаблонный код (НЕ РАБОТАЕТ)
|
||||
|
||||
```python
|
||||
# backend/documents/rules.py (develop) - TEMPLATE ONLY!
|
||||
|
||||
@rules.predicate
|
||||
def is_document_owner(user, document):
|
||||
"""ПРОБЛЕМА: document.created_by не существует в модели!"""
|
||||
return document.created_by == user or document.owner == user
|
||||
|
||||
@rules.predicate
|
||||
def is_document_author(user, document):
|
||||
"""ПРОБЛЕМА: document.author не существует!"""
|
||||
return document.author == user
|
||||
|
||||
# Правила определены, но НЕ используются в коде
|
||||
rules.add_rule('documents.view_document', ...)
|
||||
rules.add_rule('documents.approve_document', ...)
|
||||
# ... но нет rules.add_perm(), поэтому Django не видит эти permissions
|
||||
```
|
||||
|
||||
**Вердикт:** Это просто пример/шаблон. Реальные permissions в `api/v1/documents/permissions.py`:
|
||||
|
||||
```python
|
||||
# backend/api/v1/documents/permissions.py (develop)
|
||||
|
||||
class DocumentReadOrModelPerms(AdminOrActionOrModelPerms):
|
||||
"""Простая логика:
|
||||
- READ/acknowledge: любой authenticated
|
||||
- Фильтрация в queryset: sent_to_all или recipients или departments
|
||||
- WRITE: staff или model permissions
|
||||
"""
|
||||
def has_permission(self, request, view):
|
||||
if request.method in SAFE_METHODS or action in ('acknowledge', 'download'):
|
||||
return request.user.is_authenticated
|
||||
return request.user.is_staff or super().has_permission(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### feature: Сложная система django-rules
|
||||
|
||||
```python
|
||||
# backend/documents/rules.py (feature) - РЕАЛЬНО РАБОТАЕТ
|
||||
|
||||
# 6+ predicates с object-level permissions:
|
||||
|
||||
@rules.predicate
|
||||
def is_document_uploader(user, document):
|
||||
"""Автор документа"""
|
||||
return document.uploaded_by == user
|
||||
|
||||
@rules.predicate
|
||||
def is_not_document_uploader(user, document):
|
||||
"""НЕ автор (separation of duties)"""
|
||||
return document.uploaded_by != user
|
||||
|
||||
@rules.predicate
|
||||
def can_manage_documents(user):
|
||||
"""Позиционные права через employee.can_manage_documents"""
|
||||
return user.employee.can_manage_documents
|
||||
|
||||
@rules.predicate
|
||||
def can_view_document_in_status(user, document):
|
||||
"""Видимость зависит от статуса FSM"""
|
||||
if document.status == 'published':
|
||||
return True
|
||||
if document.status == 'draft':
|
||||
return document.uploaded_by == user
|
||||
# ... etc
|
||||
|
||||
# Permissions для каждого FSM action:
|
||||
rules.add_perm('documents.submit_for_review_document',
|
||||
is_superuser | is_document_uploader | can_manage_documents)
|
||||
|
||||
rules.add_perm('documents.approve_document',
|
||||
is_superuser | (can_manage_documents & is_not_document_uploader))
|
||||
|
||||
rules.add_perm('documents.publish_document',
|
||||
is_superuser | (can_manage_documents & is_not_document_uploader))
|
||||
|
||||
rules.add_perm('documents.archive_document', ...)
|
||||
rules.add_perm('documents.unarchive_document', ...)
|
||||
```
|
||||
|
||||
**Два уровня проверки:**
|
||||
|
||||
1. **DRF permission class:**
|
||||
```python
|
||||
# backend/api/v1/documents/permissions.py (feature)
|
||||
|
||||
class DocumentFSMPermission(DjangoModelPermissions):
|
||||
def has_object_permission(self, request, view, obj):
|
||||
action = view.action
|
||||
if action == "submit_for_review":
|
||||
return user.has_perm("documents.submit_for_review_document", obj)
|
||||
elif action == "approve":
|
||||
return user.has_perm("documents.approve_document", obj)
|
||||
# ... для каждого FSM action
|
||||
```
|
||||
|
||||
2. **FSM transition decorators:**
|
||||
```python
|
||||
@transition(..., permission=lambda instance, user:
|
||||
user.has_perm('documents.approve_document', instance))
|
||||
def approve(self, by=None):
|
||||
...
|
||||
```
|
||||
|
||||
**Проблема:** Двойная проверка (DRF + FSM) = больше мест для ошибок.
|
||||
|
||||
---
|
||||
|
||||
## 4. Тестовое покрытие
|
||||
|
||||
### develop: 1 файл, 37 тестов, 888 строк
|
||||
|
||||
```
|
||||
backend/tests/api/v1/documents/test_documents_api.py (888 lines)
|
||||
|
||||
Структура тестов:
|
||||
- TestAuthAndPermissions (4 теста)
|
||||
- anonymous → 401
|
||||
- regular user access policy
|
||||
- users with individual perms
|
||||
- staff/superuser full access
|
||||
|
||||
- TestCreate (5 тестов)
|
||||
- sent_to_all=true
|
||||
- sent_to_all=false with recipients (JSON/CSV/list)
|
||||
- validations
|
||||
- skip nonexistent/inactive recipients
|
||||
|
||||
- TestRead (2 теста)
|
||||
- list with pagination
|
||||
- detail fields + file_url
|
||||
|
||||
- TestUpdate (4 теста)
|
||||
- update title/description
|
||||
- replace file multipart
|
||||
- toggle sent_to_all
|
||||
- patch recipient_ids
|
||||
|
||||
- TestDelete (1 тест)
|
||||
- delete + cascade acknowledgements
|
||||
|
||||
- TestAcknowledge (3 теста)
|
||||
- first acknowledge + repeat
|
||||
- detail reflects is_acknowledged
|
||||
- forbidden if not recipient
|
||||
|
||||
- TestSerialization (1 тест)
|
||||
- uploaded_by + recipients shape
|
||||
|
||||
- TestErrorsAndEdgeCases (4 теста)
|
||||
- wrong content type
|
||||
- put/patch/delete without perms
|
||||
- get without view_perm
|
||||
- big file over limit
|
||||
|
||||
- TestPerformance (2 теста)
|
||||
- no N+1 in list
|
||||
- pagination navigation
|
||||
|
||||
- TestSecurityAndPolicy (2+ теста)
|
||||
- JWT/session auth
|
||||
- policy without perms
|
||||
|
||||
Итого: ~37 тестов
|
||||
```
|
||||
|
||||
**Покрытие:** CRUD + permissions + acknowledgement + edge cases
|
||||
|
||||
---
|
||||
|
||||
### feature: 5 файлов, 86 тестов, 2613 строк
|
||||
|
||||
```
|
||||
backend/tests/api/v1/documents/ (4 файла):
|
||||
|
||||
1. test_documents_api.py (~1100 lines)
|
||||
- Базовый CRUD (как в develop)
|
||||
- + Cabinet management
|
||||
- + DocumentType endpoints
|
||||
- + Tags, metadata
|
||||
|
||||
2. test_fsm_workflow.py (~700 lines, 30+ тестов)
|
||||
- TestFSMTransitions
|
||||
- submit_for_review
|
||||
- approve/reject
|
||||
- publish
|
||||
- archive/unarchive
|
||||
- TestFSMPermissions
|
||||
- who can transition
|
||||
- FSM + django-rules integration
|
||||
- TestAuthorCanSubmitOwnDocument (новые 3 теста)
|
||||
- TestSeparationOfDuties (WIP, 2 теста не запускались)
|
||||
|
||||
3. test_status_visibility.py (~400 lines)
|
||||
- Видимость документов зависит от статуса
|
||||
- draft → только автор
|
||||
- in_review → автор + managers
|
||||
- published → все получатели
|
||||
- archived → никто (или только managers)
|
||||
|
||||
4. test_new_features.py (~400 lines)
|
||||
- Related documents
|
||||
- Comments (nested)
|
||||
- Audit log
|
||||
- Metadata fields
|
||||
|
||||
backend/tests/integration/test_related_documents_notifications.py (~13 lines)
|
||||
- Integration test для уведомлений
|
||||
|
||||
Итого: 86 тестов, 2613 строк
|
||||
```
|
||||
|
||||
**Покрытие:** Всё из develop + FSM + Cabinet + Tags + Comments + Audit + Metadata + Related docs
|
||||
|
||||
---
|
||||
|
||||
## 5. django-filer Integration
|
||||
|
||||
### develop: Plain FileField
|
||||
|
||||
```python
|
||||
file = models.FileField(upload_to='documents/%Y/%m/%d/')
|
||||
```
|
||||
|
||||
- Простое хранение в `/media/documents/YYYY/MM/DD/`
|
||||
- Нет папок, нет организации
|
||||
|
||||
---
|
||||
|
||||
### feature: Full django-filer
|
||||
|
||||
```python
|
||||
from filer.fields.file import FilerFileField
|
||||
|
||||
class Document(models.Model):
|
||||
file = FilerFileField(
|
||||
verbose_name=_('Файл'),
|
||||
related_name='documents',
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
cabinet = models.ForeignKey(Cabinet, ...) # Folder structure
|
||||
```
|
||||
|
||||
**Возможности:**
|
||||
- Централизованное хранилище файлов
|
||||
- Папочная структура (Cabinet → вложенные папки)
|
||||
- Переиспользование файлов между документами
|
||||
- Thumbnails для изображений
|
||||
- Permissions на уровне файлов
|
||||
|
||||
**Проблема:** Для use case "регламенты" это избыточно.
|
||||
|
||||
---
|
||||
|
||||
## 6. API Endpoints
|
||||
|
||||
### develop: Простой REST
|
||||
|
||||
```
|
||||
GET /api/v1/documents/ - список (scope=mine для фильтра)
|
||||
POST /api/v1/documents/ - создать
|
||||
GET /api/v1/documents/{id}/ - детали
|
||||
PUT /api/v1/documents/{id}/ - обновить
|
||||
PATCH /api/v1/documents/{id}/ - частичное обновление
|
||||
DELETE /api/v1/documents/{id}/ - удалить
|
||||
POST /api/v1/documents/{id}/acknowledge/ - ознакомиться
|
||||
GET /api/v1/documents/{id}/acknowledgements/ - ведомость (staff only)
|
||||
```
|
||||
|
||||
**8 endpoints**, стандартный REST.
|
||||
|
||||
---
|
||||
|
||||
### feature: Extended REST + FSM actions
|
||||
|
||||
```
|
||||
# Base CRUD (как в develop)
|
||||
GET /api/v1/documents/
|
||||
POST /api/v1/documents/
|
||||
GET /api/v1/documents/{id}/
|
||||
PUT /api/v1/documents/{id}/
|
||||
PATCH /api/v1/documents/{id}/
|
||||
DELETE /api/v1/documents/{id}/
|
||||
|
||||
# FSM actions (новые)
|
||||
POST /api/v1/documents/{id}/submit_for_review/
|
||||
POST /api/v1/documents/{id}/approve/
|
||||
POST /api/v1/documents/{id}/reject/
|
||||
POST /api/v1/documents/{id}/publish/
|
||||
POST /api/v1/documents/{id}/archive/
|
||||
POST /api/v1/documents/{id}/unarchive/
|
||||
|
||||
# Acknowledgement
|
||||
POST /api/v1/documents/{id}/acknowledge/
|
||||
GET /api/v1/documents/{id}/acknowledgements/
|
||||
|
||||
# Cabinet management (новые)
|
||||
GET /api/v1/cabinets/
|
||||
POST /api/v1/cabinets/
|
||||
GET /api/v1/cabinets/{id}/
|
||||
PUT /api/v1/cabinets/{id}/
|
||||
DELETE /api/v1/cabinets/{id}/
|
||||
GET /api/v1/cabinets/{id}/documents/
|
||||
|
||||
# Tags (новые)
|
||||
GET /api/v1/documents/tags/
|
||||
POST /api/v1/documents/tags/
|
||||
|
||||
# Types (новые)
|
||||
GET /api/v1/documents/types/
|
||||
POST /api/v1/documents/types/
|
||||
|
||||
# Comments (новые)
|
||||
GET /api/v1/documents/{id}/comments/
|
||||
POST /api/v1/documents/{id}/comments/
|
||||
DELETE /api/v1/documents/{id}/comments/{comment_id}/
|
||||
|
||||
# Audit log (новые)
|
||||
GET /api/v1/documents/{id}/audit_log/
|
||||
|
||||
# Related documents (новые)
|
||||
POST /api/v1/documents/{id}/add_related/
|
||||
POST /api/v1/documents/{id}/remove_related/
|
||||
```
|
||||
|
||||
**~25+ endpoints** (в 3 раза больше).
|
||||
|
||||
---
|
||||
|
||||
## 7. Use Case Analysis
|
||||
|
||||
### Исходное требование
|
||||
> "Система для публикации регламентов с отслеживанием ознакомлений"
|
||||
|
||||
### Что нужно для этого use case?
|
||||
|
||||
**Минимум (MVP):**
|
||||
1. ✅ Загрузить документ (файл + название + описание)
|
||||
2. ✅ Выбрать получателей (всем / отделам / конкретным)
|
||||
3. ✅ Опубликовать (сделать доступным)
|
||||
4. ✅ Пользователи видят документы
|
||||
5. ✅ Пользователи нажимают "Ознакомлен"
|
||||
6. ✅ Ведомость ознакомлений (кто прочитал, кто нет)
|
||||
|
||||
**Есть в develop:** ✅ ВСЁ!
|
||||
|
||||
### Что добавлено в feature, но НЕ нужно для use case?
|
||||
|
||||
| Фича | Нужна? | Почему нет |
|
||||
|------|--------|------------|
|
||||
| FSM workflow (7 states) | ❌ | Регламенты не требуют approval workflow |
|
||||
| draft → in_review → approved | ❌ | Кто будет approving? Зачем? |
|
||||
| django-filer | ❌ | Файлы и так хранятся, папки не критичны |
|
||||
| Cabinet (folders) | 🤔 | Nice to have, но не критично |
|
||||
| DocumentType | ❌ | Overengineering, достаточно тегов |
|
||||
| DocumentMetadata (JSONField) | ❌ | Custom fields не требуются |
|
||||
| DocumentTag | 🤔 | Может быть полезно, но не критично |
|
||||
| DocumentComment | ❌ | Комментарии к регламентам? Зачем? |
|
||||
| DocumentAuditLog | 🤔 | Audit trail полезен, но не для MVP |
|
||||
| Related documents | ❌ | Усложняет UI, не критично |
|
||||
| Separation of duties | ❌ | Если нет approval, то не нужно |
|
||||
|
||||
**Вердикт:** 80% feature branch не нужно для use case "регламенты".
|
||||
|
||||
---
|
||||
|
||||
## 8. Complexity Metrics
|
||||
|
||||
### Code Complexity
|
||||
|
||||
```bash
|
||||
# develop branch
|
||||
backend/documents/models.py: ~100 lines (2 модели)
|
||||
backend/documents/rules.py: ~290 lines (шаблон, не используется)
|
||||
backend/api/v1/documents/views.py: ~213 lines (1 ViewSet)
|
||||
backend/api/v1/documents/serializers.py: ~393 lines (2 serializers + 1 Field)
|
||||
backend/api/v1/documents/permissions.py: ~50 lines (1 permission class)
|
||||
TOTAL: ~1046 lines
|
||||
|
||||
# feature branch
|
||||
backend/documents/models.py: ~600+ lines (8 моделей + FSM)
|
||||
backend/documents/rules.py: ~200+ lines (6+ predicates, работающие)
|
||||
backend/api/v1/documents/views.py: ~500+ lines (3+ ViewSets)
|
||||
backend/api/v1/documents/serializers.py: ~800+ lines (10+ serializers)
|
||||
backend/api/v1/documents/permissions.py: ~150+ lines (2+ permission classes)
|
||||
+ admin.py, signals.py, etc.
|
||||
TOTAL: ~2500+ lines (2.5x сложнее)
|
||||
```
|
||||
|
||||
### Test Complexity
|
||||
|
||||
```bash
|
||||
# develop: 1 file, 37 tests, 888 lines
|
||||
# feature: 5 files, 86 tests, 2613 lines (3x сложнее)
|
||||
```
|
||||
|
||||
### Maintenance Cost
|
||||
|
||||
| Аспект | develop | feature | Разница |
|
||||
|--------|---------|---------|---------|
|
||||
| Новый разработчик onboarding | 1 день | 3-5 дней | **3-5x** |
|
||||
| Время на bug fix | 30 мин | 1-2 часа | **2-4x** |
|
||||
| Regression risk | Низкий | Средний-Высокий | FSM + много связей |
|
||||
| Documentation need | Минимально | Критично | Без доки не разобраться |
|
||||
|
||||
---
|
||||
|
||||
## 9. Проблемы feature branch
|
||||
|
||||
### 1. Неясный Use Case
|
||||
- FSM workflow для чего? Кто approves регламенты?
|
||||
- Зачем draft, если регламент разрабатывается в Word?
|
||||
- Separation of duties - для каких процессов?
|
||||
|
||||
### 2. Over-engineering
|
||||
- 8 моделей вместо 2
|
||||
- 3x больше кода
|
||||
- 3x больше тестов
|
||||
- 3x сложнее поддержка
|
||||
|
||||
### 3. Неполная реализация
|
||||
- Separation of duties в WIP состоянии
|
||||
- 2 теста не запускались: `test_author_cannot_approve_own_document`, `test_author_cannot_publish_own_document`
|
||||
- FSM permissions не до конца продуманы (почему reject → publish?)
|
||||
|
||||
### 4. UX вопросы
|
||||
- Пользователь создаёт документ → статус draft
|
||||
- Что дальше? Кнопка "Submit for review"?
|
||||
- Кто получит уведомление о review?
|
||||
- Где UI для approve/reject?
|
||||
- Как пользователь понимает, что документ в архиве?
|
||||
|
||||
---
|
||||
|
||||
## 10. Рекомендации
|
||||
|
||||
### Вариант A: Использовать develop (простой)
|
||||
|
||||
**Преимущества:**
|
||||
- ✅ Работает сейчас
|
||||
- ✅ Покрывает 100% use case "регламенты + ознакомление"
|
||||
- ✅ Простой, понятный код
|
||||
- ✅ 37 тестов уже есть
|
||||
- ✅ Быстрый onboarding
|
||||
|
||||
**Недостатки:**
|
||||
- ❌ Нет папок (Cabinet)
|
||||
- ❌ Нет тегов
|
||||
- ❌ Нет audit trail
|
||||
|
||||
**Recommendation:** Добавить к develop минимум фич из feature:
|
||||
1. Cabinet (папки) - полезно для организации
|
||||
2. Tags - полезно для категоризации
|
||||
3. Audit log - полезно для compliance
|
||||
|
||||
**Estimated effort:** 1-2 дня (vs 2-3 недели доработки feature)
|
||||
|
||||
---
|
||||
|
||||
### Вариант B: Упростить feature branch
|
||||
|
||||
**Что оставить:**
|
||||
- ✅ Cabinet (папки) - полезно
|
||||
- ✅ DocumentTag - полезно
|
||||
- ✅ DocumentAcknowledgement - критично
|
||||
- 🤔 DocumentType - можно оставить, но упростить
|
||||
|
||||
**Что убрать:**
|
||||
- ❌ FSM workflow (все 7 состояний)
|
||||
- ❌ Separation of duties
|
||||
- ❌ DocumentComment
|
||||
- ❌ DocumentMetadata (JSONField)
|
||||
- ❌ Related documents
|
||||
- ❌ DocumentAuditLog (можно добавить позже)
|
||||
|
||||
**Заменить FSM на:**
|
||||
```python
|
||||
class Document(models.Model):
|
||||
is_published = models.BooleanField(default=True)
|
||||
# Если нужен draft: is_published=False
|
||||
```
|
||||
|
||||
**Estimated effort:** 3-5 дней удаления кода + рефакторинг тестов
|
||||
|
||||
---
|
||||
|
||||
### Вариант C: Оставить feature, но закончить separation of duties
|
||||
|
||||
**Преимущества:**
|
||||
- ✅ Полнофункциональная система
|
||||
- ✅ FSM workflow готов
|
||||
- ✅ Cabinet, Tags, Comments
|
||||
|
||||
**Недостатки:**
|
||||
- ❌ Нужно дописать separation of duties (1-2 дня)
|
||||
- ❌ Нужно продумать UX для FSM (кнопки, уведомления)
|
||||
- ❌ Нужна документация (1 день)
|
||||
- ❌ Нужно обучать пользователей (сложный workflow)
|
||||
|
||||
**Estimated effort:** 1 неделя до production-ready
|
||||
|
||||
---
|
||||
|
||||
## 11. Финальный вопрос
|
||||
|
||||
**Перед тем как выбрать вариант, ответьте на вопросы:**
|
||||
|
||||
1. **Кто будет approving регламенты?**
|
||||
- Если никто → FSM не нужен
|
||||
- Если юристы/руководители → FSM нужен
|
||||
|
||||
2. **Где создаются регламенты?**
|
||||
- Если в Word/Google Docs → draft в системе не нужен
|
||||
- Если в системе → draft может быть полезен
|
||||
|
||||
3. **Нужны ли комментарии к регламентам?**
|
||||
- Обычно нет (discussion в другом месте)
|
||||
|
||||
4. **Нужны ли связанные документы (related)?**
|
||||
- Для регламентов обычно нет
|
||||
|
||||
5. **Нужен ли audit trail каждого изменения?**
|
||||
- Для compliance - да
|
||||
- Для внутренних регламентов - может быть избыточно
|
||||
|
||||
6. **Есть ли у пользователей файловые шары?**
|
||||
- Если да → зачем дублировать хранение?
|
||||
- Может лучше ссылки на файловые шары?
|
||||
|
||||
---
|
||||
|
||||
## 12. Сравнение по чек-листу "Публикация регламентов"
|
||||
|
||||
| Задача | develop | feature | Нужно? |
|
||||
|--------|---------|---------|--------|
|
||||
| Загрузить файл | ✅ FileField | ✅ FilerFile | ✅ |
|
||||
| Навание/описание | ✅ | ✅ | ✅ |
|
||||
| Организация (папки) | ❌ | ✅ Cabinet | 🤔 Nice to have |
|
||||
| Выбрать получателей | ✅ sent_to_all/departments/recipients | ✅ Same | ✅ |
|
||||
| Опубликовать | ✅ Сразу после create | ⚠️ draft → ... → publish | ✅ Simpler! |
|
||||
| Видят получатели | ✅ Сразу | ⚠️ Только после publish | ✅ Simpler! |
|
||||
| Кнопка "Ознакомлен" | ✅ | ✅ | ✅ |
|
||||
| Ведомость ознакомлений | ✅ | ✅ | ✅ |
|
||||
| Версионирование | ❌ | ❌ (но есть audit) | 🤔 Позже |
|
||||
| Уведомления | ❌ (но легко добавить) | ❌ (тоже не готово) | ✅ Нужно! |
|
||||
|
||||
**Вывод:** develop проще и покрывает базовый use case. feature добавляет сложность без явной пользы.
|
||||
|
||||
---
|
||||
|
||||
## 13. Conclusion
|
||||
|
||||
### Текущая ситуация
|
||||
- develop работает, проста, покрывает 100% базового use case
|
||||
- feature в 3 раза сложнее, но не даёт критичных преимуществ для use case "регламенты"
|
||||
- feature в WIP состоянии (separation of duties не закончена)
|
||||
|
||||
### Вопрос к пользователю
|
||||
> "Зачем нам FSM workflow draft → in_review → approved → published для публикации регламентов?"
|
||||
|
||||
Если ответ "не нужен" → **Вариант A: develop + минимум из feature**
|
||||
|
||||
Если ответ "нужен approval process" → **Вариант C: доделать feature**
|
||||
|
||||
### Рекомендация агента: **Вариант A**
|
||||
|
||||
**Почему:**
|
||||
1. Use case "регламенты" не требует сложного workflow
|
||||
2. develop работает и тестируется
|
||||
3. Можно добавить Cabinet + Tags за 1-2 дня
|
||||
4. Быстрее time-to-market
|
||||
5. Проще поддержка
|
||||
6. Проще обучение пользователей
|
||||
|
||||
**Что добавить к develop:**
|
||||
```python
|
||||
# backend/documents/models.py (develop + improvements)
|
||||
|
||||
class Cabinet(models.Model):
|
||||
"""Папка для организации документов"""
|
||||
name = models.CharField(max_length=255)
|
||||
parent = models.ForeignKey('self', null=True, blank=True)
|
||||
|
||||
class DocumentTag(models.Model):
|
||||
"""Тег для категоризации"""
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
color = models.CharField(max_length=7, default='#007bff')
|
||||
|
||||
class Document(models.Model):
|
||||
# ... existing fields ...
|
||||
cabinet = models.ForeignKey(Cabinet, null=True, blank=True)
|
||||
tags = models.ManyToManyField(DocumentTag, blank=True)
|
||||
```
|
||||
|
||||
**Estimated effort:** 1-2 дня → production ready
|
||||
|
||||
vs
|
||||
|
||||
**feature branch completion:** 1 неделя → production ready (но сложнее)
|
||||
|
||||
---
|
||||
|
||||
## Приложение: Метрики по файлам
|
||||
|
||||
### develop branch
|
||||
|
||||
```
|
||||
backend/documents/
|
||||
├── models.py (100 lines, 2 models)
|
||||
├── rules.py (290 lines, template/example)
|
||||
├── admin.py
|
||||
├── views.py
|
||||
└── ...
|
||||
|
||||
backend/api/v1/documents/
|
||||
├── views.py (213 lines, 1 ViewSet)
|
||||
├── serializers.py (393 lines, 2 serializers)
|
||||
├── permissions.py (50 lines, 1 class)
|
||||
└── urls.py
|
||||
|
||||
backend/tests/api/v1/documents/
|
||||
└── test_documents_api.py (888 lines, 37 tests)
|
||||
|
||||
TOTAL FILES: ~8
|
||||
TOTAL LINES: ~1900
|
||||
TOTAL TESTS: 37
|
||||
COMPLEXITY: Low
|
||||
```
|
||||
|
||||
### feature branch
|
||||
|
||||
```
|
||||
backend/documents/
|
||||
├── models.py (600+ lines, 8 models)
|
||||
├── rules.py (200+ lines, 6+ predicates)
|
||||
├── admin.py (enlarged)
|
||||
├── signals.py (new)
|
||||
├── tasks.py (new)
|
||||
└── ...
|
||||
|
||||
backend/api/v1/documents/
|
||||
├── views.py (500+ lines, 3+ ViewSets)
|
||||
├── serializers.py (800+ lines, 10+ serializers)
|
||||
├── permissions.py (150+ lines, 2+ classes)
|
||||
└── urls.py (enlarged)
|
||||
|
||||
backend/tests/api/v1/documents/
|
||||
├── test_documents_api.py (~1100 lines)
|
||||
├── test_fsm_workflow.py (~700 lines)
|
||||
├── test_status_visibility.py (~400 lines)
|
||||
└── test_new_features.py (~400 lines)
|
||||
|
||||
backend/tests/integration/
|
||||
└── test_related_documents_notifications.py
|
||||
|
||||
TOTAL FILES: ~15+
|
||||
TOTAL LINES: ~4200+
|
||||
TOTAL TESTS: 86
|
||||
COMPLEXITY: High
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Дата создания:** 2025-01-XX
|
||||
**Автор:** AI Agent после анализа обеих веток
|
||||
**Статус:** Ожидание решения пользователя
|
||||
563
docs/reports/DOCUMENTS_FRONTEND_AUDIT_2026.md
Normal file
563
docs/reports/DOCUMENTS_FRONTEND_AUDIT_2026.md
Normal file
@ -0,0 +1,563 @@
|
||||
# 🔍 Полный Аудит Фронтенд-Модуля Документов
|
||||
|
||||
**Дата:** 5 марта 2026 г.
|
||||
**Версия:** EUSRR v2.0
|
||||
**Статус:** 🔴 КРИТИЧЕСКИЕ ПРОБЛЕМЫ ОБНАРУЖЕНЫ
|
||||
|
||||
---
|
||||
|
||||
## 📊 Метрики Проекта
|
||||
|
||||
### Размер Кодовой Базы
|
||||
- **Главный файл:** `page.tsx` - **1,202 строки** ⚠️ (норма: 200-400)
|
||||
- **Компонентов:** 19 главных + 30+ вложенных
|
||||
- **State переменных:** 20+ useState в одном компоненте ⚠️
|
||||
- **Модальных окон:** 6+ в одном файле
|
||||
|
||||
### Архитектурные Проблемы
|
||||
|
||||
#### 🔴 КРИТИЧНЫЕ
|
||||
|
||||
1. **God Component Anti-Pattern**
|
||||
- `page.tsx` содержит **1,202 строки кода**
|
||||
- 20+ локальных state переменных
|
||||
- Миксует логику документов, папок, поиска, фильтров, модалов
|
||||
- **Проблема:** невозможно поддерживать, высокий риск багов
|
||||
- **Рекомендация:** разбить на 5-7 умных компонентов
|
||||
|
||||
2. **Prop Drilling Hell**
|
||||
```tsx
|
||||
// Передача пропсов через 3-4 уровня вложенности
|
||||
<Parent onUpdate={reload}>
|
||||
<Child onUpdate={reload}>
|
||||
<GrandChild onUpdate={reload} />
|
||||
</Child>
|
||||
</Parent>
|
||||
```
|
||||
- **Решение:** Context API или Zustand для глобального состояния
|
||||
|
||||
3. **Модальные Окна - Плохая Архитектура**
|
||||
- ❌ Использовался устаревший Tailwind UI v1 подход (inline-block hack)
|
||||
- ✅ **ИСПРАВЛЕНО:** переделано на Flexbox + fixed positioning
|
||||
- **Но:** все модалы в одном файле = плохая изоляция
|
||||
- **Проблема toast:** `console.log` вместо настоящей библиотеки
|
||||
|
||||
4. **Дублирование Кода**
|
||||
- Логика CRUD повторяется для тегов, типов, папок
|
||||
- Нет переиспользуемых хуков (`useCRUD`, `useModal`)
|
||||
- Каждый модал переписывает одну и ту же логику
|
||||
|
||||
#### 🟡 СРЕДНЯЯ ВАЖНОСТЬ
|
||||
|
||||
5. **Отсутствие State Management**
|
||||
- Локальный state разбросан по 20+ useState
|
||||
- Каждый дочерний компонент запрашивает данные заново
|
||||
- **Решение:** React Query или Zustand
|
||||
|
||||
6. **Неоптимальная Загрузка Данных**
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
loadDocuments();
|
||||
loadFolders();
|
||||
loadTags();
|
||||
loadTypes();
|
||||
}, []); // 4 параллельных запроса при монтировании
|
||||
```
|
||||
- Нет кеширования
|
||||
- Нет debounce для поиска
|
||||
- Рефетч при каждом изменении фильтров
|
||||
|
||||
7. **Accessibility (a11y) Проблемы**
|
||||
- Модалы не фокусируются автоматически
|
||||
- Нет Escape для закрытия
|
||||
- Нет trap focus внутри модала
|
||||
- Нет aria-labels для кнопок без текста
|
||||
|
||||
8. **TypeScript Type Safety**
|
||||
```tsx
|
||||
// Слабые типы
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Лучше создать Error type
|
||||
type AppError = {
|
||||
code: string;
|
||||
message: string;
|
||||
field?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### 🟢 НИЗКАЯ ПРИОРИТЕТ
|
||||
|
||||
9. **Performance**
|
||||
- Нет мемоизации `filteredDocuments` (пересчет каждый рендер)
|
||||
- Нет виртуализации для длинных списков
|
||||
- Большие компоненты не ленивозагружаются
|
||||
|
||||
10. **UX/UI Inconsistencies**
|
||||
- Разные стили модалов (размеры, отступы)
|
||||
- Нет единой системы Toast уведомлений
|
||||
- Загрузка без skeleton screens
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Детальный Анализ По Модулям
|
||||
|
||||
### 1. `/app/documents/page.tsx` (1,202 строки)
|
||||
|
||||
**Проблемы:**
|
||||
- Содержит логику для:
|
||||
- Управления документами
|
||||
- Управления папками
|
||||
- Фильтрации и поиска
|
||||
- Тегов и типов
|
||||
- Дашборда
|
||||
- 6+ модальных окон
|
||||
|
||||
**Должно быть:**
|
||||
```
|
||||
/app/documents/
|
||||
├── page.tsx (100-150 строк) - роутинг + layout
|
||||
├── components/
|
||||
│ ├── DocumentsView.tsx - список документов
|
||||
│ ├── DocumentsFilters.tsx - поиск + фильтры
|
||||
│ ├── DocumentsDashboard.tsx - статистика
|
||||
│ └── DocumentsModals.tsx - все модалы
|
||||
└── hooks/
|
||||
├── useDocuments.ts
|
||||
├── useFolders.ts
|
||||
└── useDocumentFilters.ts
|
||||
```
|
||||
|
||||
### 2. Модальные Окна
|
||||
|
||||
**Текущие модалы:**
|
||||
1. ✅ `TagManagementModal` - управление тегами
|
||||
2. ✅ `DocumentTypeManagementModal` - управление типами
|
||||
3. `DocumentDetailModal` - детали документа
|
||||
4. `DocumentUploadForm` - загрузка
|
||||
5. `CreateFolder` - создание папки (inline, не модал)
|
||||
6. `AcknowledgementsReport` - отчет по ознакомлениям
|
||||
|
||||
**Проблемы:**
|
||||
- Все рендерятся в одном компоненте
|
||||
- Нет единого Modal Manager
|
||||
- Нет transition анимаций
|
||||
- Нет Portal для z-index изоляции
|
||||
|
||||
**Рекомендация:**
|
||||
```tsx
|
||||
// Использовать библиотеку:
|
||||
import { Modal } from '@radix-ui/react-dialog';
|
||||
// или
|
||||
import { Modal } from '@headlessui/react';
|
||||
|
||||
// Или создать свой ModalContext:
|
||||
<ModalProvider>
|
||||
<App />
|
||||
</ModalProvider>
|
||||
```
|
||||
|
||||
### 3. Формы (Tags & Types)
|
||||
|
||||
**✅ Что хорошо:**
|
||||
- Валидация данных (уникальность, длина)
|
||||
- Показ ошибок inline
|
||||
- Loading states
|
||||
- Disabled при загрузке
|
||||
|
||||
**❌ Что плохо:**
|
||||
- Нет react-hook-form (ручная валидация)
|
||||
- Нет zod для схем валидации
|
||||
- Toast через console.log вместо react-hot-toast
|
||||
|
||||
**Рекомендация:**
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
const tagSchema = z.object({
|
||||
name: z.string()
|
||||
.min(2, 'Минимум 2 символа')
|
||||
.max(50, 'Максимум 50 символов'),
|
||||
color: z.string().regex(/^#[0-9A-F]{6}$/i),
|
||||
});
|
||||
|
||||
function TagForm() {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm({
|
||||
resolver: zodResolver(tagSchema)
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 4. API Клиент
|
||||
|
||||
**Проблемы:**
|
||||
- Все запросы через голый fetch
|
||||
- Нет retry логики
|
||||
- Нет кеширования
|
||||
- Нет оптимистичных обновлений
|
||||
|
||||
**Рекомендация:**
|
||||
```tsx
|
||||
// Использовать TanStack Query (React Query)
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
function useDocuments() {
|
||||
return useQuery({
|
||||
queryKey: ['documents'],
|
||||
queryFn: () => apiClient.getDocuments(),
|
||||
staleTime: 5 * 60 * 1000, // 5 минут кеш
|
||||
});
|
||||
}
|
||||
|
||||
function useCreateTag() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: apiClient.createTag,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(['tags']);
|
||||
toast.success('Тег создан!');
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Priority Roadmap
|
||||
|
||||
### 🔴 P0: Критичные (1-2 недели)
|
||||
|
||||
**Неделя 1:**
|
||||
1. ✅ **Исправить модалы** (DONE TODAY)
|
||||
- Флекс-центрирование вместо inline-block
|
||||
- Правильный z-index layering
|
||||
|
||||
2. **Добавить Toast уведомления**
|
||||
```bash
|
||||
npm install react-hot-toast
|
||||
```
|
||||
- Заменить все console.log на toast
|
||||
- Единый UX для успеха/ошибок
|
||||
|
||||
3. **Разбить page.tsx на части**
|
||||
- Извлечь логику в хуки (useDocuments, useFolders)
|
||||
- Создать отдельные компоненты для view/filters/modals
|
||||
|
||||
**Неделя 2:**
|
||||
4. **Внедрить React Query**
|
||||
```bash
|
||||
npm install @tanstack/react-query
|
||||
```
|
||||
- Кеширование запросов
|
||||
- Автоматический refetch
|
||||
- Оптимистичные обновления
|
||||
|
||||
5. **Accessibility fixes**
|
||||
- Focus trap в модалах
|
||||
- Escape to close
|
||||
- ARIA labels
|
||||
|
||||
### 🟡 P1: Важные (2-3 недели)
|
||||
|
||||
6. **Form Management**
|
||||
```bash
|
||||
npm install react-hook-form zod @hookform/resolvers
|
||||
```
|
||||
- Переписать все формы на RHF
|
||||
- Схемы валидации через Zod
|
||||
|
||||
7. **State Management**
|
||||
- Zustand для глобального стейта
|
||||
- Вынести filters/search в store
|
||||
|
||||
8. **Performance оптимизации**
|
||||
- useMemo для filteredDocuments
|
||||
- React.memo для списков
|
||||
- Виртуализация (@tanstack/react-virtual)
|
||||
|
||||
### 🟢 P2: Nice-to-have (месяц+)
|
||||
|
||||
9. **Testing**
|
||||
- Unit тесты для хуков
|
||||
- Integration тесты для форм
|
||||
- E2E для CRUD операций
|
||||
|
||||
10. **Documentation**
|
||||
- Storybook для компонентов
|
||||
- JSDoc комментарии
|
||||
- Архитектурные диаграммы
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Предложенная Архитектура
|
||||
|
||||
### Новая Структура
|
||||
|
||||
```
|
||||
frontend/src/app/documents/
|
||||
├── page.tsx # ~150 строк, главный роутинг
|
||||
├── layout.tsx # Обертка с провайдерами
|
||||
│
|
||||
├── components/ # UI компоненты
|
||||
│ ├── DocumentsList/
|
||||
│ │ ├── DocumentsList.tsx # Список документов
|
||||
│ │ ├── DocumentCard.tsx # Карточка документа
|
||||
│ │ └── DocumentsEmpty.tsx # Пустое состояние
|
||||
│ │
|
||||
│ ├── DocumentsFilters/
|
||||
│ │ ├── FiltersBar.tsx # Панель фильтров
|
||||
│ │ ├── SearchInput.tsx # Поиск
|
||||
│ │ ├── TagsFilter.tsx # Фильтр по тегам
|
||||
│ │ └── TypesFilter.tsx # Фильтр по типам
|
||||
│ │
|
||||
│ ├── DocumentsModals/
|
||||
│ │ ├── UploadModal.tsx
|
||||
│ │ ├── DetailModal.tsx
|
||||
│ │ ├── FolderModal.tsx
|
||||
│ │ ├── TagManagementModal.tsx # ✅ Уже создан
|
||||
│ │ └── TypeManagementModal.tsx # ✅ Уже создан
|
||||
│ │
|
||||
│ └── DocumentsDashboard/
|
||||
│ ├── DashboardStats.tsx
|
||||
│ └── DashboardCharts.tsx
|
||||
│
|
||||
├── hooks/ # Бизнес-логика
|
||||
│ ├── useDocuments.ts # React Query для документов
|
||||
│ ├── useFolders.ts # Логика папок
|
||||
│ ├── useTags.ts # CRUD тегов
|
||||
│ ├── useTypes.ts # CRUD типов
|
||||
│ ├── useDocumentFilters.ts # Логика фильтрации
|
||||
│ └── useDocumentSelection.ts # Bulk операции
|
||||
│
|
||||
├── store/ # Zustand stores
|
||||
│ ├── documentsStore.ts # Глобальный стейт
|
||||
│ └── filtersStore.ts # Состояние фильтров
|
||||
│
|
||||
└── utils/ # Утилиты
|
||||
├── documentHelpers.ts
|
||||
└── validators.ts
|
||||
```
|
||||
|
||||
### Пример Рефакторинга
|
||||
|
||||
**Было (1,202 строки):**
|
||||
```tsx
|
||||
export default function DocumentsPage() {
|
||||
const [documents, setDocuments] = useState<Document[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadDocuments = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await apiClient.getDocuments();
|
||||
setDocuments(response.results || []);
|
||||
} catch (err) {
|
||||
setError('Failed');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { loadDocuments(); }, []);
|
||||
|
||||
// ... еще 1150 строк
|
||||
}
|
||||
```
|
||||
|
||||
**Стало (~100 строк):**
|
||||
```tsx
|
||||
export default function DocumentsPage() {
|
||||
return (
|
||||
<DocumentsProvider>
|
||||
<DocumentsLayout>
|
||||
<DocumentsFilters />
|
||||
<DocumentsList />
|
||||
<DocumentsModals />
|
||||
</DocumentsLayout>
|
||||
</DocumentsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// hooks/useDocuments.ts (40 строк)
|
||||
export function useDocuments() {
|
||||
return useQuery({
|
||||
queryKey: ['documents'],
|
||||
queryFn: apiClient.getDocuments,
|
||||
});
|
||||
}
|
||||
|
||||
// components/DocumentsList.tsx (80 строк)
|
||||
export function DocumentsList() {
|
||||
const { data, isLoading, error } = useDocuments();
|
||||
const filters = useDocumentFilters();
|
||||
|
||||
const filtered = useMemo(() =>
|
||||
applyFilters(data, filters),
|
||||
[data, filters]
|
||||
);
|
||||
|
||||
if (isLoading) return <Skeleton />;
|
||||
if (error) return <Error />;
|
||||
|
||||
return <List documents={filtered} />;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Что Уже Сделано Хорошо
|
||||
|
||||
1. ✅ TypeScript используется везде
|
||||
2. ✅ Компонентная структура (не монолит)
|
||||
3. ✅ Разделение на папки (tags, types, folders)
|
||||
4. ✅ API клиент изолирован
|
||||
5. ✅ Валидация форм работает
|
||||
6. ✅ Responsive дизайн (Tailwind)
|
||||
7. ✅ Loading states показываются
|
||||
8. ✅ Модалы исправлены (только что)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Рекомендуемые Библиотеки
|
||||
|
||||
### Must Have
|
||||
```bash
|
||||
npm install @tanstack/react-query # Кеширование + data fetching
|
||||
npm install react-hot-toast # Toast уведомления
|
||||
npm install react-hook-form # Управление формами
|
||||
npm install zod # Валидация схем
|
||||
npm install @hookform/resolvers # RHF + Zod интеграция
|
||||
```
|
||||
|
||||
### Highly Recommended
|
||||
```bash
|
||||
npm install zustand # State management (легче Redux)
|
||||
npm install @radix-ui/react-dialog # Accessible модалы
|
||||
npm install @tanstack/react-virtual # Виртуализация списков
|
||||
npm install framer-motion # Анимации
|
||||
```
|
||||
|
||||
### Nice to Have
|
||||
```bash
|
||||
npm install @dnd-kit/core # Drag & Drop (для Phase 3)
|
||||
npm install recharts # Графики для дашборда
|
||||
npm install date-fns # Работа с датами
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Ожидаемые Результаты
|
||||
|
||||
### После P0 (2 недели):
|
||||
- ✅ Модалы работают корректно
|
||||
- ✅ Toast уведомления вместо console.log
|
||||
- ✅ Файл page.tsx < 300 строк
|
||||
- ✅ Хуки изолированы и переиспользуемы
|
||||
- ✅ Accessibility базовый уровень
|
||||
|
||||
### После P1 (месяц):
|
||||
- ✅ React Query = моментальная загрузка (кеш)
|
||||
- ✅ Формы с RHF = меньше багов
|
||||
- ✅ Performance +50% (мемоизация)
|
||||
- ✅ Zustand = предсказуемый стейт
|
||||
|
||||
### После P2 (2-3 месяца):
|
||||
- ✅ 80%+ test coverage
|
||||
- ✅ Storybook документация
|
||||
- ✅ Виртуализация = 10,000+ документов без лагов
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Срочные TODO (Сегодня/Завтра)
|
||||
|
||||
1. **✅ DONE:** Исправить модалы (flexbox вместо inline-block)
|
||||
2. **TODO:** Установить react-hot-toast
|
||||
```tsx
|
||||
// app/layout.tsx
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
{children}
|
||||
<Toaster position="top-right" />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
3. **TODO:** Заменить console.log на toast в модалах
|
||||
```tsx
|
||||
// Было:
|
||||
console.log('[SUCCESS] Тег создан');
|
||||
|
||||
// Стало:
|
||||
import toast from 'react-hot-toast';
|
||||
toast.success('Тег успешно создан!');
|
||||
```
|
||||
|
||||
4. **TODO:** Добавить Escape key handler в модалы
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') onClose();
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
return () => document.removeEventListener('keydown', handleEscape);
|
||||
}, [isOpen, onClose]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Ключевые Инсайты
|
||||
|
||||
### Главное Правило
|
||||
> **"Компонент должен иметь одну ответственность"**
|
||||
> Если файл > 300 строк → разбивай на части
|
||||
|
||||
### Best Practices
|
||||
1. **Composition over Configuration**
|
||||
- Маленькие composable компоненты лучше больших с пропсами
|
||||
|
||||
2. **Co-location**
|
||||
- Держи styles, tests, types рядом с компонентом
|
||||
|
||||
3. **Server vs Client**
|
||||
- Используй Server Components где возможно (Next.js App Router)
|
||||
|
||||
4. **TypeScript строго**
|
||||
- `any` только в крайних случаях
|
||||
- Создавай типы для всех API responses
|
||||
|
||||
---
|
||||
|
||||
## 📞 Контакты и Ресурсы
|
||||
|
||||
### Для вопросов:
|
||||
- Архитектура: Лид Frontend-разработчик
|
||||
- React Query: [TanStack Query Docs](https://tanstack.com/query/latest)
|
||||
- Accessibility: [ARIA Practices](https://www.w3.org/WAI/ARIA/apg/)
|
||||
|
||||
### Полезные статьи:
|
||||
- [React Component Patterns](https://bit.ly/react-patterns)
|
||||
- [State Management Guide](https://bit.ly/state-mgmt)
|
||||
- [Next.js Best Practices](https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts)
|
||||
|
||||
---
|
||||
|
||||
**Заключение:** Модуль документов требует серьезного рефакторинга, но фундамент хороший. Главная задача - декомпозировать God Component на умные части. Приоритет - P0 таски (2 недели).
|
||||
|
||||
**Статус исправлений:**
|
||||
✅ Модалы работают (исправлено сегодня)
|
||||
⏳ Toast notifications (TODO)
|
||||
⏳ Рефакторинг page.tsx (TODO)
|
||||
⏳ React Query integration (TODO)
|
||||
376
docs/reports/TYPES_CABINETS_DASHBOARD_CLEANUP.md
Normal file
376
docs/reports/TYPES_CABINETS_DASHBOARD_CLEANUP.md
Normal file
@ -0,0 +1,376 @@
|
||||
# Отчет: Удаление типов документов, кабинетов и дашборда
|
||||
|
||||
**Дата:** 2024
|
||||
**Статус:** ✅ Завершено
|
||||
|
||||
## Цель
|
||||
|
||||
Упрощение системы управления документами путем удаления избыточной функциональности:
|
||||
- Типы документов (DocumentType)
|
||||
- Кабинеты (Cabinet) - виртуальные коллекции
|
||||
- Дашборд с аналитикой
|
||||
|
||||
Сохранить только основную функциональность: **документы, папки и теги**.
|
||||
|
||||
## Выполненные изменения
|
||||
|
||||
### 1. Backend - Django Models
|
||||
|
||||
**Удалено:**
|
||||
- Поле `document_type` из модели `Document` ([backend/documents/models.py](backend/documents/models.py))
|
||||
- ForeignKey связь с DocumentType
|
||||
- Все связанные метаданные
|
||||
|
||||
**Результат:** Модель Document теперь использует только folder (папки) и tags (теги) для организации.
|
||||
|
||||
### 2. Backend - Serializers
|
||||
|
||||
**Файл:** [backend/api/v1/documents/serializers.py](backend/api/v1/documents/serializers.py)
|
||||
|
||||
**Удалено:**
|
||||
- Класс `DocumentTypeSerializer` (~45 строк)
|
||||
- Класс `CabinetSerializer` (~55 строк)
|
||||
- Поле `document_type` из `DocumentReadSerializer`
|
||||
- Поле `cabinet_ids` из `DocumentWriteSerializer`
|
||||
- Метод `_set_cabinets()` (~40 строк)
|
||||
- Обработка `document_type` и `cabinet_ids` в методах `create()` и `update()`
|
||||
|
||||
**Результат:** Сериализаторы работают только с папками и тегами.
|
||||
|
||||
### 3. Backend - ViewSets
|
||||
|
||||
**Файл:** [backend/api/v1/documents/views.py](backend/api/v1/documents/views.py)
|
||||
|
||||
**Удалено:**
|
||||
- Импорты: `DocumentType`, `Cabinet`, `DocumentTypeSerializer`, `CabinetSerializer`
|
||||
- Класс `DocumentTypeViewSet` (~50 строк)
|
||||
- GET list, retrieve
|
||||
- POST create
|
||||
- PATCH/PUT update
|
||||
- DELETE delete
|
||||
- Метод `documents` (список документов по типу)
|
||||
- Класс `CabinetViewSet` (~120 строк)
|
||||
- CRUD операции
|
||||
- Иерархическая структура
|
||||
- Методы: `children`, `documents`, `add_document`, `remove_document`
|
||||
|
||||
**Результат:** API упрощено до работы только с документами, папками и тегами.
|
||||
|
||||
### 4. Backend - URL Routes
|
||||
|
||||
**Файл:** [backend/api/v1/urls.py](backend/api/v1/urls.py)
|
||||
|
||||
**Удалено:**
|
||||
- Регистрация маршрута `r"document-types"` → `DocumentTypeViewSet`
|
||||
- Регистрация маршрута `r"cabinets"` → `CabinetViewSet`
|
||||
|
||||
**Результат:** Больше нет API эндпоинтов:
|
||||
- `/api/v1/document-types/`
|
||||
- `/api/v1/cabinets/`
|
||||
|
||||
### 5. Frontend - TypeScript Interfaces
|
||||
|
||||
**Файл:** [frontend/src/types/api.ts](frontend/src/types/api.ts)
|
||||
|
||||
**Удалено:**
|
||||
- `interface DocumentType` (13 полей)
|
||||
- `interface CreateDocumentTypeData` (7 полей)
|
||||
- `interface Cabinet` (9 полей)
|
||||
- `interface CreateCabinetData` (3 поля)
|
||||
- `interface CabinetHierarchy` (4 поля, рекурсивная)
|
||||
- Поля `document_type` и `cabinets` из `interface Document`
|
||||
|
||||
**Результат:** Document interface содержит только: id, title, description, file, folder, tags, user, dates, acknowledgement.
|
||||
|
||||
### 6. Frontend - API Client
|
||||
|
||||
**Файл:** [frontend/src/lib/api.ts](frontend/src/lib/api.ts)
|
||||
|
||||
**Удалено методы:**
|
||||
- `getDocumentTypes()`, `getDocumentType(id)`
|
||||
- `createDocumentType(data)`, `updateDocumentType(id, data)`, `deleteDocumentType(id)`
|
||||
- `getDocumentsByType(typeId)`
|
||||
- `getCabinets()`, `getCabinet(id)`
|
||||
- `createCabinet(data)`, `updateCabinet(id, data)`, `deleteCabinet(id)`
|
||||
- `getCabinetDocuments(id)`, `getCabinetChildren(id)`, `getCabinetHierarchy(id)`
|
||||
- `addDocumentToCabinet(cabinetId, documentId)`, `removeDocumentFromCabinet(cabinetId, documentId)`
|
||||
|
||||
**Удалено из методов:**
|
||||
- `createDocument()`: параметры `document_type`, `cabinet_ids`
|
||||
- `updateDocument()`: параметры `document_type`, `cabinet_ids`
|
||||
|
||||
**Результат:** ~150 строк кода удалено. API клиент работает только с документами, папками, тегами.
|
||||
|
||||
### 7. Frontend - Documents Page
|
||||
|
||||
**Файл:** [frontend/src/app/documents/page.tsx](frontend/src/app/documents/page.tsx)
|
||||
|
||||
**Удалена функциональность дашборда:**
|
||||
- Импорт `LayoutDashboard` иконки
|
||||
- Импорт компонента `DocumentsDashboard`
|
||||
- Тип `ViewMode` ("documents" | "dashboard")
|
||||
- Состояние `viewMode`
|
||||
- Расчет `dashboardStats` (использовал useMemo)
|
||||
- UI: кнопки переключения "Документы" / "Дашборд"
|
||||
- UI: рендеринг компонента `<DocumentsDashboard />`
|
||||
|
||||
**Удалена функциональность типов и кабинетов:**
|
||||
- Все импорты связанные с DocumentType и Cabinet
|
||||
- Состояния: `selectedTypes`, `availableTypes`, `selectedCabinetId`, `availableCabinets`
|
||||
- Функции загрузки типов и кабинетов
|
||||
- Фильтры по типам и кабинетам в UI
|
||||
- Модальные окна управления типами и кабинетами
|
||||
|
||||
**Результат:** Простой интерфейс с единственным представлением - списком документов. Фильтрация только по тегам и поиску.
|
||||
|
||||
### 8. Frontend - DocumentMetadataEditor
|
||||
|
||||
**Файл:** [frontend/src/components/documents/DocumentMetadataEditor.tsx](frontend/src/components/documents/DocumentMetadataEditor.tsx)
|
||||
|
||||
**Удалено:**
|
||||
- Импорты: `DocumentType`, `Cabinet`, иконки `FileType`, `HardDrive`
|
||||
- Состояния: `selectedDocumentType`, `selectedCabinets`, `documentTypes`, `cabinets`, `loadingTypes`, `loadingCabinets`
|
||||
- Загрузка: вызовы `apiClient.getDocumentTypes()`, `apiClient.getCabinets()`
|
||||
- useEffect для загрузки `document.cabinets`
|
||||
- Функция `toggleCabinet()`
|
||||
- UI секция "Тип документа" с dropdown селектором
|
||||
- UI секция "Кабинеты" с multiselect чекбоксами
|
||||
- Обработка `document_type` и `cabinet_ids` в `handleSave()`
|
||||
|
||||
**Результат:** Компонент редактирует только теги и папку. Размер файла: 385 → 241 строка (-144 строки, -37%).
|
||||
|
||||
### 9. Frontend - DocumentUploadForm
|
||||
|
||||
**Файл:** [frontend/src/components/documents/DocumentUploadForm.tsx](frontend/src/components/documents/DocumentUploadForm.tsx)
|
||||
|
||||
**Удалено:**
|
||||
- Состояния: `selectedDocumentType`, `selectedCabinets`, `documentTypes`, `cabinets`, `loadingDocumentTypes`, `loadingCabinets`
|
||||
- Загрузка типов и кабинетов в useEffect
|
||||
- UI секция "Тип документа" с dropdown
|
||||
- UI секция "Кабинеты" с multiselect
|
||||
- Передача `document_type` и `cabinet_ids` в `createDocument()`
|
||||
- Сброс этих полей при успешной загрузке
|
||||
|
||||
**Результат:** Форма загрузки работает только с тегами, папками и базовыми полями (название, описание, файл, получатели).
|
||||
|
||||
### 10. Frontend - Search Component
|
||||
|
||||
**Файл:** [frontend/src/components/documents/search/AdvancedSearch.tsx](frontend/src/components/documents/search/AdvancedSearch.tsx)
|
||||
|
||||
**Удалено:**
|
||||
- Поле `documentTypes` из `interface SearchFilters`
|
||||
- Значение по умолчанию `documentTypes: []` из `DEFAULT_FILTERS`
|
||||
- Проверка `documentTypes` в подсчете активных фильтров
|
||||
- UI секция "Тип документа" с multiselect
|
||||
|
||||
**Результат:** Расширенный поиск работает без фильтрации по типам. Остались: даты, статусы, теги, авторы, сортировка.
|
||||
|
||||
### 11. Frontend - Удаленные компоненты
|
||||
|
||||
**Удалены полностью (с директориями):**
|
||||
|
||||
**Папка `frontend/src/components/documents/cabinets/`:**
|
||||
- `CabinetManagementModal.tsx` - модальное окно управления кабинетами
|
||||
- `CabinetForm.tsx` - форма создания/редактирования кабинета
|
||||
- `CabinetTree.tsx` - дерево иерархии кабинетов
|
||||
- `CabinetIconPicker.tsx` - выбор иконки для кабинета
|
||||
- `DocumentSelector.tsx` - выбор документов для кабинета
|
||||
- `index.ts` - экспорты
|
||||
|
||||
**Папка `frontend/src/components/documents/types/`:**
|
||||
- `DocumentTypeManagementModal.tsx` - модальное окно управления типами
|
||||
- `DocumentTypeForm.tsx` - форма создания/редактирования типа
|
||||
- `DocumentTypeIconPicker.tsx` - выбор иконки для типа
|
||||
- `index.ts` - экспорты
|
||||
|
||||
**Результат:** ~10 файлов удалено, ~1500+ строк кода удалено.
|
||||
|
||||
## Статистика изменений
|
||||
|
||||
### Backend
|
||||
- **Моделей изменено:** 1 (Document)
|
||||
- **Сериализаторов удалено:** 2 (DocumentTypeSerializer, CabinetSerializer)
|
||||
- **ViewSets удалено:** 2 (DocumentTypeViewSet, CabinetViewSet)
|
||||
- **API эндпоинтов удалено:** 2 группы
|
||||
- **Удалено строк кода:** ~300+
|
||||
|
||||
### Frontend
|
||||
- **Интерфейсов удалено:** 5 (DocumentType, CreateDocumentTypeData, Cabinet, CreateCabinetData, CabinetHierarchy)
|
||||
- **Методов API удалено:** 16
|
||||
- **Компонентов удалено:** 10 (полностью)
|
||||
- **Файлов изменено:** 6
|
||||
- **Директорий удалено:** 2
|
||||
- **Удалено строк кода:** ~2000+
|
||||
|
||||
### Общая статистика
|
||||
- **Всего файлов изменено/удалено:** 20+
|
||||
- **Всего строк кода удалено:** ~2500+
|
||||
- **Сокращение сложности:** ~40% функциональности удалено
|
||||
|
||||
## Новая архитектура системы
|
||||
|
||||
### Организация документов
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ ДОКУМЕНТЫ │
|
||||
├─────────────────────────────────────┤
|
||||
│ Организация: │
|
||||
│ • Папки (Folders) - иерархия │
|
||||
│ • Теги (Tags) - категоризация │
|
||||
│ │
|
||||
│ Метаданные: │
|
||||
│ • Название, описание │
|
||||
│ • Файл (с версионированием) │
|
||||
│ • Получатели и отделы │
|
||||
│ • Статус ознакомления │
|
||||
│ • Автор и даты │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### API Endpoints (оставшиеся)
|
||||
|
||||
**Documents:**
|
||||
- `GET /api/v1/documents/` - список документов
|
||||
- `POST /api/v1/documents/` - создание документа
|
||||
- `GET /api/v1/documents/{id}/` - детали документа
|
||||
- `PATCH /api/v1/documents/{id}/` - обновление документа
|
||||
- `DELETE /api/v1/documents/{id}/` - удаление документа
|
||||
- `GET /api/v1/documents/{id}/versions/` - версии
|
||||
- `POST /api/v1/documents/{id}/acknowledge/` - ознакомление
|
||||
|
||||
**Folders:**
|
||||
- `GET /api/v1/folders/` - список папок
|
||||
- `POST /api/v1/folders/` - создание папки
|
||||
- `GET /api/v1/folders/{id}/` - детали папки
|
||||
- `PATCH /api/v1/folders/{id}/` - обновление
|
||||
- `DELETE /api/v1/folders/{id}/` - удаление
|
||||
|
||||
**Tags:**
|
||||
- `GET /api/v1/document-tags/` - список тегов
|
||||
- `POST /api/v1/document-tags/` - создание тега
|
||||
- `PATCH /api/v1/document-tags/{id}/` - обновление
|
||||
- `DELETE /api/v1/document-tags/{id}/` - удаление
|
||||
|
||||
## Преимущества изменений
|
||||
|
||||
### 1. Упрощение для пользователей
|
||||
- ✅ Меньше выборов при загрузке документа
|
||||
- ✅ Более простая навигация (нет переключения дашборд/список)
|
||||
- ✅ Интуитивная организация: папки (структура) + теги (категории)
|
||||
- ✅ Меньше отвлекающих элементов UI
|
||||
|
||||
### 2. Снижение технической сложности
|
||||
- ✅ Меньше моделей данных для поддержки
|
||||
- ✅ Проще тестирование (меньше комбинаций)
|
||||
- ✅ Меньше API эндпоинтов
|
||||
- ✅ Меньше кода для поддержки (~2500 строк удалено)
|
||||
|
||||
### 3. Производительность
|
||||
- ✅ Меньше SQL запросов (нет JOIN с типами/кабинетами)
|
||||
- ✅ Быстрее загрузка списка документов
|
||||
- ✅ Меньше данных в API ответах
|
||||
- ✅ Простые фильтры работают быстрее
|
||||
|
||||
### 4. Поддержка и развитие
|
||||
- ✅ Легче добавлять новые функции
|
||||
- ✅ Меньше мест для багов
|
||||
- ✅ Проще онбординг новых разработчиков
|
||||
- ✅ Меньше документации для поддержки
|
||||
|
||||
## Миграция данных
|
||||
|
||||
### ⚠️ Требуется Django миграция
|
||||
|
||||
После применения изменений в коде **необходимо** создать миграцию для удаления поля `document_type`:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python manage.py makemigrations documents
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
**Примечание:** Данные о типах документов будут потеряны. Если требуется сохранить историю, можно:
|
||||
1. Создать снимок данных перед удалением
|
||||
2. Добавить теги соответствующие старым типам
|
||||
3. Применить миграцию
|
||||
|
||||
### Опциональное удаление таблиц
|
||||
|
||||
Если таблицы `DocumentType` и `Cabinet` больше не используются нигде в системе, их можно удалить вручную:
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS documents_document_cabinets;
|
||||
DROP TABLE IF EXISTS documents_cabinet;
|
||||
DROP TABLE IF EXISTS documents_documenttype;
|
||||
```
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Проверено:
|
||||
- ✅ Компиляция TypeScript без ошибок
|
||||
- ✅ Синтаксис Python валиден
|
||||
- ✅ Нет импортов удаленных компонентов
|
||||
- ✅ API методы используют только существующие поля
|
||||
|
||||
### Требует ручного тестирования:
|
||||
- [ ] Загрузка документа с тегами и папкой
|
||||
- [ ] Редактирование метаданных документа (теги, папка)
|
||||
- [ ] Поиск документов по тегам
|
||||
- [ ] Навигация по папкам
|
||||
- [ ] Фильтрация документов
|
||||
- [ ] Ознакомление с документами
|
||||
|
||||
## Возможные проблемы
|
||||
|
||||
### 1. Кэш TypeScript
|
||||
**Проблема:** VSCode может показывать ошибки для удаленных файлов.
|
||||
**Решение:**
|
||||
```bash
|
||||
cd frontend
|
||||
rm -rf .next node_modules/.cache
|
||||
# Перезапуск VSCode
|
||||
```
|
||||
|
||||
### 2. Старые API запросы
|
||||
**Проблема:** Могут быть скрытые вызовы `getDocumentTypes()` или `getCabinets()`.
|
||||
**Решение:** Проверить логи Network в браузере на 404 ошибки.
|
||||
|
||||
### 3. Django миграции
|
||||
**Проблема:** Конфликты при удалении поля `document_type`.
|
||||
**Решение:** Создать промежуточную миграцию с `null=True`, потом удалить.
|
||||
|
||||
## Следующие шаги
|
||||
|
||||
### Немедленно
|
||||
1. ✅ Очистить кэш frontend (`rm -rf .next`)
|
||||
2. ⏳ Создать Django миграцию
|
||||
3. ⏳ Применить миграцию на dev окружении
|
||||
4. ⏳ Протестировать основные сценарии
|
||||
|
||||
### Краткосрочно
|
||||
1. Удалить неиспользуемые таблицы БД (опционально)
|
||||
2. Обновить документацию проекта
|
||||
3. Добавить теги для категоризации (вместо типов)
|
||||
4. Добавить unit-тесты для измененных компонентов
|
||||
|
||||
### Долгосрочно
|
||||
1. Рассмотреть добавление "избранное" вместо дашборда
|
||||
2. Улучшить поиск по тегам (фильтры в один клик)
|
||||
3. Добавить статистику использования тегов
|
||||
4. Визуализация структуры папок (дерево)
|
||||
|
||||
## Заключение
|
||||
|
||||
Система управления документами успешно упрощена:
|
||||
- **Удалено:** типы документов, кабинеты, дашборд
|
||||
- **Сохранено:** папки, теги, все основные функции (загрузка, версии, ознакомление)
|
||||
- **Результат:** Более простая, быстрая и понятная система
|
||||
|
||||
Кодовая база стала **легче на ~2500 строк**, что упрощает поддержку и развитие проекта.
|
||||
|
||||
---
|
||||
|
||||
**Автор изменений:** GitHub Copilot
|
||||
**Дата:** 2024
|
||||
**Версия документа:** 1.0
|
||||
Loading…
x
Reference in New Issue
Block a user