Quantity ledger
A quantity-ledger column is a server-owned balance projected from contributions made elsewhere — stock on hand, reserved quantity, accumulated units, or any derived balance that must not be edited freely. The mechanics live in a per-profile quantity_ledger system table that records every contribution as an entry and carries the running balance.
What can be quantity-ledger backed
Section titled “What can be quantity-ledger backed”Only INT, BIGINT, DECIMAL, and MONEY columns can be quantity-ledger backed. Such a column:
- is read-only to clients — it is projected by the server, not written by users;
- is non-nullable and reads as the latest projected balance;
- cannot also be a Steel computed target;
- can be the target of Steel quantity effects (see below).
See Columns and links for the column-level rules.
The per-profile ledger table
Section titled “The per-profile ledger table”Every profile is provisioned with a quantity_ledger system table when it is created. Its structure belongs to the backend, so general table-definition operations cannot modify or delete it. Provisioning fails if the profile already contains a user table named quantity_ledger.
| Column | Meaning |
|---|---|
name |
The quantity being tracked (see naming below) |
type |
1 = integer, 2 = decimal, 3 = money |
unit |
The unit — for money quantities this is the ISO currency code |
sub |
The subtraction amount of this entry, or NULL |
add |
The addition amount of this entry, or NULL |
balance |
The running balance after this entry |
Every entry is either an addition or a subtraction with a positive amount, never both. The balance after each entry is stored, so the current value is simply the newest row for the name.
Posting an entry
Section titled “Posting an entry”Posting appends one entry and moves the balance:
- the entry is validated — name and unit must be present, the change must be positive, integer quantities must be whole, and money quantities must use a valid currency with the right precision;
- the ledger is locked per profile and name, so concurrent postings serialize;
- the type and unit must match the existing entries for that name — a quantity cannot change from pieces to kilograms;
- the new balance is the previous balance plus or minus the change, appended as a new row.
The balance is allowed to go negative; there is no automatic overdraft rule.
get_current_quantity returns the newest entry for a name, or reports that the quantity does not exist.
Where ledger names come from
Section titled “Where ledger names come from”Script-driven contributions name their entries column:<table definition id>:<row id>:<physical column>, which ties the ledger entry to the exact row and column it projects. Directly posted entries may use any name.
The Steel functions
Section titled “The Steel functions”A script contributes with quantity-add or quantity-subtract:
(quantity-add "product" "stock" 5) ; add 5 to product.stock(quantity-subtract "product" "stock" 2) ; subtract 2- The first argument names the target table, the second the quantity-ledger column on it. The target must be reachable by a link from the current row; the current row’s link value decides which target row receives the contribution.
- The amount must be positive and numeric. A money amount must carry the target column’s currency.
- The call returns its amount, so it can also be used as an expression value.
- The target column’s type decides how the quantity is kept:
| Target column | Quantity type | Unit |
|---|---|---|
INT or BIGINT |
integer | unit |
DECIMAL(p,s) |
decimal | unit; the amount cannot exceed the column’s scale |
MONEY |
money | the ISO currency code |
Each call is also recorded as a stored quantity_effect dependency, so the server knows and validates the complete effect set before the script runs (see Dependencies and hydration).
A typical example — every delivery row adds its quantity to the product’s stock:
(quantity-add "product" "stock" (steel_get_column "delivery" "quantity"))Script contributions and reconciliation
Section titled “Script contributions and reconciliation”A Steel script can declare a quantity effect: add to or subtract from a quantity-ledger-backed column of a target row. The effect must travel a link from the source row to the target row, so the server can resolve which row to change.
Effects are recorded during the run only — nothing touches the ledger until the row is saved. When a script runs over a row, the server:
- loads the contributions the row and script previously created;
- for each effect, resolves the target row and column and verifies the target is quantity-ledger backed;
- applies the difference between the new effect and the stored one — an unchanged effect changes nothing, a changed effect is reversed and reapplied;
- stores the new contribution so the next run can reconcile against it;
- projects the resulting balance onto the target row’s column;
- queues the target row for search reindexing.
Deleting a source row reverses every contribution the row made and removes them.
The target row is locked for the transaction and must exist and not be soft-deleted. The delta between the new and the stored contribution decides what is posted:
| New effect vs stored contribution | Ledger change |
|---|---|
| Same target, same amount | Nothing — no entry is posted |
| Same target, different amount | Only the difference (new − old) is posted |
| Different target row or column | The old contribution is reversed, the new one posted |
| New effect | The full amount is posted |
| Effect no longer produced | The stored contribution is reversed and removed |
Continue
Section titled “Continue”- Table data operations shows what happens when a row that carries quantity effects is written or deleted.
- Steel explains computed columns and scripts.