Supplia Manual, as shipped in the download
Supplia — User Manual
A supplier register with contract-renewal tracking. Version 3.1.4 [src: app/controllers/api.php:16].
About this manual
Every statement here was written by reading Supplia'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 Supplia is, and the three things it deliberately is not
A supplier and vendor register with contract-renewal tracking [src: app/src/Supply.php:19].
The source rules out three things explicitly [src: app/src/Supply.php:20]:
- It does not process, collect or move money. Payment terms are a free-text label only — no processor, no card, no bank rail, no crypto.
- It signs nothing [src: app/src/Supply.php:23].
- It holds no health data [src: app/src/Supply.php:23].
It does keep documents. A signed contract uploads onto its contract, and insurance certificates, certifications, licences, NDAs and tax forms upload onto the supplier, each with an optional expiry date [src: app/controllers/v3.php:256]. The file's bytes decide its type (PDF, PNG, JPEG or WebP), the cap is 10 MB, the stored name is generated [src: app/controllers/v3.php:1038], and a file is served only to a signed-in user with document.read, re-checked against the stored bytes [src: app/controllers/v3.php:1140]. A document reference — a link to wherever else the contract lives — can still be recorded instead.
The last day to give notice
A contract can record its notice period: the days of notice its cancellation clause asks for before the renewal date (or the end date, when there is no renewal date). Supplia counts back to the last day to give notice [src: app/src/Supply.php:246], and that date — not the renewal date — is the one the dashboard, the renewal radar and the supplier page watch, because it is the day a rolling contract is decided [src: app/controllers/app.php:168]. It also goes into the calendar feed as its own event, and into the CSV export and the API.
The calendar feed carries end dates, renewal decisions, last days to give notice and document expiries for contracts that are not closed; a closed contract leaves the feed [src: app/controllers/v3.php:521].
Supplia sends no reminder emails. Its dates show on the dashboard, on the radar and in your own calendar through the feed.
Contract status is derived, never stored
Only two states are ever written to the database: active and closed [src: app/src/Supply.php:49]. The four states you see — active, expiring, expired, closed — are worked out from that base plus the end date against today [src: app/src/Supply.php:272].
Nothing writes "expiring" or "expired" to a row, so a status can never go stale [src: app/src/Supply.php:274].
The derivation is short enough to state in full [src: app/src/Supply.php:275]:
| Condition | Status |
|---|---|
| Base is closed | closed — user-terminated, terminal, dates ignored |
| End date is in the past | expired |
| End date inside the notice window | expiring |
| Otherwise, including no end date | active |
The renewal windows
Dates are classified against three thresholds — notice, warning and critical — escalating towards expiry [src: app/src/Supply.php:185].
The thresholds are clamped into non-negative descending order before use [src: app/src/Supply.php:190], so a settings row where the warning window is smaller than the critical one cannot invert the buckets.
The date maths
Both endpoints are anchored at midnight UTC, so a daylight-saving transition can never add or drop a day and "days until" is exact with no off-by-one [src: app/src/Supply.php:10].
The database is never asked for "now" — today flows in from the application in your business's timezone [src: app/src/Supply.php:11].
Extending a contract
Extension pushes the end date out by whole months, and drags the renewal date along by the same span so the notice window keeps its shape [src: app/src/Supply.php:206].
A lapsed contract restarts from today
This is the rule worth knowing, and the source says exactly why it exists [src: app/src/Supply.php:219]:
A lapsed contract restarts from today: silently back-dating an extension would produce a contract that is "extended" and still expired, which is the bug this rule exists to stop.
So extending a contract that ran out last month gives you months from today, not months from the date it lapsed. You cannot extend backwards into the past [src: app/src/Supply.php:205].
Two refusals
Extending by less than one month or more than sixty is refused [src: app/src/Supply.php:213], and extending a contract with no end date is refused with an instruction rather than an error — set one first [src: app/src/Supply.php:217].
Month arithmetic is clamped
31 January plus one month is 28 or 29 February, never 3 March [src: app/src/Supply.php:229].
Money
Contract values are integer cents, and the spend rollup by category is summed in PHP with a final clamp, never in SQL — because a SQL SUM() over many integer rows can overflow under MySQL's strict mode [src: app/src/Supply.php:15].
Values clamp to a bound that fits a MySQL integer with headroom [src: app/src/Supply.php:30].
Exporting
CSV export carries a numeric-aware formula-injection guard [src: app/src/Supply.php:16].
Users and roles
Three roles [src: app/controllers/v3.php:34]:
| Permission | viewer | member | admin |
|---|---|---|---|
vendor.read, contract.read, document.read, report.read | yes | yes | yes |
vendor.write, contract.write, document.write | — | yes | yes |
| everything else | — | — | yes |
Administrative routes are gated by a wildcard permission — _sup_require('*') — which only the admin role satisfies [src: app/controllers/v3.php:37]. Running the product's own permission function confirms it: a viewer and a member are both refused, and only an admin passes.
The gate returns a 403 page with the permission named, rather than a silent no-op [src: app/controllers/v3.php:52].
Backups are an administrator's download
Both backup downloads — the JSON export and the raw SQLite file — take the admin permission [src: app/controllers/api.php:337]. Before 3.1.4 any signed-in user could take them, a viewer included.
What Supplia does not do
It does not move money [src: app/src/Supply.php:20]. Payment terms are a label.
It does not send reminder emails. Renewal dates, notice deadlines and document expiries show on the dashboard, the radar and the calendar feed.
It does not let a status go stale [src: app/src/Supply.php:274].
It does not extend a contract backwards [src: app/src/Supply.php:219].
It does not obey an inverted settings row [src: app/src/Supply.php:190].
It does not sum money in SQL [src: app/src/Supply.php:15].