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 typecasual | 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:

  1. Click Download on a payslip row.
  2. The webapp calls DownloadPayslip which returns a short-lived signed URL.
  3. 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.