Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# Ответ на #113 — Двухслойная модель исполнения: делегированный + личный

> Дата: 2026-05-28 | Статус: PROPOSAL, ответ на [#113](113-functiontask-execution-boards.md) | В ответ на: Codex | Размер: XL

## TL;DR

Спецификация #113 описывает **только верхний слой** — делегированную работу: руководитель ставит задачу → исполнитель внутри неё заводит доску. Это правильно, и мы это принимаем целиком.

Но #113 в Non-goals **вырезает то, ради чего FlowTask вообще встраивают** — возможность сотруднику самостоятельно вести и структурировать *свою* ежедневную работу, а не только то, что ему явно спустили сверху.

Предлагаем не «или», а **два слоя одной модели**:

| Слой | Кто инициирует | Зачем | Видимость |
|---|---|---|---|
| **Делегированный** (#113) | Руководитель → исполнителю | Контроль сложного поручения | Управленческие surfaces: `/tasks`, weekly, свод |
| **Личный** (это предложение) | Сотрудник → себе | Управление собственной ежедневной работой | Только личный кокпит сотрудника |

Оба слоя порождают одну и ту же пару `FunctionTask → ExecutionBoard`. Разница — **кто инициирует** и **в каком scope живёт** задача. Никакого второго трекера, никакого нового top-level домена.

---

## 1. Что мы принимаем из #113 без изменений

- `FunctionTask` остаётся единственной управленческой семантикой. `FunctionTaskStatus` **не меняем**.
- Доска исполнения живёт **внутри задачи**, находится через неё (`/tasks/[id]/execution`).
- Подзадачи — отдельная сущность `ExecutionSubtask`, не глобальные `FunctionTask`, плоские в MVP.
- Кастомные статусы — только внутри доски, обязательный semantic bucket.
- Roadmap по `startDate/dueDate`, цвета из semantic-токенов.
- Миграция только аддитивная, soft-delete, без backfill.
- ITOM workflow не трогаем.
- `FunctionTaskTopic` не заменяем.

**Top-down модель #113 принимается как Слой 1 целиком.** Спор только об одном Non-goal — об исключении личного слоя.

---

## 2. Точка несогласия

#113 строит модель вокруг тезиса: *«доска нужна только для сложного делегированного поручения; всё личное — это шум, который надо держать вне портала»*.

Конкретно мы оспариваем формулировки:

- §69 «Не добавлять новый top-level sidebar domain» — **согласны** (домен не нужен), но из этого #113 делает вывод, что и личного входа быть не должно. Это не следует.
- Неявное допущение, что доску заводят только под *спущенную* задачу. На деле код портала **уже разрешает self-assign** (`function-tasks/route.ts:825` — `isSelfAssign` проходит без лидерства). То есть сотрудник уже может завести себе задачу — #113 просто не достраивает над этим рабочий слой.

---

## 3. Зачем нужен личный слой (продуктовый аргумент)

Коллеги, тестировавшие FlowTask, ждут от встроенной версии **ровно того же, что было в FlowTask** — не «урезанный канбан для чужих поручений», а инструмент, которым ведут свой день.

Реальная работа сотрудника состоит из двух потоков:

1. **Спущенное** — задачи, которые поставил руководитель (Слой 1).
2. **Самоорганизованное** — то, что сотрудник декомпозирует и ведёт сам: подготовка к спущенной задаче, свои инициативы, рутинные дела, личные дедлайны.

Если портал поддерживает только (1), сотрудник всё равно ведёт (2) — но в Notion / стикерах / голове. Тогда портал теряет полноту картины, а руководитель — реальную загрузку команды.

**Killer feature встраиваемого FlowTask = свести оба потока в одном месте**, не засоряя при этом управленческие сводки.

---

## 4. Схема 1 — Делегированный слой (top-down)

Это модель #113 без изменений.

```
РУКОВОДИТЕЛЬ
│ создаёт FunctionTask «Имплементировать портал»
│ назначает → Крылов (assignee ≠ self → нужен лидер/MANAGE_ALL)
ЗАДАЧА (scope = FUNCTION, видна в /tasks, weekly, своде)
│ Крылов открывает /tasks/FT-128
ДОСКА ИСПОЛНЕНИЯ (1:1, заводит исполнитель)
├─ ExecutionSubtask[] ← подзадачи
├─ ExecutionWorkflow ← кастомные статусы
└─ Roadmap
│ агрегированный summary (прогресс / просрочки / блокеры)
РУКОВОДИТЕЛЬ видит итог в той же задаче
```

**Свойства:** задача видна управленческим surfaces; статус закрывает руководитель вручную; доска — необязательное продолжение для сложного исполнения.

---

## 5. Схема 2 — Личный слой (bottom-up, self-serve)

Сотрудник управляет своей работой из личного кокпита. Точка входа — **существующий `/my` («Мои задачи»)**, расширенный, а не новый домен.

```
СОТРУДНИК (личный кокпит /my)
├─ создаёт себе задачу (assignee = self → лидерство НЕ нужно)
│ scope = PERSONAL
│ │
│ └─ при желании заводит доску исполнения (1:1, та же модель #113)
│ ├─ ExecutionSubtask[]
│ ├─ ExecutionWorkflow (или дефолтный «To do / Doing / Done»)
│ └─ Roadmap
├─ ведёт ежедневные задачи (короткие, без доски)
└─ ВСЁ ЛИЧНОЕ видно только ему:
✗ не попадает в /tasks функции
✗ не попадает в weekly / management summary
✗ не считается в управленческих метриках просрочки
✓ видно сотруднику в /my как личный канбан/список/roadmap
```

**«Сколько угодно досок» = сколько угодно личных задач, каждая со своей доской 1:1.** Мы не ломаем правило `parentTaskId @unique` — мы даём свободу создавать сами задачи-контейнеры. Это сохраняет инвариант #113 и одновременно снимает ограничение, которое не понравилось.

---

## 6. Точка схождения — это одна модель, а не две

Ключевой тезис для мейнтейнера: **мы не строим второй трекер.** Оба слоя — это та же пара сущностей:

```
FunctionTask (+ поле scope) ExecutionBoard (1:1) → ExecutionSubtask[] / Workflow / Roadmap
├─ scope = FUNCTION → Слой 1 (делегированное, видно всем по RBAC)
└─ scope = PERSONAL → Слой 2 (личное, видно только автору)
```

Разница между слоями — **одно поле `scope` и правила видимости**. Доска, подзадачи, workflow, roadmap, API — идентичны. Это и закрывает страх #113 о «втором портале задач»: код, схема и UI доски — одни и те же.

---

## 7. Модель данных — delta к #113

Добавляем к модели #113 ровно одно поле на `FunctionTask` и ничего нового сверху.

```prisma
enum FunctionTaskScope {
FUNCTION // делегированное / управленческое (дефолт, текущее поведение)
PERSONAL // личное, видно только автору
}

model FunctionTask {
// ...существующие поля без изменений...
scope FunctionTaskScope @default(FUNCTION) // NEW · аддитивно
@@index([scope])
}
```

Всё остальное (`FunctionTaskExecutionBoard`, `ExecutionSubtask`, `ExecutionWorkflow*`) — **ровно как в #113**, без изменений. `scope = FUNCTION` по умолчанию → существующие задачи и поведение не меняются.

> Осознанно НЕ вводим отдельную сущность `PersonalTask`. Отдельная сущность = именно тот «второй трекер», которого боится #113. Переиспользование `FunctionTask` + `scope` даёт личный слой бесплатно с точки зрения доски/подзадач/API.

---

## 8. Видимость и фильтры — как НЕ засорить backlog

Главный риск, который называет #113 (§470, §61): личные задачи как шум в `/tasks`, weekly, метриках. Закрывается правилом видимости по `scope`:

| Surface | Что показывает | Правило |
|---|---|---|
| `/tasks` (функция) | только `scope = FUNCTION` | `WHERE scope = 'FUNCTION'` по умолчанию |
| Weekly / management summary | только `scope = FUNCTION` | personal исключены жёстко |
| Метрики просрочки функции | только `scope = FUNCTION` | personal не влияют на KPI функции |
| `/my` (личный кокпит) | `assignee = me` обоих scope | personal + делегированные мне вместе |
| Доска `/tasks/[id]/execution` | по доступу к задаче | personal-доску видит только автор |

Ключ: **personal-задачи не существуют для управленческих surfaces вообще**. Руководитель не видит личную кухню сотрудника; сотрудник не мусорит в backlog функции.

---

## 9. RBAC

Опираемся на уже существующую логику (`function-tasks/route.ts`) — почти ничего нового:

| Действие | Правило |
|---|---|
| Создать `scope = PERSONAL` задачу себе | `WRITE_FUNCTION_TASKS` + `assignee = self` (уже разрешено кодом, лидерство не нужно) |
| Назначить personal-задачу другому | ❌ запрещено — personal по определению только себе |
| Создать доску под своей personal-задачей | автор задачи (правило #113 §456 — author/assignee) |
| Читать чужие personal-задачи | ❌ только автор; не отдаются даже лидеру функции |
| Перевести personal → function (эскалация) | автор + наличие `WRITE_FUNCTION_TASKS` на функцию; assignee остаётся, задача «всплывает» в backlog |

> Эскалация `PERSONAL → FUNCTION` — приятный бонус: сотрудник вёл что-то для себя, оно выросло в реальную задачу функции → одним действием делает её видимой руководителю, не пересоздавая.

---

## 10. Точки входа — без нового домена

Полностью соблюдаем Non-goal #113 §69 «нет top-level домена».

- **Личный слой = расширение `/my`** («Мои задачи»), который уже есть в портале. Добавляем режимы просмотра: список (как сейчас) + канбан + roadmap по личным+делегированным задачам сотрудника.
- **Создание personal-задачи** — кнопка «+ Задача» в `/my` с дефолтом `scope = PERSONAL, assignee = self`.
- **Доска** — та же `/tasks/[id]/execution`, через задачу.
- Сайдбар не получает новых доменов. `/my` уже там.

---

## 11. Ответ на Non-goals #113 по пунктам

| Non-goal #113 | Наша позиция |
|---|---|
| Не менять `FunctionTaskStatus` | ✅ принимаем, не трогаем |
| Не добавлять кастомные статусы к обычным задачам | ✅ принимаем (кастом только в доске) |
| Не закрывать родителя автоматически | ✅ принимаем |
| **Не добавлять top-level domain Boards/FlowTask** | ✅ **принимаем** — личный слой живёт в существующем `/my` |
| Не переносить айдентику FlowTask, палитру | ✅ принимаем — палитра MOEX |
| Не заменять `FunctionTaskTopic` | ✅ принимаем |
| Не заменять ITOM workflow | ✅ принимаем |
| **(неявно) Доска только для делегированного** | ⚠️ **оспариваем** — доска доступна и для personal-задач; механика та же, scope разный |

Из восьми Non-goals оспариваем **один неявный**. Остальные семь — принимаем дословно.

---

## 12. Открытые вопросы

1. **Группировка личных задач.** Оставляем строго 1 доска : 1 задача (как #113), или даём сотруднику лёгкую группировку нескольких personal-задач в одном личном представлении? (Предлагаем: группировка только как view в `/my`, без новой сущности-контейнера.)
2. **Лимит на personal-задачи.** Нужен ли мягкий лимит, чтобы `/my` не превратился в свалку? (Предлагаем: нет лимита, но архивация/soft-delete по умолчанию.)
3. **Personal-задачи в поиске / ⌘K.** Показывать ли свои personal-задачи в глобальном поиске? (Предлагаем: да, но только автору.)
4. **Data transfer / export.** Personal-задачи входят в экспорт функции или только в личный экспорт пользователя?
5. **Видит ли руководитель факт наличия** личной загрузки (агрегат «у сотрудника N личных задач») без доступа к содержимому — или личное полностью невидимо? (Предлагаем: полностью невидимо в MVP.)

---

## 13. Фазы

Слой 1 (делегированный) = фазы #113 без изменений. Личный слой добавляется поверх:

- **Phase L0** — добавить `FunctionTaskScope` (аддитивная миграция), дефолт `FUNCTION`. Правила видимости в `/tasks`, weekly, метриках (исключить PERSONAL). Без UI.
- **Phase L1** — `/my`: создание personal-задачи (scope=PERSONAL, self-assign), список personal + делегированных.
- **Phase L2** — доска под personal-задачей (переиспользует доску #113 как есть).
- **Phase L3** — `/my` канбан + roadmap по личным+делегированным.
- **Phase L4** — эскалация `PERSONAL → FUNCTION`; экспорт; поиск.

---

## 14. Резюме для мейнтейнера

Мы **не предлагаем альтернативу #113** — мы предлагаем **достроить #113 одним полем**.

- Делегированный слой = #113 целиком, принят.
- Личный слой = `FunctionTask.scope = PERSONAL` + правила видимости + расширение `/my`.
- Та же доска, те же подзадачи, тот же API, та же палитра.
- Никакого второго трекера, никакого нового домена, никакого шума в backlog.
- Закрываем главное ожидание тестировавших коллег: вести *свою* ежедневную работу там же, где спущенную.
Loading
Loading