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_SERIESwith the same provider code. - A table field whose
FieldDef.CodeProviderpoints 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
CUSTOMERprovider. - 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
Codefield. - The field provides the code provider name.
- The module resolves
CodeProviderDeffromDataRegistry.CodeProviders. - During commit, the module locks the matching
SYS_NUMBER_SERIESrow. - 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
Codefield.
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-XXXXcan produceC-0003.SO-YYYY-XXXXXXcan produceSO-2026-000123.INV-YYYY-MM-XXXXXcan produceINV-2026-06-00001.
Pattern tokens include:
Xpositions for the sequential number.YYYYorYYfor year.MMfor month.DDfor day.Qfor quarter.Sfor semester.WWfor 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
Idas the primary key. - Use a normal
Codefield for the generated value. - Register provider definitions before modules.
- Ensure
SYS_NUMBER_SERIES.Codematches the provider name. - Seed or create a
SYS_NUMBER_SERIESrow before the provider is used. - Mark generated code fields as read-only in normal editing.
- Use clear provider names such as
Customer,ProductorSalesInvoice. - Use
DRAFT-providers only when the business workflow really has draft and final numbering. - Keep pattern capacity large enough for expected volume.