Skip to content

Табели ​

Табель — документ на выплату за любой отрезок дат (ТЗ «Табель, зарплата и передача выплат в финансы», п.6). Сотрудник только отмечает часы; руководитель в любой день формирует табель от нужной даты, система считает начисление по условиям оплаты, и табель уходит в финансы. Месячной сдачи больше нет.

Реализовано: модуль internal/modules/timesheets, миграции 0135 (таблица timesheet_periods), 0170 (история правок часов), 0171 (табель любого периода, строки начисления timesheet_lines, time_entries.timesheet_id), 0172 (выплата по табелю в «Платежах», счёт для зарплаты, настройки выплат).


Схема ​

Сотрудник: отмечает часы (таймер задачи, «Добавить часы»)        получает выплату и уведомление
                │                                                           ▲
                ▼                                                           │
Руководитель: «Сформировать табель» с любой даты ─▶ «Подтвердить и передать в финансы»
                ▲                                              │
                │ вернуть с причиной                           ▼
Финансы:        └───────── запланированная выплата в «Платежах» ─▶ «Провести»: табель закрыт

Часы и табель ​

Запись часов входит ровно в один табель (time_entries.timesheet_id). Пока она ни в каком — «ждёт табеля». Новые часы за любую дату принимаются всегда: если на эту дату уже есть выплаченный табель, запись войдёт в следующий группой «часы за прошлые даты». Месячных блокировок нет — таймер задачи и «Добавить часы» не отказывают из-за табеля.

Состояние записиСотрудникРуководитель, администратор
Ждёт табеляменяет и удаляетменяет, удаляет, добавляет за сотрудника
В черновике или возвращённом табелетолько смотритменяет, исключает из табеля с причиной; табель пересчитывается
В финансахтолько смотритсначала отзывает табель
Выплаченакорректировкакорректировка сотрудника

В сутках 24 часа: сумма записей сотрудника за день не может быть больше 24 ч — ни при добавлении, ни при правке, ни у руководителя за сотрудника (400 timeentries_day_hours_exceeded с тем, сколько уже записано и сколько ещё можно). Забытый таймер задачи записывает не больше, чем осталось до 24 ч за день.

Исключённая запись возвращается сотруднику с пометкой «Нужно поправить» (time_entries.review_note); после его правки пометка снимается, и запись попадает в следующий табель.

Отсутствия в табеле ​

Отпуска, больничные и отгулы (см. «Отпуска, больничные и отгулы») денег не меняют: строки начисления, оклад и смены считаются как раньше, вычет за отгул — ручной строкой. Табель только подсказывает:

  • предпросмотр и карточка табеля — absences_in_period: отсутствия сотрудника в периоде;
  • «Команда» — у сотрудника, который сегодня отсутствует, absence и метка «В отпуске до …» вместо предупреждений;
  • «Мои часы» (overview.absences) — дни отсутствия закрашены в сетке месяца, праздники календаря компании затенены;
  • срок «Выплатить до» считается в рабочих днях календаря компании (праздники не входят).

Работа вне проектов ​

С этапа 5 ТЗ часы и задачи бывают вне проектов — внутренняя работа отделов (миграция 0174).

  • Задачу без проекта (POST /tasks без project_id) ставит любой сотрудник. Управляет ею автор, работают исполнитель и соисполнители, отдел задачи — отдел исполнителя. Статус проекта такая задача не двигает.
  • Часы без проекта (project_id не передан) записываются только по задаче без проекта (400 timeentries_task_required_without_project) и только её участниками (403 timeentries_not_task_participant). Таймер задачи пишет такие часы сам.
  • В табеле они идут строкой «Часы» без проекта по личной ставке из условий оплаты. В трудозатраты проектов не попадают.

Формирование ​

POST /timesheets/preview считает табель без сохранения, POST /timesheets сохраняет (черновик или сразу «в финансы», send=true).

  • «С даты» по умолчанию — день после прошлого табеля сотрудника; табелей не было — самая ранняя дата его часов без табеля. «По дату» — выбирает руководитель.
  • В табель входят все часы без табеля по «По дату» включительно, кроме исключённых; записи раньше «С даты» помечены past («часы за прошлые даты»).
  • Периоды неотменённых табелей сотрудника не пересекаются (409 timesheets_overlap с датами другого табеля); строка сотрудника блокируется на время формирования.
  • Свой табель формирует только администратор (409 timesheets_self_approval_forbidden).
  • Срок выплаты по умолчанию — 3 рабочих дня от сегодня.

Начисление ​

Строки timesheet_lines — снимок на момент расчёта; черновик и возвращённый пересчитываются при каждой правке часов, смене дат, ручной строке.

СтрокаКак считается
salaryОклад целиком за каждый день начисления (число месяца или день недели из условий оплаты), попавший в период; пропущенный после прошлого табеля догоняется; один день — один раз
hoursЧасы по проекту × ставка: сначала ставка участника на проекте, иначе ставка за час из условий оплаты на дату записи; условий оплаты на дату нет вовсе — ставка за час отдела сотрудника (rate_source = department); есть условия без ставки за час — сумма 0 (часы оплачены окладом)
shiftДни с часами × ставка за смену
feeСумма за проект, когда проект сдан или закрыт; один раз
adjustment, deduction, fee вручнуюДоплата, вычет (отгул, больничный — до учёта отсутствий), гонорар частью: POST /timesheets/:id/lines

К выплате = начислено (удержаний нет), не меньше нуля. Суммы видят сам сотрудник, администратор и доступ к заработку (earnings_margin); остальным API отдаёт часы без сумм (amounts_visible=false).

Пересчёт: изменили условия оплаты сотрудника (впервые задали свою ставку) или отдел (его ставку) — пересчитываются невыплаченные табели («черновик», «возвращён»); табели «в финансах» и «выплачен» остаются как были — часы, оплаченные по ставке отдела, так и остаются. Часы вне табеля считаются на лету по текущим ставкам.

Суммы сотруднику: настройка «Показывать сотрудникам суммы» (payroll_settings.show_amounts_to_employees, по умолчанию включена). Выключена — в своих табелях, «Моих часах», на главной и в своих условиях оплаты сотрудник видит только часы (amounts_visible = false); администратору и тем, кому открыт заработок, суммы видны всегда.

Статусы ​

СтатусЧто значитПереходы
draftЧерновик у руководителяsend → sent, cancel → cancelled
sentВ финансахreturn (финансы) или recall (руководитель) → returned, pay (финансы) → paid
returnedВозвращён с причинойsend → sent, cancel → cancelled
paidВыплачен, часы закрыты навсегда—
cancelledРасформирован, часы снова ждут табеля—

Возврат и отзыв требуют причину. Выплату отмечают и табель из финансов возвращают финансы: администратор, запись затрат «все», accounting.manage. Каждая смена статуса — в истории (timesheet_period_events).

Уведомления: timesheet.sent — сотруднику, timesheet.payout — финансовому директору и ассистенту, timesheet.returned — тому, кто сформировал, timesheet.paid — сотруднику и руководителю, timesheet.payout_overdue — финансам, когда выплата не проведена к сроку (один раз на платёж), timesheet.stale_hours — начальнику по пятницам, если у подчинённых есть часы без табеля старше порога из настроек выплат.

Выплата в финансах ​

Переданный табель сам встаёт в очередь финансов (этап 3 ТЗ, п.7):

  1. send создаёт в «Платежах» запланированную выплату: исходящий платёж сотруднику (counterparty_type=employee) на сумму «к выплате», дата — «Выплатить до», назначение «Зарплата: Имя, 16.09–30.09.2026», связь payments.timesheet_id. Табель с нулевой суммой уходит без платежа.
  2. Счёт — «Счёт для зарплаты» (payment_accounts.is_payroll_default, не больше одного), иначе первый активный расчётный счёт. Активных счетов нет — табель не уходит (409 payments_no_payroll_account).
  3. Проведение платежа — в «Платежах» (PATCH /payments/:id/status, массово POST /payments/complete) или кнопкой «Отметить выплату» в карточке (POST /timesheets/:id/pay {account_id?}) — закрывает табель (paid). Счёт можно сменить при проведении.
  4. return и recall сначала отменяют запланированную выплату, потом возвращают табель. В «Платежах» выплату по табелю не отменить (409 payments_payroll_cancel_forbidden): только возвратом табеля с причиной.
  5. Карточка табеля (GET /timesheets/:id) показывает выплату: payment {id, status, date, account_id, account_name, amount}.
  6. С этапа 5 передача создаёт и задачу без проекта «Выплатить зарплату: Имя, сумма» финансовой службе: исполнитель — ассистент финдиректора (нет — финдиректор), остальные финансы — соисполнители, срок — «Выплатить до» (payments.task_id). Проведение платежа закрывает задачу, возврат табеля — отменяет.

«Выплатить до» по умолчанию — через pay_due_workdays рабочих дней от формирования, порог напоминания — reminder_days; оба в настройках выплат (GET/PUT /payroll/settings, по умолчанию 3 и 14). Прежние месячные ведомости (/payroll/runs) остаются только для чтения.

Права ​

Кто«Команда», формирование, решенияСуммы
Администраторвсе сотрудники, включая себяда
Запись earnings_margin (владелец, OPS, финдиректор)все, кроме себяда
Начальник по оргструктуреподчинённые (рекурсивно)нет
others_timesheets запись«все» или свой дивизионнет
Финансы, чтение заработка, чтение others_timesheetsвидят табели в охвате, не формируютпо заработку

Признаки для интерфейса в /org/me: timesheets_team, timesheets_form (= timesheets_approve), timesheets_self_approve, timesheets_amounts, timesheets_pay.

API ​

МетодПутьОписание
GET/api/v1/timesheets/my/overview?from&to«Мои часы»: лента, сегодня и неделя, невыплаченные часы и сумма, последняя выплата, свои табели
GET/api/v1/timesheets/team«Команда»: часы без табеля, с какой даты, «нужно поправить», последний табель, сводка (в том числе chain_days — «скорость цепочки»: в среднем дней от часа до выплаты по табелям этого месяца)
GET/api/v1/timesheetsТабели в охвате (status, user_id, пагинация)
POST/api/v1/timesheets/previewРасчёт без сохранения
POST/api/v1/timesheetsСформировать
GET, PATCH/api/v1/timesheets/:idТабель; изменить даты и срок выплаты
POST/api/v1/timesheets/:id/{recalculate,send,recall,return,pay,cancel}Пересчёт и переходы
POST, DELETE/api/v1/timesheets/:id/lines[/:line_id]Ручные строки
POST/api/v1/timesheets/:id/entries/:entry_id/excludeИсключить запись с причиной
GET/api/v1/payments?kind=payrollВыплаты по табелям (фильтр «Зарплата» в «Платежах»)
POST/api/v1/payments/completeПровести несколько выплат одним подтверждением
GET, PUT/api/v1/payroll/settingsСрок выплаты и порог напоминания

Правка и удаление часов ​

С этапа 1 ТЗ «Табель, зарплата и передача выплат в финансы» (docs/specs/timesheet-payroll-spec.md, п.5) запись часов можно изменить или удалить. Кто и что может:

КтоКогда можноОграничения
Сам сотрудникСвоя запись, ещё не вошедшая в табельПроект не закрыт (409 timeentries_project_closed), новая дата — в сроках проекта, при смене проекта — участие в нём. Строка из корректировки не правится (409 timeentries_entry_from_correction)
Управляющий часами сотрудника: администратор, начальник по оргструктуре (users.manager_id, рекурсивно), проверяющий по матрице others_timesheets (запись, в своём охвате)Любая запись без табеля или в черновике и возвращённом табеле (табель пересчитывается), в том числе по закрытому проекту и вне его сроковТабель в финансах — сначала отозвать (409 timeentries_entry_timesheet_sent), выплаченные часы — только корректировкой (409 timeentries_entry_paid)

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

Тот же управляющий записывает часы сотруднику через POST /time-entries без прежнего права time_entries.manage и без ограничений по срокам и закрытию проекта. Сам сотрудник по закрытому проекту часы не вносит.

Каждая правка пишется в историю time_entry_events (миграция 0170): кто, действие created / updated / deleted, снимки «было» и «стало», признак правки по закрытому проекту. История переживает удаление записи. Запись, добавленную или изменённую не самим сотрудником, он получает уведомлением timesheet.entry_changed («Иванов изменил ваши часы за 12.10: 8 ч → 6 ч»), а в строке табеля — поля edited_by, edited_by_name; ещё строка несёт project_closed.

Трудозатраты пересчитываются сразу. Правка по закрытому проекту меняет только его статьи «Трудозатраты» (finance.SyncLaborCostsIncludingClosed), остальные финансы закрытого проекта не трогаются. Удалённая или перенесённая в другой проект запись забирает свою статью трудозатрат.

МетодПутьДоступОписание
PATCH/api/v1/time-entries/:idвладелец или управляющий{project_id?, task_id?, date?, hours?, comment?}; отсутствующее поле не меняется, task_id/comment со значением null снимаются
DELETE/api/v1/time-entries/:idвладелец или управляющий204
GET/api/v1/time-entries/:id/historyвладелец или управляющий{items: [...]}, старые события первыми, с названиями проекта и задачи

Корректировки ​

Корректировка нужна только для часов, которые уже в финансах или выплачены: остальные правятся напрямую (409 timesheets_correction_not_needed). Сотрудник подаёт её за день или период (entry_date…date_to, например неделя) с причиной; решает тот же круг, что формирует табели. Учтённая становится записью времени без табеля (correction_id) и войдёт в следующий табель. Убрать больше, чем было по проекту в табеле, нельзя.

МетодПутьОписание
GET, POST/api/v1/timesheets/my/correctionsСвои корректировки; подать
DELETE/api/v1/timesheet-corrections/:idОтозвать свою, пока нет решения
POST/api/v1/timesheet-corrections/:id/review{approve, note}

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