Skip to content

Жизненный цикл проекта ​

Это описание того, как проект в 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) и производственная цепочка.

text
Пресейл (полный граф: любой -> любой)
  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 команда уточняет объем работ:

  1. Загружает ТЗ, чертежи и исходные файлы в проект, ТЗ — в папку с типом tz.
  2. Создает версию спецификации: POST /api/v1/projects/:id/specs (file_id и структурированные поля в meta).
  3. Отправляет спецификацию на согласование: POST /api/v1/projects/:id/specs/:version/submit.
  4. Уведомление project_spec.submitted получают менеджер проекта и руководители всех дивизионов проекта (departments.manager_id), кроме автора отправки.
  5. Спецификация подтверждается или отклоняется через 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 — задача проектная. Отчеты по «незапланированным работам» и загрузке опираются на этот признак.

Актуальные статусы задач:

text
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 — закрывает сессию, создает auto time_entries, но не меняет статус задачи;
  • POST /api/v1/tasks/:id/finish — закрывает сессию, создает auto time_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.

Статусы командировки:

text
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.


Резюме: путь проекта ​

text
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); данные других модулей она не меняет.

Загружаем документацию…