HR module
The HR module manages employees, departments, leave requests, payroll runs, and document signing (contracts). It's optimized for small-to-mid teams where one person handles HR alongside other duties, not for enterprise HRIS depth.
Entities
| Entity | What it represents |
|---|---|
| Employee | A person on the team. Has first/last name, email, position, hire_date, status (active/inactive/terminated), salary, currency, department_id. |
| Department | A grouping. Has name, head employee, members. |
| Leave request | Time-off ask. Has type (vacation/sick/personal/etc.), start/end, status (pending/approved/rejected), approved_by. |
| Leave policy | Per-tenant accrual rules. |
| Leave balance | Per-employee remaining days, derived from policy + history. |
| Payroll run | A bounded pay period — period_start, period_end, status (draft/processing/completed/cancelled), totals. |
| Payroll item | One employee's gross/deductions/net inside a run. |
| Contract | A signable document for a contractor or new hire — has template, parties, status (sent/signed/voided), audit-trail signature record. |
Schema lives under hr with public.* shims for backward compat.
Leave workflow
- Employee submits via Self-service → Leave → New request.
- Manager / HR admin approves or rejects at
/hr/leave/[id]/approve. Approval emitshr.leave.approved; rejection emitshr.leave.rejected(the gate correctly fires the matching event, not always one or the other). - Approved requests deduct from
leave_balanceonce the period starts; the deduction is computed lazily on read, not stored.
Public-facing leave-request form available at
/hr/leave-form/[token] for tenants that want employees to
submit via a link rather than the dashboard.
Payroll lifecycle
Monthly cron payroll-batch opens a DRAFT payroll_runs row at
the start of each calendar month for every tenant that has at
least one active salaried employee. The cron does NOT process
automatically — it just opens the run so HR admins land on a
ready-to-review draft.
Lifecycle:
draft → processing → completed
↘ cancelled
Processing computes per-employee gross/deductions/net, inserts a
payroll_items row per employee, and updates the run totals.
Idempotency: UNIQUE on (tenant_id, period_start) prevents
duplicate-month drafts even if the cron runs twice.
Contracts (e-sign)
Tenants can issue contracts using HR contract templates:
- Pick a template + fill the variables (name, role, dates, compensation).
- Send → contract goes out with a signing link via Resend.
- Recipient signs in-browser; we capture the signature image + IP + user agent as the signature record.
- Generated PDF is stored in tenant-scoped storage.
Events: hr.contract.sent, hr.contract.updated (with
payload.status='signed' on sign).
Employee directory
The directory view shows active employees with avatars + status chips. Filter by department, status, hire date. Per-row drawer opens the employee detail with:
- Personal info + emergency contacts
- Leave balance summary + history
- Payroll history
- Documents (contracts, certifications, custom-fields uploads)
Cross-module insights
- HR × Projects: workload-vs-capacity card on the Today surface uses contracted hours from HR.
- HR × Finance: payroll runs feed expense tracking for total cost-of-employment reporting.
- HR × Analytics: scheduled "employee roster" exports run via
the
hr_employee_rosterreport type.
Permissions
Standard hr.employees.read|create|update|delete plus the
sensitive hr.payroll.process permission (gated, audited).
Employees see their own profile + leave balance via the
self-service surface even without admin permissions.
Read next
- HR API — endpoint reference for employees, leave, payroll, contracts.