Весь контент перенесён в content/yasha/ (7 подразделов). Создан content/semen/ с подразделами: changelog, kb, diary. Навигация — dropdown в навбаре, двухуровневое меню в hugo.toml. Шаблоны, сайдбар, поиск обновлены под вложенные секции.
24 KiB
title, date, tags
| title | date | tags | ||||||
|---|---|---|---|---|---|---|---|---|
| Leadera: Ограничения секций в диаграмме Ганта | 2026-04-06 |
|
Бизнес-требования
BR-1: Лимит одновременных задач (Concurrency Limit)
У каждой секции появляется параметр max_concurrent_tasks (целое число ≥ 0, по умолчанию 0).
Поведение:
- Если
max_concurrent_tasks = 3— в любой данный день в этой секции не может быть больше 3 активных задач одновременно - Если
max_concurrent_tasks = 0— ограничение отключено, лимита нет - Пересечение считается по дате: задача занимает дни с
start_dateпоend_dateвключительно. Две задачи «пересекаются», если у них есть хотя бы один общий день - Ограничение работает только в рамках одной секции. Задачи из разных секций не влияют друг на друга
Срабатывание при:
- Создании задачи — если добавление нарушает лимит → Alert
- Перетаскивании задачи (drag) — если новая позиция нарушает лимит → Alert
- Растягивании задачи (resize) — если новый размер нарушает лимит → Alert
- Редактировании задачи (форма) — если изменённые даты нарушают лимит → Alert
Важно: Действие НЕ отменяется. Задача создаётся/перемещается в любом случае, но пользователь видит предупреждение.
Текст Alert: «В секции "[название секции]" на [дата/диапазон] превышен лимит одновременных задач (максимум: N)»
BR-2: Блоки секций (Section Blocks)
У каждой секции появляется набор блоков — особый вид записей, отображаемых на таймлайне как заштрихованные области.
Сущность GanttSectionBlock:
| Поле | Тип | Описание |
|---|---|---|
| id | UUID | Идентификатор |
| section_id | UUID | Принадлежность к секции |
| title | string | Название (например: «Отпуск», «Командировка») |
| start_date | date | Начало интервала (минимум 1 день) |
| end_date | date | Конец интервала |
| block_concurrency | boolean | Запрещать ли пересечение обычных задач с данным блоком |
Валидация:
end_dateне может быть раньшеstart_date- Все поля обязательны
- Блок может быть в прошлом
Визуальное отображение на Ганте:
- Заштрихованная область на всю высоту секции (включая все строки задач в секции)
- Название блока отображается поверх штриховки с подложкой (для читаемости)
Поведение block_concurrency:
- Если
block_concurrency = true— при пересечении задачи с данным блоком показывается Alert (аналогично BR-1), но задача всё равно создаётся/перемещается - Если
block_concurrency = false— блок отображается визуально, но не влияет на задачи
Срабатывание при:
- Создании задачи — пересекает блок с
block_concurrency = true→ Alert - Перетаскивании задачи (drag) → Alert
- Растягивании задачи (resize) → Alert
- Редактировании задачи (форма) → Alert
Текст Alert: «В секции "[название секции]" задача пересекает блок "[название блока]" ([дата] — [дата])»
BR-3: Система Alert-ов
Все предупреждения (от BR-1 и BR-2) выводятся в единую область над диаграммой Ганта.
Поведение:
- Alert-ы — Bootstrap Alert (warning), постоянные
- Пользователь НЕ может закрыть Alert вручную
- Alert исчезает автоматически, когда текущая дата прошла дату конфликта (конфликт больше не актуален)
- Проверка конфликтов — только на фронтенде (на основе загруженных данных: задачи секции, блоки, max_concurrent_tasks)
- Если Alert-ов много — область сворачиваемая с счётчиком в бейдже (например: «⚠️ 3 предупреждения»)
- По умолчанию — развёрнуто
- Сворачивание/разворачивание — клик по заголовку области
BR-4: Настройки секции
- Настройки совмещены с существующим модальным окном редактирования секции (не отдельное окно)
Окно создания секции — добавляется:
- Поле «Макс. одновременных задач» (числовой ввод, 0 = без лимита, по умолчанию 0)
Окно редактирования секции — добавляется:
- Поле «Макс. одновременных задач» (числовой ввод, 0 = без лимита)
- Список блоков (таблица/список с CRUD: добавить, редактировать, удалить)
- Сортировка: по
start_dateпо убыванию (будущее сверху) - Каждый блок — форма: title, start_date, end_date, block_concurrency (чекбокс: «Запретить пересечение задач»)
- Валидация: end_date >= start_date, все поля заполнены
- Сортировка: по
BR-5: Наследование
- При появлении функционала копирования/дублирования диаграммы — настройки секций (лимит + блоки) наследуются
Технические требования
База данных
Новая таблица: gantt_section_blocks
CREATE TABLE gantt_section_blocks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
section_id UUID NOT NULL REFERENCES gantt_sections(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
block_concurrency BOOLEAN NOT NULL DEFAULT false,
created_by UUID NOT NULL REFERENCES users(id),
updated_by UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT chk_block_dates CHECK (end_date >= start_date)
);
CREATE INDEX idx_gantt_section_blocks_section ON gantt_section_blocks(section_id);
CREATE INDEX idx_gantt_section_blocks_dates ON gantt_section_blocks(start_date, end_date);
Изменение таблицы: gantt_sections
ALTER TABLE gantt_sections ADD COLUMN max_concurrent_tasks INTEGER NOT NULL DEFAULT 0;
Backend (Go API)
Этап 1: Миграция + Домен
Промт для агента:
Добавь в проект новую таблицу
gantt_section_blocksи колонкуmax_concurrent_tasksвgantt_sections.
- Создай миграцию в
cmd/migrate/:
- Новая таблица
gantt_section_blocksс полями: id (UUID PK), section_id (UUID FK → gantt_sections, ON DELETE CASCADE), title (VARCHAR 255 NOT NULL), start_date (DATE NOT NULL), end_date (DATE NOT NULL), block_concurrency (BOOLEAN DEFAULT false), created_by (UUID FK → users), updated_by (UUID FK → users), created_at, updated_at, deleted_at. CHECK constraint: end_date >= start_date. Индексы по section_id и (start_date, end_date).- ALTER TABLE gantt_sections ADD COLUMN max_concurrent_tasks INTEGER NOT NULL DEFAULT 0.
- Создай доменную модель
GanttSectionBlockвinternal/domain/gantt.goсо всеми полями таблицы.- Добавь поле
MaxConcurrentTasks intв структуруGanttSection.- Обнови маппинг в relevant repository файлах, чтобы новое поле
max_concurrent_tasksчиталось/писалось.
Этап 2: CRUD для GanttSectionBlocks
Промт для агента:
Реализуй полный CRUD для сущности
GanttSectionBlockпо аналогии с существующими handlers/services/repositories в проекте.Роуты (внутри группы секции):
GET /api/gantt-charts/:chartId/sections/:sectionId/blocks— список блоков секцииPOST /api/gantt-charts/:chartId/sections/:sectionId/blocks— создать блокPUT /api/gantt-charts/:chartId/sections/:sectionId/blocks/:blockId— обновить блокDELETE /api/gantt-charts/:chartId/sections/:sectionId/blocks/:blockId— удалить блок (soft delete)Repository:
internal/repository/gantt_section_block_repository.go
GetBySectionID(ctx, sectionID) ([]domain.GanttSectionBlock, error)GetByID(ctx, id) (*domain.GanttSectionBlock, error)Create(ctx, block) errorUpdate(ctx, block) errorDelete(ctx, id) error— soft delete (set deleted_at)- Все запросы — raw SQL через pgx, как в остальных репозиториях проекта
Service:
internal/service/gantt_section_block_service.go
- Бизнес-логика: валидация dates (end >= start), проверка прав доступа к секции
- Inject repository
Handler:
internal/handler/gantt_section_block_handler.go
- Swagger-аннотации для всех эндпоинтов
- Валидация входных данных
- Ошибки в формате проекта (GanttErrorResponse)
Модели запросов:
- Create: title (required), start_date (required), end_date (required), block_concurrency (bool, default false)
- Update: title, start_date, end_date, block_concurrency — все опциональны
Этап 3: Обновление настроек секции
Промт для агента:
Обнови CRUD секций (gantt_sections) для поддержки нового поля
max_concurrent_tasks.
- Добавь
max_concurrent_tasksвGanttSectionCreateRequest(опциональное, int ≥ 0, default 0)- Добавь
max_concurrent_tasksвGanttSectionUpdateRequest(опциональное, int ≥ 0)- Обнови SQL-запросы в repository: INSERT и UPDATE должны включать
max_concurrent_tasks- При GET секции (или списке секций) — возвращать
max_concurrent_tasksв ответе- Валидация: значение должно быть ≥ 0
Этап 4: Включение блоков в ответ секций
Промт для агента:
При запросе списка секций или одной секции включай связанные блоки в ответ.
- Обнови
GET /api/gantt-charts/:chartId/sections/:sectionId— добавить полеblocksс массивом GanttSectionBlock- Обнови
GET /api/gantt-charts/:chartId/sections— добавить полеblocksдля каждой секции- Блоки с
deleted_at IS NOT NULLне возвращаются- Блоки сортируются по start_date по убыванию (будущее сверху)
Frontend (Angular)
Этап FE-1: Модели + Сервис
Промт для агента:
Добавь в проект поддержку ограничений секций Ганта.
- В
pages/gantt/models/gantt-chart.model.tsдобавь:export interface GanttSectionBlock { id: string; section_id: string; title: string; start_date: string; end_date: string; block_concurrency: boolean; created_by_user: UserInfo; updated_by_user: UserInfo; created_at: string; updated_at: string; } export interface GanttSectionBlockCreateRequest { title: string; start_date: string; end_date: string; block_concurrency?: boolean; } export interface GanttSectionBlockUpdateRequest { title?: string; start_date?: string; end_date?: string; block_concurrency?: boolean; } export interface ConflictCheck { has_conflicts: boolean; conflicts: ConflictItem[]; } export interface ConflictItem { type: 'concurrency' | 'block'; section_id: string; section_title: string; block_title?: string; start_date?: string; end_date?: string; message: string; dates?: string[]; }
Добавь
max_concurrent_tasks: numberв интерфейсGanttSectionДобавь
blocks: GanttSectionBlock[]в интерфейсGanttSectionДобавь
max_concurrent_tasks?: numberвGanttSectionCreateRequestиGanttSectionUpdateRequestСоздай сервис
pages/gantt/services/gantt-section-block.service.ts:
- Inject HttpClient
- Методы: getBlocks(chartId, sectionId), createBlock(chartId, sectionId, data), updateBlock(chartId, sectionId, blockId, data), deleteBlock(chartId, sectionId, blockId)
- Все методы возвращают Observable с типизированными ответами (по аналогии с GanttChartService)
Этап FE-2: Интеграция в модальные окна секции
Промт для агента:
Интегрируй настройки ограничений в существующие модальные окна создания и редактирования секции Ганта.
- Найди существующие компоненты модальных окон секции (создание/редактирование).
Окно создания секции — добавить:
- Числовой ввод: «Максимум одновременных задач»
- Подсказка: «0 = без ограничений»
- Валидация: число ≥ 0
- Значение по умолчанию: 0
- Отправляется в
GanttSectionCreateRequestкакmax_concurrent_tasksОкно редактирования секции — добавить:
- Числовой ввод: «Максимум одновременных задач» (аналогично созданию)
- Секция «Блоки (запретные интервалы)»:
- Таблица/список существующих блоков с колонками: Название, Начало, Конец, Блокировка (чекбокс/иконка), Действия (редактировать/удалить)
- Сортировка: по
start_dateпо убыванию (будущее сверху)- Кнопка «Добавить блок» — inline-форма:
- title (текстовый ввод, обязательный)
- start_date (datepicker, обязательный)
- end_date (datepicker, обязательный)
- block_concurrency (чекбокс: «Запретить пересечение задач»)
- Валидация: end_date >= start_date, все поля заполнены
- Редактирование — inline или через ту же форму
- Удаление — с подтверждением
- При открытии окна редактирования — загрузить текущие настройки секции + блоки
- При сохранении — обновить max_concurrent_tasks и сохранить изменения блоков
- Инжектировать GanttChartService и GanttSectionBlockService
Этап FE-3: Визуализация блоков на Ганте
Промт для агента:
Отобрази блоки секций (GanttSectionBlock) на canvas-диаграмме Ганта.
Проект использует Konva.js для рендеринга. Основной компонент:
gantt-chart-canvas.
- При загрузке данных диаграммы — извлечь блоки из каждой секции
- В рендерере секций (canvas/timeline-renderer.ts или аналогичный) — для каждого блока:
- Нарисовать заштрихованную область (diagonal hatch pattern) на всю высоту секции (все строки задач в этой секции)
- Область ограничена по горизонтали: от start_date до end_date (в пикселях по шкале времени)
- Цвет штриховки: полупрозрачный серый или configurable
- Поверх штриховки — текст с названием блока (block.title) на подложке (прямоугольник с фоном, чтобы текст читался)
- Блоки рендерятся до задач (z-index ниже), чтобы задачи были поверх
- Блоки с
block_concurrency = trueиfalseвизуально не отличаются (штриховка одинаковая)- При скролле/зуме — блоки пересчитываются как обычные элементы таймлайна
Этап FE-4: Система Alert-ов
Промт для агента:
Создай компонент для отображения Alert-ов о конфликтах на диаграмме Ганта.
Создай компонент
pages/gantt/components/gantt-conflict-alerts/Компонент размещается над canvas-диаграммой Ганта
Входные данные: массив
ConflictItem[](из модели)Визуал:
- Bootstrap Alert (warning) для каждого конфликта
- Каждый Alert показывает message из ConflictItem
- Без кнопки закрытия — Alert нельзя закрыть вручную
- Alert исчезает автоматически, когда текущая дата прошла дату конфликта
- Область сворачиваемая: заголовок «⚠️ Предупреждения (N)» — клик сворачивает/разворачивает список
- По умолчанию — развёрнуто
- При 0 конфликтов — область скрыта
Состояние хранится в сервисе или в компоненте диаграммы:
- Добавление нового конфликта → area auto-expands
- Alert удаляется из массива автоматически, когда текущая дата прошла дату конфликта
- Проверка актуальности конфликтов — при каждой отрисовке / изменении данных
- Не использовать toast — Alert-ы постоянные
Этап FE-5: Проверка конфликтов при операциях с задачами
Промт для агента:
Интегрируй проверку конфликтов во все операции с задачами на диаграмме Ганта.
Проект использует interaction-handler.ts для drag/resize и формы для создания/редактирования.
Важно: Проверка конфликтов — только на фронтенде, без вызова API. Используются загруженные данные секции (задачи, блоки, max_concurrent_tasks).
Вынеси логику проверки конфликтов
checkConflicts()в отдельный метод в gantt-canvas.service.ts:
- Принимает: section (GanttSection — содержит max_concurrent_tasks и blocks), tasks (GanttTask[] — задачи секции), task_id (nullable — исключить текущую задачу), start_date, end_date
- Логика: a. Если section.max_concurrent_tasks > 0: посчитать кол-во задач (исключая task_id), пересекающихся с [start_date, end_date] на каждый день. Если в любой день count > max_concurrent_tasks — добавить конфликт concurrency b. Для каждого блока секции с block_concurrency = true, пересекающегося с [start_date, end_date] — добавить конфликт block
- Возвращает ConflictCheck
После каждой успешной операции с задачей (создание, обновление, drag, resize):
- Вызвать checkConflicts с параметрами задачи
- Если has_conflicts = true — передать конфликты в GanttConflictAlertsComponent
- Alert-ы показываются, действие НЕ отменяется
Точки интеграции:
- Создание задачи (форма): после успешного POST → checkConflicts → show alerts
- Редактирование задачи (форма): после успешного PUT → checkConflicts → show alerts
- Drag задачи (interaction-handler.ts): после успешного обновления позиции → checkConflicts → show alerts
- Resize задачи (interaction-handler.ts): после успешного обновления размера → checkConflicts → show alerts
При переключении диаграммы или секции — очищать Alert-ы
Alert-ы автоматически удаляются, когда текущая дата прошла дату конфликта — область скрывается, когда конфликтов не осталось
Порядок реализации
| # | Этап | Сторона | Зависимости |
|---|---|---|---|
| 1 | Миграция + Домен | Backend | — |
| 2 | CRUD блоков | Backend | Этап 1 |
| 3 | Обновление настроек секции | Backend | Этап 1 |
| 4 | Включение блоков в ответ секций | Backend | Этап 2 |
| 5 | Модели + Сервис | Frontend | — (можно параллельно с backend) |
| 6 | Интеграция в модальные окна | Frontend | Этапы 5 + backend 2,3 |
| 7 | Визуализация блоков на Ганте | Frontend | Этапы 5 + backend 4 |
| 8 | Система Alert-ов | Frontend | Этап 5 |
| 9 | Проверка конфликтов при операциях | Frontend | Этапы 5,7,8 |