Ownware
Home›Fleetora›Manual
Fleetora · Manual

Fleetora Manual, as shipped in the download

Fleetora — User Manual

A fleet register: vehicles, servicing, documents, defects, mileage and who is driving what. Version 1.0.3 [src: app/controllers/api.php:16].

About this manual

Every statement here was written by reading Fleetora'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.

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

⭐ The rule the product lives or dies on

A service is due at whichever comes first — the date, or the odometer reading [src: app/src/Fleet.php:7].

Either leg can be switched off by setting its interval to zero, which means "not tracked that way" [src: app/src/Fleet.php:8]. Executed, with today at 2026-09-05:

both legs, date sooner        state ok        due by both        24 days,  8,000 to run
both legs, distance sooner    state soon      due by distance   299 days,    200 to run
both legs overdue             state overdue   due by both       −66 days, −1,000
date leg only                 state ok        due by date        26 days
distance leg only             state critical  due by distance             100 to run
neither tracked               state none      —

Two things to read out of that. due_by names which leg won, so a scheduler knows whether to book by calendar or by mileage. And a vehicle with nothing tracked comes back none rather than ok — the source is explicit that it must "say so, do not pretend it is healthy" [src: app/src/Fleet.php:132].

⭐ An unknown is reported as unknown

This is the sharp edge of the same rule. When a distance interval is set but no mileage has ever been logged, the distance leg is unknown, and never quietly treated as "not due" [src: app/src/Fleet.php:9].

The trap is that the date leg may look perfectly healthy. Executed — a service with a 365-day interval done four days ago, a 10,000-unit interval, and no odometer reading on file:

date leg alone would say:  ok  (361 days to run)
what Fleetora reports:     unknown

The source names that case in the code: a distance interval whose reading is missing must not be hidden by a healthy date leg [src: app/src/Fleet.php:162].

⭐ "I cannot tell you" sorts above "this is fine"

The ordering of the what-expires-next list is a deliberate judgement [src: app/src/Fleet.php:171]: a row whose urgency is unknown sorts after everything actionable but before the healthy, because an operator should see "I cannot tell you" above "this is fine".

Executed:

expired 0   overdue 0   critical 1   soon 2   unknown 3   ok 4   none 5

Document expiry

A date, a warning window and a status you can read in one glance [src: app/src/Fleet.php:12]. Five states, and "no date recorded" is one of them rather than a blank [src: app/src/Fleet.php:84]. Executed with a 30-day warning and a 7-day critical window:

expires 2026-09-04  ->  expired        expires 2026-09-25  ->  soon
expires 2026-09-05  ->  critical       expires 2026-12-01  ->  ok
expires 2026-09-10  ->  critical       no date             ->  none

Reminders by email

Off until you switch them on, and always through your own SMTP server. The reminder run [src: app/src/Reminders.php:38] reads the same ranked list the dashboard shows and sends:

  • the assigned driver, about their own vehicle only, when "Tell the assigned driver when something on their vehicle is about to expire" is on [src: app/src/Reminders.php:58]. Each item is told at most three times per round — entering the warning window, entering the critical window, and once when it has passed. Renewing the document or completing the service starts a new round [src: app/src/Reminders.php:125]. A service notice names the service and its due line in the vehicle's own units.
  • the office address, one digest of everything inside its window (and anything the register cannot tell for want of an odometer reading), at most once a day and only when the list is not empty [src: app/src/Reminders.php:74].

The master switch "Allow Fleetora to send mail at all" gates both, and with no SMTP server set up the run sends nothing and says so [src: app/src/Reminders.php:47]. Every notice has a reference, so a run repeated the same day writes to nobody twice.

Fleetora runs no background job of its own, so something has to start the run: schedule php /path/to/app/cron/expiry.php once a day (--dry-run lists who would be written to), or press Send reminders now in Settings, which runs the same code [src: app/controllers/app.php:737]. The script answers only on the command line [src: app/cron/expiry.php:22]. Without either, the calendar feed is the reminder.

⭐ An odometer does not run backwards

A lower reading is refused with the reason, because silently accepting it corrupts every distance-based service due date that follows [src: app/src/Fleet.php:183]. Executed:

last 52,000  new 52,100  ->  accepted
last 52,000  new 51,999  ->  REFUSED — "That is lower than the last reading on file (52000).
                             Odometers do not run backwards — log a correction against the
                             vehicle instead."
last 52,000  new     −1  ->  REFUSED — "A reading cannot be negative."
no reading   new 30,000  ->  accepted

The refusal tells you what to do instead, which is the difference between a validation message and a dead end.

Defects

Four severities, worst first, and immobilise means the vehicle must not be driven [src: app/src/Fleet.php:26].

The lifecycle has one unusual edge: a fixed defect can be reopened, and the source calls it an honest reopen — the record of the first fix stands [src: app/src/Fleet.php:34]. Dismissed is terminal in the same way, and also reopenable [src: app/src/Fleet.php:28].

Units are per vehicle

Miles or kilometres, stored per vehicle because a mixed fleet is normal [src: app/src/Fleet.php:23], and distances are always printed with the unit that vehicle actually uses [src: app/src/Fleet.php:199].

Ownership — owned, leased or hired — is recorded, never inferred [src: app/src/Fleet.php:20].

Users and roles

Four roles, and the extra one is the point [src: app/controllers/v3.php:26]:

A driver is a real role here: they report a fault and log a reading on the vehicle they hold, and they must not be able to edit the fleet or read its costs.

Permissionviewerdrivermemberadmin
fleet.readyesyesyesyes
defect.report · mileage.log—yesyesyes
fleet.write · defect.triage · service.write · document.write · assign.write——yesyes
fleet.dispose · doctype.write · settings.write———yes

The ladder is enumerated in full, with no wildcard [src: app/controllers/v3.php:32] — a new permission is not granted to anybody until it is added to a role deliberately.

The audit trail is admin-only: it requires settings.write [src: app/controllers/v3.php:156], which no role below admin holds. Unlike much of this family, a read-only account here cannot see it.

Backups are admin-only

Both backup downloads require settings.write, the admin permission — the JSON export [src: app/controllers/api.php:286] and the SQLite file [src: app/controllers/api.php:296]. A backup contains every table, so the fleet, its costs, its defect history and the driver assignments come out together; it sits with the same role as restore. Before 1.0.3 both downloads checked sign-in alone, and a driver account was enough.

One server name on every door

The REST interface [src: app/controllers/api.php:16], the MCP specification [src: app/controllers/v3.php:200] and the unauthenticated transport probe [src: app/controllers/v3.php:357] all name the server fleetora. Before 1.0.3 the two MCP doors said Fleetora.

An API key carries its holder's role at the REST door as it does at the agent door: adding a vehicle needs fleet.write, logging a reading mileage.log, reporting a fault defect.report [src: app/controllers/api.php:60]. Before 1.0.3 the REST writes checked only the key's read/write scope.

Webhooks

Four events are offered [src: app/src/Webhook.php:18], and all four genuinely fire — each from every interface that can perform the action:

eventbrowserRESTagent
vehicle.created[src: app/controllers/app.php:293][src: app/controllers/api.php:88]no such tool
defect.reported[src: app/controllers/app.php:547][src: app/controllers/api.php:141][src: app/controllers/v3.php:343]
vehicle.disposed[src: app/controllers/app.php:341]disposal is browser-only—
service.completed[src: app/controllers/app.php:475]completion is browser-only—

The two browser-only rows are not gaps. The REST write surface is three routes — create a vehicle, log mileage, report a defect [src: app/index.php:196] — and the agent tool set is the same two writes, so there is no other path that could have fired those events.

The API and agent access

Six tools [src: app/controllers/v3.php:211]: four read — the vehicle list, what expires next, one vehicle's record and the document types — and two write, logging mileage [src: app/controllers/v3.php:295] and reporting a defect [src: app/controllers/v3.php:321].

Note what an agent cannot do: it cannot edit the fleet, complete a service, triage a defect or dispose of a vehicle. The tool set is deliberately the driver's half of the product rather than the manager's.

What Fleetora does not do

It does not treat a missing odometer reading as "not due" [src: app/src/Fleet.php:9].

It does not let a healthy date leg hide an unknown distance leg [src: app/src/Fleet.php:162].

It does not report an untracked service as healthy [src: app/src/Fleet.php:132].

It does not accept an odometer reading lower than the last one [src: app/src/Fleet.php:183].

It does not infer ownership [src: app/src/Fleet.php:20].

It does not erase the record of a fix when a defect is reopened [src: app/src/Fleet.php:34].

← Back to Fleetora · 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 →