The stock ledger
Inklura keeps stock in a signed ledger: a journal of movements whose sum gives the
ledger stock. The ledger stock is the authority and can go negative. The displayed
stock is only its mirror floored at zero: displayed stock = max(0, ledger stock). This page
describes the model, the anatomy of a movement, how reordering reads stock and the checks that
keep everything consistent.
For the end-to-end flow (stock → reordering → supplier email), see the overview; for every action that writes stock, see write paths.
Two values, one contract
Every tracked product carries two values:
| Value | Role | Can be negative? |
|---|---|---|
| Ledger stock | Signed authority: opening + Σ receptions − Σ deliveries ± corrections. A negative value signals an outflow recorded before the inflow that covers it (customer delivery confirmed before the supplier reception). |
Yes |
| Displayed stock | Mirror shown in back-office screens and lists: max(0, ledger stock). |
No |
Every physical movement records the true signed delta in the ledger, even when it takes the ledger stock below zero. The next reception nets out that negative instead of adding to a counter stuck at 0.
A physical movement (reception, delivery, count, manual movement) updates the journal and both values in the same operation. Every change to a tracked product's stock leaves an entry in the journal, and the daily checks flag any gap.
Why signed stock
In a just-in-time operation, physical stock often sits at 0 between two receptions. A counter that refuses to go below zero loses information every time: a customer delivery of 5 units recorded while the counter is at 2 only removes 2, and the other 3 vanish. The next reception then creates phantom stock (units that exist in the application but not on the shelf), and the following supplier order is undersized.
Signed stock makes this impossible: the delivery of 5 is recorded in full (ledger stock −3), and the reception of 10 brings stock to 7, not 10.
Anatomy of a movement
Every movement, real or planned, is a row in the stock movement journal (the Movements
screen, « Mouvements », /manage/ecommerce/inventory/stock-actions; the back office is in
French):
| Field | Role |
|---|---|
| Type | Inflow or outflow. The journal also knows descriptive types (damaged, lost, found…) that have no effect on the ledger stock |
| Origin | Manual correction, customer delivery, supplier reception, inventory count, customer order (planned outflows), storefront sync, opening balance, system, API. A reception or delivery validated automatically keeps the supplier-reception or customer-delivery origin, with a note that identifies it |
| Status | Planned (projection, « En attente » on screen), applied (counted in the ledger stock, « Terminé » on screen) or cancelled |
| Before / change / after | Real ledger stock before the movement, signed change, ledger stock after. change = after − before always holds. |
| Reference | The originating business object: customer order, delivery, supplier order, count… |
| Business date | The date of the originating object (expected or actual arrival of a supplier order, delivery date of a customer order), distinct from when the movement was entered |
Only applied movements count: the ledger stock is the sum of the changes of the product's applied inflows and outflows. Planned movements are projections (the expected inflow from a sent supplier order, the expected outflow from a customer order): they touch neither the ledger stock nor the displayed stock.
Lifecycle and reversal
stateDiagram-v2
[*] --> PLANNED : projection created (order sent, customer order)
PLANNED --> APPLIED : confirmed at the physical movement
PLANNED --> CANCELLED : source cancelled or recalculated (no stock effect)
APPLIED --> [*] : deletion with exact reversal
CANCELLED --> [*]
note right of APPLIED
The only state that changes stock.
Before and after are recomputed
against the real ledger stock.
end note
- Planned → applied: at confirmation time, quantities are recomputed from the current ledger stock. The projection's values are never reused as they are.
- Planned → cancelled: no stock effect. This is the normal fate of a projection whose source is cancelled or recalculated.
- An applied movement is never silently cancelled. Deleting it removes exactly its change from the ledger stock, then recomputes the displayed stock from the result. This stays correct even for a movement that took stock below zero.
Tracked and untracked products
A product's manage stock setting separates two behaviours:
| Stock managed | Stock not managed | |
|---|---|---|
| Stock movements | Recorded, with their journal entry | None: stock stays at 0 |
| Read by reordering | Ledger stock, through the guardrails below | 0: the order covers the full customer demand |
| Projections (planned movements) | Yes | Never, except for a generic product |
An untracked product has no stock truth: reordering treats it as full demand. The only exception for projections is a generic product, which carries the supplier order lines and gets their planned inflows even without stock management.
How reordering reads stock
The supplier-ordering engine does not blindly subtract the displayed number. It decides how many units on hand can really cover demand:
flowchart TD
A[Product] --> C{Stock managed?}
C -->|no| Z0["retain 0"]
C -->|yes| D{"Just-in-time supplier<br/>and stock not validated?"}
D -->|yes| Z0
D -->|no| H["value = max(0, ledger stock)"]
H --> J["− promised customer deliveries<br/>not recorded yet"]
J --> K["+ inbound units claimed<br/>by no customer"]
K --> L["retain max(0, result)"]
The guardrails, one by one:
- Stock not managed ⇒ 0. An untracked product provides no coverage.
- Just-in-time ⇒ 0. A supplier that holds no inventory has no real shelf; its stock is treated as zero, unless you validated it by counting it or entering it yourself.
- Negative stock ⇒ 0. A negative ledger stock signals an inflow that was never recorded, not a future need: it is always brought back to 0 for the calculation and never increases the next order. This behaviour is not a setting.
- Committed outflows. Customer deliveries already promised that no upcoming supplier slot can serve (gone without being recorded, or due before the next arrival) are taken out of the available stock: those units are no longer free.
- Inbound units. Units already ordered that no customer order claims (the rest of a whole pack, for example) count as stock for the next slot.
The result never goes below 0.
Phantom stock, for review
Positive stock that looks too high compared with recent consumption is not capped by the
calculation: it is subtracted as it is. It is however flagged on the Stock gaps screen
(« Écarts de stock », /manage/ecommerce/vendors/orders/phantom-stock; the links to it from Supplier orders and Outflows without inflow are still labelled « Stock fantôme ») when it covers more than 8 weeks of
consumption (measured over the last 8 weeks), or when the product had no outflow over that period.
Recount it, correct it, or confirm it is right: stock you have counted, entered yourself
or validated leaves that list.
Consistency checks
Inklura checks every day that the ledger stays consistent. The main checks:
| Check | What it catches |
|---|---|
| No negative displayed stock | A mirror that escaped the max(0, …) rule |
| No non-zero stock on an untracked product | A write to an untracked product |
| No stale inflow projection | A planned reception whose order is cancelled, or sent and well overdue. A draft awaiting review is a normal state. |
| No orphan projection | A planned movement pointing to a deleted supplier order |
Displayed stock = max(0, sum of applied movements) |
Any change to displayed stock without a journal entry |
| Ledger stock = sum of applied movements | A gap in the value reordering actually reads |
| No delivery recorded with a zero change | A delivery whose consumption was lost |
Stock of variations that manage their own stock is kept separately, with its own movements; the checks above and reordering work on the product's stock. Details of the checks and catch-ups: Safety nets.
Example: a delivery before the reception
The typical just-in-time scenario: a customer delivery goes out before the reception that covers it is recorded. Watch both values.
The product is tracked (stock managed).
| Ledger stock | Displayed stock |
|---|---|
| 2 | 2 (= max(0, 2)) |
The sum of applied movements is +2 (the opening balance). All checks are green.
You confirm the delivery (or automatic validation catches it later). The recorded movement:
before = 2 (real ledger stock)
change = −5 (the true delta, no clamping)
after = −3 (signed ledger stock)
displayed = max(0, −3) = 0
| Ledger stock | Displayed stock |
|---|---|
| −3 | 0 |
The −3 means 3 delivered units are still waiting for their reception. A counter stuck at zero would only have removed 2 units, and 3 units of consumption would have vanished.
Reordering brings the −3 back to 0: the retained stock is 0 and the uncovered customer demand is enough to feed the supplier order. Nothing is lost, and nothing is ordered twice for a delivery that has already left.
The supplier order arrives and you confirm the reception:
before = −3
change = +10
after = 7
displayed = max(0, 7) = 7
| Ledger stock | Displayed stock |
|---|---|
| 7 | 7 |
The reception nets out the −3 instead of adding to a counter stuck at 0, which would have given 10, that is 3 phantom units.
Sum of applied movements = +2 − 5 + 10 = 7 = ledger stock. Displayed stock = 7 = max(0, 7). No delivery with a zero change. Every physical unit is accounted for; the next supplier order starts from a real number.
See also
- Overview: the full flow on one page
- Stock write paths: every action that writes to the ledger
- Supplier orders: the reordering that reads this stock
- Daily timeline: when each scheduled task runs
- PrestaShop sync: how the storefront feeds the ledger
- Safety nets: consistency checks and catch-ups
- Back office: movement journal, corrections, counts
- Configuration: stock and ordering settings