Ownware
Home›Cashora›Manual
Cashora · Manual

Cashora Manual, as shipped in the download

Cashora — User Manual

A petty-cash ledger: boxes, floats, disbursements and replenishments. Version 3.1.4 [src: app/controllers/api.php:19].

About this manual

Every statement here was written by reading Cashora'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 Cashora is, and what it is not

A petty-cash ledger. Each box has an opening float; disbursements take money out and replenishments put it back [src: app/controllers/v3.php:68].

It records cash movements only. The source states the positioning without hedging: it is not a payment processor, it never touches cards, bank rails or crypto, and it documents money that has already changed hands [src: app/src/Cash.php:13].

The invariant

A box can never go below zero. A payout larger than the box holds is refused — and it is refused for an agent exactly as it is for a person at the tin [src: app/controllers/v3.php:70].

The refusal is not a code. It is a sentence that tells you what to do: the box holds this much, a payout of that much would take it below zero, which a cash box cannot do — and if the cash really was there, record the replenishment that topped it up first, then the disbursement [src: app/controllers/app.php:421].

One rule, one function, three callers

The invariant lives in a single pure function, and the screen, the REST API and the agent endpoint all call it [src: app/controllers/v3.php:6]. An agent therefore gets the identical refusal sentence a person gets, word for word [src: app/controllers/v3.php:8].

The REST documentation says the same thing to an integrator: a payout that would take a box below zero is refused with 422, "the same rule, and the same sentence, the browser form applies" [src: app/controllers/api.php:171].

A check is not a claim — and the product learned that the hard way

This is the part worth reading, because it is the difference between a rule and a guarantee.

Checking the balance and then writing the row is not enough. Between the read and the write, another payout can land. Two clerks emptying the same tin in the same moment both see enough cash, both write, and the box goes negative — the one thing the product promises cannot happen [src: app/controllers/app.php:429].

The source records that this was proven, not theorised: an audit interleaved two reads before either write, and box 4 holding €267.10 allowed both payouts of €267.10, landing at −€267.10 [src: app/controllers/app.php:432].

The fix is one serialised critical section per box [src: app/controllers/app.php:435]:

  • On SQLite, the write lock is taken up front with BEGIN IMMEDIATE — a deferred transaction would try to upgrade mid-way and deadlock the second writer instead of queueing it [src: app/controllers/app.php:436].
  • On MySQL, the box row is locked with SELECT … FOR UPDATE, so a concurrent payout on the same box waits and then re-reads the balance the winner left behind [src: app/controllers/app.php:438].

Boxes are independent, so payouts on different boxes never block each other [src: app/controllers/app.php:439].

And the rule itself is not duplicated inside the locking code — the balance still comes from the snapshot and the sentence still comes from the refusal function. The atomic writer only decides when they are read [src: app/controllers/app.php:441].

The money

Integer cents throughout, with sums computed in PHP and clamped, never in SQL — because a SQL SUM() over many integer rows can overflow and fail under MySQL's strict mode [src: app/src/Cash.php:10].

Values are clamped to a bound that fits a MySQL integer with headroom [src: app/src/Cash.php:21].

Thirty currencies are offered; storage is the three-letter code and display prefixes a symbol where one is known, otherwise showing the code itself [src: app/src/Cash.php:24], [src: app/src/Cash.php:30].

Balance, float and variance

A box's balance is its opening float, less disbursements, plus replenishments [src: app/src/Cash.php:76].

Variance is the balance measured against the imprest float [src: app/src/Cash.php:86] — the gap between what the tin should hold under the float system and what the ledger says it holds — with a label attached [src: app/src/Cash.php:91] and a status combining both figures [src: app/src/Cash.php:102].

For a period report, the opening figure is the float adjusted by everything before the period [src: app/src/Cash.php:114], and the closing figure follows from the period's own movements [src: app/src/Cash.php:123].

Categories and export

Spending rolls up by category [src: app/src/Cash.php:133].

CSV export is hardened in the same engine as the rest of the money maths [src: app/src/Cash.php:168], so the exporters and the screen cannot disagree about a figure.

Deliberate omissions, recorded as decisions

The source lists three things the product does not have, and says they are recorded "so they read as decisions rather than gaps" [src: app/controllers/v3.php:11]:

  • No email, carried over from the previous version's opt-out.
  • No calendar feed — a petty-cash ledger has no future-dated events, because the transaction date is history [src: app/controllers/v3.php:13].
  • No data-subject kit, because there is no data-subject table to serve one from [src: app/controllers/v3.php:14].

Documented here because a buyer comparing products should be able to tell a considered absence from an oversight, and these are the former.

The API and agent access

record_entry is the only tool that writes; every other tool is read-only [src: app/controllers/v3.php:72].

The agent instructions carry the invariant in full [src: app/controllers/v3.php:70], and the write tool repeats it: a payout larger than the box holds is refused, because a cash box cannot go below zero [src: app/controllers/v3.php:141].

The REST interface takes decimals in and returns integer cents [src: app/controllers/api.php:171], and signs its webhook [src: app/controllers/api.php:171].

Backups, and the receipt files

Both database backups — the JSON export and the raw SQLite file — are an administrator's download (settings.edit) [src: app/controllers/api.php:285]. They carry table rows only. A receipt photo or PDF is a file under data/uploads/receipts/ that its disbursement row points at, so from 3.1.4 the receipt files download beside them as one archive, Download receipt files (.tar), also for administrators only [src: app/controllers/api.php:301]. To move the ledger to a new server, restore the database and extract that archive into data/uploads/receipts/.

The JSON export and the scheduled backup leave out the same list of credentials: password hashes, API-key hashes, webhook secrets, the SSO client secret, the backup-token hash and every two-factor seed and recovery code [src: app/controllers/v3.php:431].

Counting the tin

A count records what is physically in the box against what the ledger says should be there, with expected frozen beside counted at that moment; a difference is recorded as a difference, and no entry is invented to hide it [src: app/controllers/v3.php:512].

What Cashora does not do

It does not move money [src: app/src/Cash.php:13]. It records cash that has already changed hands.

It does not let a box go negative — for a person, an integrator or an agent [src: app/controllers/v3.php:70], and not under concurrency either [src: app/controllers/app.php:435].

It does not re-implement its own rule in three places [src: app/controllers/v3.php:6]. There is one function, and everything calls it.

It does not sum money in SQL [src: app/src/Cash.php:10].

It does not email, publish a calendar or ship a data-subject kit [src: app/controllers/v3.php:11] — each for a stated reason.

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