Розробка системи управління проектами (Project Management)
PM системи діляться на два полюси: універсальні SaaS-продукти вроде Jira або Asana та самописні рішення під конкретний процес. Перші підходять більшості, але коли процес нестандартний — розробка власної системи стає економічно обґрунтованою. Ключові сущності: Project → Milestone → Task → Subtask, плюс поперечні: User, Team, Comment, Attachment, TimeLog, Activity.
Архітектурні рішення
Монолит vs мікросервіси. Для проектів до 50 000 активних користувачів монолит з чіткими доменними границями — правильний вибір. Мікросервіси оправдані, коли окремі компоненти масштабуються незалежно. На практиці 90% PM систем будуються як модульний монолит.
Event-driven всередину моноліту. Переходи стану задач, назначення виконавців, зміни дедлайнів — це eventi, які повинні тригерити побічні ефекти (сповіщення, оновлення дашбордів, запис в лог активності) без прямого зв'язку. Використовуємо внутрішній event bus.
Модель даних
Схема повинна підтримувати ієрархію задач довільної глибини. Adjacency list з матеріалізованим шляхом замість чистого Nested Sets:
CREATE TABLE tasks (
id BIGSERIAL PRIMARY KEY,
project_id BIGINT NOT NULL REFERENCES projects(id),
parent_id BIGINT REFERENCES tasks(id),
path LTREE NOT NULL, -- PostgreSQL ltree: '1.5.23'
title VARCHAR(500) NOT NULL,
status task_status NOT NULL DEFAULT 'todo',
priority SMALLINT NOT NULL DEFAULT 2,
assignee_id BIGINT REFERENCES users(id),
due_date DATE,
estimate INTEGER, -- в хвилинах
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX tasks_path_gist ON tasks USING GIST (path);
Розширення ltree дозволяє запити вроде «всі підзадачи задачі 5» одним запитом: WHERE path <@ '1.5'.
Залежності між задачами — окремотна таблиця:
CREATE TABLE task_dependencies (
task_id BIGINT REFERENCES tasks(id),
depends_on_id BIGINT REFERENCES tasks(id),
type dep_type NOT NULL, -- 'blocks', 'required_by', 'related'
PRIMARY KEY (task_id, depends_on_id)
);
Перед збереженням залежності перевіряємо циклічність — обхід графу або рекурсивний запит.
Статусні машини та воркфлоу
Воркфлоу — головна причина, по якій компанії розробляють власну PM-систему. Підхід: настраиваємі воркфлоу на рівні типу задачі. Кожен тип (Task, Bug, Epic) має власну state machine:
'bug' => [
'states' => ['new', 'triaged', 'in_progress', 'in_review', 'resolved', 'closed', 'reopened'],
'transitions' => [
'triage' => ['from' => ['new'], 'to' => 'triaged'],
'start' => ['from' => ['triaged'], 'to' => 'in_progress'],
'review' => ['from' => ['in_progress'], 'to' => 'in_review'],
'resolve' => ['from' => ['in_review'], 'to' => 'resolved'],
'close' => ['from' => ['resolved'], 'to' => 'closed'],
'reopen' => ['from' => ['resolved', 'closed'], 'to' => 'reopened'],
],
],
Конфіг воркфлоу зберігається в базі (JSON-поле), редагується через візуальний конструктор.
Реалтайм-оновлення
PM система без реалтайму — це система для роботи в самотності. WebSocket стек:
- Сервер: Laravel Reverb, Soketi (сумісний з Pusher), або Ably
-
Канали: приватні канали
private-project.{id}, канали задачprivate-task.{id} - Presence: показує, хто зараз переглядає задачу
// Frontend: Laravel Echo + React
const channel = window.Echo.private(`project.${projectId}`);
channel
.listen('.task.updated', (e) => {
queryClient.invalidateQueries(['tasks', e.task.id]);
})
.listen('.comment.created', (e) => {
setComments(prev => [...prev, e.comment]);
});
Представлення: Kanban, список, Gantt, календар
Kanban: колонки = статуси воркфлоу. Drag-and-drop через @dnd-kit/sortable. Віртуалізація списків > 50 карток (TanStack Virtual).
Список з вложеністю: tree-table з розгортанням підзадач. Сортування та фільтрування на сервері.
Діаграма Ганта: складний компонент. Будуємо на frappe-gantt або @dhtmlx/gantt. Складність — відображення залежностей стрілками та автоматичний пересчет дат.
Календарний вид: FullCalendar з кастомним рендером.
Тайм-трекінг
Опціональний модуль: вбудований таймер тайм-трекінгу.
CREATE TABLE time_logs (
id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES tasks(id),
user_id BIGINT NOT NULL REFERENCES users(id),
started_at TIMESTAMPTZ NOT NULL,
stopped_at TIMESTAMPTZ,
duration INTEGER GENERATED ALWAYS AS (
EXTRACT(EPOCH FROM (stopped_at - started_at))::INTEGER
) STORED,
description TEXT
);
Глобальний елемент UI, зберігає стан {taskId, startedAt} в localStorage + синхронізація з сервером.
Права доступу та ролі
RBAC з project-scope ролями:
System roles: admin, member
Project roles: owner, manager, developer, viewer, external
Кожна роль має набір permissions. Використовуємо spatie/laravel-permission з кастомними scoped ролями.
Гостьовий доступ: зовнішні користувачі (клієнти, підрядники) бачать тільки вибрані задачи або тільки читають.
Сповіщення
Багатоканальні зі налаштуванням підписок:
| Подія | Push | In-app | Slack | |
|---|---|---|---|---|
| Задача назначена мне | ✓ | ✓ | ✓ | опційно |
| Дедлайн через 24ч | ✓ | ✓ | ✓ | — |
| Коментар з упоміманням | ✓ | ✓ | ✓ | опційно |
Користувач керує в налаштуваннях профілю. Очередь сповіщень через Laravel Queues, батчинг email.
Пошук
Глобальний пошук по задачах, проектах, коментарях, файлах. PostgreSQL FTS через tsvector достатньо до ~200K задач. Далі: OpenSearch з Tika.
Аналітика та звіти
- Velocity: середня швидкість закриття задач по спринтам
- Bottleneck: задачи, які дольш за все висять в конкретному статусі
- Звіт по часу: кто скільки залогував
- Кастомні дашборди: drag-and-drop віджети, кешування (Redis, TTL 5хв)
Інтеграції
-
Git-репозиторії: автосмена статусу при упоміманні в commit (
closes #123) - CI/CD: статус деплоя в карточці задачі
- Slack / Teams: сповіщення, створення задач зі сообщений
- Confluence / Notion: двусторонній link
- Calendars: синхронізація дедлайнів з Google Calendar через CalDAV
Будуємо через OAuth 2.0 + webhooks + REST/GraphQL API.
Сроки
| Етап | Вміст | Тривалість |
|---|---|---|
| Проектування | Воркфлоу, ролі, інтеграції | 3–4 нед. |
| Ядро системи | Проекти, задачи, воркфлоу, права | 6–8 нед. |
| UI: список + Kanban | Базові представлення | 4–5 нед. |
| Реалтайм + сповіщення | WebSocket, email, push | 2–3 нед. |
| Gantt + календар | Складні представлення | 3–4 нед. |
| Тайм-трекінг | Таймер, логи, звіти | 2 нед. |
| Інтеграції (2–3) | Git + Slack + Calendar | 3–4 нед. |
| Тестування, запуск | E2E, нагрузкове | 2–3 нед. |
Повний проект: 22–32 тижні. Ітеративний запуск реальний через 10–12 тижнів — core без Gantt та інтеграцій.







