Жизненный цикл проекта
Это описание того, как проект в CRM RMS проходит путь от первичной карточки до закрытия: какие данные появляются на каждом этапе, какие модули подключаются и какие статусы реально поддерживает backend.
Общая схема
Проект — центральная сущность системы. Вокруг него собираются клиент, команда, задачи, файлы, спецификации, логистика, командировки, бюджет и документы.
В текущей версии backend проект хранится в projects и управляется через:
GET /api/v1/projectsиPOST /api/v1/projects— список и создание карточки проекта;GET /api/v1/projects/:id,PUT /api/v1/projects/:id,PATCH /api/v1/projects/:id/status— чтение, обновление и смена статуса;POST /api/v1/projects/:id/specs/...— версии проектной спецификации и их согласование;GET /api/v1/projects/:id/tasks— задачи проекта;GET /api/v1/projects/:id/team-historyи/api/v1/project-team— команда и история изменений;GET /api/v1/projects/:id/budgetи/api/v1/finance/projects/:id/...— бюджет и финансовая сводка;POST /api/v1/projects/:id/documents/invoice|contract|act|estimate— генерация проектных документов.
Актуальная матрица статусов проекта (internal/modules/projects/status.go): пресейл-воронка из спецификации «Структура RMS» (п.5.1) и производственная цепочка.
Пресейл (полный граф: любой -> любой)
planned <-> waiting_feedback <-> waiting_brief <-> waiting_budget <-> waiting_mf <-> estimating
| |
+----------------------------> approved <---------------------------------+
| |
+--> lost (обратно в любой v
пресейл-статус) in_progress -> done -> closed
\-> cancelled| Статус | Этап | Когда используется | Вероятность по умолчанию |
|---|---|---|---|
planned | пресейл | Карточка создана, исходные данные еще собираются | 10 |
waiting_feedback | пресейл | Отправили предложение, ждем обратную связь от клиента | 40 |
waiting_brief | пресейл | Ждем бриф / ТЗ от клиента | 20 |
waiting_budget | пресейл | Ждем подтверждение бюджета | 50 |
waiting_mf | пресейл | Ждем сметно-финансовое решение (МФ) | 50 |
estimating | пресейл | Команда готовит состав работ, спецификацию и смету | 60 |
approved | производство | Проект согласован и готов к исполнению | 100 |
in_progress | производство | Задачи, логистика, командировки и фактические расходы в работе | 100 |
done | производство | Операционная работа завершена, осталось формальное закрытие | 100 |
closed | финал | Проект закрыт, новые финансовые записи запрещены | 100 |
lost | финал пресейла | Сделка не прошла; можно реанимировать обратно в воронку | 0 |
cancelled | финал | Проект отменен после завершения операционного маршрута (done -> cancelled) | 0 |
Переходы:
- внутри пресейла — свободно: реальная сделка возвращается на пересчет, повторно ждет фидбэк и т.д., поэтому шесть пресейл-статусов связаны полным графом; из любого из них можно уйти в
approvedилиlost; - производственная цепочка линейна:
approved -> in_progress -> done -> closed, отмена возможна только изdone; lost -> любой пресейл-статус— проигранную сделку можно вернуть в воронку;closedиcancelled— терминальные.
Обычный пользователь должен двигаться по матрице (PATCH /api/v1/projects/:id/status вернет ошибку перехода). admin может выполнять административный переход между любыми валидными статусами. Кто вообще может менять статус — см. canManageProject в Матрице прав RMS.
Вероятность закрытия и даты карточки
Поля из спецификации (миграция 0133, internal/modules/projects/card.go):
| Поле | Что это |
|---|---|
close_probability | Ручное переопределение вероятности закрытия, 0–100. null в PUT сбрасывает к значению по умолчанию |
effective_close_probability | Вероятность, применяемая в расчетах: ручная, если задана, иначе значение по умолчанию для статуса (таблица выше). Значения 40 и 20 — из спецификации (п. 5.1), 10, 50 и 60 — решение 2026-09-30: без них взвешенная воронка на дашборде показывала 0 ₽. Вычисляется, не хранится |
status_changed_at | «Дата статуса»: обновляется при каждой смене статуса, для старых проектов заполнена из updated_at |
mount_date | «Дата монтажа», может быть пустой |
contract_deadline | «Дедлайн контрактации» = start_planned минус 1 месяц и 15 дней (AddDate(0, -1, -15)). Вычисляется, не хранится; без start_planned отсутствует |
department_ids | Все дивизионы проекта (project_departments, миграция 0132); всегда включает основной department_id |
close_probability и effective_close_probability вырезаются из ответа у ролей без доступа к объекту probability_weighted (OWNER, OPS, FIN_DIR), planned_budget/actual_revenue — без client_budget.
Менеджер: "Создаю проект и привязываю клиента"
↓
Система: "Проект в planned, можно загружать файлы и собирать вводные"
↓
Менеджер: "Готовлю спецификацию, команду и предварительную смету"
↓
Система: "Проект в estimating"
↓
Менеджер/директор: "Проект согласован"
↓
Система: "Проект в approved"
↓
Команда: "Запускаем задачи, логистику, командировки и учет времени"
↓
Система: "Проект в in_progress"
↓
Команда: "Все активные задачи закрыты"
↓
Система: "Проект можно перевести в done"
↓
Менеджер: "Генерирую финальные документы, сверяю бюджет"
↓
Система: "Проект закрыт в closed или отменен в cancelled"Этап 1: Карточка проекта
Что происходит
Менеджер или пользователь с доступом к отделу создает проект через POST /api/v1/projects.
Минимально нужен name. Остальные поля можно уточнять позже:
company_idиcontact_id— клиентская сторона;location_id— площадка или город проекта;department_id— основной (владеющий) дивизион проекта;department_ids— все дивизионы, по которым идет проект (видео + застройка и т.п.); основной добавляется автоматически;manager_id— менеджер проекта;event_category— тип события/проекта;planned_budget,actual_revenue— денежные ориентиры;start_planned,end_planned,start_actual,end_actual— даты в RFC3339;close_probability,mount_date— вероятность закрытия и дата монтажа (см. выше).
Если статус не передан, backend ставит planned. Создавать проект в дивизионе можно при записи project_card в охвате «все» (OWNER, OPS, TECH_DIR) или «свой» и совпадении department_id с дивизионом пользователя (DIV_HEAD, MANAGER).
Что важно для фронта
- Даты проекта в create/update ожидаются как RFC3339, а не как короткая дата.
department_id,department_idsиmanager_idважны для последующей видимости проекта и финансового доступа: проект виден сотрудникам всех его дивизионов с охватом «свой».contract_deadline,effective_close_probabilityиstatus_changed_atприходят только в ответах — вPUTих не отправляют.- Отсутствие
close_probability/effective_close_probabilityилиplanned_budgetв ответе означает, что у роли нет доступа к полю, а не ноль. actual_profit— фактическая выручка минус фактические затраты по статьям (MVP, раздел 5). Приходит только ролям с доступом кearnings_margin(OWNER,OPS,FIN_DIR), в выгрузке — отдельной колонкой при том же праве.- Список фильтруется по
department_id(основной дивизион или любой изproject_departments),manager_id,company_id,location_id,statusи по прибылиprofit_min/profit_max(включительно). Фильтр по прибыли без праваearnings_marginотвечает403 projects_profit_forbidden: иначе прибыль можно было бы подобрать перебором границ. - Проектный чат может создаваться автоматически, если включен
APP_AUTO_CREATE_PROJECT_CHAT. - Стартовая задача «Старт проекта: …» ставится менеджеру проекта при создании (
APP_AUTO_CREATE_PROJECT_KICKOFF_TASK, по умолчанию включено, отключаетсяfalse). Без менеджера проекта задачи нет. - Главное действие шапки карточки меняет статус с подтверждением: «Начать смету» переводит пресейл в «Сметим» и открывает «ТЗ и смета», «Подтвердить проект» (при утверждённом ТЗ) — в «Подтверждён».
Этап 2: Спецификация и оценка
Что происходит
На этапе estimating команда уточняет объем работ:
- Загружает ТЗ, чертежи и исходные файлы в проект, ТЗ — в папку с типом
tz. - Создает версию спецификации:
POST /api/v1/projects/:id/specs(file_idи структурированные поля вmeta). - Отправляет спецификацию на согласование:
POST /api/v1/projects/:id/specs/:version/submit. - Уведомление
project_spec.submittedполучают менеджер проекта и руководители всех дивизионов проекта (departments.manager_id), кроме автора отправки. - Спецификация подтверждается или отклоняется через
approve/reject.
После утверждения спецификации проект получает has_approved_spec=true.
Версии читаются через GET /api/v1/projects/:id/specs: канонический конверт {items, meta}, новые версии первыми, список отдаётся целиком. Видит тот, кому открыта карточка проекта; согласует тот, кто вправе править карточку (запись project_card «все» или «свой дивизион», либо менеджер проекта).
Поля упрощённого ТЗ
MVP (раздел 3.3) допускает ТЗ как файл или простую форму. Форма хранится в meta версии:
Ключ meta | Что это |
|---|---|
goal | Цель мероприятия/проекта |
format_venue | Формат и площадка |
equipment | Основные элементы технического оснащения |
mount_schedule | Сроки монтажа/демонтажа |
special_requirements | Особые требования |
Сервер meta не валидирует: ключи задаёт интерфейс, при смене формы старые версии остаются читаемыми.
Практический смысл
estimating — это не только "считаем деньги". Здесь фиксируется рабочий объем: что делаем, какие документы есть, кого нужно добавить в команду, какое оборудование и какие поездки могут понадобиться.
Этап 3: Команда проекта
Что происходит
Команда ведется через /api/v1/project-team.
В записи участника есть:
project_idиuser_id;role_in_project— legacy-основная роль;role_codes[]— канонический набор проектных ролей;hourly_rate— проектная ставка, если она отличается от обычной;is_lead;is_active.
Канонические роли отдает GET /api/v1/project-team/roles. Для финансового раздела важны роли manager, lead, accountant, finance и их legacy-алиасы.
Team Requests
Если нужен сотрудник из другого отдела, менеджер создает заявку:
POST /api/v1/projects/:id/team-requests— черновик заявки со строками по отделам и ролям;POST /api/v1/team-requests/:id/submit— переводdraft -> pending;POST /api/v1/team-requests/:id/approve— решениеapproved,rejectedилиcancelled;POST /api/v1/team-request-items/:id/assign— назначение конкретного сотрудника или отказ по строке.
Статусы заявки: draft, pending, approved, rejected, cancelled. Статусы строки: pending, assigned, declined.
При assigned backend добавляет сотрудника в project_team_members.
Этап 4: Задачи и исполнение
Как ставятся задачи
Задачи создаются через POST /api/v1/tasks или просматриваются в контексте проекта через GET /api/v1/projects/:id/tasks.
Флаг is_internal (миграция 0131) отделяет внутреннюю работу дивизиона (разработка, обслуживание) от клиентской: по умолчанию false — задача проектная. Отчеты по «незапланированным работам» и загрузке опираются на этот признак.
Актуальные статусы задач:
todo -> in_progress -> done
\ \-> blocked -> in_progress
\-> cancelled| Статус | Смысл |
|---|---|
todo | Задача создана, работа не началась |
in_progress | Исполнитель начал работу |
blocked | Есть блокер |
done | Работа завершена через endpoint finish или системный workflow |
cancelled | Задача отменена |
Work sessions
Для задач есть персональные рабочие сессии:
POST /api/v1/tasks/:id/start— открывает сессию для текущего пользователя и переводитtodo -> in_progress;POST /api/v1/tasks/:id/pause— закрывает сессию, создает autotime_entries, но не меняет статус задачи;POST /api/v1/tasks/:id/finish— закрывает сессию, создает autotime_entriesи переводит задачу вdone;PATCH /api/v1/tasks/:id/statusне принимает прямой перевод вdone: backend вернетuse_finish_endpoint.
При включенной автосинхронизации задач backend может перевести проект в in_progress, когда задача стартует, и в done, когда в проекте не осталось активных задач (todo, in_progress, blocked).
Этап 5: Оборудование, логистика и командировки
Оборудование
Проектная потребность в оборудовании оформляется через фасад заявок:
POST /api/v1/projects/:id/equipment-requests;GET /api/v1/projects/:id/equipment-status;GET /api/v1/equipment-requests/:id/source-options;POST /api/v1/equipment-requests/:id/operation-plan/preview;POST /api/v1/equipment-requests/:id/operation-plan/confirm;POST /api/v1/equipment-requests/:id/auto-plan/preview;POST /api/v1/equipment-requests/:id/auto-plan/confirm.
Детальный процесс описан в Логистическом контуре. Для проектного цикла важно:
- менеджер фиксирует проектную потребность; backend хранит ее отдельно в
project_equipment_requirements, а проектная заявка остается логистическим исполнением этой потребности; - логист создает дочерние операции вручную или через автоплан;
- дочерние операции связаны с исходной заявкой через
parent_request_id; - основной склад
СКЛАД КАЗАНЬсчитается свободным от проектной занятости, но активные резервы дочерних операций из него вычитаются; - проект считает позицию обеспеченной, когда нужное количество уже есть на проектной точке или дочерняя операция закрыта;
GET /api/v1/projects/:id/equipment-statusагрегируетrequested_qty/project_requested_qty, available, reserved, in_transit, closed и shortage для фронтенда;- подтвержденные расходы логистики попадают в
finance.cost_itemsсsource_type=logistic_operation.
Если заявка отклонена или отменена без активных операций, backend архивирует ее и скрывает из активных списков проекта. История остается в аудите, а проектная потребность не сбрасывается: equipment-status продолжает показывать shortage, пока оборудование не появится на точке или не будет закрыта дочерняя операция.
Командировки
Командировки ведутся через /api/v1/trips.
Статусы командировки:
planned -> approved -> in_progress -> completed
\-> cancelledПосле создания или обновления командировки с проектом сервис синхронизирует расходы в Finance как source_type=trip. При отмене командировки плановая и фактическая сумма источника обнуляются.
Этап 6: Бюджет и финансовый контроль
Как собирается бюджет
GET /api/v1/projects/:id/budget не пересчитывает бизнес-события напрямую. Он агрегирует уже созданные finance.cost_items, группирует их по категориям и применяет markup_percent.
Источники cost items:
- ручной ввод через
POST /api/v1/finance/cost-items; - логистика через
source_type=logistic_operation; - командировки через
source_type=trip; - трудозатраты через
source_type=time_entry, если интеграция записала соответствующую статью затрат.
Обычные time_entries и auto time entries от задач хранят часы. Денежная строка labor появляется только когда финансовый контур создает или обновляет cost item для этого источника.
Закрытый проект
Finance запрещает создание и обновление cost items для проекта в статусе closed. Поэтому перед переводом в closed менеджер должен сверить:
- все нужные статьи затрат заведены;
- логистика закрыта или помечена как исключение;
- командировки завершены или отменены;
- документы и выручка отражены корректно.
Этап 7: Документы и закрытие
Что генерируется
Проектные документы создаются через:
POST /api/v1/projects/:id/documents/estimate;POST /api/v1/projects/:id/documents/invoice;POST /api/v1/projects/:id/documents/contract;POST /api/v1/projects/:id/documents/act.
Статусы generated documents: draft, issued, paid, acted, cancelled.
Финальное состояние
done означает, что операционная работа закончена. closed означает, что проект формально закрыт и финансовые изменения больше не должны вноситься. Архивирование — отдельный флаг is_archived; архивный проект скрывается из обычных списков, но остается доступен для истории и отчетности.
Исключительные ситуации
Проект отменили
Используется статус cancelled. Для задач — cancelled, для логистики — cancelled или exception, для командировок — cancelled. Уже созданные расходы остаются в истории, если их явно не обнулила доменная логика источника.
Сделка не состоялась
На любой пресейл-стадии проект переводится в lost. Вероятность закрытия становится 0, проект остается в списках и отчетах по воронке. Если клиент вернулся, lost -> planned (или другой пресейл-статус) допустим без участия admin.
Работы остановлены
Отдельного статуса on_hold нет. Фронт должен показывать остановку через заметки, блокирующие задачи (blocked) и отсутствие новых действий, а не через несуществующий статус проекта.
Нужно исправить статус
Обычный пользователь следует матрице переходов. Административное исправление выполняет admin, потому что сервис проекта разрешает admin переходить между любыми валидными статусами.
Ключевые правила проектного цикла
1. Статусы должны совпадать с backend enum
Допустимые статусы проекта: planned, waiting_feedback, waiting_brief, waiting_budget, waiting_mf, estimating, approved, in_progress, done, closed, lost, cancelled (валидируются oneof в POST/PUT и в PATCH .../status). Русские лейблы воронки из ТЗ («Лид», «Обсуждаем», «Сметим», «Подтвержден», «Потерян» и т.д.) — это подписи на фронте, а не коды: значений lead, discussion, estimate, confirmed, delivered, postponed в enum нет.
2. done для задачи ставится через finish
Прямой PATCH /tasks/:id/status в done намеренно отклоняется. Так backend гарантирует, что закрытие задачи связано с рабочей сессией и auto time entry.
3. Бюджет показывает Finance, а не все события подряд
Если событие не создало finance.cost_items, оно не появится в budget summary. Это нормально для черновых или еще не интегрированных частей процесса.
4. Проектная видимость задается матрицей прав, дивизионами и участием
Источник правил — Матрица прав RMS, объект project_card:
- охват «все» (
OWNER,OPS,TECH_DIRна запись;FIN_DIR,FIN_ASSIST,LEGAL,ENGINEER,DIV_STAFFна чтение) — видны все проекты; - охват «свой» (
DIV_HEAD,MANAGER) — проекты своих дивизионов (department_idилиproject_departments) плюс проекты, где сотрудник участвует лично; - без доступа — только личное участие: менеджер, активная команда, автор/исполнитель/соисполнитель задач.
Руководитель отдела продаж видит все проекты. Финансовая секция дополнительно открывается объектом costs (охват «все») либо проектными финансовыми ролями команды; документы — объектом contracts, логистика — equipment_stock. Денежные поля карточки вырезаются сервером по client_budget и probability_weighted.
Резюме: путь проекта
planned
Карточка создана, собираем вводные
↓
waiting_feedback / waiting_brief / waiting_budget / waiting_mf
Пресейл: ждем клиента, бриф, бюджет, МФ — переходы свободные
↓
estimating
Спецификация, команда, смета, потребности
↓
approved
Проект согласован и готов к работе
↓
in_progress
Задачи, логистика, командировки, файлы, учет времени
↓
done
Операционная работа завершена
↓
closed
Документы и финансы закрыты, новые cost items запрещеныlost закрывает пресейл, если сделка не состоялась (ее можно вернуть в воронку); cancelled используется для отмены проекта после done, когда работу не доводят до обычного закрытия.
Предупреждения перед закрытием
«Закрыт» — финальный статус: новые расходы по проекту не принимаются. Поэтому перед переводом в «Закрыт» интерфейс запрашивает GET /api/v1/projects/:id/close-check и показывает в подтверждении, что по проекту не доведено до конца (решение 2026-09-30: предупреждать, а не запрещать — закрыть можно в любом случае):
- оборудование в пути — перевозки со статусом «В пути»: не доставлено или не вернулось на склад;
- незавершённые перевозки — подготовка, согласование, поиск поставщика;
- заявки на оборудование, по которым склад ещё не спланировал перевозки;
- суммы перевозок, которые ждут решения финансов;
- незавершённые командировки;
- открытые задачи;
- обязательства и платежи по проекту без факта исполнения.
Перевозки в архиве и отменённые не учитываются. Проверка — один сводный запрос на чтение (Repository.CloseCheckCounts); данные других модулей она не меняет.