Table of Contents

Code Providers

Code providers generate sequential user-visible codes.

They are used for values such as customer codes, product codes and document numbers.

A code provider is not the primary key.

The primary key remains the Id field.

The generated code is a business value stored in a normal field, usually named Code.

Role

A code provider declaration connects three things:

  • A provider name registered in DataRegistry.CodeProviders.
  • A row in SYS_NUMBER_SERIES with the same provider code.
  • A table field whose FieldDef.CodeProvider points to that provider name.

At commit time, the data module uses that metadata to generate the next code and write it to the row before saving.

Number Series Table

Code generation is backed by SYS_NUMBER_SERIES.

The table stores the runtime state of each series.

CREATE TABLE {TableName} (
    Id              @NVARCHAR(40) @NOT_NULL primary key,
    Code            @NVARCHAR(40) @NOT_NULL,
    Name            @NVARCHAR(96) @NOT_NULL,
    Pattern         @NVARCHAR(64) @NOT_NULL,
    ResetPeriodId   integer default 0 @NOT_NULL,
    NextNumber      integer default 1 @NOT_NULL,
    LastResetValue  @NVARCHAR(16) @NULL,
    IsActive        @BOOL default 1 @NOT_NULL,

    CONSTRAINT UQ_NumberSeries_Code UNIQUE (Code),
    CONSTRAINT UQ_NumberSeries_Name UNIQUE (Name)
    )

Important fields are:

  • Code, the provider name.
  • Name, the display name.
  • Pattern, the final code format.
  • ResetPeriodId, the reset period.
  • NextNumber, the next integer to use.
  • LastResetValue, the last reset key.
  • IsActive, whether the series is active.

Registering Code Providers

Register provider definitions before modules.

The tERP registry uses a dedicated registration step.

public override void RegisterCodeProviders()
{
    DataRegistry.AddOrUpdateCodeProvider("Product");
    DataRegistry.AddOrUpdateCodeProvider("SalesInvoice");
    DataRegistry.AddOrUpdateCodeProvider("DRAFT-SalesInvoice");
}

The provider name must match SYS_NUMBER_SERIES.Code.

The descriptor says that a provider exists.

The SYS_NUMBER_SERIES row stores the pattern and current numbering state.

Linking A Field To A Provider

A field uses SetCodeProviderName() to point to a provider.

Table.AddString("Code", 40, Flags: FieldFlags.Required | FieldFlags.Searchable | FieldFlags.ReadOnlyEdit | FieldFlags.ReadOnlyUI).SetCodeProviderName("CUSTOMER");

This means:

  • The field is named Code.
  • The field value is generated by the CUSTOMER provider.
  • The field is required.
  • The field is searchable.
  • The user should not edit it normally.

The ReadOnlyEdit and ReadOnlyUI flags are the usual choice for generated code fields.

Commit Flow

The default DataModule handles code generation during commit.

The flow is:

  • The module finds the Code field.
  • The field provides the code provider name.
  • The module resolves CodeProviderDef from DataRegistry.CodeProviders.
  • During commit, the module locks the matching SYS_NUMBER_SERIES row.
  • It reads NextNumber.
  • It applies reset logic when required.
  • It increments NextNumber.
  • It formats the final code using Pattern.
  • It writes the value into the row's Code field.

This happens inside the commit transaction.

The code is assigned only for inserted rows whose Code field is empty.

Patterns

Pattern controls the final code text.

Examples:

  • C-XXXX can produce C-0003.
  • SO-YYYY-XXXXXX can produce SO-2026-000123.
  • INV-YYYY-MM-XXXXX can produce INV-2026-06-00001.

Pattern tokens include:

  • X positions for the sequential number.
  • YYYY or YY for year.
  • MM for month.
  • DD for day.
  • Q for quarter.
  • S for semester.
  • WW for ISO week.

The pattern must contain at least one numeric position.

The reset period must be compatible with the date tokens in the pattern.

For example, a monthly reset needs year and month tokens.

Number Series Module

Applications normally expose SYS_NUMBER_SERIES through a module.

The 04-mini-crm sample registers the module with CodeProviderModule.

ModuleDef Module = DataRegistry.AddModule("NumberSeries", TitleKey: "Number Series", ClassName: typeof(CodeProviderModule).FullName, ListSelectSql: SqlText, IsSingleSelect: true);
TableDef Table = Module.Table;
Table.Name = "SYS_NUMBER_SERIES";
Table.AddId();
Table.AddString("Code", 40, Flags: FieldFlags.Required | FieldFlags.ReadOnlyUI);
Table.AddString("Name", 96, Flags: FieldFlags.Required);
Table.AddString("Pattern", 64, Flags: FieldFlags.Required);
Table.AddEnumLookupId("ResetPeriodId", "ResetPeriod", TypeStore.Get("ResetPeriod"), Flags: FieldFlags.Required);
Table.AddInteger("NextNumber", Flags: FieldFlags.Required | FieldFlags.ReadOnlyUI);
Table.AddString("LastResetValue", 16, Flags: FieldFlags.ReadOnlyUI);
Table.AddBoolean("IsActive", Flags: FieldFlags.Required);

This lets the application inspect and manage number series rows.

Draft And Final Codes

Some business documents need two series.

For example:

  • A draft sales invoice may use DRAFT-SalesInvoice.
  • The posted sales invoice may use SalesInvoice.

tERP uses this pattern for documents.

The document data module resolves both providers and chooses the draft provider during normal editing and the final provider during posting.

This is application behavior built on the same code provider descriptors.

Generated Code Providers

The Registration Builder can discover code provider metadata from schema comments.

It generates the same declarations a developer would write manually.

public override void RegisterCodeProviders()
{
    DataRegistry.AddOrUpdateCodeProvider("Product");
    DataRegistry.AddOrUpdateCodeProvider("DRAFT-SalesInvoice");
    DataRegistry.AddOrUpdateCodeProvider("SalesInvoice");
}

It also generates SYS_NUMBER_SERIES seed statements for discovered provider patterns.

The generated module fields still use SetCodeProviderName().

tblTop.AddString("Code", MaxLength: 40, Flags: FieldFlags.Required | FieldFlags.ReadOnlyEdit | FieldFlags.ReadOnlyUI).SetNullable(false).SetCodeProviderName("Product");

Manual registration and generated registration use the same runtime model.

Practical Rule

When declaring code providers manually:

  • Use code providers for business codes, not primary keys.
  • Keep Id as the primary key.
  • Use a normal Code field for the generated value.
  • Register provider definitions before modules.
  • Ensure SYS_NUMBER_SERIES.Code matches the provider name.
  • Seed or create a SYS_NUMBER_SERIES row before the provider is used.
  • Mark generated code fields as read-only in normal editing.
  • Use clear provider names such as Customer, Product or SalesInvoice.
  • Use DRAFT- providers only when the business workflow really has draft and final numbering.
  • Keep pattern capacity large enough for expected volume.