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

EntityWhat it represents
EmployeeA person on the team. Has first/last name, email, position, hire_date, status (active/inactive/terminated), salary, currency, department_id.
DepartmentA grouping. Has name, head employee, members.
Leave requestTime-off ask. Has type (vacation/sick/personal/etc.), start/end, status (pending/approved/rejected), approved_by.
Leave policyPer-tenant accrual rules.
Leave balancePer-employee remaining days, derived from policy + history.
Payroll runA bounded pay period — period_start, period_end, status (draft/processing/completed/cancelled), totals.
Payroll itemOne employee's gross/deductions/net inside a run.
ContractA 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

  1. Employee submits via Self-service → Leave → New request.
  2. Manager / HR admin approves or rejects at /hr/leave/[id]/approve. Approval emits hr.leave.approved; rejection emits hr.leave.rejected (the gate correctly fires the matching event, not always one or the other).
  3. Approved requests deduct from leave_balance once 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:

  1. Pick a template + fill the variables (name, role, dates, compensation).
  2. Send → contract goes out with a signing link via Resend.
  3. Recipient signs in-browser; we capture the signature image + IP + user agent as the signature record.
  4. 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_roster report 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.