Полное руководство: Табель, KPI и Зарплата
Как мы создали интегрированную HR-экосистему в Budget Manager
1. Введение: Почему три модуля стали одним решением
Когда мы начинали проект Budget Manager, перед нами
стояла задача автоматизировать управление бюджетом компании. Но очень
быстро стало понятно, что самая большая статья расходов — это
Фонд Оплаты Труда (ФОТ), и именно здесь больше всего
хаоса.
Типичная проблема: В компании 50 сотрудников. Табель
ведётся в Excel, KPI обсуждаются устно, а зарплату считает бухгалтер
вручную. Каждый месяц — это 2 дня нервотрепки, ошибки, пересчёты и
недовольные сотрудники, которые не понимают, откуда взялась итоговая
сумма.
Мы задали себе вопрос:
А что, если зарплата будет рассчитываться автоматически как
математическая функция?</strong >
Формула расчёта: Зарплата = f(Оклад, Отработанные_Часы,
KPI%, Налоги)
Для этого нам нужны были три компонента:
| Модуль | Задача | Результат |
|---|---|---|
| Табель (Timesheet) | Точный учёт времени работы | Данные для расчёта пропорциональной ЗП |
| KPI | Объективная оценка результативности | Размер премий и бонусов |
| Payroll | Автоматический расчёт | Точная ЗП без ручных расчётов |
И самое главное — эти три модуля должны интегрироваться.
Каждое изменение в табеле должно мгновенно влиять на прогноз зарплаты.
Каждое обновление KPI должно автоматически пересчитывать премии.
Что мы получили в итоге
| Результат | Описание |
|---|---|
| Расчёт зарплаты | 15 минут вместо 2 дней |
| Ошибки в начислениях | Снизились до нуля |
| Прозрачность | Сотрудники видят свою будущую зарплату в реальном времени |
| Прогнозирование | Точный прогноз ФОТ на 6-12 месяцев вперёд |
Давайте разберём каждый модуль подробно, с примерами из реальных
бизнес-кейсов.
2. Модуль 1: Умный Табель (Timesheet)

документ, который напрямую влияет на расчёт зарплаты, отпускных и
больничных.
2.1. Архитектура и типы дней
В нашей системе табель состоит из двух сущностей:
- WorkTimesheet: Табель на месяц для одного сотрудника
(заголовок) - DailyWorkRecord: Запись по каждому дню месяца
(детализация)
Ключевая особенность: Мы различаем 6 типов дней:
| Тип дня | Код | Описание | Влияние на зарплату |
|---|---|---|---|
| Рабочий день | WORK |
Обычный рабочий день | Оплачивается по часовой ставке |
| Выходной | WEEKEND |
Суббота, воскресенье | Не оплачивается (если нет переработок) |
| Отпуск | VACATION |
Оплачиваемый отпуск | Исключается из оклада, оплата по среднему |
| Больничный | SICK_LEAVE |
Лист нетрудоспособности | Оплата по законодательству (ФСС) |
| Отгул | UNPAID_LEAVE |
Отпуск за свой счёт | Уменьшает оклад пропорционально |
| Праздник | HOLIDAY |
Государственный праздник | Сокращает норму рабочих часов |
2.2. Производственный календарь России
Одна из самых сложных задач — корректный учёт праздников и переносов
выходных. Российское правительство ежегодно публикует переносы, и мы
должны их учитывать.
Компоненты производственного календаря
| Компонент | Описание | Примеры |
|---|---|---|
| Фиксированные праздники | Нерабочие дни, установленные законом | 1 января, 9 мая, 12 июня, 4 ноября |
| Переносы выходных | Рабочие субботы и дополнительные выходные | В 2025: 3 января (сб) — рабочий, 8 мая (чт) — выходной |
| Автоматический расчёт | Система считает рабочие дни в месяце | Май 2025: 18 рабочих дней вместо обычных 21-22 |
Почему это важно?
Реальный кейс: Май 2025 года имеет всего 18 рабочих дней
(вместо обычных 21-22 из-за майских праздников). Это значит, что стоимость
одного рабочего дня в мае выше. Если сотрудник возьмёт отгул в
мае, он потеряетОклад / 18, а неОклад / 22.
2.3. Жизненный цикл табеля
Табель проходит строгий workflow, чтобы исключить ошибки:
| Статус | Описание | Кто может редактировать | Следующий шаг |
|---|---|---|---|
DRAFT |
Черновик, заполняется HR | HR, Руководитель | Утверждение → APPROVED |
APPROVED |
Утверждён руководителем | Только Руководитель (откат на DRAFT) | Используется для расчёта ЗП |
Важное правило: Зарплата не может быть рассчитана, пока
табель не утверждён.
2.4. Кейсы и примеры
Кейс №1: «Майские праздники»
| Параметр | Значение |
|---|---|
| Ситуация | Сотрудник хочет взять отгул 2-3 мая 2025 |
| Оклад | 120 000 руб. |
| Рабочих дней в мае | 18 дней (вместо обычных 22) |
| Стоимость 1 дня | 120 000 / 18 = 6 667 руб. |
| Потери за 2 дня | 13 334 руб. |
| Преимущество | Сотрудник видит это в личном кабинете до заявления |
Кейс №2: «Больничный в середине месяца»
| Шаг | Действие | Результат |
|---|---|---|
| 1 | Разработчик заболел 10-15 ноября (5 дней) | Сотрудник уведомляет HR |
| 2 | HR меняет статус дней на SICK_LEAVE | Система пересчитывает часы: 176 → 136 |
| 3 | Табель возвращается в DRAFT | Требуется переутверждение |
| 4 | Руководитель утверждает изменения | Данные готовы для Payroll |
| 5 | Payroll пересчитывает зарплату | Оклад уменьшен на 40 часов + строка «Больничные» |
Кейс №3: «Сотрудник вышел 15-го числа»
| Параметр | Значение | Комментарий |
|---|---|---|
| Дата выхода | 15 ноября | Новый дизайнер |
| Оклад (полный) | 100 000 руб. | При полном месяце |
| Плановых часов | 176 часов | Норма для ноября |
| Отработано | 88 часов | Ровно половина месяца |
| Формула | (100 000 / 176) × 88 | Пропорциональный расчёт |
| Зарплата | 50 000 руб. | Без «на глаз» |
Кейс №4: «Переработки в выходные»
| Параметр | Значение |
|---|---|
| Ситуация | Программист работал в субботу 8 часов (критический релиз) |
| Действие HR | Ставит в табеле: day_type = WORK, hours_worked = 8 |
| Реакция системы | Добавляет 8 часов к total_hours_worked |
| Оплата | По обычной ставке (или с повышающим коэффициентом) |
Кейс №5: «Корпоративный праздник (сокращённый день)»
| Параметр | Значение |
|---|---|
| Ситуация | 31 декабря компания отпустила всех в 14:00 |
| Норма | 8 часов (до 18:00) |
| Фактически | 4 часа (до 14:00) |
| Действие HR | hours_worked = 4, day_type = WORK |
| Опция | Если руководство оплачивает полный день — добавить комментарий |
3. Модуль 2: KPI и Система Мотивации

Самый сложный и одновременно самый интересный модуль. Здесь мы превратили
абстрактные «цели» в математически рассчитываемые премии.
3.1. Иерархия целей: Monthly, Quarterly, Annual
Мы отказались от плоской системы «оклад + премия». Вместо этого создали
три горизонта планирования:
| Тип цели | Период | Когда выплачивается | Примеры |
|---|---|---|---|
| MONTHLY | 1 месяц | В конце каждого месяца | Выполнение плана продаж, количество закрытых тикетов |
| QUARTERLY | 3 месяца | Март, Июнь, Сентябрь, Декабрь | Запуск нового продукта, внедрение CRM |
| ANNUAL | 12 месяцев | Декабрь (13-я зарплата) | Прибыль компании, достижение стратегических целей |
Архитектура данных
| Сущность | Назначение | Примеры данных |
|---|---|---|
| KPIGoal | Библиотека целей (общие для компании) | «План продаж», «Качество кода», «Удовлетворённость клиентов» |
| EmployeeKPI | KPI сотрудника за конкретный период | Ноябрь 2025, статус DRAFT/APPROVED, итоговый KPI% |
| EmployeeKPIGoal | Связь сотрудника с целями + трекинг | Цель, вес, target, actual, achievement% |
3.2. Математика расчёта KPI%
Итоговый KPI% сотрудника — это взвешенное среднее всех
его целей. Причём для каждого типа премии (месячная, квартальная, годовая)
рассчитывается свой KPI%.
Формула: KPI% = (Цель1_Выполнение × Цель1_Вес +
Цель2_Выполнение × Цель2_Вес + …) / Сумма_Весов
Пример расчёта: Менеджер по продажам
| Цель | Вес | Выполнение | Взвешенный результат |
|---|---|---|---|
| План продаж | 50% | 100% | 50 × 100 = 5000 |
| Количество звонков | 30% | 80% | 30 × 80 = 2400 |
| Обучение новичков | 20% | 50% | 20 × 50 = 1000 |
| Итоговый KPI% | 8400 / 100 = 84% | ||
| База премии | 50 000 руб. | ||
| Премия к выплате | 42 000 руб. | ||
3.3. Типы бонусов: Performance, Fixed, Mixed
Не все премии должны зависеть от KPI. Мы реализовали три типа:
| Тип бонуса | Описание | Формула | Пример |
|---|---|---|---|
| PERFORMANCE_BASED | Премия напрямую зависит от KPI% | Премия = База × (KPI% / 100) | База 50k, KPI 84% → Премия 42k |
| FIXED | Выплачивается полностью при KPI ≥ порога | Премия = База (если KPI ≥ 80%), иначе 0 | База 30k, KPI 84% → Премия 30k KPI 70% → Премия 0 |
| MIXED | Часть гарантирована, часть зависит от KPI | Премия = База × Фикс% + База × (1 — Фикс%) × (KPI% / 100) | База 100k, фикс 30%, KPI 80% → 30k + 70k×0.8 = 86k |
3.4. Кейсы и примеры
Кейс №6: «Прозрачная мотивация менеджера»
Персонаж: Алексей, менеджер по продажам
| Параметр | Значение |
|---|---|
| Оклад | 60 000 руб. |
| Месячная премия (база) | 40 000 руб. (PERFORMANCE_BASED) |
| Цели на ноябрь: | |
| План продаж (вес 60%) | 1 000 000 руб. |
| Количество звонков (вес 40%) | 200 звонков |
Сценарий 1: Отличный месяц
| Показатель | План | Факт | Выполнение |
|---|---|---|---|
| Продажи | 1 000 000 руб. | 1 200 000 руб. | 120% |
| Звонки | 200 | 220 | 110% |
| KPI% | 116% | ||
| Премия | 46 400 руб. | ||
| Итого ЗП | 106 400 руб. | ||
Сценарий 2: Плохой месяц
| Показатель | План | Факт | Выполнение |
|---|---|---|---|
| Продажи | 1 000 000 руб. | 600 000 руб. | 60% |
| Звонки | 200 | 180 | 90% |
| KPI% | 72% | ||
| Премия | 28 800 руб. | ||
| Итого ЗП | 88 800 руб. | ||
Что видит Алексей: В личном кабинете есть виджет «Прогноз
зарплаты». Он показывает текущий KPI% и прогнозируемую премию
в реальном времени.
Кейс №7: «Квартальная премия разработчика»
Персонаж: Мария, Senior Developer
| Период | Прогресс цели | Выплата | Причина |
|---|---|---|---|
| Октябрь | 30% | 0 руб. | Не конец квартала |
| Ноябрь | 70% | 0 руб. | Не конец квартала |
| Декабрь | 100% | 100 000 руб. | Конец Q4, релиз выпущен |
| Итого зарплата Марии в декабре: 250 000 (оклад) + 100 000 (квартальная) = 350 000 руб. |
|||
Кейс №8: «Годовой бонус для всех»
| Уровень | База годовой премии | KPI компании | Выплата |
|---|---|---|---|
| Junior | 50 000 руб. | 120% | 60 000 руб. |
| Middle | 100 000 руб. | 120% | 120 000 руб. |
| Senior | 200 000 руб. | 120% | 240 000 руб. |
| Компания выполнила годовой план прибыли на 120%. Все получают «13-ю зарплату» в декабре.</em > |
|||
Кейс №9: «Смешанный бонус Sales Director»
Персонаж: Иван, директор по продажам. Нужен стабильный
доход для ипотеки + мотивация на результат.
| Параметр | Значение |
|---|---|
| База премии | 150 000 руб. |
| Фиксированная часть (40%) | 60 000 руб. (гарантировано) |
| Переменная часть (60%) | 90 000 руб. (зависит от KPI) |
| Сценарий: KPI = 80% | |
| Расчёт | 60 000 + 90 000 × 0.8 = 132 000 руб. |
| Сценарий: KPI = 0% (катастрофа) | |
| Расчёт | 60 000 + 90 000 × 0 = 60 000 руб. |
| Даже при полном провале остаётся минимальная гарантия | |
Кейс №10: «Депремирование за низкий KPI»
| Условие | Действие |
|---|---|
| KPI < 10% | Премия обнуляется (депремирование) |
| Пример | Сотрудник выполнил KPI на 5% → Премия = 0 |
| Цель | Защита от ситуаций, когда сотрудник формально «работал», но не принёс результата |
4. Модуль 3: Расчёт Зарплаты (Payroll)

деньги.
4.1. Интеграция с Табелем и KPI
| Сущность | Назначение | Когда создаётся |
|---|---|---|
| PayrollPlan | Плановые начисления (что мы ожидаем выплатить) | В начале месяца автоматически |
| PayrollActual | Фактические начисления (что мы выплатили) | После утверждения табеля и KPI |
4.1. Главная формула расчёта
| Компонент формулы | Описание |
|---|---|
| Часовая_Ставка | = Оклад / Плановые_Часы |
| ЗП_За_Часы | = Часовая_Ставка × Фактические_Часы |
| Gross | = ЗП_За_Часы + Премии |
| НДФЛ | = Gross × 13% |
| Net | = Gross — НДФЛ |
| К выплате | = Net — Аванс — Удержания |
Пример полного расчёта
| Параметр | Значение | Комментарий |
|---|---|---|
| Оклад | 200 000 руб. | |
| Плановых часов | 176 часов | 22 дня × 8 часов |
| Фактически | 160 часов | Брал 2 дня за свой счёт |
| Премия по KPI | 50 000 руб. | |
| Аванс | 100 000 руб. | Выплачен 25 октября |
| Расчёт: | ||
| Часовая ставка | 1 136.36 руб/час | 200 000 / 176 |
| ЗП за часы | 181 818 руб. | 1 136.36 × 160 |
| Gross | 231 818 руб. | 181 818 + 50 000 |
| НДФЛ (13%) | 30 136 руб. | |
| Net | 201 682 руб. | |
| К выплате | 101 682 руб. | 201 682 — 100 000 |
4.2. Аванс и окончательный расчёт
| Выплата | Дата | Формула | Зависит от табеля? |
|---|---|---|---|
| Аванс | 25-е число | Оклад × 50% | НЕТ (табель ещё не закрыт) |
| Окончательный расчёт | 10-е число | (Оклад / План_Часы) × Факт_Часы — Аванс + Премии | ДА |
4.4. Налоги и удержания
| Налог/Взнос | Ставка | Кто платит | Формула |
|---|---|---|---|
| НДФЛ | 13% (прогрессивная ) | Удерживается из ЗП сотрудника | НДФЛ = Gross × 0.13 |
| Страховые взносы | 30.2% | Платит работодатель сверх ЗП | Взносы = Gross × 0.302 |
| На руки (Net) | 87% | Получает сотрудник | Net = Gross × 0.87 |
| Полная стоимость | 130.2% | Реальные затраты компании | Стоимость = Gross × 1.302 |
Пример: Оклад 200 000 руб.
| Показатель | Сумма | Комментарий |
|---|---|---|
| Оклад (Gross) | 200 000 руб. | То, что указано в договоре |
| НДФЛ | -26 000 руб. | 200 000 × 0.13 |
| На руки (Net) | 174 000 руб. | То, что получает сотрудник |
| Страховые взносы | +60 400 руб. | 200 000 × 0.302 (платит компания) |
| Полная стоимость | 260 400 руб. | Реальные затраты компании |
Вывод: Когда HR говорит «оклад 200 000», компания тратит
260 400 руб.
Gross ↔ Net калькулятор
| Направление | Формула | Пример |
|---|---|---|
| Gross → Net | Net = Gross × 0.87 | 200 000 → 174 000 руб. |
| Net → Gross | Gross = Net / 0.87 | 200 000 → 229 885 руб. |
| Net → Полная стоимость | Стоимость = (Net / 0.87) × 1.302 | 200 000 → 299 350 руб. |
4.5. Кейсы и примеры
Кейс №11: «Полный цикл расчёта Senior Developer»
Персонаж: Дмитрий, Senior Python Developer
| Параметр | Значение |
|---|---|
| Оклад | 300 000 руб. |
| Плановых часов | 176 (22 дня × 8 часов) |
| Отработано | 168 часов (брал 1 день за свой счёт) |
| KPI | 100% |
| Премия | 50 000 руб. |
| Аванс | 150 000 руб. |
| Расчёт системы: | |
| Часовая ставка | 1 704.55 руб/час |
| ЗП за время | 286 364 руб. (❌ -13 636 руб. из-за отгула) |
| Премия | +50 000 руб. (✅ KPI 100%) |
| Gross | 336 364 руб. |
| НДФЛ (13%) | -43 727 руб. |
| Net | 292 637 руб. |
| Аванс | -150 000 руб. |
| К выплате 10 декабря | 142 637 руб. |
Кейс №12: «Сотрудник вышел 15-го числа»
| Параметр | Значение |
|---|---|
| Ситуация | Новый дизайнер вышел 15 ноября |
| Оклад | 100 000 руб. |
| Отработано | 88 часов (половина месяца) |
| Часовая ставка | 568.18 руб/час |
| ЗП | 50 000 руб. |
| НДФЛ | -6 500 руб. |
| На руки | 43 500 руб. |
| Никаких «делений на 30» или «на глаз» | |
Кейс №13: «Больничный + Отгул в одном месяце»
| Тип дня | Часы | Как оплачивается |
|---|---|---|
| Рабочие | 136 часов | По часовой ставке: 115 909 руб. |
| Больничный | 24 часа (3 дня) | Отдельно, ~15 000 руб. (по среднему) |
| Отгул | 16 часов (2 дня) | Не оплачивается (вычтено из рабочих) |
| Итого Gross | 130 909 руб. | |
| НДФЛ (13%) | -17 018 руб. | |
| На руки (Net) | 113 891 руб. | |
Кейс №14: «Квартальная + Годовая премия в декабре»
| Компонент | Сумма |
|---|---|
| Оклад | 200 000 руб. |
| Месячная премия | 30 000 руб. |
| Квартальная премия (Q4) | 50 000 руб. |
| Годовая премия | 100 000 руб. |
| Gross | 380 000 руб. |
| НДФЛ (13%) | -49 400 руб. |
| Net | 330 600 руб. |
| Если годовой доход > 2.4 млн, применяется 15% на сумму свыше</em > | |
Кейс №15: «Переработки в выходные (x2)»
| Параметр | Значение |
|---|---|
| Оклад | 180 000 руб. |
| Плановых часов | 176 |
| Отработано (норма) | 176 часов |
| Работа в выходные | 16 часов |
| Коэффициент | ×2 (двойная оплата) |
| Часовая ставка | 1 022.73 руб/час |
| ЗП за норму | 180 000 руб. |
| Переработки | 1 022.73 × 16 × 2 = 32 727 руб. |
| Gross | 212 727 руб. |
5. Технологический стек и особенности реализации
Стек технологий
| Компонент | Технология | Назначение |
|---|---|---|
| Backend | ||
| Фреймворк | Python 3.11 + FastAPI | Современный асинхронный API |
| База данных | PostgreSQL | Надёжное хранение данных |
| ORM | SQLAlchemy | Строгая типизация, миграции |
| Валидация | Pydantic | Валидация на уровне API |
| Миграции | Alembic | Версионирование БД |
| Frontend | ||
| UI | React 18 + TypeScript | Типизированный интерфейс |
| Сборка | Vite | Быстрая разработка |
| Данные | TanStack Query | Кэширование и синхронизация |
| Стили | Tailwind CSS | Утилитарный CSS |
Ключевые паттерны
| Паттерн | Зачем | Где применяется |
|---|---|---|
| Decimal вместо Float | Избежать ошибок округления | Все финансовые операции |
| Atomic Transactions | Гарантировать целостность данных | Утверждение табеля, расчёт ЗП |
| Service Layer | Изолировать бизнес-логику | KPICalculationService, PayrollKPISyncService |
| Строгая типизация (Enums) | Избежать ошибок в статусах | DayTypeEnum, TimesheetStatusEnum, BonusTypeEnum |
| API Versioning | Безопасное изменение API | /api/v1/timesheets |
Оптимизация производительности
| Техника | Описание | Эффект |
|---|---|---|
| Индексы БД | На все FK, даты, статусы | Быстрые запросы |
| Eager Loading | Предзагрузка через joinedload | Избежать N+1 запросов |
| Pagination | Все списки с пагинацией | Быстрая загрузка больших списков |
6. Заключение и результаты
Что мы получили
| Метрика | До внедрения | После внедрения | Улучшение |
|---|---|---|---|
| Время расчёта ЗП (50 чел.) | 2 дня | 15 минут | 192× быстрее |
| Ошибки в начислениях | 5-7 в месяц | 0 | 100% точность |
| Прозрачность для сотрудников | Нет | 100% (личный кабинет) | Полная прозрачность |
| Точность прогноза ФОТ | ±15% | ±2% | 7.5× точнее |
| Время на вопросы «Почему так мало?» | 5-10 часов в месяц | 0 часов | 100% экономия |
Ключевые преимущества
| Преимущество | Описание | Ценность |
|---|---|---|
| Детерминированность | Зарплата = функция от входных данных | Нет «человеческого фактора» |
| Прозрачность | Каждый сотрудник понимает свою ЗП | Доверие и мотивация |
| Интеграция | Три модуля работают как единое целое | Автоматическая синхронизация |
| Масштабируемость | 1 000 зарплат = 10 зарплат по времени | Рост без затрат |
| Аудит | Полная история изменений | Контроль и безопасность |
Дальнейшее развитие
| Функция | Описание | Статус |
|---|---|---|
| Прогрессивная шкала НДФЛ | Автоматический расчёт 13%/15%/18% | Уже готово |
| Интеграция с 1С | Автоматическая выгрузка начислений | В планах |
| Мобильное приложение | Отметки в табеле через телефон | В планах |
| AI-ассистент | Рекомендации по KPI на основе истории | Исследование |
| Сценарное моделирование | «Что если повысим всем на 10%?» | Уже готово |


