Module: HRM & Payroll¶
Без модуля: базова зарплата відбивається у Essentials через документ «Операція» /
JournalEntry— згорнутими проводками без per-employee розкладки. Деталі та повний приклад: essentials/journal-entry.md → Use-case: зарплата без модуля HRM. HRM-модуль — це розширення, що додає per-employee облік, розрахункові листи з UA 4-leg податками, табель, відпустки, payroll register та PDF.Статус: ✅ Production-ready (2026-05-06)
✅ Реалізовано: - Phase F-1 (2026-04-22) — Master data (Position/Employee), MVP-payroll з єдиним
tax_rate_pct, AttendanceLog (S5 Gatehouse, 2026-04-28). - Phase G-HRM (2026-05-06) — UA-payroll рівня production: - F1: UA 4-leg tax engine — окремі поля та проводки для ПДФО (18%) + Військ (1.5%) + ЄСВ employee (0%) + ЄСВ employer (22% — нарахування понад gross). Знятий placeholdertax_rate_pct/tax_amount/tax_payable_account. Ставки snapshotуються на slip приcompute. - F1: PayrollTaxConfig — per-tenant ставки з історією valid_from.resolve(tenant, on_date)обирає актуальний конфіг або повертає UA-defaults без save. - F2: Posting consistency hygiene — серіалізатори блокують тиху зміну posted-документа (ruledocument-posting.md);stateу read_only_fields;perform_update→ 403 на posted. - F3: Timesheet —Timesheet(TransactionModel) +TimesheetLinesubtable. Сервісиrecompute_from_attendance(з AttendanceLog) іapply_to_payroll(push days_worked у matching slips, skip posted). - F4: Leave management —LeaveType(master),LeaveBalance(per-employee per-year),LeaveRequestз 4-state workflow (draft → submitted → approved/rejected). Approval auto-incrementує LeaveBalance.used_days, idempotent. - F5: Payroll Register report —payroll-registerчерез Report Framework: list per-slip з повною 4-leg розкладкою + totals row, drill-down до slip. - F6: Payslip PDF —payroll-slipчерез Print Framework: шапка, працівник, нарахування + 4-leg withholdings + net + ESV employer accrual + місце для підписів. - 49 backend-тестів покривають compute / 5-leg post / TaxConfig resolve / Timesheet / LeaveRequest workflow / Payroll Register / Payslip render.
Реалізована модель — детально¶
Models¶
Master Data¶
Position(MasterDataModel) — посада зbase_salary+department.Employee(MasterDataModel) —first_name/last_name,tax_id, контакти,position,department,hire_date,termination_date,base_salary, SCUD-поля (external_scud_id,card_uid,access_level),photo. Методdisplay_name()повертає "Прізвище Ім'я".LeaveType(MasterDataModel) —is_paid,accrual_days_per_year,requires_doc. Default seed: Vacation (24, paid), Sick (paid), Unpaid.PayrollTaxConfig(TenantAwareModel) —valid_from+ 4 ставки (pdfl_rate_pct/military_rate_pct/esv_employee_rate_pct/esv_employer_rate_pct). UNIQUE(tenant, valid_from).
Transactions¶
PayrollPeriod(TransactionModel) —(year, month, standard_workdays), UNIQUE per tenant.PayrollSlip(TransactionModel) —(period, employee)UNIQUE per tenant. Поля:days_worked,base_salary,gross_amount+ 4 пари*_rate_pct/*_amount(PDFL / military / ESV-employee / ESV-employer) +net_amount+ 7 FK наChartOfAccounts(salary_expense, salary_payable, pdfl, military, esv_employee, esv_employer_expense, esv_employer_payable).Timesheet(TransactionModel) +TimesheetLinesubtable — місячна аґрегація AttendanceLog → days_worked для slip.LeaveRequest(TransactionModel) —(employee, leave_type, date_from, date_to, days, leave_status). Workflow: draft → submitted → approved/rejected.
Registers / accumulated¶
AttendanceLog(TenantAwareModel, S5 Gatehouse) — daily in/out roll-up.LeaveBalance(TenantAwareModel) —(employee, leave_type, year)UNIQUE;accrued_days,used_days,remaining_days(computed).
Services¶
| Файл | Функція | Що робить |
|---|---|---|
services/payroll.py |
compute_slip(slip) |
Pulls ставки з PayrollTaxConfig.resolve(tenant, slip.date), snapshotує на slip 4 *_rate_pct. gross = base × days/std (cap), pdfl/military/esv_emp/esv_empr = gross × rate, net = gross − withholdings. |
services/payroll.py |
post_slip(slip) |
Recompute → wipe попередній PostingGroup → створити новий з 1-5 booking-pairs (gross + 0-3 withholdings + ESV employer accrual). Idempotent. |
services/payroll.py |
unpost_slip(slip) |
Видаляє PostingGroup. |
services/payroll.py |
post_period(period) |
Bulk-post all draft slips. |
services/timesheet.py |
recompute_from_attendance(timesheet) |
Wipe lines, перебудувати з AttendanceLog за (year, month). |
services/timesheet.py |
apply_to_payroll(timesheet, period=None) |
Push line.days_worked у matching PayrollSlip.days_worked (skip posted). Auto-resolve period по year+month. |
services/leave.py |
submit_request(req) |
draft → submitted, auto-fill days з date range. |
services/leave.py |
approve_request(req) |
submitted → approved + LeaveBalance.used_days += req.days (idempotent). |
services/leave.py |
reject_request(req, reason) |
submitted → rejected. |
Endpoints¶
/api/v1/hrm/positions/ CRUD
/api/v1/hrm/employees/ CRUD + upload-photo, delete-photo, form-refs
/api/v1/hrm/payroll-periods/ CRUD + post_all
/api/v1/hrm/payroll-slips/ CRUD + compute, post_document, unpost_document, recompute-days-worked
/api/v1/hrm/attendance-logs/ CRUD (Sprint 5 Gatehouse)
/api/v1/hrm/tax-configs/ CRUD (PayrollTaxConfig — ставки з історією)
/api/v1/hrm/timesheets/ CRUD + recompute-from-attendance, apply-to-payroll, post_document, unpost_document
/api/v1/hrm/timesheet-lines/ CRUD (subtable rows)
/api/v1/hrm/leave-types/ CRUD
/api/v1/hrm/leave-balances/ CRUD
/api/v1/hrm/leave-requests/ CRUD + submit, approve, reject
Reports¶
| Code | Title | Source |
|---|---|---|
payroll-register |
Реєстр зарплати | PayrollSlip per period з 4-leg breakdown + totals + drill-down до slip |
Print Templates¶
| Doc type | Template | Fields |
|---|---|---|
payroll-slip |
hrm/print_templates/payroll-slip/base.html |
gross / 4 withholdings / net / ESV empr / accounts / signatures |
Frontend¶
MasterDataPageдляemployees(custom EmployeePage з фото-табом) /positions/leaveTypes/leaveBalances/taxConfigs.- Generic
TransactionPageдляpayrollPeriods/payrollSlips/timesheets(з subtablelines) /leaveRequests. - Custom report wrapper:
PayrollRegisterReport(@/components/HRM/PayrollRegisterReport.tsx). - Print wired автоматично через
printDocumentSafe(entityCode='payrollSlips', recordId).
Очікувані розширення (planned scope для повного модуля)¶
🔮 Backlog (deferred)¶
- KPI Templates / KPI Records / KPI-bonuses — engine для змінної частини оплати. Trigger: sales/team-based bonus client.
- 1ДФ year-end report — UA річна звітність з 4421/4422 проводок. Trigger: перший live tax-period.
- Pay grades / salary bands — для HR-driven org structure.
- Onboarding/Termination workflow — chain of personnel actions (hire date, contract, termination order). Кадрові накази.
- Garnishments — утримання за рішенням суду (аліменти).
- Recurring Leave accrual — auto-incrementувати
LeaveBalance.accrued_daysщомісяця/щороку. Зараз — manually наLeaveTypeзміні.
1. Master Data (Мастер-данные)¶
| Сущность | Описание | Ключевые поля |
|---|---|---|
| Employees | Персональные дела сотрудников | ID, Full Name, Contact, Tax ID, Hire Date, SCUD fields |
| Departments | Орг. структура (через Essentials) | Name, Parent_ID, Manager |
| Positions | Должности с базовым окладом | Title, Base Salary, Department |
| LeaveType | Типы отпусков | Code, Name, is_paid, accrual_days_per_year |
| PayrollTaxConfig | Ставки UA податков (з історією) | valid_from, 4 rate_pct |
2. Transaction Data (Транзакционные данные)¶
- Timesheet — місячний документ-аґрегатор AttendanceLog → days/hours per employee.
- Leave Request — заявка з 4-state workflow.
- Payroll Period — місячний контейнер для slips.
- Payroll Slip — per-employee розрахунок: gross + 4-leg taxes + net + employer ESV.
3. Journals & Ledgers (Журналы и реестры)¶
A. Attendance Register (S5 Gatehouse)¶
- Dimensions: Employee_ID, Date, Source.
- Resources: in_at, out_at (1 day per closed shift, 1 day per open shift).
B. Payroll Postings (через essentials.PostingGroup + PostingEntry)¶
- Per slip → 1-5 booking pairs (gross + 0-3 withholdings + ESV employer accrual).
- Drill-down via
PayrollRegisterReport→ row → slip.
C. Leave Balance Register¶
(employee, leave_type, year)→ accrued / used / remaining.
4. Business Processes¶
- Monthly Payroll Cycle:
- Створити
PayrollPeriodдля місяця. - Створити
Timesheet→recompute_from_attendance→ manual edits. apply_to_payroll→ days_worked у slip-и.- Per slip:
compute(auto з TaxConfig) →post_document. - Bulk
post_allза period. - Звіт
payroll-registerдля перевірки. -
Друк PDF slip-ів.
-
Leave Cycle:
- Створити
LeaveRequest(draft). - Працівник
submit→ submitted. - HR/manager
approve→ approved + LeaveBalance.used_days +=.
5. Analytics & BI¶
- Payroll Register — реалізовано (cм. Reports).
- 🔮 Headcount Dynamics, Turnover Rate, Labor Cost Distribution — backlog.
- 🔮 KPI Fulfillment Heatmap — після KPI engine.