Финансы — бухгалтерия владельца, книги отделов, выплаты по задачам
Простыми словами: денежная сторона Staffing — конфиденциальная бухгалтерия, которую ведёт владелец платформы, отдельная независимая бухгалтерия, которую ведёт каждый руководитель отдела для своего отдела, переговоры о выплате по каждой задаче с исполнителем, и сверка, которая сравнивает записи владельца с записями руководителей, никогда их не синхронизируя.
Живёт внутри backend-модуля Staffing (apps/core-api/src/modules/staffing/finance.service.ts, task-payouts.service.ts), но это своя область прав, намеренно не часть platform:staffing:* — см. запись от 2026-07-28 в docs/iam/PERMISSIONS_CATALOG.md. Деньги конфиденциальны: каждое изменение пишется в журнал аудита, но никогда — в TeamEvent, ленту /team/activity, которую видит любой обладатель platform:staffing:read — гораздо более широкий круг, чем те, кому доступны финансы.
Как сделать...
Сгруппировано так же, как технические разделы ниже — иконка помощи каждой страницы ведёт прямо в свою группу, а не во весь этот список.
Бухгалтерия владельца
Записать выделение бюджета или расход — открой «Финансы» в левом меню → Добавить запись (справа вверху) → выбери Тип (Доход/Бюджет/Расход), Сумму, Валюту, Дату, Описание, при желании привяжи к Проекту/Отделу → Создать. См. гайд по полям ниже.
Отредактировать или удалить запись бухгалтерии — строка записи → ⋯ → Редактировать запись или Удалить запись.
Сравнить свои записи с книгой руководителя отдела — «Финансы» → вкладка Сверка. По умолчанию показаны только расхождения; включи Показать совпадающие, чтобы увидеть всё, включая совпадающие строки.
Записать/отредактировать запись бухгалтерии — гайд по полям
Один и тот же диалог с одними и теми же полями используется в бухгалтерии владельца, собственной книге отдела и вкладке «Финансы» проекта — меняется только то, какое необязательное поле привязки показывается (Проект, Отдел или ни одно), в зависимости от того, откуда его открыли.
- Тип (обязательно) — Доход (реально полученные деньги), Бюджет (выделенные/ зарезервированные, но ещё не потраченные деньги) или Расход (реально потраченные деньги). Это три разных экономических события, которые никогда не смешиваются в один общий итог — например, Доход намеренно исключён из графика «бюджет против расхода» на вкладке «Отчёты».
- Сумма (обязательно) — должна быть положительным числом; ноль или отрицательное значение отклоняется ещё до отправки на сервер. Отдельного направления/знака указывать не нужно — прибавляется сумма к итогу или вычитается из него, определяется целиком Типом, а не знаком числа.
- Валюта (обязательно) — AMD / USD / EUR / RUB. Каждый итог в этом модуле считается по каждой валюте отдельно, без конвертации между ними — запись одного и того же вида трат в двух разных валютах даёт две отдельные суммы, а не одну смешанную цифру.
- Дата (обязательно) — по умолчанию сегодняшняя при создании; можно указать любую дату в прошлом или будущем, и именно она — а не момент фактического сохранения записи — определяет, в каком месяце запись сгруппирована в журнале и какие фильтры периода (Этот месяц, Прошлый квартал, …) её захватывают.
- Описание (обязательно) — от 1 до 500 символов.
- Проект (необязательно, скрыто на собственной вкладке «Финансы» проекта, где он и так подразумевается) — привязывает эту строку к проекту для разбивки «куда уходят деньги» на вкладке «Отчёты»; оставь «Без проекта» для общекомпанейских записей не про конкретный проект.
- Отдел (необязательно, скрыто в собственной книге отдела на бухгалтерии владельца и заранее заполнено/заблокировано там, где книга уже и есть один конкретный отдел) — та же цель привязки, что и у Проекта, но для сравнения по отделам на вкладке «Сверка».
Собственная книга отдела
Записать в собственную книгу своего отдела (только руководители отделов) — открой свой отдел → вкладку Финансы → Добавить запись. Это отдельная книга от бухгалтерии владельца выше — см. Собственная книга отдела. Те же поля, что и в гайде по полям бухгалтерии владельца ниже, минус Отдел (он подразумевается).
Отредактировать или удалить запись в книге отдела — строка записи → иконки карандаша/корзины (только у руководителя).
Вкладка «Финансы» проекта
Посмотреть, как выглядят траты по своему проекту — открой проект → его вкладку Финансы. Это та же бухгалтерия владельца, просто заранее отфильтрованная по этому проекту.
Записать, отредактировать или удалить запись из вида проекта (platform:finance:manage) — те же Добавить запись / ⋯ → Редактировать запись / Удалить запись, что и в бухгалтерии владельца, просто заранее отфильтровано по этому проекту. Тот же гайд по полям ниже.
Выплаты по задачам
Предложить выплату исполнителю задачи — открой задачу → карточку Оплата за задачу → Добавить выплату → выбери Исполнителя, Сумму, Валюту → Создать (это черновик, исполнитель его ещё не видит) → ⋯ у этой строки → Отправить исполнителю. См. гайд по полям ниже.
Ответить на предложенную тебе выплату — открой задачу; если ты исполнитель, кнопки Принять/ Отклонить видны прямо на твоей выплате, без меню.
Записать ответ исполнителя без аккаунта — в том же меню ⋯ вместо ожидания его собственного клика — пункты «Отметить как принято/отклонено (договорились вне платформы)».
Отредактировать или удалить выплату — ⋯ у выплаты → Редактировать запись или Удалить запись. Изменение суммы/валюты уже отправленного офера сбрасывает его в черновик — его нужно отправить заново. Удаление заблокировано, если по выплате уже есть реальный платёж или ожидающий отчёт.
Добавить/отредактировать выплату — гайд по полям
- Исполнитель (обязательно, только при создании — сменить его после создания выплаты нельзя) — в списке показаны только уже назначенные на эту задачу люди (Ответственный или Помощник), у кого ещё нет выплаты; как только у всех назначенных уже есть выплата, сама кнопка «Добавить выплату» пропадает с карточки.
- Сумма (обязательно) — должна быть положительным числом.
- Валюта (обязательно) — AMD / USD / EUR / RUB, тот же список валют, что и в бухгалтерии.
- Полей Описание или Дата нет — выплата это пока просто причитающаяся сумма, ещё не строка бухгалтерии; они появятся позже, в отчёте о платеже (ниже).
- Новая выплата всегда начинается как Черновик — невидима для исполнителя, пока явно не нажмёшь Отправить (переводит в Предложено). Изменение Суммы/Валюты уже Предложенного офера незаметно сбрасывает его обратно в Черновик — то, что исполнитель уже видел, стирается, и его нужно отправить заново, прежде чем он увидит изменения.
- Удалить дополнительно заблокировано, если по выплате уже записан реальный платёж (
paidAmount > 0) или пока отчёт о платеже ещё ожидает подтверждения — сначала отмени отчёт (см. Подтверждение платежа ниже). - Над этим списком (не часть формы) появляется предупреждение о несоответствии оценке задачи, если сумма всех Принятых выплат в собственной валюте задачи превышает собственное поле «Цена» задачи — это лишь мягкое предупреждение, ничего им не блокируется. Выплаты в другой валюте, чем у задачи, помечаются отдельным предупреждением, поскольку сравнить их без курса конвертации, которого в этом модуле нет, нельзя.
Подтверждение платежа
Записать, что ты отправил(а) кому-то выплату (только platform:finance:manage) — на принятой выплате: ⋯ → Сообщить о переводе → сумма/дата/описание уже заполнены (сумма = сколько ещё причитается) → отправить. Это ещё не трогает бухгалтерию — получатель должен сам подтвердить, что деньги дошли. См. гайд по полям ниже.
Подтвердить, что платёж получен — на задаче появится баннер («Владелец сообщает об отправке X числа Y — дошло?») с кнопками Подтвердить получение / Не получил(а).
Записать итог платежа за контрактора без аккаунта — в том же меню ⋯ — пункты «Отметить как полученное/не полученное (договорились вне платформы)» для менеджера.
Отменить ожидающий отчёт о платеже — ⋯ у выплаты → Отменить сообщение о переводе (например, чтобы исправить опечатку в сумме, не дожидаясь, пока получатель оспорит).
Сообщить о переводе — гайд по полям
- Сумма (обязательно) — заранее заполнена суммой, которая ещё причитается по этой выплате (сумма минус уже подтверждённое как оплаченное), а не полной суммой выплаты — частичный платёж не нужно вручную вычитать из общей суммы. Ограничение действует и на сервере: ввести больше, чем реально ещё причитается, невозможно — запрос отклоняется, а не тихо пропускается.
- Дата (обязательно) — заранее заполнена сегодняшней датой.
- Описание (обязательно) — заранее заполнено сгенерированной строкой («выплата ⟨имя⟩ за задачу ⟨название⟩»), полностью редактируемо, до 500 символов.
- Одновременно может ожидать подтверждения только один отчёт по выплате — само действие Сообщить о переводе пропадает из меню ⋯, пока текущий отчёт не подтверждён, не оспорен или не отменён.
- Подтверждение (получателем, либо менеджером от его имени для контрактора без аккаунта) — это именно то, что создаёт реальную запись Расход в бухгалтерии; до этого момента ничего в бухгалтерию не пишется.
Баннер на дашборде
Увидеть, что ждёт твоего ответа, по всем задачам сразу — /team/dashboard → баннер «Мои выплаты», когда у тебя есть предложенный офер или ожидающий отчёт о платеже. Настраивать нечего — он появляется только когда действительно есть что решить.
Модель прав
Два права из каталога плюс одно данные-ориентированное правило, друг над другом:
| Кто | Что может |
|---|---|
platform:finance:read | Читать бухгалтерию владельца и книгу любого отдела (последнюю — только на чтение) |
platform:finance:manage | Писать в бухгалтерию владельца; управлять выплатами по задачам на уровне владельца; единственный, кто может сообщить/подтвердить реальный платёж |
Текущий руководитель отдела (Department.leaderId, без строки права) | Читать и писать только книгу своего отдела; управлять выплатами по задачам своего отдела — но не может сообщать/подтверждать реальные платежи (см. ниже) |
Проверка руководителя — тот же данные-ориентированный паттерн, что и везде в Staffing (вкладки «Финансы»/«Роли» отдела) — никакого права из каталога, просто «является ли leaderId этого отдела текущим пользователем». platform:finance:manage намеренно исключено из набора прав по умолчанию у P4P Admin — финансы принадлежат P4P Owner; SUPER_ADMIN тоже их получает через общий цикл выдачи всего каталога прав, который проходит каждое платформенное право.
Более узкое исключение внутри выплат по задачам (этап 5 финансов, 2026-07-30): создание черновика выплаты, её отправка и ответ исполнителя (принять/отклонить) доступны на уровне владельца или руководителю отдела задачи (TaskPayoutsService.canManage). Но фактическое сообщение или подтверждение реального платежа (reportPayment/confirmPayment-от-имени-менеджера/ markPayment/cancelPaymentReport) требует именно platform:finance:manage (hasFinanceManage/assertCanRecordPayment) — руководитель отдела, который может предлагать и управлять офером, всё равно не может трогать реальные деньги в бухгалтерии за ним. Найдено в аудите 2026-07-30: у старого действия «Отметить как оплачено» такой проверки вообще не было.
Бухгалтерия владельца (/team/finances)
Простыми словами: единственное место, где владелец отслеживает движение денег по всей компании — три вкладки на одной странице, один общий переключатель периода над всеми ими.
- Records tab («Записи») — сама лента записей: сводная плашка по валютам (доход/потрачено/к прошлому периоду), затем журнал, сгруппированный по месяцам. «Добавить запись» (
:manage) открывает диалог: тип (INCOME/BUDGET/EXPENSE— три разных экономических события, никогда не смешиваются; суммы всегда положительные, направление определяетсяtype), сумма, валюта (AMD/USD/EUR/RUB, курируемый список — добавление новой валюты — это изменение кода, поскольку суммы считаются по каждой валюте отдельно, без конвертации). Редактирование/удаление строки (:manage). - Reports tab («Отчёты») — без собственных данных, тот же отфильтрованный набор, что и Records: график бюджет-против-расходов по месяцам/кварталам/годам (с зумом) и разбивка «куда уходят деньги» по проекту и по отделу, каждая строка кликабельна — переносит в Records с готовым фильтром. Доход намеренно исключён из обоих — он есть только в сводной плашке на Records.
- Reconciliation tab («Сверка», в интерфейсе — «Reconciliation», внутри кода —
comparison) — одна строка на пару отдел × валюта, собственные записи владельца, привязанные к отделу, рядом с книгой этого отдела, бюджет и расход сравниваются независимо. Ненулевая разница — это информация, а не ошибка: две книги никогда не связаны и не синхронизированы. Совпадающие строки по умолчанию скрыты (переключатель «Показать совпадающие (N)» открывает их), так что таблица показывает только то, что реально требует внимания; сама вкладка получает бейдж только когда есть на что смотреть. - Переключатель периода (Этот месяц/Прошлый месяц/Этот квартал/.../Свой диапазон) общий для всех трёх вкладок и сохраняется в URL (
?view=&period=) — ссылка на «Сверку за прошлый квартал» переживает перезагрузку. Специально НЕ запоминается между визитами: открыв страницу через недели на старом окне, легко принять устаревшие цифры за текущие.
Собственная книга отдела (/team/departments/[id]/finances)
Простыми словами: личная бухгалтерия руководителя отдела для своего отдела — независимая от владельческой, намеренно, чтобы вкладке «Сверка» выше было что реально сравнивать.
- Видна только текущему руководителю этого отдела (чтение и запись) и обладателям
platform:finance:read(только чтение, через владельческий список — маршрут только для руководителя вернёт им 403). Тем, у кого есть толькоplatform:staffing:readбез доступа к финансам, вместо вкладки показывается сообщение «недоступно». - Та же форма записи, что и в бухгалтерии владельца (тип/сумма/валюта/дата/описание, опциональная привязка к проекту — отдел подразумевается сам собой). Плитки по каждой валюте (Доход/Бюджет/ Потрачено/Остаток) над таблицей.
- Владелец читает эту книгу на вкладке «Сверка» выше, но никогда не может в неё писать — весь смысл двух независимых книг — в их сравнении; правки владельца здесь испортили бы это.
Вкладка «Финансы» в карточке проекта (/team/projects/[id]/finances)
Простыми словами: бухгалтерия владельца, отфильтрованная по одному проекту — срез общекорпоративных финансов по этому проекту, а не отдельная бухгалтерия.
- Отображается и доступна только обладателям
platform:finance:read— сама вкладка скрыта без этого права, а страница дополнительно перепроверяет при прямом переходе по URL. - Та же форма, что и Records на
/team/finances(сводная плашка, журнал, переключатель периода, фильтры по отделу/валюте/типу минус «Проект», который здесь подразумевается сам собой) — запись всё равно идёт через эндпоинты бухгалтерии владельца (:manage), просто заранее отфильтрованные по этому проекту.
Выплаты по задачам (карточка «Оплата за задачу» на странице задачи)
Простыми словами: предложение оплаты конкретному исполнителю по конкретной задаче, которое этот исполнитель должен явно принять или отклонить — не автоматическое следствие назначения на задачу.
Жизненный цикл: DRAFT (создано, исполнителю не видно) → Отправить исполнителю → PROPOSED → исполнитель Принять/Отклонить → ACCEPTED/DECLINED. Редактирование суммы/валюты уже отправленного офера сбрасывает его обратно в DRAFT — изменённый офер нужно явно отправить заново, никогда не подменяется молча под уже данным ответом. Отклонённый (DECLINED) офер можно предложить снова.
- Отображается только для тех, кто управляет выплатами (уровень владельца или руководитель отдела задачи), и для исполнителя, у которого на этой задаче есть хотя бы одна не-
DRAFTвыплата. - Контракторы без аккаунта (запись из кадрового резерва, не привязанная к
User): не могут ответить в интерфейсе, поэтому менеджер фиксирует итог вручную — «Записать как принято/отклонено (договорились вне платформы)». Категорически недоступно для любого исполнителя, у которого есть аккаунт — менеджер никогда не может говорить от имени реального аккаунта (taskPayout.assigneeCanRespond).assigneeHasAccount/isMineв ответе сервера вычисляются специально для того, чтобы аккаунт, связанный через кадровый резерв (чейuserIdиначе нигде не раскрывается), всё равно видел свои кнопки «Принять»/«Отклонить» (исправлено 2026-07-30 — раньше этот случай был виден только через баннер на дашборде). - Удаление выплаты заблокировано, если по ней уже есть записанный реальный платёж или ожидающий ответа отчёт о платеже — и то, и другое иначе молча оставило бы без привязки реальную транзакцию или открытый вопрос к получателю.
Подтверждение платежа (этап 5 финансов)
«Я отправил деньги» от менеджера и то, что деньги реально пришли, — два разных факта: реальная запись EXPENSE в бухгалтерии создаётся только когда получатель сам подтвердит получение, никогда только со слов менеджера (исправлено 2026-07-30: старое «Отметить как оплачено» создавало запись сразу же, только на основании отчёта менеджера).
- Сообщить о платеже (только
platform:finance:manage— не руководители отделов, см. таблицу прав выше) — сумма/дата/описание, не больше остатка к оплате по этой выплате. Фиксирует только заявление;FinanceEntryпока не создаётся. В моменте может быть только один открытый отчёт по выплате. - Исполнитель видит баннер («Владелец сообщает об отправке X числа Y — дошло?») и либо Подтверждает получение (создаёт реальную запись
EXPENSE, приписанную тому, кто сообщил о переводе, а не тому, кто подтвердил), либо Я не получил(а) (оспаривает, запись не создаётся). - Для контрактора без аккаунта: Записать как полученное/не полученное (договорились вне платформы) — то же разделение «только менеджер, только для исполнителя без аккаунта», что и в пути принятия офера.
- Менеджер, подавший отчёт, может Отменить отчёт о платеже, чтобы отозвать своё же заявление (например, опечатку в сумме), не дожидаясь, пока получатель его оспорит.
paidAmount/lastPaymentConfirmedAtу выплаты вычисляются из её подтверждённых строкFinanceEntry— выплата может быть оплачена несколькими частичными подтверждёнными платежами, никогда не считается оплаченной полностью только потому, что онаACCEPTED.
Баннер на дашборде (/team/dashboard)
«Мои выплаты» — показывается только когда у текущего пользователя есть что-то, ожидающее именно его ответа, сразу по всем задачам: предложенный (PROPOSED) офер (принять/отклонить условия) или принятая (ACCEPTED) выплата с ожидающим отчётом о платеже (подтвердить/оспорить получение) Закрывает случай, когда человек ещё не знает, какую именно задачу открывать (исправлено 2026-07-30 — раньше это было видно только при открытии конкретной задачи).
Тестирование этого модуля
Работа с книгой владельца (создание/редактирование), доступ к книге отдела только у её руководителя (включая отрицательный случай — «не руководитель»), обнаружение расхождения на вкладке «Сверка», срез по проекту и полный цикл выплаты по задаче (черновик → отправка → принятие → сообщение о переводе → подтверждение получения) покрыты постоянным набором scripts/e2e/menu/finance.mjs (14/14, можно перезапускать когда угодно — см. scripts/e2e/README.md). Вкладка «Отчёты»/аналитика, отображение нескольких валют, удаление, пути отклонения/оспаривания и ручное подтверждение для контрактора без аккаунта — достаточно визуальные или разветвлённые, чтобы вместо этого числиться ручным проходом — см. docs/MANUAL_TESTING.md.