Appearance
Guide · Add Operational Registers for Inventory and Receivables
This guide shows one clean way to add two Operational Registers in an American-market Trade vertical:
- Inventory by Warehouse
- Customer Receivables Open Items
These two examples demonstrate the two most common OR patterns:
- quantity balance register;
- open-item amount register.
Goal
We want durable operational facts that answer questions such as:
- how many units of an item are on hand in a warehouse?
- how much is still open on a customer invoice?
Step 1: Define the business question first
Register A: Inventory by Warehouse
Use this when you need:
- quantity on hand;
- warehouse balances;
- inventory movement traceability.
Register B: Customer Receivables Open Items
Use this when you need:
- outstanding invoice balance by document;
- aging support;
- payment application traceability.
Step 2: Choose clear register codes
Example codes:
trd.inventory_by_warehousetrd.customer_receivables_open_items
The exact format can vary, but keep the code stable and descriptive.
Step 3: Define dimensions and resources
Inventory by Warehouse
Dimensions:
- item
- warehouse
- optionally lot, serial, unit of measure
Resources:
- quantity
- optional amount / cost basis
Customer Receivables Open Items
Dimensions:
- customer
- document
- document date
- due date
- optional business unit
Resources:
- open amount
- open amount in document currency
Step 4: Create register metadata or definition
Your vertical should declare the register so the platform knows:
- the code;
- dimensions;
- resources;
- read semantics;
- which documents are expected to append movements.
Pseudo-example:
csharp
public sealed class InventoryByWarehouseRegister : OperationalRegisterDefinition
{
public const string Code = "trd.inventory_by_warehouse";
public override string RegisterCode => Code;
public override IReadOnlyList<DimensionDefinition> Dimensions => new[]
{
Dimension.Guid("item_id", "Item"),
Dimension.Guid("warehouse_id", "Warehouse")
};
public override IReadOnlyList<ResourceDefinition> Resources => new[]
{
Resource.Decimal("quantity", "Quantity"),
Resource.Decimal("amount", "Amount")
};
}Step 5: Add storage migrations
A simple example movement table for inventory might look like:
sql
create table if not exists trd_or_inventory_movement
(
movement_id uuid primary key,
register_code text not null,
posting_utc timestamp with time zone not null,
source_document_id uuid not null,
item_id uuid not null,
warehouse_id uuid not null,
quantity numeric(18,4) not null,
amount numeric(18,4) not null default 0
);
create index if not exists ix_trd_or_inventory_movement__item_wh_posting
on trd_or_inventory_movement (item_id, warehouse_id, posting_utc);A receivables movement table may look similar but use customer/document/due-date dimensions.
Step 6: Decide sign conventions
This is critical.
Inventory
- inbound quantity = positive
- outbound quantity = negative
Receivables open items
- invoice open amount = positive
- payment or credit application = negative
If sign conventions are unclear, the register will become hard to debug.
Step 7: Produce movements from documents
Sales Invoice
Typically produces:
- negative inventory quantity movements;
- positive receivable open-item movement.
Customer Payment
Typically produces:
- negative receivable open-item movement when applied.
Example movement builder
csharp
public static IReadOnlyList<OperationalRegisterMovement> BuildSalesInvoiceInventoryMovements(SalesInvoice doc)
{
return doc.Lines.Select(line => new OperationalRegisterMovement
{
RegisterCode = "trd.inventory_by_warehouse",
PostingUtc = doc.PostingUtc,
SourceDocumentId = doc.Id,
Dimensions = new Dictionary<string, object?>
{
["item_id"] = line.ItemId,
["warehouse_id"] = line.WarehouseId
},
Resources = new Dictionary<string, decimal>
{
["quantity"] = -line.Quantity,
["amount"] = -line.CostAmount
}
}).ToArray();
}Step 8: Read balances and details
The OR should expose read paths that match the business question.
Examples:
- inventory balance by item and warehouse as of date;
- open receivables by customer as of date;
- detail movements for one document.
Step 9: Think about finalization and performance
If the register will be read heavily across periods, design for:
- good movement indexes;
- dirty-period tracking if applicable;
- bounded finalization jobs;
- report-friendly read models.
Checklist
Before considering the register ready, verify:
- sign conventions are documented;
- documents append the correct movements;
- reversal or storno flows are explicit;
- balances reconcile with expected business behavior;
- reports can read the register without UI-side N+1 lookups.
These two examples cover the most important OR patterns you will reuse across real NGB verticals.