Skip to content

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.

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.

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 appends one entry and moves the balance:

  1. 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;
  2. the ledger is locked per profile and name, so concurrent postings serialize;
  3. the type and unit must match the existing entries for that name — a quantity cannot change from pieces to kilograms;
  4. 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.

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.

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"))

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:

  1. loads the contributions the row and script previously created;
  2. for each effect, resolves the target row and column and verifies the target is quantity-ledger backed;
  3. applies the difference between the new effect and the stored one — an unchanged effect changes nothing, a changed effect is reversed and reapplied;
  4. stores the new contribution so the next run can reconcile against it;
  5. projects the resulting balance onto the target row’s column;
  6. 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
  • Table data operations shows what happens when a row that carries quantity effects is written or deleted.
  • Steel explains computed columns and scripts.