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) |
| Фронт: сборка / typecheck | pnpm 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. Единый контур прав
Сейчас три несогласованных механизма на бекенде и три на фронте.
Бекенд:
- Коды прав в middleware (
projects.manage,finance.manage, …). Выданы старым ролям (admin, director, head_of_department, manager, engineer, logist, accountant, lawyer). Новым 11 ролям выданы толькоobligations.*,timesheets.*,nda.*. - Casbin по домену отдела на запись.
- Матрица спецификации (
internal/permissions/matrix.go). Применяется в 4 модулях.ScopeOwnне реализован нигде: везде проверяется только «больше, чем none». - Проверки по строке роли:
head_of_departmentвprojects/service.go:66,tasks/service.go:180,pm/finance/lead/managerвprojects/service.go:1378, аналогично в logistics. Новые роли через них не проходят. Всего 14 таких мест.
Фронт:
canAccessAdminпо подстрокам названия роли/должности («директор»,director, «админ») —store/auth.ts:84-96. «Арт-директор» получит админку.usePermissions(матрица из/org/me). Не сбрасывается при выходе: следующий пользователь в той же вкладке унаследует права предыдущего.- 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 itemlabor. Пересчёт при утверждении табеля. - 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.md1–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
Прогонять после каждого этапа. Роли указаны по спецификации.
- OWNER: создаёт отделы и пользователей, выдаёт роли. Проверка: у каждого пользователя меню соответствует матрице.
- MANAGER: создаёт компанию по ИНН, контакт, проект (город, дивизион, даты, плановый доход). Проверка: проект в статусе «Лид», создан чат, kickoff-задача, папки.
- MANAGER: загружает ТЗ, создаёт спецификацию, отправляет на согласование. DIV_HEAD утверждает. Проверка: статус «Сметим» → «Подтверждён»,
has_approved_spec. - MANAGER: заявка на людей (2 роли). DIV_HEAD другого дивизиона назначает сотрудников. Проверка: они в команде и в чате проекта.
- MANAGER: заявка на оборудование (позиции, количество, авторасчёт). FIN_DIR согласует сумму. WAREHOUSE/логист: план операций, подтверждение маршрута. Проверка: оборудование
reserved→in_transit, cost itemlogistics. - MANAGER: командировка для инженера. Проверка: задачи «билеты / жильё / аванс», cost item
travel. - ENGINEER: задачи start / pause / finish, загрузка фотоотчёта в папку. Проверка: auto time entries, нельзя закрыть задачу без часов (этап 3).
- ENGINEER: сдаёт табель. DIV_HEAD: утверждает. Проверка: часы заблокированы, строка
laborв бюджете (этап 3). - FIN_DIR: смета, счёт, оплата. Проверка: без утверждённого ТЗ оплата невозможна (этап 5);
actual_revenueобновился. - OWNER: дашборд. Проверка: проект виден в воронке, прибыль, оборудование в пути, командировка на неделе (этап 4).
- MANAGER: проект «Сдан» → «Закрыт». Проверка: новые cost items запрещены, экспорт в Excel совпадает с экраном.
- ENGINEER пытается открыть чужой проект, чужой табель, бюджет. Проверка: 403 и скрытые поля, а не пустой экран.
6. Открытые вопросы к заказчику
Часовой пояс компании для напоминаний 18:00 / 10:00 (сейчас UTC).Решено 2026-09-30: оставляем UTC.- «Мини-ТЗ» для срочных проектов: достаточно ли флага с комментарием, чтобы открыть оплату?
Резерв на риски (15–20 %): фиксированный на компанию или задаётся в проекте? Нужен для этапа 7.Решено 2026-09-30: свой в каждом проекте (project_budget_settings.risk_reserve_percent, так и сделано).Функциональные роли внутри отделов: нужны ли как отдельный справочник или хватит должностей (Решено 2026-09-30: отдельный справочник по отделам.positions)?Дашборд: какие 4 виджета обязательны на первом экране, остальные во вкладке «Отчёты»?Решено 2026-09-30: выбираем сами, только действительно нужные и в виде графиков, а не списков.Склад: нужна ли «аренда в месяц» как поле локации или как постоянный платёж в реестре обязательств (второе уже есть)?Решено 2026-09-30: платёж в реестре обязательств, поле локации не нужно.
7. Правила ведения этого документа
- Этап закрыт, когда: CI зелёный, контракты перегенерированы, концепт-страница написана, сценарий раздела 5 пройден под ролями.
- При закрытии этапа обновить таблицы раздела 2 (статусы ✅ / 🟡 / ❌) и дату в шапке.
- Новые требования сначала попадают в раздел 2 или 4.7 (бэклог), а не сразу в код.