Ownware
Home›Leavora›Manual
Leavora · Manual

Leavora Manual, as shipped in the download

Leavora — User Manual

Leave entitlement, requests, approvals and balances. Version 3.1.1 [src: app/controllers/api.php:18].

About this manual

Every statement here was written by reading Leavora's own source, and each one carries the file it came from in a bracketed src marker. Paths are relative to the folder this docs directory sits in. If a sentence disagrees with the application, the application is right and this manual has a bug — the markers exist so you can check rather than take our word for it.

Nothing is described that the code does not do. Where something is deliberately limited, that is said plainly rather than left out.

What Leavora is

It tracks leave only — entitlement, requests, approvals and balances [src: app/src/Leave.php:17].

It is not payroll. It never computes wages, never touches money, cards or bank rails, and initiates no payments [src: app/src/Leave.php:18].

Leave is counted in hundredths of a day

Amounts are integer centidays, where one day is 100 and half a day is 50 [src: app/src/Leave.php:11].

That is not fussiness — it is what makes accrual exact. A monthly accrual of "20 days a year" is credited as intdiv(2000 × months, 12), so twelve months lands on exactly 2000 — twenty days — with zero floating-point drift, and every intermediate month is an exact hundredth of a day [src: app/src/Leave.php:12].

A typed amount like 20, 1.5, 0.5 or 20.25 is parsed with string arithmetic and no float round-trip [src: app/src/Leave.php:35].

Sums are computed in PHP with a final clamp, never with a SQL SUM() — which the source describes as "a strict-mode 500 waiting to happen" [src: app/src/Leave.php:14].

Working days

A request's length counts only working days: weekends and company holidays are both excluded [src: app/src/Leave.php:5].

Your weekend is configurable. It is a list of weekday numbers rather than an assumption — 0,6 is the Sunday-and-Saturday default, but a Friday-Saturday weekend is simply a different list [src: app/src/Leave.php:68]. Anything out of range is dropped, and an empty result falls back to the standard weekend rather than counting every day.

The count is DST-safe: it iterates UTC-anchored calendar days, so a span crossing a clock change counts the exact number of dates with no 23-hour or 25-hour drift [src: app/src/Leave.php:106].

Holidays may be supplied either as a list of dates or as a map keyed by date — both are accepted, and weekends are still excluded on top [src: app/src/Leave.php:102].

Accrual

Three methods [src: app/src/Leave.php:29], all prorated by the employee's start date so nobody accrues for months that began before they joined [src: app/src/Leave.php:198]:

  • Annual — the whole start-prorated entitlement is granted up front, the moment the leave year (or the employee's start, if later) begins [src: app/src/Leave.php:201].
  • Monthly — one twelfth per leave-month, credited at each month's start, with integer division keeping a full year landing on exactly the annual figure [src: app/src/Leave.php:203].
  • None — never accrues, for something like unpaid leave [src: app/src/Leave.php:205].

The leave year does not have to be the calendar year: it starts on a month and day you choose [src: app/src/Leave.php:180].

Carryover

Leftover balance carries into the next year, capped and floored at zero [src: app/src/Leave.php:240] — a negative closing balance never becomes a negative opening one.

The balance calculation walks every leave year from the employee's start year to the as-of year, carrying the capped leftover across each boundary [src: app/src/Leave.php:251].

The balance

Stated as an equation the source defines exactly [src: app/src/Leave.php:264]:

entitlement = carry_in + accrued
available   = entitlement − taken − pending

Pending requests are deducted. A request awaiting approval already reduces what an employee can book, which is what stops somebody booking the same days twice while the first request sits in a queue.

Clashes

Overlapping requests are detected [src: app/src/Leave.php:156], and a clash check can exclude the request being edited so amending a request does not collide with itself [src: app/src/Leave.php:167].

Users and roles

Four roles [src: app/controllers/v3.php:23], and two of the decisions behind them are worth stating.

Permissionviewermemberemployeeadmin
leave.read, employee.read, report.readyesyes—yes
leave.write—yes—yes
self.read, self.write——yesyes
deciding, settings, team———yes

Submitting and approving are separate authorities. The source separates them on purpose: "submitting a request and approving one are different authorities, and a product about approvals should not blur them" [src: app/controllers/v3.php:17].

The self-service role cannot approve anything. An employee sees and books their own leave and nothing else — no colleague's balance, no register, and deliberately no decision. Approving stays admin-only, so, in the source's words, "opening the doors to staff cannot open the door to self-approval" [src: app/controllers/v3.php:26].

Backups are for administrators

Both backup downloads — the JSON export and the SQLite file — ask for the administrator role [src: app/controllers/api.php, backup_json() and backup_sqlite()]. Viewer, member and self-service employee accounts are refused, and the Security page shows the backup buttons to administrators only.

A backup contains every table, so the employee register, everyone's leave history and the audit trail come out together. Before 3.1.4 a sign-in alone was enough to download it.

One version, one name

Every surface that states a version reads the same constant, App::VERSION [src: app/src/App.php]: the REST ping, the OpenAPI document, the health check, the agent specification and its transport probe. The agent specification, its probe and the REST API all name the server leavora in lower case [src: app/controllers/v3.php]. Before 3.1.4 the probe said Leavora, so a client probing with GET and one calling with POST were told two different names.

The API and agent access

Tools cover listing requests, request detail, a team calendar, submitting a request and deciding one [src: app/controllers/v3.php:84].

An unauthenticated GET is answered rather than rejected, and the source explains why: a client probes with GET to find out whether a server speaks the protocol at all, and answering 401 first "gets 401 and concludes there is no MCP endpoint here, which is the opposite of the truth" [src: app/controllers/v3.php:222]. No tools are named to an unauthenticated caller [src: app/controllers/v3.php:224].

What Leavora does not do

It does not do payroll [src: app/src/Leave.php:18] — no wages, no money, no payments.

It does not let a self-service employee approve anything [src: app/controllers/v3.php:26].

It does not assume your weekend [src: app/src/Leave.php:68].

It does not drift on accrual [src: app/src/Leave.php:12], and it does not sum leave in SQL [src: app/src/Leave.php:14].

It does not carry a negative balance forward [src: app/src/Leave.php:240].

← Back to Leavora · Quickstart · API · Test run

Affiliate program
Recommend tools people own — earn 35% on every sale. 90-day tracking, instant delivery, payouts by Lemon Squeezy.
Become an affiliate →