Зарплата (payroll)
Расчёт с сотрудниками: условия оплаты, настройки выплат и выплата по табелям через реестр платежей; прежние месячные ведомости — только для чтения. Четвёртый блок АРМ бухгалтера и юриста.
Реализовано: модуль
internal/modules/payroll, миграции0113(схема) и0114(RBAC).
Зачем
Раньше salary существовала только как категория затрат в бюджете. Полноценного расчёта с сотрудниками не было. Этот блок добавляет ведомости: начисления из окладов и табеля, удержание НДФЛ, выплата через кассу/платежи.
Сущности
Условия оплаты (CompensationProfile)
Условия оплаты сотрудника с историей: каждое изменение — новая запись со своим effective_from, прежняя закрывается днём раньше (effective_to). С миграции 0170 условия состоят из независимых частей, которые складываются (ТЗ «Табель и зарплата», п.4):
| Поле | Описание |
|---|---|
user_id | Сотрудник |
salary_amount, salary_period, salary_day | Оклад за месяц (month, день — число 1–31, 31 — последний день месяца) или за неделю (week, день недели 1–7). Начисляется целиком в свой день, сколько бы дней ни было отработано |
hourly_rate | Ставка за каждый подтверждённый час, поверх оклада |
shift_rate | Ставка за смену — день с подтверждёнными часами |
effective_from / effective_to | Период действия |
created_by, created_by_name | Кто задал условия |
kind, base_amount | Прежняя модель (оклад либо ставка) для месячной ведомости: сервис заполняет их сам. Оклад за неделю в ней — в пересчёте на месяц (×52/12) |
Правила:
- Нужна хотя бы одна часть (
400 payroll_pay_conditions_empty); нули значат «не задано». Оклад требует период и день (payroll_salary_period_invalid,payroll_salary_day_invalid). - Новые условия действуют только с даты позже начала последних (
409 payroll_profile_not_after_latestназывает дату). Задним числом история не переписывается. - Ошибку ввода исправляют удалением последних условий (
DELETE /compensation-profiles/:id, иначе409 payroll_profile_not_latest): предыдущие снова действуют без даты окончания. - После изменения трудозатраты проектов с часами сотрудника с этой даты пересчитываются: ставка за час из условий — одна из ставок трудозатрат, а оклад без ставки идёт как оклад / норму часов (
APP_LABOR_MONTHLY_NORM_HOURS). - Прежний формат запроса (
kind+base_amount) ещё принимается: оклад переводится в оклад за месяц с днём 31, ставка — в ставку за час. Перенос существующих записей — в миграции0170.
Фиксированная сумма за проект хранится у участника команды (project_team_members.fixed_fee, поле fixed_fee в API команды): видна и меняется по тем же правам, что ставка в час (объект costs). В табель она войдёт на этапе 2.
Настройки выплат (payroll_settings)
Одна строка на компанию: pay_due_workdays — «Выплатить до» по умолчанию, рабочих дней от формирования табеля (0–60, по умолчанию 3); reminder_days — порог напоминания начальнику о часах без табеля (1–365, по умолчанию 14). Читает любой вошедший, меняет payroll.manage.
Выплата
С этапа 3 ТЗ «Табель, зарплата и передача выплат в финансы» зарплата выплачивается по табелям: переданный табель ставит запланированную выплату в платежи на «Счёт для зарплаты», проведение закрывает табель. НДФЛ не удерживается: к выплате равно начисленному.
Прежние месячные ведомости (PayrollRun)
Ведомости (payroll_runs, payroll_items) больше не создаются, существующие доступны только для чтения: period, status (draft → approved → paid), итоги total_accrued / total_withheld / total_net, строки с source_breakdown и payment_id. Корректировки табеля, вошедшие в ведомость деньгами, привязаны к ней через timesheet_corrections.payroll_run_id.
API
| Метод | Путь | Право | Описание |
|---|---|---|---|
GET | /api/v1/compensation-profiles | payroll.view | Список условий оплаты (?user_id=), новые первыми |
GET | /api/v1/compensation-profiles/my | любой | Свои условия оплаты (профиль) |
POST | /api/v1/compensation-profiles | payroll.manage | Новые условия с даты, прежние закрываются |
DELETE | /api/v1/compensation-profiles/:id | payroll.manage | Удалить последние условия |
GET | /api/v1/payroll/settings | любой | Настройки выплат |
PUT | /api/v1/payroll/settings | payroll.manage | Изменить срок выплаты и порог напоминания |
GET | /api/v1/payroll/runs | payroll.view | Прежние ведомости (только чтение) |
GET | /api/v1/payroll/runs/:id | payroll.view | Прежняя ведомость со строками |
Пример
POST /api/v1/compensation-profiles
{ "user_id": 2, "salary_amount": 60000.0, "salary_period": "month", "salary_day": 15,
"hourly_rate": 500.0, "shift_rate": 2000.0, "effective_from": "2026-10-01" }
PUT /api/v1/payroll/settings
{ "pay_due_workdays": 3, "reminder_days": 14 }Доступ (RBAC)
Права payroll.view / payroll.manage (seeded в migrations/0114), закреплены за ролью accountant (и admin). director видит через system.global_read. Подробнее — rbac_matrix.md.
Что дальше
- Попадание начислений в
financeкак затрат категорииsalaryдля сводного P&L — запланировано (требует привязки ФОТ к проекту/организации; модульfinanceсейчас project-scoped). - Расширенные удержания (авансы, займы) и отчёт по НДФЛ войдут в фазу сводной отчётности (см. дорожную карту).