HRM — Leave & Holidays

Time off: what's allowed, how much is left, and who approves it

What This Module Does (Plain Language)

This module handles staff time off — annual leave, sick days, casual leave, and so on. It tracks how many days each person has, lets them apply, routes the request to a manager to approve or reject, and keeps the balance honest the whole way. It also stores a company holiday calendar so public holidays aren't counted as absences or against someone's leave balance.

The Building Blocks

EntityWhat it isKey fields
Leave TypeA category of leave (Annual, Sick, Casual…)name, code, isPaid, isBalanceTracked, allowHalfDay, requiresDocument, maxConsecutiveDays
Leave PolicyEntitlement & accrual rules (can vary by seniority level)entitledDaysPerYear, accrualMethod, carryForwardMax, minTenureMonths
Leave BalancePer-employee, per-type yearly trackerentitledDays, usedDays, pendingDays, carriedDaysavailableDays is derived
Leave RequestThe application + its workflow statestartDate, endDate, isHalfDay, totalDays, reason, status
HolidayA calendar entry (public/company/etc.), one day or a rangedate, endDate (null = single day), type, isRecurring, storeId (null = all stores)
availableDays is always calculated, never stored

availableDays = entitled + carried − used − pending. Because it is derived, it can never drift out of sync with the real bookings.

Accrual methods & holiday types

EnumValues
accrualMethodANNUAL_LUMP · MONTHLY_ACCRUAL · PER_WORKED_DAYS · NONE
HolidayTypePUBLIC · RELIGIOUS · OPTIONAL · COMPANY
LeaveRequestStatusPENDING · APPROVED · REJECTED · CANCELLED · CANCELLED_POST

How the Balance Stays Honest

POSVelo uses a reserve-on-request, deduct-on-approve model. This prevents someone from "double-spending" the same days across two pending requests.

1

On request

The days are reserved: they move from available to pending. If you don't have enough available, the request is rejected (409 INSUFFICIENT_BALANCE).

2

On approve

The reservation converts to used: pending → used.

3

On reject / cancel-before-start

The reservation is released back to available.

4

On cancel after approval (before it starts)

The days are refunded: used → available, and the request is marked CANCELLED_POST.

Once leave starts, it's locked in

Approved leave can be cancelled only before its start date. After the start date the leave is considered consumed and the record is final.

Every balance change happens inside a transaction that locks the balance row, so two requests submitted at the same moment can't both grab the last day.

Applying for Leave

As an employee (self-service)

Go to My Space › Leave › Apply (/me/leave/apply), pick a type, choose dates, and submit. Track status at /me/leave.

Single day or a date range — pick explicitly

The apply form has a Single day / Date range toggle. Single day takes one date; Date range shows From / To with a live summary such as “Mon, 30 Jun – Fri, 4 Jul · 5 calendar days”. Half-day is offered only for a single-day request. The request body is unchanged — both produce a startDate / endDate pair.

http
json

As HR (for another employee)

The HR equivalent at /hr/leave/requests takes the same body plus an employeeId. Permission: leave-request create.

json
What the server works out for you

On submit, POSVelo validates there's no overlap with another request, computes totalDays by excluding weekends and holidays, checks maxConsecutiveDays and the document requirement, reserves the balance (for paid, tracked leave), and notifies the approvers (HR managers, managers, admins) with a live notification.

Approving & Rejecting

Approvers see pending requests at /hr/leave/requests.

ActionEndpointEffect
ApprovePOST /api/v2/hr/leave/requests/:id/approveStatus → APPROVED; pending days convert to used; employee notified.
RejectPOST /api/v2/hr/leave/requests/:id/rejectStatus → REJECTED; reservation released; employee notified.
CancelPOST /api/v2/hr/leave/requests/:id/cancelReleases or refunds depending on whether it was approved yet.
Stale approval badges clear automatically

A leave request notifies every approver. When one of them approves it, the now-stale "New leave request" notification is resolved for all the others too — so nobody is left with a phantom unread badge. See Real-Time Notifications.

The Team Leave Calendar

A shared “who’s out” month view shows approved leave as a continuous bar across the days it covers, coloured by leave type, with an “Away today” / “Upcoming” side rail. It has two entry points backed by one endpoint.

AudienceRouteSees
HR / managers/hr/leave/calendarWhole tenant (HR/admin) or own store plus direct reports, with an all-stores filter for the all-scope permission
Employees/me/leave/calendarTheir own store plus direct reports — a “Team Leave” view
http
Approved-only, and privacy-bounded

The calendar shows approved leave only. HR / admins see the whole tenant; everyone else sees their own store and direct reports. The response is a deliberately narrow projection — person, leave type, and the from–to range — and omits the reason and any attached document, so a teammate’s private note never leaks into the shared view. No new permission is needed: every ESS user already holds leave-read, and the server narrows what each role can see.

Validation Rules to Know

RuleBehavior
Paid vs unpaidOnly paid + balance-tracked leave touches the balance. Unpaid leave (LWP) reserves 0 days and is docked later in payroll.
Half-dayAllowed only on a single-day request (start = end); counts as 0.5 days.
Document requiredIf the leave type needs a document and none is attached, the request is refused.
EligibilityThe employee must meet the policy's minimum tenure; terminated/resigned staff can't file new requests.
No self-approvalIf the requester is also the approver, it escalates to an admin/HR manager.
Carry-forwardAt year rollover, unused days carry over up to the policy cap; the rest lapse.

The Holiday Calendar

Holidays keep public/company days from being counted as absent or deducted from leave. You can add them one at a time, or import a country preset.

Dev Only POST /api/v2/hr/holidays/import-preset · permission hr.holidays.manage

json

Supported presets: US, UK, IN, BD, UAE. A storeId of null applies to all stores. View the merged calendar at GET /api/v2/hr/holidays/calendar?year=&storeId=.

Holidays can span several days

A holiday can be a single date or a date range — set an endDate for a multi-day holiday such as a multi-day Eid. A null endDate means a single day (the effective end is the start date), so existing single-day holidays need no change. Every working day inside the span is excluded from leave-day totals, and the holiday calendar tints the whole range. Setting endDate equal to the start collapses it back to a single-day holiday.

In the app

PageRoute
Leave overview/hr/leave
Requests/hr/leave/requests
Balances/hr/leave/balances
Policies/hr/leave/policies
Team leave calendar/hr/leave/calendar
Holidays/hr/holidays
Holiday calendar/hr/holidays/calendar

Who Can Do What

CapabilityADMINHR_MANAGERMANAGEREMPLOYEE
Manage leave types & policiesYesYesNoNo
Read / adjust all balancesYesYesTeam readOwn (via ESS)
Approve / reject requestsYesYesTeamNo
Apply / cancel own leaveYesYesYesYes (via ESS)
Manage holidaysYesYesNoNo
Next step

Approved paid leave and unpaid (LWP) days both feed into Payroll — set that up next.