ZuloOne
English
Sign in
Menu

🗃️ The information register (Information)

Updated

← Developer wiki

The registerEngineType: "Information" engine stores a point on the time axis, not an accumulation. The rule follows 1C's default: one row per combination of dimensions as of Period; the next row with the same key and a later Period closes the previous one. The slice is SliceLast / SliceFirst.

This is not a from/to interval: dated intervals with gaps live as a dictionary or as a link table with an "effective from / to" pair — for example LT_PriceTypeHistory (prices in the EffectiveFrom / EffectiveTo window). The difference is fundamental: a slice pulls the last value forward forever, while a closed window ends and after it there is no value at all. Information is for a history of "what value was in force on a date" without that gap.

What it is not#

Accumulation (Standard / FIFO / …) Information (Information)
TR_ + TB_ tables (movements and balances) IR_{Name} only
A resource is a delta, the balance is summed A resource is a value (number, string, date, reference, boolean)
IRegisterMovementService / ITotalsManager GetService<IInformationRegisterService>()
Posting a document writes movements The script calls SetAsync itself; cancelling the document deletes the rows by RecorderMetaId
Double entry, balance, a totals driver None of those flags exist

Information is not part of RegisterEngines.HoldsPhysicalMovements: it has no TR_, and DocumentPostingService does not look for it among the postable registers.

The IR_ schema#

  • MetaId — the PK
  • Period DATETIME2 — the moment of the slice (not a user dimension)
  • RecorderMetaId — the document that wrote the row (nullable)
  • the columns of the physical dimensions (MetaRegisterDimension)
  • the columns of the resources (MetaRegisterResource, the type from the EDT)
  • CreatedDateTime / ModifiedDateTime
  • the unique index: Period + the operational, non-informational dimensions
  • an index on RecorderMetaId

The row key is Period + the dimensions with isOperational && !isInformational. The same combination → an UPDATE of the resources and of the recorder. A different Period → an INSERT.

The API and the scripts#

POST   /api/registers/{id}/info                 — Set (upsert)
POST   /api/registers/{id}/info/slice-last      — { asOf?, dimensions?, skip?, take?, filter?, orderBy? }
POST   /api/registers/{id}/info/column-values   — distinct SliceLast text values { column, search, take? }
POST   /api/registers/{id}/info/slice-first
GET    /api/registers/{id}/info/records         — ?from=&to=&recorderMetaId=&skip=&take=&filter=&orderBy=
GET    /api/registers/{id}/info/{rowMetaId}     — one row
DELETE /api/registers/{id}/info/{rowMetaId}
DELETE /api/registers/{id}/info/recorder/{doc}

The journal asks for a window of 40 rows (skip / take), the same way a dictionary list does. take=0 returns the count without the rows. Scripts that omit take still receive the whole slice. Typing in a column's Values filter calls POST .../info/column-values (DISTINCT on the slice, capped) instead of scanning the loaded page.

Reading is Register / Read, writing and deleting are Register / Write. An empty matrix restricts nothing; as soon as a Read row appears on the register, Write is no longer fail-open: an explicit Write grant is required. An explicit deny is a 403.

Set / Delete check the posting boundary the same way a document's postings do: a Period date that is no later than the closed date is rejected.

From a script:

var info = GetService<IInformationRegisterService>();

await info.SetAsync(new TaxRate
{
    Period = document.DocumentDate,
    TaxCode = line.TaxCode,
    Rate = line.Rate,
    RecorderMetaId = document.MetaId,
});

var slice = await info.SliceLastAsync<TaxRate>(document.DocumentDate,
    new Dictionary<string, object?> { ["TaxCode"] = line.TaxCode });
decimal rate = slice[0].Rate;

TaxRate is the generated POCO ([Table("IR_TaxRate")], the class = the name of the register). The untyped SetAsync("TaxRate", period, dims, values) also remains.

SliceLast — for each combination of the key, the last row with Period <= asOf. SliceFirst — the first with Period >= asOf.

Dimensions are references to records#

A dimension that points to master data (an item, a store, a tax, a counterparty) is typed as a Reference EDT onto a dictionary. In IR_ it is the record's Guid; SliceLast / SliceFirst return that same Guid. The slice journal resolves the caption and opens the record's card — like a grouping in a totals report.

Do not store the item's name as a string ("Widget", "A"). A string is a scalar, not a relation: the slice will not open a card, and renaming in the dictionary breaks the key. A scalar String / Decimal / Boolean is only for when the value has no record of its own (a rate, an exchange rate, a mode flag).

Cancelling a document (ReverseAllMovementsAsync) calls DeleteByRecorderAsync(documentId) across every Information register.

In an integration test the same operations go through the harness, without resolving the service:

await Db.SetInformationAsync(new TaxRate { Period = date, TaxCode = code, Rate = 15m, RecorderMetaId = doc });
var slice = await Db.SliceLastAsync<TaxRate>(date, dims);
await Db.DeleteInformationAsync("TaxRate", rowId);

The untyped SetInformationAsync("TaxRate", date, dims, values) also remains.

Values are bound by the column's EDT type#

InformationRegisterService converts every value of SetAsync (dimensions and resources) and every dimension filter of SliceLastAsync / SliceFirstAsync by the field's EDT type (InformationRegisterValues), not by the look of the value:

EDT type Accepts Bound as
String, LargeText anything text (a Guid or a date as its text)
Reference, Guid Guid, Guid text uuid
DateTime, Date DateTime, ISO text timestamp
Int, Enum a number or numeric text integer
Decimal a number or numeric text decimal
Boolean bool, "true" / "false" boolean

A value that does not convert fails the call with an ArgumentException that names the field. Guessing from the look of the value would bind a Guid-shaped string as uuid even on a String column. Postgres then fails with operator does not exist: character varying = uuid, and only on real data: a 1C Ref_Key, an ISO date kept as text.

Some scripts still shield such values with a leading ~; OneCSyncState in the Okrasheno client model is one. The shield still works. Removing it needs a data migration, because the stored keys carry the ~.

The journal in the UI#

The register's menu item and the Journal button in the designer open the same TotalsWindow. For Information it is a dated-values journal, not a totals report. The window title is the register's localized caption. A one-line hint says the window is the slice as of a date; scripts write it, the journal does not.

The grid is SliceLast on a date (default: now), optionally filtered by dimensions — one row per key, the value in force. It loads a window and fetches the next page on scroll. The Values-tab search in a column filter looks up distinct values on the slice, not the already-loaded page (POST .../info/column-values). There is no History tab and no create / edit / delete: Set / Delete stay on IInformationRegisterService and on tests. History of every IR_ row remains on GET .../info/records for scripts.

SliceFirst stays on the API and in scripts; the journal does not expose it. Column headers use the UI language (displayCaption). A reference dimension cell is a relation to a record (a Guid), not merely a caption: clicking it opens the dictionary card. Related dictionary fields are offered in the column picker the same way document movements are.

A live example in Sales: the IR AbcClassification (the profile + the Guid of the item/customer) is written by IAbcClassifier — ABC/XYZ.

A document's movements (DocumentMovementsWindow) also show the IR rows with RecorderMetaId = the document.

When to create one#

  • a rate, an exchange rate, a status, "the value in force on a date";
  • the key is known in advance (item, tax, store — references to records, not names) and changes as a whole at a moment in time.

Do not create one if:

  • a sum or a balance is needed — that is an accumulation register;
  • intervals with gaps and a ban on overlaps are needed — a dictionary with ValidFrom/ValidTo.

IRegisterMovementService (balances / movements / transactions.Add) does not accept Information — NotSupportedException. Deleting the register cascades to the IR_ table and its rows. The assistant reads the slice from IR_, not from TB_.

POST /api/schema/sync drops the physical tables that no longer belong to a register:

  • IR_* whose suffix is no longer the name of any register (a rename, a delete without a cascade);
  • TR_ / TB_ / TL_ / TC_ of a register that has become Information;
  • IR_ of a register that has become an accumulation register.

Data from a dropped table does not migrate: a change of name or of engine means a new table. The rights on a register are Read / Write (the "Permissions" tab in the designer, the same operations as in Security).

Where next#

Was this page helpful?