Skip to content

CRM RMS: фундамент и план доработки ​

Дата: 2026-09-23. Ветка: feature/rms-structure-stage1 (бекенд), feature/rms-structure-stage1 (фронт rms_crm_front).

Назначение документа: единая точка правды о том, что уже сделано, чем это отличается от ТЗ, и в каком порядке доводить систему до законченного состояния. Обновляется при закрытии каждого этапа.


0. Источники требований ​

ДокументГдеЧто описываетСтатус в репо
«Идея подхода / План-стратегия внедрения / MVP CRM RMS v1.0 / 12-недельный план»Google Docs 1a3YXPVtLp6BGitwJfJZpIXLW-aZP3cOmСтратегия, MVP-скелет (проекты, клиенты, города, задачи, оборудование, склады, логистика, командировки, финансы-лайт, дашборд), финансовое ядро (статьи, ставки, ТЗ → бюджет), дисциплина и KPI, регламентыНе хранится в репо
«Структура RMS для CRM» (ред. 21.07.2026) + «Матрица прав RMS.xlsx»Только в коммитах 0d2968e…843c82aДерево дивизионов, 11 ролей, воронка пресейла, матрица 16 объектов × 11 ролей, реестр обязательств, табели, NDAНе хранится в репо

Первое действие плана: положить оба документа в docs/specs/ (md или pdf), чтобы код и ТЗ жили вместе.


1. Состояние на сегодня ​

1.1. Факты, которые проверены ​

ПроверкаРезультат
go test ./...Все пакеты проходят, включая internal/contractcheck (контракты перегенерированы после этапов 1–6)
go vet ./...Чисто
gofmt -l ./cmd ./internalЧисто
CI.github/workflows/go-quality.yml: gofmt, go vet, go test ./... -count=1 на каждый push и PR
OpenAPI / endpoints.jsonМаршруты /obligations, /timesheets/*, /nda/* и поле close_probability присутствуют
docs/rbac_matrix.md, docs/concepts/Этапы 4–6 описаны: concepts/permissions-matrix.md, obligations.md, timesheets.md, nda.md; project-lifecycle.md обновлён под воронку; rbac_matrix.md содержит 11 ролей спецификации и org.view/org.manage
Фронт: тестыНет ни одного (нет vitest, playwright, *.spec)
Фронт: lintПочинен: eslint src -c .eslintrc.cjs; есть typecheck (vue-tsc --noEmit)
Фронт: сборка / typecheckpnpm typecheck — 0 ошибок, pnpm build проходит; lint — около 4400 унаследованных замечаний стиля
Фронт: главная страницаЦеликом на моках (pages/index.vue)

1.2. Что бекенд умеет (кратко) ​

Бекенд заметно шире MVP из ТЗ. Реализованы: org/RBAC (Casbin + матрица прав), CRM (компании, контакты, реквизиты, DaData), проекты со спецификациями (ТЗ с версиями и согласованием), команда и заявки на людей, задачи с рабочими сессиями, учёт времени + табели + KPI-заполняемость + напоминания, чаты и уведомления (WS + email), файлы и генерация документов (смета, счёт, договор, акт, допник в PDF), финансы (cost items, бюджет с наценкой/УСН/НДС, платежи, ДДС, дебиторка, НДС-регистр, payroll, договоры), логистика (заявки на оборудование с маршрутом Менеджер → Финансы → Закупки → операции, автоплан), командировки, оборудование и остатки по локациям, реестр обязательств и постоянные платежи, NDA-гейт, аудит, дашборд и отчёты, XLSX-выгрузки.

1.3. Что фронт покрывает ​

Покрыто: авторизация, админка (пользователи, отделы, должности, роли, реквизиты, оборудование, локации, NDA-отчёт), компании и контакты, проекты (задачи, файлы, оборудование, логистика, команда, бюджет, документы), задачи, логистика, командировки, табели, обязательства, чаты, уведомления, профиль, поиск.

Не покрыто (бекенд есть, UI нет): дашборд на данных, отчёты, KPI, спецификации проекта (ТЗ), заявки на людей, поставщики, платежи и финотчёты, договоры, payroll, аудит, экспорт в XLSX, папки файлов, публикация версии NDA, спецификации и закупка по заявкам на оборудование.


2. Сверка с ТЗ ​

Легенда: ✅ есть и работает, 🟡 частично / есть только на бекенде, ❌ нет.

2.1. MVP v1.0 (раздел 3–9 ТЗ) ​

Требование ТЗБекендФронтКомментарий
Проект: название, клиент, город, ответственный, статус, план/факт доход, статьи расходов, даты, ссылка на ТЗ✅🟡ТЗ хранится как файл или spec-версия; вкладки спецификаций на фронте нет
Клиент✅✅companies + contacts
ТЗ (упрощённая форма)✅❌POST /projects/:id/specs с submit/approve/reject. UI нет
Задачи: проект, ответственный, дедлайн, статус, комментарии, файлы✅🟡Комментарии на бекенде есть, во фронте объявлен путь, не используется
Оборудование: тип, id, текущее место, следующее место, статус, период, проект✅✅Следующее место и период выводятся из резервов логистики
Город / Локация: название, тип, адрес, ответственный✅✅Отдельной сущности «город» нет; локация типа city/venue/warehouse. Достаточно
Логистическая операция: откуда → куда, даты план/факт, статус, способ, перевозчик, расходы, ответственный✅✅Статусов больше, чем в ТЗ (10 + cancelled/exception), UI их показывает
Склад: название, город, статус, аренда в месяц🟡🟡Локация типа warehouse; поле «аренда в месяц» не проверено, скорее всего нет
Командировка: сотрудник, маршрут, даты, статус, билеты/проживание/суточные, бюджет✅✅Подзадачи «билеты / жильё / аванс / авансовый отчёт» создаются автоматически
Воронка статусов проекта: Лид → Обсуждаем → Сметим → Подтверждён → В работе → Сдан → Закрыт✅🟡Код: planned → waiting_feedback/brief/budget/mf → estimating → approved → in_progress → done → closed, плюс lost, cancelled. Соответствие полное, но на фронте нужны русские лейблы по ТЗ и фильтр «воронка»
Финансы-лайт: план/факт доход, 10 статей, прибыль = доход − расходы✅✅Категории cost items совпадают с ТЗ один в один
Фильтр списка проектов по прибыли❌❌
Роли: руководители видят всё, PM только свои, сотрудники свои задачи✅✅Матрица 16×11 применяется на сервере с охватом «свой дивизион»; UI гейтит маршруты, меню и кнопки. См. permissions-matrix
Дашборд: воронка, топ прибыль/убыток, оборудование в транзите, логистика/командировки на период, план/факт логистики, загрузка складов✅✅Главная — 4 графика (воронка, прибыль, деньги по месяцам, лента перевозок с оборудованием в пути), остальное — «Отчёты» → «Операции» (2026-09-30, Дашборд руководства)
Экспорт любого списка в Excel🟡❌Бекенд: проекты, задачи, командировки, cost items. UI кнопок нет

2.2. Стратегия и финансовое ядро (первая половина ТЗ) ​

ТребованиеСтатусКомментарий
Отделы + функциональные роли внутри отделов✅Дерево дивизионов (dept_type) и справочник функциональных ролей отделов для подбора команды (2026-09-30, Управление командой)
Справочники: города, проекты, оборудование, поставщики🟡Поставщики: бекенд есть, UI нет
При создании проекта: задача менеджеру, группа, папка с подпапками🟡Kickoff-задача (по конфигу) и проектный чат есть. Автоструктура папок «КП / Договоры / Фотоотчёты / Отчётность» нет
Заявка на подбор команды🟡team-requests на бекенде полностью, UI нет
Заявка на оборудование с маршрутом Менеджер → Финансы → Закупки✅Реализовано глубже ТЗ
Фотоотчёты в папку проекта🟡Файлы проекта есть, папок/категорий нет
Таймтрекер в задачах✅start / pause / finish → auto time entries
Статьи расходов, почасовая ставка сотрудника (конфиденциально)✅hourly_rate в команде + compensation profiles; поле скрывается по матрице
ТЗ → автоматический расчёт бюджета (часы × ставка + резерв %)❌Ни модели «блоков ТЗ с часами», ни расчёта. Кандидат на этап 7
Факт трудозатрат в деньгах из учёта времени❌finance.UpsertForTimeEntry написан, но нигде не вызывается
Виджет «Динамика бюджета» план/факт/отклонение🟡finance/projects/:id/summary есть; виджета и графика нет
Напоминания 18:00 сотруднику, 10:00 руководителю✅Воркер работает в UTC, нужен часовой пояс компании
Нельзя закрыть задачу без фактического времени🟡finish создаёт запись из сессии; флаг required_time_logging есть, но жёсткого запрета «done без часов» нет
KPI заполняемости (факт / план по закрытым задачам), светофор🟡/kpi/time-logging считает fill_rate; UI нет; светофора нет
Финансовые ворота: «без ТЗ нет денег», «без расходов в системе нет оплаты»❌Платежи не проверяют наличие утверждённой спецификации/сметы
Дашборд рисков: заполняемость по отделам, незапланированные работы, ТОП-5 рискованных проектов❌
Раздел «Регламенты», цели компании/отделов❌Вне MVP, бэклог

2.3. Спецификация «Структура RMS» (этапы 1–6, коммиты июля 2026) ​

ПунктБекендФронтГде ломается
Дерево дивизионов, коды, типы✅✅
Проект ↔ несколько дивизионов✅🟡Создание учитывает отделы, фильтры в списке нет
Воронка пресейла, вероятность закрытия, mount_date, contract_deadline✅🟡Лейблы и подсказки по вероятности
Матрица прав 16 × 11🟡🟡Применяется только в projects (скрытие полей), obligations, timesheets, timeentries. Охват «свой» (O) работает как «все». См. 3.1
Реестр обязательств, постоянные платежи✅✅Нет уведомлений об эскалации; создание следующего периода не в транзакции
Табели: черновик → сдан → утверждён/возвращён✅✅После сдачи часы не блокируются в timeentries (только флаг is_editable для клиента). Payroll берёт сырые часы, а не утверждённые табели
NDA при первом входе✅✅Публикация версии только через API; код nda.manage заведён, но маршрут закрыт nda.report

3. Фундамент: что нужно выправить до новых фич ​

Эти пункты не «фичи», а условия, без которых каждая следующая доработка будет удваивать долг.

3.1. Единый контур прав ​

Сейчас три несогласованных механизма на бекенде и три на фронте.

Бекенд:

  1. Коды прав в middleware (projects.manage, finance.manage, …). Выданы старым ролям (admin, director, head_of_department, manager, engineer, logist, accountant, lawyer). Новым 11 ролям выданы только obligations.*, timesheets.*, nda.*.
  2. Casbin по домену отдела на запись.
  3. Матрица спецификации (internal/permissions/matrix.go). Применяется в 4 модулях. ScopeOwn не реализован нигде: везде проверяется только «больше, чем none».
  4. Проверки по строке роли: head_of_department в projects/service.go:66, tasks/service.go:180, pm/finance/lead/manager в projects/service.go:1378, аналогично в logistics. Новые роли через них не проходят. Всего 14 таких мест.

Фронт:

  1. canAccessAdmin по подстрокам названия роли/должности («директор», director, «админ») — store/auth.ts:84-96. «Арт-директор» получит админку.
  2. usePermissions (матрица из /org/me). Не сбрасывается при выходе: следующий пользователь в той же вкладке унаследует права предыдущего.
  3. CASL объявлен в меню, но плагин не подключён: can() всегда true.

Целевое состояние:

  • Матрица спецификации — единственный источник для видимости и полей. Коды *.manage остаются как технический слой, но каждой роли спецификации выдаётся полный набор кодов через миграцию.
  • ScopeOwn реализован: «свой» = свой дивизион (по project_departments) или свои проекты (manager_id, команда).
  • Все 14 проверок по строке роли заменены на вызов резолвера матрицы или на код права.
  • Фронт: один composable usePermissions, сброс при logout, гард роутера и меню только по нему. canAccessAdmin = users_and_rights.read.
  • Тест-матрица: таблица «роль × действие × ожидание» в internal/permissions/matrix_test.go и e2e-смоук по ролям.

3.2. Дисциплина контрактов и CI ​

  • go run ./cmd/generate_contracts && go run ./cmd/generate_endpoints после каждого изменения DTO. Сейчас отставание на 6 этапов.
  • В CI: gofmt -l, go vet, go test ./... (включая contractcheck), фронт pnpm typecheck и pnpm lint (после починки скрипта).
  • docs/rbac_matrix.md расширить на 11 ролей и 16 объектов; docs/concepts/ дополнить страницами obligations.md, timesheets.md, nda.md, permissions-matrix.md, project-funnel.md.

3.3. API-слой фронта ​

  • 19 копий parseList/unwrapList. Заменить одним unwrapList<T>(): {items, meta} в src/api/index.ts, читать meta.total и передавать limit/offset во всех списках (сейчас только в 5 модулях).
  • Захардкоженные URL (api/companies.ts, pages/admin/equipment.vue '/suppliers') перенести в API_PATHS.
  • Дубли: api/clients и api/companies оба содержат listCompanies; /companies зарегистрирован в роутере дважды.

3.4. Деньги в отчётах ​

dashboard/model.go и reports/model.go используют float64. Перевести на money.Money и применить скрытие полей по матрице (earnings_margin, client_budget) — сейчас прибыль видит любой обладатель dashboard.view, включая инженера.

3.5. Тестовая база ​

  • Бекенд без тестов: companybankaccounts, companycontacts, entitylinks, org, requisites, search, подпакеты documents, транспорт obligations, почти весь timesheets.
  • Фронт: завести vitest для API-слоя и composables, Playwright для одного сквозного сценария (раздел 5).

4. План доработки по этапам ​

Каждый этап самодостаточен и заканчивается регенерацией контрактов, обновлением docs/concepts/ и зелёным CI. Оценки в рабочих днях для команды «1 бекенд + 1 фронт».

Этап 0. Гигиена и фиксация базы (2–3 дня) — сделано ​

Сделано:

  • Бекенд: контракты перегенерированы, gofmt чист, go test ./... зелёный включая contractcheck; добавлен CI .github/workflows/go-quality.yml (gofmt, go vet, go test); ТЗ положены в docs/specs/; docs/rbac_matrix.md обновлён, написаны концепт-страницы permissions-matrix, obligations, timesheets, nda, обновлён project-lifecycle.
  • Фронт: lint починен (starter-kit → src), typecheck есть; usePermissions сбрасывается при logout (resetPermissions); убран дубль маршрута /companies; удалён мёртвый код Vuexy (examples/, components/dialogs/*) и неиспользуемые зависимости.

Бекенд:

  • Перегенерировать контракты, отформатировать 2 файла, зелёный go test ./....
  • Добавить в CI gofmt, go vet, go test.
  • Положить оба ТЗ в docs/specs/.
  • Обновить docs/rbac_matrix.md и написать 4 концепт-страницы по этапам 4–6.

Фронт:

  • Починить lint (starter-kit → src), добавить typecheck в CI.
  • Сброс usePermissions при logout. Убрать дубль /companies, мёртвые components/dialogs/*, examples/, dist/, неиспользуемые зависимости (firebase, mapbox-gl, swiper, @fullcalendar/*, chart.js — оставить один chart-пакет для дашборда).
  • Заменить бейдж «5» и пункт «Прочее».

Критерий готовности: CI зелёный на обоих репо, документация соответствует коду.

Этап 1. Единый контур прав (5–7 дней) — сделано ​

Сделано:

  • Бекенд: миграция 0137 выдала 11 ролям спецификации коды прав по матрице; ScopeOwn реализован в internal/permissions и применён к project_card, costs, reports_dashboards, others_timesheets; проверки по строке роли заменены на матрицу (RequireObjectAccess/RequireObjectWrite в app.go, Actor.Perms в projects, tasks, dashboard, reports, разделы проекта в authorization/access.go); nda.manage на публикацию версии; матрица отдаётся фронту в GET /org/me; тесты роль × объект в internal/permissions/matrix_test.go, rbac/service_test.go, authorization/access_test.go, projects/service_test.go.
  • Фронт: src/permissions/routeAccess.ts — единая карта «маршрут → объект матрицы» для гарда роутера и меню; canAccessAdmin считается из users_and_rights; кнопки создания/редактирования скрыты по canWrite(object); CASL оставлен заглушкой (@layouts/plugins/casl.ts) поверх матрицы.
  • Попутно: миграции 0002 и 0048 не поднимались на чистой базе (CREATE TYPE IF NOT EXISTS и тело plpgsql без маркеров goose) — исправлены, цепочка 0001…0137 проходит с нуля; обновление проекта больше не отвечает 403 из-за побочной синхронизации проектного чата.

Проверено вживую (scratch-база, сид + пользователи WAREHOUSE, DIV_HEAD, TECH_DIR, 54 запроса): склад не видит проекты и контрагентов, но ведёт склад и локации; руководитель дивизиона видит и правит только проекты, табели, часы и бюджет своего дивизиона, прибыль в отчётах скрыта; инженер читает все карточки, но не бюджет и не чужие часы; техдиректор правит все проекты, видит все табели, прибыль скрыта.

Осталось (перенесено в следующие этапы):

  • Проверки admin и director по строке роли в части модулей (files, chats, search, audit, users, documents) остались как legacy-путь; переводить при работе с модулем.
  • GET /time-entries закрыт time_entries.manage до сужения по матрице: FIN_DIR и FIN_ASSIST («Р» по чужим табелям) получают 403 на список.
  • Legacy-роли сохраняют старые коды через fallback: например, manager создаёт компании, хотя MANAGER в матрице только читает контрагентов. Уйдёт после перевода пользователей на роли спецификации.
  • obligations.amount и суммы сводки во float64 вместо money.Money.
  • Объекты project_status_dates и own_timesheet в коде не используются.
  • Фронт не видит legacy-коды (nda.report, nda.manage): /org/me отдаёт только матрицу.

Бекенд:

  • Миграция: выдать 11 ролям спецификации коды прав согласно матрице (таблица соответствия объект → коды).
  • Реализовать ScopeOwn в резолвере и применить к others_timesheets, costs, project_card, reports_dashboards.
  • Заменить 14 проверок по строке роли на матрицу/коды.
  • Применить матрицу к dashboard, reports, finance, contracts, suppliers, departments, users, equipment.
  • nda.manage на публикацию версии.
  • Тесты: таблица роль × объект × ожидание.

Фронт:

  • Один usePermissions; canAccessAdmin через users_and_rights; гард /admin/* включая /admin/nda, /admin/equipment, /admin/locations.
  • Скрывать кнопки создания/редактирования по canWrite(object) в проектах, CRM, логистике, задачах.
  • Удалить CASL или подключить его как обёртку над матрицей.

Критерий: под каждой из 11 ролей пройден смоук «что вижу / что могу», результаты совпадают с xlsx-матрицей.

Этап 2. Замкнуть проектный цикл в UI (7–10 дней) — сделано, ждёт ручного прогона ​

Цель: сценарий из ТЗ «проект → ТЗ → команда → оборудование → логистика → командировки → финансы → закрытие» полностью проходим из интерфейса.

Сделано:

  • Бекенд: GET /projects/:id/specs и GET /projects/:id/team-requests?status= (канонический конверт); миграция 0138 — набор папок ТЗ, Коммерческие предложения, Договоры, Фотоотчёты, Отчётность, Прочее для новых и существующих проектов (флаг конфигурации не понадобился: папки дешёвые, триггер уже был); actual_profit в проекте и выгрузке (только при earnings_margin), фильтр profit_min/profit_max с 403 без права; status принимает список через запятую для групп воронки; уведомление о ТЗ на согласовании уходит и руководителям всех дивизионов проекта; заявки на людей переведены на матрицу прав и больше не требуют основного отдела; папки и комментарии задач отдаются в каноническом конверте; CORS отдаёт Content-Disposition.
  • Безопасность: GET /trips/export отдавал все командировки компании любому с trips.manage — теперь выгрузка сужается так же, как список.
  • Фронт: общий модуль статусов с русскими лейблами воронки; список проектов с серверной пагинацией, группами воронки, фильтрами дивизиона, менеджера и прибыли, экспортом; в карточке — смена статуса по графу переходов, вероятность со сбросом к значению статуса, дата монтажа, дедлайн контрактации, дивизионы, бейдж «ТЗ утверждено»; вкладки «ТЗ» (версии, форма упрощённого ТЗ, файл в папку ТЗ, согласование) и «Заявки на людей» (создание, отправка, решение, назначение по строкам); папки в файлах проекта с превью фотоотчётов; комментарии к задачам; «Экспорт в Excel» для проектов, задач, командировок и статей затрат.

Проверено: бекенд — unit-тесты и живой смоук на scratch-базе; фронт — typecheck, build, lint новых файлов. В браузере не проверялось: нужен вход под учётной записью, прогон сценария раздела 5 остаётся за командой.

Осталось:

  • Поиск по тексту в списке проектов работает только по текущей странице: у бекенда нет параметра поиска.
  • Экспорт задач и командировок не знает браузерных фильтров (сроки, поиск, «без проекта»): интерфейс предупреждает тостом.
  • Назначение по заявке принимает только сотрудников, чей основной отдел совпадает с отделом строки.
  • Два API-модуля папок во фронте (api/files и api/projects/specs) дублируют друг друга.

Бекенд:

  • Автоструктура папок при создании проекта: «Коммерческие предложения», «Договоры», «Фотоотчёты», «Отчётность» (через существующие /folders), флаг конфигурации по аналогии с чатом.
  • Фильтр проектов по дивизиону (project_departments) и по прибыли (profit_min/max).
  • Уведомления для team-requests уже есть; добавить для спецификаций тем, кто согласует.

Фронт:

  • Вкладка «ТЗ / Спецификация» в карточке проекта: версии, submit / approve / reject, ссылка на файл.
  • Вкладка «Заявки на людей» (team-requests): создание, отправка, назначение по строкам.
  • Русские лейблы воронки по ТЗ (Лид / Обсуждаем / Сметим / Подтверждён / В работе / Сдан / Закрыт / Потерян / Отменён), вероятность закрытия с подсказкой «по умолчанию для статуса», contract_deadline и mount_date в карточке.
  • Кнопки «Экспорт в Excel» на списках проектов, задач, командировок, cost items.
  • Папки в файлах проекта, загрузка фотоотчётов в свою папку.
  • Комментарии к задачам.

Критерий: сквозной сценарий (раздел 5) проходится вручную без API-клиента.

Этап 3. Время → деньги → дисциплина (7–10 дней) — сделано, ждёт ручного прогона ​

Сделано (бекенд):

  • Часы за месяц со сданным или утверждённым табелем не принимаются ни ручным вводом, ни паузой/завершением сессии задачи (409 timeentries_period_locked, сессия остаётся открытой).

  • Трудозатраты: строка labor на каждую запись времени, ставка — проектная hourly_rate → почасовой профиль → оклад / норма часов (APP_LABOR_MONTHLY_NORM_HOURS). Пересчёт после каждой записи и после утверждения табеля. План у таких строк пустой.

  • Payroll считает почасовую оплату только по утверждённым табелям, статус табеля — в расшифровке строки.

  • Задачу с обязательным учётом времени нельзя закрыть, если по ней набралось меньше минуты (400 time_required). Раньше закрытие через «старт-финиш» за секунды проходило.

  • APP_TIMEZONE (по умолчанию Europe/Moscow) для дат записей и напоминаний 18:00 / 10:00; карты отправленных напоминаний больше не растут бесконечно.

  • Табели: история статусов, обязательная причина возврата, защита от гонки решений; уведомления «сдан» руководителям отделов, «утверждён» и «возвращён» сотруднику.

  • Обязательства: суммы в money.Money; исполнение постоянного платежа и следующий период — одна транзакция, без задвоения при повторном исполнении; уведомление о новом периоде; воркер эскалаций ответственному и FIN_DIR.

  • KPI: охват по матрице reports_dashboards, поле level (светофор), канонический конверт.

  • Карточка задачи отдаёт logged_hours (сумма всех записей по задаче); GET /time-entries открыт всем с сужением до своих записей; ошибка time_required приходит одним кодом из всех путей.

Сделано (фронт): страница «KPI учёта времени» со светофором, по отделам и по сотрудникам; в табеле — история статусов, причина возврата, уведомление о блокировке, обязательная причина при возврате; понятные сообщения о закрытом месяце при вводе времени, паузе и завершении; в карточке задачи — план и факт часов, отметка «Учёт времени обязателен», подтверждение при слишком короткой сессии, кнопка «Добавить время»; в бюджете трудозатраты свёрнуты в одну строку с раскрытием, строки с источником только для чтения, список статей грузится целиком.

Проверено: бекенд — unit-тесты и живой смоук на scratch-базе (28 проверок API + воркер эскалаций с дедупликацией); фронт — typecheck, build, lint изменённых файлов не хуже HEAD. В браузере не проверялось.

Решения, которые стоит подтвердить заказчику: часовой пояс по умолчанию — Москва; оклад переводится в стоимость часа по норме 168 часов; минимум для закрытия задачи с обязательным учётом — 1 минута.

Бекенд:

  • Блокировка записей времени за сданный/утверждённый период в timeentries (сервер, не только флаг).
  • Вызов finance.UpsertForTimeEntry при создании/изменении записи времени: часы × ставка (проектная hourly_rate, иначе compensation profile) → cost item labor. Пересчёт при утверждении табеля.
  • Payroll считает из утверждённых табелей.
  • Жёсткое правило: задача с required_time_logging не переводится в done без часов (ошибка time_required).
  • Часовой пояс компании для напоминаний (APP_TIMEZONE), сейчас UTC.
  • Уведомления: сдан табель (руководителю), табель возвращён (сотруднику), обязательство в зоне эскалации (финдиру), постоянный платёж создан.
  • Постоянные платежи: закрытие + создание следующего периода в одной транзакции.

Фронт:

  • Страница KPI заполняемости: по отделу и по сотруднику, период день/неделя/месяц, светофор >95 / 85–95 / <85.
  • В карточке задачи: план часов, факт, предупреждение при закрытии без времени.
  • В табеле: причина возврата, история статусов.

Критерий: у проекта после недели работы команды появляется строка labor в бюджете, совпадающая с утверждённым табелем; закрыть задачу без времени нельзя.

Этап 4. Дашборд руководства на реальных данных (7–10 дней) — сделано, ждёт ручного прогона ​

Сделано (бекенд):

  • GET /dashboard/executive?horizon_days=7|30 — все блоки главной одним запросом: воронка по статусам и городам, портфель (активные проекты, их бюджет, взвешенная воронка), ТОП-5 прибыли и убытка, оборудование в пути, логистика и командировки на горизонт, план/факт логистики за месяц, загрузка складов по статусам оборудования, загрузка сотрудников против нормы, заполняемость времени по отделам со светофором, ТОП-5 проектов с риском недостоверности данных.

  • Каждый денежный или закрытый блок скрывается по матрице прав на сервере и попадает в hidden; охват «свой» сужает выборку до своих дивизионов.

  • GET /projects/:id/budget/dynamics — план, накопленный факт по неделям и отклонение.

  • Сводка /dashboard/overview и отчёты /reports/* переведены на money.Money.

  • Документация: Дашборд руководства.

  • Отчёты /reports/projects_by_status и /reports/expense_breakdown отдаются в каноническом конверте; дата «по» в фильтрах включает весь последний день (раньше терялся почти весь день).

Сделано (фронт): главная / — дашборд руководства из одного запроса, виджеты скрываются по hidden, переключатель горизонта 7/30, при отсутствии доступа — приветственная карточка со ссылками на задачи и табель; моки и картинки старой главной удалены; во вкладке бюджета проекта — «Динамика бюджета» (план, факт, отклонение, график накопленного факта); страница «Отчёты» /reports с фильтрами по периоду и отделу, тремя разделами и экспортом в CSV.

Проверено: бекенд — unit-тесты скрытия блоков и охвата, живой прогон на scratch-базе под администратором, руководителем дивизиона и инженером, динамика сверена с суммами в базе; фронт — typecheck, build, lint изменённых файлов не хуже HEAD, страницы прогнаны на заглушке API по Go-контрактам. С настоящим бекендом в браузере не проверялось.

Осталось: загрузка складов считает позиции оборудования, а не занятую площадь — площади склада в модели нет; фильтр отчётов по периоду идёт по дате создания проекта.

Бекенд:

  • money.Money в dashboard/reports; редакция полей по матрице.
  • Новые агрегаты (один эндпоинт GET /dashboard/executive с блоками, чтобы фронт делал один запрос):
    • воронка проектов по статусам и по городам;
    • топ-N проектов по прибыли и убытку;
    • сумма бюджетов активных проектов;
    • оборудование в транзите (единицы, маршрут, ETA);
    • логоперации и командировки на 7 / 30 дней;
    • план/факт по статьям «Логистика» за период;
    • загрузка складов (остатки по локациям типа warehouse);
    • загрузка сотрудников (часы по табелям / норма);
    • заполняемость времени по отделам (светофор);
    • ТОП-5 проектов по риску недостоверности (доля задач без часов + доля задач вне спецификации).
  • Виджет «Динамика бюджета» проекта: план / факт / отклонение по неделям.

Фронт:

  • Заменить pages/index.vue на дашборд из виджетов с одним chart-пакетом; виджеты скрываются по матрице.
  • Страница «Отчёты»: projects_by_status, financial_overview, expense_breakdown с фильтрами и экспортом.

Критерий: главная страница без единого мока; руководитель видит 4 блока из ТЗ (воронка, прибыль, оборудование, логистика/командировки).

Этап 5. Финансовые ворота и закрытый контур финотдела (5–8 дней) — бекенд сделан ​

Сделано (бекенд):

  • «Без учёта расходов — нет оплаты»: исходящий платёж обязан ссылаться на статью затрат проекта, позицию реестра обязательств или документ (400 payments_expense_required). Входящие деньги не блокируются; выплата утверждённой ведомости обоснована самой ведомостью.
  • «Без ТЗ — нет денег»: платёж по проекту без утверждённого ТЗ отклоняется (409 payments_spec_required, в сообщении названо имя проекта). Проверяются все проекты платежа — по статье затрат, обязательству и документам.
  • Мини-ТЗ: POST /projects/:id/specs/express с обязательной причиной создаёт сразу утверждённую версию ТЗ и разблокирует оплату срочного проекта; причина остаётся в истории версий и в аудите.
  • Миграция 0140: у платежа появились ссылки на статью затрат и обязательство.
  • GET /suppliers/:id/deals — история сделок поставщика: его оборудование и логистические операции с ним.
  • Документация: Финансовые ворота.

Проверено: unit-тесты обоих правил и мини-ТЗ; живой прогон на scratch-базе — 15 проверок, включая исключение для зарплаты.

Бекенд:

  • Правило «без ТЗ нет денег»: платёж/счёт по проекту невозможен, пока у проекта нет утверждённой спецификации (или флаг «мини-ТЗ» с обязательным комментарием). Ошибка spec_required.
  • Правило «без расходов нет оплаты»: исходящий платёж обязан ссылаться на cost item или обязательство.
  • Поставщики: история сделок (связь с заявками на оборудование и cost items).

Фронт:

  • Справочник поставщиков (CRUD, реквизиты, история).
  • Платежи-лайт: счета, платёж по документу, статус оплаты в карточке проекта.
  • Договоры-лайт: список, сроки, привязка к проекту (бекенд готов).
  • Публикация новой версии NDA из админки.

Критерий: бухгалтер не может провести оплату по проекту без утверждённого ТЗ и cost item; поставщики ведутся в системе.

Этап 6. Качество, UX, безопасность — бекенд сделан ​

Сделано (бекенд, безопасность):

  • Подключение по WebSocket — по одноразовому тикету (POST /ws/ticket, миграция 0141): access-токен больше не ездит в URL и не оседает в логах прокси. Тикет живёт минуту, гасится при первом использовании, хранится хешем. Старый способ ?token= работает, но считается устаревшим.
  • Гейт соглашения о неразглашении кеширует положительный ответ на минуту: раньше он делал 1–2 запроса к базе на каждый запрос API. Принятие и выпуск новой версии сбрасывают кеш.

Сделано (бекенд, известные проблемы аудита):

  • N+1 в задачах (соисполнители при выдаче списка и при сохранении) и в чатах (вложения сообщения) — теперь по одному запросу.
  • Миграция 0142: индексы под выборки учёта времени и комментариев; убран дубль уникального индекса на источник статьи затрат.
  • Откат миграции 0017 больше не удаляет таблицу задач вместе с данными.
  • Последние списки без конверта (реквизиты, банковские счета, связи компаний и контактов) приведены к канону.
  • Фоновые задачи получили жизненный цикл приложения вместо context.Background(); оставшиеся два случая — обновление присутствия после разрыва соединения, с таймаутом.
  • co_assignee_ids всегда массив: с omitempty пустой список пропадал из ответа (та же ошибка, что чинили для department_ids).
  • Связанные сущности в карточке больше не двоятся, если запись связана и структурно, и вручную; в ответе остаётся та связь, которую интерфейс умеет отвязать.
  • Тесты для модулей без покрытия: банковские счета компании и связи сущностей.

Проверено: unit-тесты, живой прогон на scratch-базе (11 проверок: тикеты, повторное использование, конверты списков, соисполнители), откат и повторное применение миграций 0141–0142 без потери данных.

Фронт:

  • Разбить гигантские SFC: logistics.vue (7393), LiveChatPanel (4000), CompanyDetailsPage (3835), ProjectEquipmentTab (3131), ProjectDocumentsTab (2468), UserProfile (2413), AdminUsersPage (2091). Правило: файл ≤ 600 строк, вкладка = компонент.
  • Общие компоненты: DataTable с серверной пагинацией, EmptyState, ConfirmDialog, скелетоны. Убрать window.confirm.
  • Валидация форм через :rules + серверные field errors одним хелпером.
  • Решить про i18n: либо выключить и удалить vue-i18n, либо вынести строки. Рекомендация: выключить, интерфейс русский.
  • Мобильный режим: минимум для задач, табеля, чата.
  • WebSocket чата: обрабатывать событие точечно, а не полной перезагрузкой состояния.

Безопасность (не блокирует тестирование бизнес-логики, но в проде обязательно):

  • Cookie токенов: Secure, SameSite=Strict; рассмотреть httpOnly refresh через backend-прокси.
  • WS-токен в query: заменить на короткоживущий ticket (POST /ws/ticket).
  • Известные пункты из CLAUDE.md 1–11 (N+1, индексы, context.Background() в воркерах, неограниченный рост карт напоминаний).
  • NDA-гейт: кеш результата на TTL, сейчас 1–2 SQL на каждый запрос.

Тесты:

  • Playwright: сквозной сценарий раздела 5 под ролями OWNER, MANAGER, ENGINEER, FIN_DIR, WAREHOUSE.
  • Бекенд: покрыть модули без тестов из 3.5.

Этап 7. Бэклог — расчёт сметы из ТЗ сделан, ждёт ручного прогона ​

Сделано (бекенд):

  • Смета из ТЗ: миграция 0143 (строки оценки, ставка отдела, резерв на риски в настройках бюджета); GET/PUT /projects/:id/specs/:version/estimate и POST .../estimate/apply. Расчёт повторяет пример из ТЗ: 50 ч × 2500 + 30 ч × 2000 = 185 000, резерв 20 % = 37 000, итог 222 000.
  • Строка без ставки не считается бесплатной работой: она попадает в missing_rates, и менеджер видит, что смета неполная.
  • Применение доступно только по утверждённому ТЗ (в том числе мини-ТЗ); ставки фиксируются в строках, поэтому правка ставки отдела не меняет согласованный бюджет; повторное применение обновляет те же строки бюджета.
  • Задача на согласование бюджета ставится менеджеру проекта. Попутно исправлено: сервис задач подключался только при включённом флаге стартовой задачи, из-за чего задача согласования не создавалась; флаг теперь управляет только стартовой задачей.
  • Отчёт «Незапланированные работы» (GET /reports/unplanned_work): доля задач без плановых часов, их часы и стоимость; стоимость скрывается без доступа к затратам.
  • Попутно исправлено: сохранение настроек бюджета стирало заметки проекта, если поле не передали. Теперь отсутствующее поле сохраняет прежнее значение, как и остальные необязательные; очистить заметку можно пустой строкой.
  • Документация: Смета из ТЗ.

Сделано (фронт): во вкладке ТЗ — выбор версии и редактор сметы: строки по блокам с итогами, добавление и правка в диалоге, сводка «Смета без резерва / Резерв / Итого», резерв правится на месте, кнопка применения с подтверждением и ссылкой на задачу согласования; без прав на карточку проекта редактор только для чтения. В отчётах — раздел «Незапланированные работы» с выгрузкой в CSV.

Проверено: бекенд — unit-тесты расчёта (включая пример из ТЗ, фиксацию ставки и пустую смету) и живой прогон на scratch-базе, 17 проверок; фронт — typecheck, build, lint без новых ошибок, экраны прогнаны на заглушке API. С настоящим бекендом в браузере не проверялось.

Расхождения, которые стоит знать: ставка берётся только из отдела — проектной ставки по умолчанию в системе нет, строка без отдела всегда попадает в пробелы; резерв на риски ложится в статью «Прочее», отдельной статьи под него нет.

Сделано 2026-09-30: функциональные роли внутри отделов для подбора команды, аудит-лента в интерфейсе (журнал действий и вкладка «История»).

Осталось в бэклоге: раздел «Регламенты» и цели компании и отделов, шаблоны документов в интерфейсе.

  • ТЗ → автосмета: блоки ТЗ с оценкой часов по статьям, ставки по отделам, резерв %, задача на согласование бюджета.
  • Отчёт «отклонение из-за незапланированных работ» (задачи вне спецификации).
  • Функциональные роли внутри отделов для подбора команды.
  • Раздел «Регламенты» (живые документы), цели компании/отделов.
  • Аудит-лента в UI, шаблоны документов в UI.

5. Сквозной сценарий для тестирования бизнес-логики и UX ​

Прогонять после каждого этапа. Роли указаны по спецификации.

  1. OWNER: создаёт отделы и пользователей, выдаёт роли. Проверка: у каждого пользователя меню соответствует матрице.
  2. MANAGER: создаёт компанию по ИНН, контакт, проект (город, дивизион, даты, плановый доход). Проверка: проект в статусе «Лид», создан чат, kickoff-задача, папки.
  3. MANAGER: загружает ТЗ, создаёт спецификацию, отправляет на согласование. DIV_HEAD утверждает. Проверка: статус «Сметим» → «Подтверждён», has_approved_spec.
  4. MANAGER: заявка на людей (2 роли). DIV_HEAD другого дивизиона назначает сотрудников. Проверка: они в команде и в чате проекта.
  5. MANAGER: заявка на оборудование (позиции, количество, авторасчёт). FIN_DIR согласует сумму. WAREHOUSE/логист: план операций, подтверждение маршрута. Проверка: оборудование reserved → in_transit, cost item logistics.
  6. MANAGER: командировка для инженера. Проверка: задачи «билеты / жильё / аванс», cost item travel.
  7. ENGINEER: задачи start / pause / finish, загрузка фотоотчёта в папку. Проверка: auto time entries, нельзя закрыть задачу без часов (этап 3).
  8. ENGINEER: сдаёт табель. DIV_HEAD: утверждает. Проверка: часы заблокированы, строка labor в бюджете (этап 3).
  9. FIN_DIR: смета, счёт, оплата. Проверка: без утверждённого ТЗ оплата невозможна (этап 5); actual_revenue обновился.
  10. OWNER: дашборд. Проверка: проект виден в воронке, прибыль, оборудование в пути, командировка на неделе (этап 4).
  11. MANAGER: проект «Сдан» → «Закрыт». Проверка: новые cost items запрещены, экспорт в Excel совпадает с экраном.
  12. ENGINEER пытается открыть чужой проект, чужой табель, бюджет. Проверка: 403 и скрытые поля, а не пустой экран.

6. Открытые вопросы к заказчику ​

  1. Часовой пояс компании для напоминаний 18:00 / 10:00 (сейчас UTC). Решено 2026-09-30: оставляем UTC.
  2. «Мини-ТЗ» для срочных проектов: достаточно ли флага с комментарием, чтобы открыть оплату?
  3. Резерв на риски (15–20 %): фиксированный на компанию или задаётся в проекте? Нужен для этапа 7. Решено 2026-09-30: свой в каждом проекте (project_budget_settings.risk_reserve_percent, так и сделано).
  4. Функциональные роли внутри отделов: нужны ли как отдельный справочник или хватит должностей (positions)? Решено 2026-09-30: отдельный справочник по отделам.
  5. Дашборд: какие 4 виджета обязательны на первом экране, остальные во вкладке «Отчёты»? Решено 2026-09-30: выбираем сами, только действительно нужные и в виде графиков, а не списков.
  6. Склад: нужна ли «аренда в месяц» как поле локации или как постоянный платёж в реестре обязательств (второе уже есть)? Решено 2026-09-30: платёж в реестре обязательств, поле локации не нужно.

7. Правила ведения этого документа ​

  • Этап закрыт, когда: CI зелёный, контракты перегенерированы, концепт-страница написана, сценарий раздела 5 пройден под ролями.
  • При закрытии этапа обновить таблицы раздела 2 (статусы ✅ / 🟡 / ❌) и дату в шапке.
  • Новые требования сначала попадают в раздел 2 или 4.7 (бэклог), а не сразу в код.

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