bp/TASKS_MODULE_ROADMAP.md

454 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# План доработки модуля Tasks
> На основе ТЗ п.3.6 и анализа текущего состояния кода
---
## Этап 1: Базовые улучшения (Critical)
### 1.1 RBAC — добавить проверку прав
**Файлы:** `TasksController.php`
```php
// Добавить в начало методов:
public function store()
{
if (!$this->access->canCreate('tasks')) {
return $this->forbiddenResponse('Нет прав для создания задач');
}
}
public function edit(int $id)
{
if (!$this->access->canEdit('tasks')) {
return $this->forbiddenResponse('Нет прав для редактирования');
}
}
```
**Аналогично:** `update()`, `destroy()`, `moveColumn()`, `complete()`, `reopen()`
### 1.2 Валидация входных данных
**Файлы:** `TasksController.php`
```php
public function store()
{
$validation = \Config\Services::validation();
$validation->setRules([
'title' => 'required|min_length[3]|max_length[255]',
'board_id' => 'required|integer',
'column_id' => 'required|integer',
'priority' => 'in_list[low,medium,high,urgent]',
'due_date' => 'valid_date',
]);
if (!$validation->withRequest($this->request)->run()) {
return redirect()->back()->withErrors($validation->getErrors())->withInput();
}
}
```
### 1.3 Исправить Events нейминг
**Файл:** `TaskService.php`
```php
// Было:
Events::trigger('tasks.created', $taskId, $data, $userId);
// Стало (согласно ТЗ и документации):
Events::trigger('task.created', $taskId, $data, $userId);
```
**Аналогично:** `task.updated`, `task.column_changed`, `task.completed`, `task.reopened`
---
## Этап 2: Подзадачи (Subtasks)
### 2.1 Миграция
```php
// database/migrations/2026-02-08-CreateTaskSubtasksTable.php
public function up()
{
$this->forge->addField([
'id' => ['type' => 'INT', 'auto_increment' => true],
'task_id' => ['type' => 'INT'],
'title' => ['type' => 'VARCHAR', 'constraint' => 255],
'is_completed' => ['type' => 'BOOLEAN', 'default' => false],
'order_index' => ['type' => 'INT', 'default' => 0],
'created_at' => ['type' => 'DATETIME'],
'updated_at' => ['type' => 'DATETIME'],
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('task_id', 'tasks', 'id', 'CASCADE', 'CASCADE');
$this->forge->createTable('task_subtasks');
}
```
### 2.2 Модель
```php
// app/Modules/Tasks/Models/TaskSubtaskModel.php
class TaskSubtaskModel extends Model
{
use TenantScopedModel;
protected $table = 'task_subtasks';
protected $tenantField = 'task.organization_id'; // через task relationship
protected $allowedFields = ['task_id', 'title', 'is_completed', 'order_index'];
}
```
### 2.3 API
```php
// TasksController.php
public function addSubtask(int $taskId)
{
$data = [
'task_id' => $taskId,
'title' => $this->request->getPost('title'),
'order_index' => $this->subtaskModel->getNextOrder($taskId),
];
$this->subtaskModel->insert($data);
return redirect()->to("/tasks/{$taskId}");
}
public function toggleSubtask(int $taskId, int $subtaskId)
{
$subtask = $this->subtaskModel->find($subtaskId);
$this->subtaskModel->update($subtaskId, [
'is_completed' => !$subtask['is_completed']
]);
}
```
### 2.4 View
```twig
{# tasks/show.twig #}
<div class="subtasks">
{% for subtask in task.subtasks %}
<div class="subtask {{ subtask.is_completed ? 'completed' }}">
<input type="checkbox" {{ subtask.is_completed ? 'checked' }}
onchange="toggleSubtask({{ subtask.id }})">
{{ subtask.title }}
</div>
{% endfor %}
<input type="text" placeholder="Добавить подзадачу...">
</div>
```
---
## Этап 3: Чек-листы (Checklists)
> Аналогично подзадачам, но чек-листы — это произвольные списки внутри задачи
### 3.1 Структура
```
task_checklists
├── id
├── task_id
├── title (например "Подготовка к встрече")
└── items (JSON array или связанная таблица)
task_checklist_items
├── id
├── checklist_id
├── text
├── is_completed
└── order_index
```
---
## Этап 4: Вложения (Attachments)
### 4.1 Миграция
```php
// database/migrations/2026-02-08-CreateTaskAttachmentsTable.php
public function up()
{
$this->forge->addField([
'id' => ['type' => 'INT', 'auto_increment' => true],
'task_id' => ['type' => 'INT'],
'file_name' => ['type' => 'VARCHAR', 'constraint' => 255],
'file_path' => ['type' => 'VARCHAR', 'constraint' => 500],
'file_size' => ['type' => 'INT'],
'file_type' => ['type' => 'VARCHAR', 'constraint' => 100],
'uploaded_by' => ['type' => 'INT'],
'created_at' => ['type' => 'DATETIME'],
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('task_id', 'tasks', 'id', 'CASCADE', 'CASCADE');
$this->forge->createTable('task_attachments');
}
```
### 4.2 Загрузка файлов
```php
// TasksController.php
public function uploadAttachment(int $taskId)
{
$file = $this->request->getFile('file');
if ($file->isValid()) {
$uploadedPath = $this->uploadService->upload($file, "tasks/{$taskId}");
$this->attachmentModel->insert([
'task_id' => $taskId,
'file_name' => $file->getName(),
'file_path' => $uploadedPath,
'file_size' => $file->getSize(),
'file_type' => $file->getMimeType(),
'uploaded_by' => $this->getCurrentUserId(),
]);
}
}
```
---
## Этап 5: Комментарии
### 5.1 Миграция
```php
// database/migrations/2026-02-08-CreateTaskCommentsTable.php
public function up()
{
$this->forge->addField([
'id' => ['type' => 'INT', 'auto_increment' => true],
'task_id' => ['type' => 'INT'],
'user_id' => ['type' => 'INT'],
'content' => ['type' => 'TEXT'],
'mentioned_users' => ['type' => 'JSON'], // массив user_id для @mentions
'created_at' => ['type' => 'DATETIME'],
'updated_at' => ['type' => 'DATETIME'],
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('task_id', 'tasks', 'id', 'CASCADE', 'CASCADE');
$this->forge->createTable('task_comments');
}
```
### 5.2 Обработка @mentions
```php
// TaskCommentService.php
public function parseMentions(string $content): array
{
preg_match_all('/@(\d+)/', $content, $matches);
return $matches[1] ?? [];
}
public function createComment(int $taskId, int $userId, string $content): int
{
$mentionedUsers = $this->parseMentions($content);
$commentId = $this->commentModel->insert([
'task_id' => $taskId,
'user_id' => $userId,
'content' => $content,
'mentioned_users' => json_encode($mentionedUsers),
]);
// Уведомить упомянутых пользователей
foreach ($mentionedUsers as $mentionedUserId) {
$this->notificationService->create([
'user_id' => $mentionedUserId,
'type' => 'mention',
'message' => "Вас упомянули в задаче",
'link' => "/tasks/{$taskId}",
]);
}
return $commentId;
}
```
---
## Этап 6: Зависимости задач
### 6.1 Миграция
```php
// database/migrations/2026-02-08-CreateTaskDependenciesTable.php
public function up()
{
$this->forge->addField([
'id' => ['type' => 'INT', 'auto_increment' => true],
'blocking_task_id' => ['type' => 'INT'], // задача, которая блокирует
'blocked_task_id' => ['type' => 'INT'], // задача, которая заблокирована
'dependency_type' => ['type' => 'ENUM', 'options' => ['blocks', 'depends_on']],
'created_at' => ['type' => 'DATETIME'],
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('blocking_task_id', 'tasks', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('blocked_task_id', 'tasks', 'id', 'CASCADE', 'CASCADE');
$this->forge->createTable('task_dependencies');
}
```
### 6.2 Проверка при изменении статуса
```php
// TaskService.php
public function completeTask(int $taskId, int $userId): bool
{
// Проверить зависимости
$blockingTasks = $this->getBlockingTasks($taskId);
if (!empty($blockingTasks)) {
foreach ($blockingTasks as $blocking) {
if (!$blocking['is_completed']) {
throw new \Exception("Задача заблокирована: {$blocking['title']}");
}
}
}
return parent::completeTask($taskId, $userId);
}
```
---
## Этап 7: Интеграция с CRM
### 7.1 События CRM → Tasks
```php
// app/Config/Events.php
// При создании сделки
Events::on('deal.created', function ($deal, $userId) {
service('eventManager')
->forModule('crm')
->moduleOn('deal.created', function ($deal, $userId) {
if (service('moduleSubscription')->isModuleActive('tasks')) {
$taskService = service('taskService');
$taskService->createTask([
'title' => "Первичный контакт: {$deal['title']}",
'description' => 'Сделка создана, необходимо связаться с клиентом',
'priority' => 'high',
'due_date' => date('Y-m-d', strtotime('+1 day')),
], $userId);
}
});
});
// При переводе на этап "Коммерческое предложение"
Events::on('deal.stage_changed', function ($deal, $oldStage, $newStage, $userId) {
if ($newStage === 'proposal') {
// Создать задачу на подготовку КП
}
});
// При завершении сделки
Events::on('deal.won', function ($deal, $userId) {
// Создать задачу "Благодарность клиенту"
});
```
### 7.2 Задачи в карточке клиента
```php
// CRM/Controllers/ClientsController.php
public function view(int $id)
{
$client = $this->clientModel->find($id);
$tasks = $this->taskService->getByClientId($id); // метод для получения задач клиента
return $this->renderTwig('@CRM/Clients/view', [
'client' => $client,
'tasks' => $tasks,
]);
}
```
---
## Этап 8: Интеграция с Booking
### 8.1 События Booking → Tasks
```php
// При создании записи
Events::on('booking.created', function ($booking, $userId) {
// Создать задачу на подготовку к встрече
});
```
---
## Приоритеты реализации
| Приоритет | Задача | Оценка |
|-----------|--------|--------|
| P0 | RBAC (права доступа) | 2ч |
| P0 | Валидация | 2ч |
| P1 | Подзадачи (subtasks) | 8ч |
| P1 | Чек-листы | 6ч |
| P1 | Вложения | 8ч |
| P2 | Комментарии + @mentions | 12ч |
| P2 | Зависимости задач | 6ч |
| P2 | Интеграция CRM → Tasks | 8ч |
| P3 | Интеграция Booking → Tasks | 4ч |
**Общая оценка:** ~56 часов
---
## Зависимости между этапами
```
Этап 1 (RBAC + валидация) → нужен перед всеми
Этап 2 (subtasks) → зависит от 1
Этап 3 (checklists) → зависит от 1
Этап 4 (attachments) → зависит от 1
Этап 5 (comments) → зависит от 2
Этап 6 (dependencies) → зависит от 1
Этап 7 (CRM integration) → зависит от 5
Этап 8 (Booking integration) → зависит от 1
```
---
## Модели для создания/обновления
```
app/Modules/Tasks/Models/
├── TaskModel.php (существует)
├── TaskColumnModel.php (существует)
├── TaskBoardModel.php (существует)
├── TaskAssigneeModel.php (существует)
├── TaskSubtaskModel.php ✨ НОВАЯ
├── TaskChecklistModel.php ✨ НОВАЯ
├── TaskChecklistItemModel.php ✨ НОВАЯ
├── TaskAttachmentModel.php ✨ НОВАЯ
└── TaskCommentModel.php ✨ НОВАЯ
```
---
## Контроллеры
```
app/Modules/Tasks/Controllers/
├── TasksController.php (существует)
├── TaskApiController.php (существует)
├── TaskSubtasksController.php ✨ НОВЫЙ
├── TaskAttachmentsController.php ✨ НОВЫЙ
└── TaskCommentsController.php ✨ НОВЫЙ
```