ZuloOne
English
Sign in
Menu

Write a totals driver hook

Updated

A totals driver is the calculation that runs because a register moved, not because a particular document posted. Most registers need none: registerEngineType: "Standard" is enough. Write a driver when the rule must follow every document that touches the register (cost write-off, first-event VAT) or when the engine itself is special (FIFO lots).

Note

The class is public partial class <Name>TotalDriver. Do not declare the base — the platform generates it from baseClassName. Do not edit Core/TotalDrivers/; those *.engine.cs files are read-only copies of the kernel engines.

How to#

1. The driver object — TotalDrivers/<Name>/<Name>.driver.json, kind: "TotalDriver". Set baseClassName to a kernel engine (DefaultTotalDriver, FifoTotalDriver, FifoStockTotalDriver, StockBaseTotalDriver, StockQtyBaseTotalDriver, MoneyTotalDriver, CashlessMoneyTotalDriver, MarginTotalDriver, CurrencyExchangeTotalDriver) and baseEngine: "Standard". isKernel is false — you are not shipping a Core engine.

2. The hook — the platform seeds <Name>TotalDriver.script.json + .cs beside it (scriptType: "TotalDriver"). Override only the hooks you need and call base:

Hook When
ValidateTransactions The platform hands the whole document movement set. Inspect, do not write yet.
EndDocument Movements are already stored. The one place after write — Costing and UA first-event accrue here.
CalculateOutcomes / CalculateIncomes Split / value a movement (FIFO family).
CalculatePartialAmount Partial lot cost. Exists from FifoTotalDriver down, not on DefaultTotalDriver.
public partial class CostingIssueTotalDriver
{
    public override void ValidateTransactions(
        TransactionPairCollection transactionPairs, TransactionCollection transactions)
    {
        base.ValidateTransactions(transactionPairs, transactions);
        // fold this register's movements by item…
    }

    public override void EndDocument(DateTime transactionDate, Guid docId)
    {
        base.EndDocument(transactionDate, docId);
        // write-off after the stock movements exist
    }
}

3. Bind it on the register: totalDriverMetaId in Registers/<Name>/<Name>.object.json. That engine replaces the register's own. Do not hang a second driver beside it — wrap this script with Chain of Command (next(), forgotten next is ZOCOC003).

4. Bump modelVersion in the same change.

An information register has no driver. A kernel engine (StockBaseTotalDriver, …) is wrapped with next(), not a partial class of the same name.

Warning

A driver sees coordinates (TransactionBase). It does not see dynamic analytics at all. Declare a cut the hook must read as a physical dimension. If you bind it only as an analytic, IsCoordinateNull drops every movement, the hook is a no-op, and the log is silent — movements stored, tax or cost not accrued. That is why UaVatFirstEvent made LegalEntity a dimension.

Costing lives in the driver on Stock so every future issue document cannot forget the write-off. Ukraine first-event VAT is the same shape: the rule is the increment of the base, not a posting on each document. See Create a register and Accumulation register.

What's not there yet#

You cannot retarget an already-issued FIFO lot from a script and "just change the amount". A quantity-zero, amount-only movement poisons both FIFO and AVG. Late price fixes go to P&L or through a StockAdjustment that carries quantity. Core/ copies are for reading signatures, not for editing.

Where next#

Was this page helpful?