# Межпроектная маршрутизация задач ## Цель Добавить в `NODE.DC TM` отдельный сценарий межпроектной постановки задач между контурами без перепрошивки штатного `Intake`. Базовая идея: - `Рабочие элементы` переименовываются в `Внутренний контур` - рядом появляется новый модуль `Внешние контуры` - пользователь из проекта-источника создает внешний запрос - запрос уходит в целевой проект - в целевом проекте он становится обычной задачей - в проекте-источнике сохраняется видимый объект маршрутизации со статусом, историей и материалами ## Почему выбран именно этот путь Не трогаем штатный `Intake` как пользовательский модуль. Используем его только как внутренний backend-мост, потому что: - механизм уже умеет создавать bridge между `Issue` и `IntakeIssue` - уже есть переход из `TRIAGE` в default-state проекта - уже есть `extra JSON` для метаданных маршрутизации - это самый безопасный путь для форка и дальнейших обновлений ## Термины ### Внутренний контур То, что сейчас в UI является обычными рабочими элементами проекта. ### Внешние контуры Новый отдельный модуль проекта, через который задача отправляется в другой проект внутри того же workspace. ### Проект-источник Проект, из которого инициируется запрос. ### Целевой проект Проект, в котором задача реально исполняется. ### Исходная карточка внешнего контура Карточка или запись в проекте-источнике, где инициатор видит: - текущий статус - целевой проект - назначенного исполнителя - историю обработки - файлы - уведомления о ходе работы ### Целевая задача Обычная задача в целевом проекте, которая попадает в обычный workflow этого проекта. ## Ключевой продуктовый результат Пользователь из `Менеджеры` должен иметь возможность: - открыть `Внешние контуры` - выбрать целевой проект, например `Бухгалтерия` - описать запрос - назначить исполнителя из `Бухгалтерия` - отправить запрос После этого: - у `Бухгалтерия` появляется обычная задача в их проекте - у отправителя в `Менеджеры` появляется запись во `Внешних контурах` - у этой записи есть статусная пришлепка - при любом изменении статуса в целевом проекте статус в источнике обновляется - если в целевом проекте меняют описание, прикладывают файлы, комментируют или закрывают задачу, это отражается в источнике - инициатор получает уведомления о существенных изменениях ## Текущий статус реализации На текущем этапе уже реализовано: - отдельный модуль `Внешние контуры` - отдельный backend endpoint маршрутизации - создание target issue в целевом проекте через intake bridge - немедленный перевод target issue в обычный workflow целевого проекта - source-side список отправленных запросов - source-side детальный экран на базе shell `Предложений` - status pill по фактическому состоянию target issue - workspace-wide выбор целевого внешнего контура по policy: - тот же `workspace` - у целевого проекта включен модуль - прямой membership в target project не требуется - source-side карточка как основная точка работы отправителя, если у него нет доступа в целевой проект - source-side редактирование открытого запроса: - `заголовок` - `описание` - source-side действия: - `Принять` - `Отклонить` - `Ответ во внешний контур` - зеркалирование из целевой задачи в source-side карточку: - комментарии - вложения - activity - обновления статуса - proxy download для вложений без прямого membership в target project - unread-индикатор новых изменений по source-side карточке - блок `Маршрутизация` в фиксированном формате `3 x 3` ## Freeze point на 2026-04-19 Текущий этап временно заморожен. Заморозка делается после достижения рабочего вертикального среза: - запрос можно отправить из проекта-источника в другой проект того же `workspace` - в целевом проекте создается обычная задача и сразу попадает в обычный workflow - отправитель видит свой source-side список и карточку без обязательного доступа в чужой проект - в source-side карточке видны статус, маршрут, назначенный, срок, комментарии, вложения и activity из целевого контура - отправитель может поправить ошибку в `заголовке` и `описании`, пока запрос открыт - отправитель может принять результат во внутренний контур или вернуть запрос обратно во внешний контур с комментарием причины На этом месте разработка следующего шага останавливается до фиксации продуктовых решений. ## Зафиксированные продуктовые решения на 2026-04-20 После freeze point приняты дополнительные продуктовые решения по следующему циклу развития `Внешних контуров`. ### 1. Нужны две обязательные системные зоны Во `Внешних контурах` должны появиться: - `Исходящие` - `Входящие` `Исходящие` обязательны, потому что пользователь должен видеть запросы, которые он сам отправил в другие контуры, до и после обработки. `Входящие` обязательны, потому что проект должен видеть запросы, пришедшие к нему из других контуров, в отдельном специализированном режиме работы, а не только как обычные задачи `Внутреннего контура`. ### 2. Обе зоны должны фильтроваться так же гибко, как во `Внутреннем контуре` Минимальный целевой принцип: - фильтрация по пользователям - фильтрация по статусам - фильтрация по назначенным - фильтрация по автору - фильтрация по контурам и связанным проектам То есть `Внешние контуры` должны получить не просто статичный список, а управляемое рабочее представление. ### 3. Карточка деталей внешнего контура должна перейти на тот же UX-паттерн, что и во `Внутреннем контуре` При клике по карточке должна открываться панель того же класса: - закрыть просмотр - открыть на весь экран - переключить макет - подписка или отписка - копирование ссылки - меню дополнительных действий При этом: - действие удаления в этом shell не является обязательным - тело карточки остается внешнеконтурным, а не превращается в обычную карточку внутренней задачи ### 4. Внешний контур — это не workflow-kanban Важно зафиксировать заранее: - пользователь не должен перетаскивать задачи между блоками - блоки во `Внешних контурах` — это представления и выборки, а не стадии исполнения - нельзя переносить сюда механику drag-and-drop из `Внутреннего контура` ### 5. Пользовательские колонки нужны, но не входят в ближайший обязательный срез Собственные пользовательские блоки и сортировки нужны как следующий этап развития, но не должны размыть ближайшие обязательные поставки: 1. единый shell карточки внешнего контура 2. двусторонняя доска `Исходящие / Входящие` ### 6. Архитектура должна остаться расширяемой Следующий слой развития не ограничивается только внешними контурами. Дальше в проекте могут появиться: - новые типы досок - агентные доски - специализированные мониторинговые представления - пользовательские рабочие поверхности под разные роли Поэтому нельзя решать следующий этап только переобертками и точечными хаками. Но и полный демонтаж текущего проекта ради абстрактной платформы тоже недопустим. Рабочий принцип: - не ломать текущий runtime - не дублировать уже существующие механики без причины - выносить только те контракты, которые реально переиспользуются дальше ## Что нужно решить перед продолжением - Что именно делает действие `Принять`: - только фиксирует решение источника - создает отдельную сущность во `Внутреннем контуре` - или переводит внешний запрос в отдельный внутренний режим без дублирования сущностей - Должен ли принятый запрос оставаться во `Внешних контурах` как историческая карточка, или он должен исчезать из рабочего списка и жить только во `Внутреннем контуре` - Нужен ли отдельный статус или отдельная вкладка для запросов, которые уже приняты во `Внутренний контур` - Должна ли коммуникация по комментариям быть полностью двусторонней, или source-side ответов достаточно только для возврата и уточнений - Нужно ли физически копировать файлы в source-side представление, или для PoC достаточно proxy-доступа к файлам целевой задачи - Нужно ли переводить обновление карточки с polling на realtime/push уже в следующем этапе, или polling пока приемлем - Нужны ли отдельные счетчики непрочитанных изменений по вкладкам `Открытые` и `Завершенные` - Должен ли отправитель после создания запроса иметь право менять только `заголовок` и `описание`, или еще и `назначенного`, `срок`, `приоритет`, `метки` - Нужно ли сохранять запрет на прямой переход в целевую задачу для пользователей без membership в target project как постоянное правило - Какой финальный lifecycle должен быть у запроса после возврата, принятия, завершения и отмены, чтобы source-side карточка не стала второй несогласованной системой учета ## Обязательные требования ### 1. Отдельный модуль `Внешние контуры` не должны быть переименованным `Intake`. Это отдельная новая вкладка проекта. ### 2. Обычная задача в целевом проекте Задача не должна висеть в ручном triage. После отправки она должна сразу попадать в обычный workflow целевого проекта, в его default-state. ### 3. Статусная пришлепка в источнике У каждой записи во `Внешних контурах` в списке должен быть видимый статус. Для первой версии правильнее показывать фактическое имя текущего статуса целевой задачи: - `В плане` - `К выполнению` - `В работе` - `Готово` - `Отменено` - либо любой другой кастомный статус целевого проекта То есть источник видит не абстрактное `accepted`, а реальный текущий статус исполнения. ### 4. Зеркалирование изменений из целевого проекта в источник Изменения в целевой задаче должны отражаться в исходной карточке внешнего контура. Минимальный обязательный набор: - изменение статуса - изменение названия - изменение описания - добавление/удаление файлов - комментарии и служебные заметки по задаче - закрытие/отмена задачи ### 5. Уведомления инициатору Инициатор должен получать уведомления, когда: - задача принята в работу - задача переведена в другой статус - добавлен файл - добавлен комментарий - задача завершена - задача отменена ### 6. Трассировка между источником и целью Связь между источником и целевой задачей должна быть явной и восстановимой. Нельзя делать фичу как “создали задачу и забыли”. ## Архитектурный подход ## Общая схема Используем существующий intake bridge как транспортный слой: - создаем `Issue` в целевом проекте - создаем `IntakeIssue` - сразу переводим bridge в `ACCEPTED` - целевая задача попадает в default-state проекта Но только этого уже недостаточно. Из-за требований на статусную пришлепку, зеркалирование файлов и уведомления нужен еще source-side слой отображения. ## Что остается от текущей идеи варианта 2 Сохраняем: - reuse intake backend-механики - отдельный orchestration endpoint - отдельный фронтовый модуль `Внешние контуры` Расширяем: - добавляем источник правды для source-side карточки - добавляем синхронизацию изменений из target issue в source representation - добавляем уведомления ## Рекомендуемая модель данных для первой итерации ### Обязательная связь Нужна явная пара: - `source_project_id` - `source_request_id` - `target_project_id` - `target_issue_id` ### Метаданные bridge В `IntakeIssue.extra` храним: - тип моста: `external-contours` - источник - цель - автора - временные метки Пример: ```json { "bridge": "external-contours", "source_project_id": "uuid", "source_project_name": "Менеджеры", "target_project_id": "uuid", "target_project_name": "Бухгалтерия", "requested_by_id": "uuid", "requested_by_name": "Иван Петров", "requested_at": "2026-04-18T19:00:00Z" } ``` ### Source-side представление Для source-side части есть два пути: 1. Легкая проекция без отдельной таблицы - список `Отправленные` собирается по `IntakeIssue.extra` - детали берутся из целевой задачи 2. Отдельная source-side сущность или shadow-record - отдельная запись для внешнего контура в проекте-источнике - в ней хранится mirrored state Для требований текущего этапа безопаснее считать, что source-side проекция понадобится. Причина: - нужен список со статусом - нужны файлы в источнике - нужна история изменений - нужны уведомления Простого “читать target issue на лету” для этого, скорее всего, будет мало. ## UX первой рабочей вертикали ## Модуль `Внешние контуры` В проекте появляется новая вкладка: - `Внешние контуры` Внутри: - кнопка `Новый внешний запрос` - список `Открытые` - список `Завершенные` ## Форма создания Поля: - целевой проект - название - описание - исполнитель из целевого проекта - приоритет - срок - метки Вложения можно заложить сразу в спецификацию, но в первую техническую поставку лучше включать аккуратно. ## Карточка в списке В строке списка должны быть: - название - целевой проект - исполнитель - дата создания - статусная пришлепка - индикатор новых изменений ## Детальный экран внешнего контура Детальный экран источника не должен быть просто копией target issue. Правильнее собрать его из блоков: - исходный запрос - текущий статус - целевой проект и исполнитель - история изменений - синхронизированные файлы - комментарии/обновления Это безопаснее, чем пытаться перетирать исходное описание данными целевого проекта. ## Правила синхронизации ## Направления ### Источник -> цель На этапе создания отправляем: - название - описание - исполнителя - приоритет - срок - метки ### Цель -> источник После создания отражаем обратно: - текущий статус - изменения описания - комментарии - файлы - итоговый результат ## Что должно быть синхронизировано в обязательном порядке - `target issue state.name` - `target issue updated_at` - файлы target issue - пользовательские комментарии - итоговый resolution state ## Что пока не нужно считать обязательным - полная двусторонняя редакция из источника после отправки - редактирование чужих project labels через источник - полный realtime-collab режим ## Уведомления Минимальный набор событий: - запрос создан - задача принята - статус изменен - добавлен файл - добавлен комментарий - задача завершена - задача отменена Каналы первой версии: - in-app notifications Каналы последующих итераций: - email - внешние integrations ## Ограничения текущего этапа ### 1. Вложения сейчас хрупкие Текущий runtime уже показывал хрупкость attachment flow. Поэтому вложения нужно учитывать в дизайне, но внедрять аккуратно и тестировать отдельно. ### 2. Права сложнее, чем кажется Отправитель должен иметь право отправлять запрос из source project, но не обязан быть участником target project. При этом: - target assignee должен быть участником target project - target issue создается в target project - источник должен иметь возможность видеть результат обработки без полного membership в target project ### 3. Простая “копия intake” уже недостаточна Как только добавляются: - статусная пришлепка - файлы - история - уведомления фича перестает быть просто формой создания. Нужен отдельный источник правды для source-side представления. ## Что считаем текущим этапом Текущий этап — это не финальная реализация, а проектирование и запуск вертикального среза. В этот этап входят: - фиксация терминологии - фиксация архитектурного подхода - разбиение на поставки - определение обязательных требований для первой версии - определение границ MVP Подробная поэтапная разработка описана в: - [phase-roadmap.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/phase-roadmap.md) - [11_STEP_external-contours-detail-shell.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/11_STEP_external-contours-detail-shell.md) - [12_STEP_external-contours-bidirectional-board.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md) - [13_STEP_external-contours-board-data-contract.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md)