Skip to content

Финансы — бухгалтерия владельца, книги отделов, выплаты по задачам

Простыми словами: денежная сторона 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: старое «Отметить как оплачено» создавало запись сразу же, только на основании отчёта менеджера).

  1. Сообщить о платеже (только platform:finance:manage — не руководители отделов, см. таблицу прав выше) — сумма/дата/описание, не больше остатка к оплате по этой выплате. Фиксирует только заявление; FinanceEntry пока не создаётся. В моменте может быть только один открытый отчёт по выплате.
  2. Исполнитель видит баннер («Владелец сообщает об отправке X числа Y — дошло?») и либо Подтверждает получение (создаёт реальную запись EXPENSE, приписанную тому, кто сообщил о переводе, а не тому, кто подтвердил), либо Я не получил(а) (оспаривает, запись не создаётся).
  3. Для контрактора без аккаунта: Записать как полученное/не полученное (договорились вне платформы) — то же разделение «только менеджер, только для исполнителя без аккаунта», что и в пути принятия офера.
  4. Менеджер, подавший отчёт, может Отменить отчёт о платеже, чтобы отозвать своё же заявление (например, опечатку в сумме), не дожидаясь, пока получатель его оспорит.
  5. 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.