Staff — Leave & Payroll#
The Stage 21 Staff HR surface gives staff a self-service flow for leave requests, payroll-view, and payslip download.
Who this is for: anyone whose primary role is staff. Managers approving leave on behalf of a team see the admin queue surface — that’s covered separately.
Where to find it#
- Submit/track leave:
/app/staff/leave. - Payroll breakdown:
/app/staff/payroll. - Payslip list + download:
/app/staff/payslips.
Source: backend/internal/hr/service.go, webapp/src/routes/app/staff/.
Leave requests#
Submitting#
/app/staff/leave opens a form with:
- Leave type —
casual | sick | earned | unpaid(backend/internal/hr/types.go:32). - From / to date — date-only, half-day granularity is supported on the balance side.
- Reason — free-form, ≤ 4 000 chars.
Submitting flips the request from draft to submitted. Your manager sees it in their approval queue.
State machine#
draft → submitted
submitted → approved (manager / admin action)
→ declined
→ cancelled (you cancel before decision)You can cancel a submitted request before it’s been decided. Once approved or declined, the request is terminal.
Balances#
Each leave type has a separate balance row (staff_hr_leave_balance). Balances are decimal with half-day granularity — internal storage is decimal-as-string to avoid float drift on 0.5 values.
A submission cannot be approved if it would push your (staff, leave_type) balance below zero — the API returns 422 (ErrBalanceOverrun). The form does a pre-flight check too, but the server is authoritative.
Auditing#
Every state change writes an audit row (bits.hr.v1.StaffService/CreateLeaveRequest, /ApproveLeave, /DeclineLeave). Your HR/admin team can investigate any decision via /admin/audit-logs.
Payroll#
/app/staff/payroll shows your CompensationRecord rows, grouped by component:
basic— base salary.hra— house rent allowance.provident_fund— PF deduction.tax— TDS.bonus— one-off bonuses.allowance_other— catch-all.
Each row has an effective_from and optional effective_until. Rows with no effective_until are current.
Note: V2 does not include any in-platform payroll calculation engine. The compensation table is a read-only projection of what BITS HR maintains externally; finance is the source of truth.
Payslips#
/app/staff/payslips lists your payslips by period (period_start, period_end). To download:
- Click Download on a payslip row.
- The webapp calls
DownloadPayslipwhich returns a short-lived signed URL. - The browser fetches the PDF directly from the object store.
The signed URL is HMAC-SHA256-signed off the BITS_PAYSLIP_SIGNING_SECRET config (backend/internal/hr/signed_url.go). It expires within minutes — once expired you have to click again.
Note: payslip PDFs are uploaded by the finance team out-of-band in V2. In-platform PDF generation is Phase 3.
Common issues#
- “My leave was declined and I can’t see why” — declines do not require a reason field today; this is a known gap. Ask your manager directly, or check the audit log via an admin.
- “My balance row is missing for a leave type” — balance rows are populated when HR runs the monthly close. If a balance is missing entirely, your account may not yet have been onboarded into the leave system — file a ticket with HR.
- “My payslip download 403’d” — cross-staff access is blocked; you can only download your own payslips. If you got 403 on your own, the signed URL probably expired between issue and click — try again.
- “Why does the cancel button disappear after my leave is approved?” — approved leave can only be cancelled by an admin (audit trail integrity). Email your manager.