00 · Common — shared foundation
Updated
Layer Base(1). Depends only on Core (framework). Geography, currencies,
units of measure, and trade terms are referenced by all other models.
Common has no documents and no registers: it is a pure layer of dictionaries
and services called by other event handlers.
Purpose#
Common answers questions that are shared by any model: what a country is called, how much one currency costs in another on a date, how many days a payment term gives, how to convert kilograms to tonnes, and what to fill in by default in a new record. None of this is industry-specific — Sales, Purchasing, Accounting, and HR use the same objects instead of creating their own copies.
There are no scalar EDTs (Money/Qty) here — they are introduced by
Accounting (Money) and Inventory
(Qty). Common provides only reference and enumerable EDTs.
What is created (object map)#
| Kind | Name | Purpose |
|---|---|---|
| Model | Common |
module model, layer Base(1) |
| Dictionary | Country, Region, City, Address |
geography, the country → region → city hierarchy |
| Dictionary | Currency, ExchangeRate |
currencies and a dated rate to the base currency |
| Dictionary | UnitOfMeasure, UnitClass |
units of measure and the physical quantity that they measure |
| Dictionary | PaymentTerm, DeliveryTerm |
payment term in days and delivery term (Incoterms) |
| Dictionary | Gender |
gender (the dictionary exists, but no model consumes it) |
| Dictionary | CommonSettings |
general tenant settings (singleton): default currency/country, trade profile |
| LinkTable | CountryCurrency |
currencies used in a country; the primary one is marked by IsPrimary |
| Service | CurrencyRateService |
converts an amount between currencies on a date |
| Service | UnitConverter |
converts quantities between units in the same class |
| Service | PaymentDueService |
payment due date from a term and a document date |
| Service | RecordDefaults |
default values for a new record (currency, legal entity, store, payment term, dates) |
| Service | TradeProfileService |
the only entry point to the tenant mode switch (Full/BuySell) |
| Enum | TradeProfile |
tenant mode: Unspecified/Full/BuySell |
| GlobalConstant | group Measurement: AmountScale, QuantityScale, WeightScale |
rounding precision for amounts/quantities/weight |
| Menu | menu.json |
the Common tab |
Total: 11 dictionaries, 1 link table, 5 services, 1 enum, and 3 constants. No documents and no registers.
model.json#
Layer Base(1). Dependency: only Core. Nothing in Common depends on
Accounting/Inventory/Sales — the dependency goes strictly in the opposite
direction: any model that needs a country, currency, or unit of measure declares
a dependency on Common.
Number sequences#
One per dictionary: CountrySeq (starts at 1000 — country codes are 1000+
because the base data package seeds 248 ready rows), CurrencySeq,
UnitOfMeasureSeq, UnitClassSeq, AddressSeq, CitySeq, RegionSeq,
GenderSeq, PaymentTermSeq, DeliveryTermSeq, ExchangeRateSeq,
CommonSettingsSeq.
EDT#
Reference EDTs: RefCountry, RefCurrency, RefUnitOfMeasure,
RefUnitClass, RefAddress, RefCity, RefRegion, RefGender,
RefPaymentTerm, RefDeliveryTerm.
Enumerable EDT: TradeProfileEdt (a wrapper over TradeProfile).
Dictionaries#
Country#
ID (system), Name (String 256, required, translatable), CodeISO2
(String 2), CodeISO3 (String 3), PhoneCode (String 8), Currency
(RefCurrency, optional — the country's default currency, separate from
CountryCurrency). displayFormat: "{Name} - ({CodeISO2})". Not hierarchical.
It is not a hierarchical dictionary, but it is part of the Country → Region →
City chain through required back references: Region and City always require
Country.
The base data package seeds 248 countries (ISO2/ISO3/phone code, name in en/ru/ar, and a currency reference by code — for example, Andorra → EUR).
Referenced by: Region.Country, City.Country, CountryCurrency.Country,
Organization.LegalEntity, HR.Employee,
HR.HRSettings.
CountryCurrency#
A link table, not a dictionary: properties Country (ref), Currency (ref),
IsPrimary (Boolean) — which currencies are used in a country, with the primary
one marked by the flag. The default legal-entity currency is taken from here
(Organization), separately from the direct
Country.Currency field: one country can settle in several currencies, and the
primary one does not have to match Country.Currency.
Region, City, Address#
Region: ID, Name (required, translatable), Country (RefCountry,
required). City: ID, Name (required, translatable), Region
(RefRegion, optional), Country (RefCountry, required). Address: ID,
Name (String 512, required — one-line address), Street, Building,
Apartment, PostalCode (strings, optional), District (String 128), City
(RefCity), Country (RefCountry).
The three levels are not alternative ways to specify a place. They are a nested
hierarchy: City.Region is optional (a city can be specified without a region),
but City.Country and Region.Country are always required.
The conditional data package cities-SA (only when the tenant country is Saudi
Arabia) seeds 13 regions and 22 cities of the kingdom (Riyadh, Jeddah, Mecca,
Medina, Dammam, and others), linked by country and region name.
Referenced by: RefAddress — Inventory.Customer,
CustomerOutlet, Organization.LegalEntity.
Currency, ExchangeRate#
Currency: ID, Name (required, translatable), Code (String 3, required —
ISO-4217), Symbol (String 8).
ExchangeRate is a dated rate to the base currency. It uses the same
EffectiveFrom/EffectiveTo window pattern as TaxRate (Tax): Currency
(required), RateToBase (Decimal 18.8, required — how many base-currency units
one unit of this currency costs), EffectiveFrom (required), EffectiveTo
(optional — empty means an open window that is still valid).
The base currency is not declared by a flag anywhere — it is the currency
whose RateToBase = 1. A rate is always transitive through the base currency:
direct pairs such as "currency A → currency B" are not stored.
The base package seeds 155 currencies, but not rates — ExchangeRate is empty
until the first manual entry or integration; the dictionary has no menu item
(unlike the other ten) — it is maintained through an API/service or integration,
not through the UI.
UnitOfMeasure, UnitClass#
UnitClass is the physical quantity measured by a unit (mass, length, volume,
and so on): ID, Code (String 16, required, unique), Name (required,
translatable). Unit conversion is possible only inside one class: kilograms
are not converted to metres not because a rule is missing, but because the
quantities are different.
UnitOfMeasure: ID, Name (required, translatable), Code, UnitClass
(RefUnitClass, optional), RatioToBase (Decimal 18.9, optional — how many
base units of this class there are in one unit; the class base unit carries
1), DecimalPlaces (Integer, optional — quantity precision; empty means the
global constant QuantityScale is used).
A unit without RatioToBase (for example, "box") is not converted by this
service at all — its factor depends on the item, and that is the job of
ItemQuantityConverter in Inventory, not UnitConverter
here.
The base package seeds 6 classes and 19 units of measure.
Referenced by: RefUnitOfMeasure — almost the whole workspace: Item,
ItemUnit, InventorySettings, lines of GoodsIssueLines, PickTaskLines,
PutAwayTaskLines, StockAdjustmentLines, StockCountLines,
StockTransferLines (Inventory), BomComponent,
ProductionOrder, ProductionOrderComponents (Production),
PurchaseOrderLines (Purchasing), SalesInvoiceLines,
SalesOrderLines (Sales), SalesQuotationLines
(CRM).
PaymentTerm, DeliveryTerm#
PaymentTerm: ID, Name (required, translatable), Days (Integer,
optional — payment deferral in days). Validation: Name is not empty.
DeliveryTerm: ID, Code (String 16, required, unique — Incoterms 2020:
EXW, FCA, FAS, FOB, CFR, CIF, CPT, CIP, DAP, DPU, DDP), Name (required,
translatable). Validation: Code and Name are not empty; Code is trimmed
and upper-cased before uniqueness comparison — fob and FOB are considered
the same string.
The base package seeds 7 PaymentTerm rows (Immediate, 7/14/30/45/60/90 days)
and all 11 DeliveryTerm rows.
Referenced by: RefPaymentTerm — Sales.SalesContract,
SalesOrder, SalesRealization, SalesSettings,
Purchasing.PurchaseOrder, PurchasingSettings,
Inventory.Customer, CRM.SalesQuotation.
RefDeliveryTerm — Sales.SalesContract, SalesOrder,
SalesRealization, CRM.SalesQuotation.
Gender#
ID, Code (String 16, optional, unique), Name (required, translatable).
The base package seeds 3 rows (MALE/FEMALE/UNSPECIFIED).
No field in any model consumes it at the time of writing — including
HR.Employee, where it would be expected. The dictionary exists and
is available (RefGender), but there are no references to it in the workspace.
CommonSettings#
Singleton (isSingleton, isCached) — one record per tenant. ID,
DefaultCountryCode (RefCountry), DefaultCurrency (RefCurrency),
DefaultCurrencyCode (String 128, hidden on the form — stamped from
DefaultCurrency automatically), TradeProfile (TradeProfileEdt).
The currency code and reference are synchronised in one direction: saving a
non-empty DefaultCurrency stamps DefaultCurrencyCode from its Code; loading
with an empty reference but a non-empty code resolves the reference back by code.
An empty reference on save does not erase an already stored code — an
external integration can write the code without a reference without risking
clearing the previous value.
The form groups DefaultCountryCode and DefaultCurrency under Country and currency, and TradeProfile under Trade; ID and DefaultCurrencyCode are
hidden.
Services#
CurrencyRateService(ICurrencyRateService) —RateToBaseAsync(currency, date)finds theExchangeRatewindow covering the date; if there is no window or the rate is ≤ 0, it returnsnull, never default1.ConvertAsync(amount, from, to, date)returns the amount unchanged for the identical currency even if no rate exists at all; otherwise it converts transitively through the base (amount × from_rate / to_rate), and a missing rate on either leg returnsnull.FactorAsyncdoes the same for amount1.UnitConverter(IUnitConverter) —ConvertAsync(quantity, from, to): identical units are unchanged; otherwise it resolves bothUnitOfMeasurerecords and delegates to the pure static helper ConvertByRatio, which returnsnullif the classes differ or either unit has noRatioToBase— this is NOT an error, but a signal that this service cannot convert the value.ConvertRoundedAsyncrounds by the target unit precision (DecimalPlacesor the global constant QuantityScale).PaymentDueService(IPaymentDueService) —DaysOfAsync(term):0if the term is empty or not found.DueOn(date, days):date + max(days, 0); a date with year< 1902(the "not set" sign) is replaced with today.RecordDefaults(IRecordDefaults) — the central service for default values, called from 30+ event handlers across the workspace.SeedAsync(objectName)collects:Currency(fromCommonSettingsorAccountingSettings),LegalEntity(fromOrganizationSettings),OutputLocation(fromProductionSettings),PaymentTerm(fromPurchasingSettingsfor purchasing objects — the code checks object namesPurchaseOrder,PurchaseCreditNote,VendorPayment, and also non-existent"Vendor"(the dictionary is calledSupplier, so this branch is dead) — otherwise fromSalesSettings), start dates (EffectiveFrom,DateFrom,HireDate,CountDate,DeliveryDate,TaxPointDate,PeriodFrom— all today) andDueDatethroughPaymentDueService.A trap that cost time. The event handler must call
SeedAsync, notForNewAsync, fromOnBeforeCreate:ForNewAsyncitself runs the object'sOnBeforeCreatebefore appending values — calling it from inside the sameOnBeforeCreaterecurses while the card is opened.TradeProfileService(ITradeProfileService) — the only allowed entry point toCommonSettings.TradeProfile; neitherProductionnor tenant setup reads the field directly.CurrentNameAsyncreturnsUnspecified/Full/BuySell(no record —Unspecified).EnsureBuySellAsyncdoes not downgrade a tenant that has already explicitly selectedFull.ProductionBlockReasonAsyncreturns user-facing refusal text forBuySell, otherwisenull.
Business logic and invariants#
ExchangeRate windows do not overlap for one currency. The discipline is the
same as for TaxRate: RateToBase > 0, EffectiveTo ≥ EffectiveFrom, and an open window (empty EffectiveTo) is considered valid
until infinity during overlap checks.
Conversion always goes through an implicit base entity. Neither currencies
nor units of measure have a separate "this is base" flag — the base is the
record whose rate/factor equals one. Missing data returns null, not a
substituted one or zero — silent equivalence is worse than an explicit refusal
here.
TradeProfile is a tenant mode switched only by the service. BuySell
forbids creating and transitioning production orders (Production) —
the refusal message is formed by TradeProfileService itself, not invented by
the document handler.
DeliveryTerm.Code is canonicalised on save (trim + upper-case) — fob and
FOB are not created as two different delivery terms.
Menu#
The Common group → Dictionaries (Country, Region, City, Address,
UnitOfMeasure, Currency, UnitClass, Gender, PaymentTerm, DeliveryTerm —
ExchangeRate is not shown in the menu) · Settings (CommonSettings).
Links with other modules#
Common does not depend on any application model — other models depend on it.
Almost every workspace model uses RecordDefaults for default values:
Accounting, Costing, CRM,
HR, Inventory, Organization,
Production, Purchasing,
Sales, Tax, and also the Ukrainian localization.
CurrencyRateService is additionally called by Accounting (exchange-rate
difference revaluation in JournalEntry), UnitConverter by Inventory
(ItemQuantityConverterService, PricingService), and TradeProfileService by
Production and TenantSetup (Inventory).
Objects#
Generated from workspace metadata by node tools/gen-reference.mjs; edit the metadata, not this block.
Dictionaries#
Address — Addresses
| Field | Caption | Type | Required |
|---|---|---|---|
Apartment |
Apartment / office | String(32) | No |
Building |
Building | String(32) | No |
City |
City | → City |
No |
Country |
Country | → Country |
No |
District |
District | String(128) | No |
ID |
Code | String(50) | Yes |
Name |
Address line | String(512) | Yes |
PostalCode |
Postal code | String(16) | No |
Street |
Street | String(256) | No |
Characteristic — Characteristics
| Field | Caption | Type | Required |
|---|---|---|---|
DecimalPlaces |
Decimal places | Integer | No |
Description |
Description | String(500) | No |
ID |
Code | String(50) | Yes |
Name |
Name | String(150) | Yes |
ReferenceDictionary |
Reference dictionary | String(128) | No |
Unit |
Unit | String(20) | No |
ValueType |
Value type | CharacteristicValueTypeEdt |
No |
CharacteristicOption — Characteristic options
| Field | Caption | Type | Required |
|---|---|---|---|
Characteristic |
Characteristic | → Characteristic |
Yes |
ColorHex |
Colour | String(9) | No |
ID |
Code | String(50) | Yes |
Name |
Name | String(150) | Yes |
SortOrder |
Order | Integer | No |
CharacteristicSection — Characteristic sections
Headings that group characteristics on a card: Product, Packaging, Storage. Translatable; template lines point at them.
| Field | Caption | Type | Required |
|---|---|---|---|
ID |
Code | String(50) | Yes |
Name |
Name | String(100) | Yes |
SortOrder |
Order | Integer | No |
CharacteristicTemplate — Characteristic templates
The set of characteristics a record fills: «Paints» asks Colour, Gloss, Volume. Lines are the link table CharacteristicTemplateLine.
| Field | Caption | Type | Required |
|---|---|---|---|
BaseTemplate |
Based on | → CharacteristicTemplate |
No |
Description |
Description | String(500) | No |
ID |
ID | String(50) | Yes |
Name |
Name | String(150) | Yes |
City — Cities
| Field | Caption | Type | Required |
|---|---|---|---|
Country |
Country | → Country |
Yes |
ID |
Code | String(50) | Yes |
Name |
Name | String(256) | Yes |
Region |
Region | → Region |
No |
CommonSettings — Common settings
| Field | Caption | Type | Required |
|---|---|---|---|
DefaultCountryCode |
Default country | → Country |
No |
DefaultCurrency |
Default currency | → Currency |
No |
DefaultCurrencyCode |
Default currency code | String(128) | No |
ID |
ID | String(50) | Yes |
TradeProfile |
Trade profile | TradeProfileEdt |
No |
Country — Countries
| Field | Caption | Type | Required |
|---|---|---|---|
CodeISO2 |
ISO2 code | String(2) | No |
CodeISO3 |
ISO3 code | String(3) | No |
Currency |
Currency | → Currency |
No |
ID |
Code | String(50) | Yes |
Name |
Name | String(256) | Yes |
PhoneCode |
Phone code | String(8) | No |
Currency — Currencies
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(3) | Yes |
ID |
ID | String(50) | Yes |
Name |
Name | String(256) | Yes |
Symbol |
Symbol | String(8) | No |
WordsMinorFew |
Fraction, 2–4 (few) | String(64) | No |
WordsMinorGender |
Fraction gender | WordGenderEdt |
No |
WordsMinorMany |
Fraction, 5+ (many) | String(64) | No |
WordsMinorOne |
Fraction, 1 (one) | String(64) | No |
WordsUnitFew |
Unit, 2–4 (few) | String(64) | No |
WordsUnitGender |
Unit gender | WordGenderEdt |
No |
WordsUnitMany |
Unit, 5+ (many) | String(64) | No |
WordsUnitOne |
Unit, 1 (one) | String(64) | No |
DeliveryTerm — Delivery terms
Incoterms of the deal (EXW, FOB, DAP…). Global Core, not a country pack.
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(16) | Yes |
ID |
Code | String(50) | Yes |
Name |
Name | String(256) | Yes |
ExchangeRate — Exchange rates
Dated rate of a currency to the base one. Pairs convert THROUGH the base: from.RateToBase / to.RateToBase.
| Field | Caption | Type | Required |
|---|---|---|---|
Currency |
Currency | → Currency |
Yes |
EffectiveFrom |
Effective from | DateTime | Yes |
EffectiveTo |
Effective to | DateTime | No |
ID |
Code | String(50) | Yes |
RateToBase |
Rate to base | Decimal(18,8) | Yes |
Gender — Gender
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(16) | No |
ID |
Code | String(50) | Yes |
Name |
Name | String(100) | Yes |
PaymentTerm — Payment terms
| Field | Caption | Type | Required |
|---|---|---|---|
Days |
Days | Integer | No |
ID |
Code | String(50) | Yes |
Name |
Name | String(256) | Yes |
Region — Regions
| Field | Caption | Type | Required |
|---|---|---|---|
Country |
Country | → Country |
Yes |
ID |
Code | String(50) | Yes |
Name |
Name | String(256) | Yes |
UnitClass — Unit classes
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(16) | Yes |
ID |
Code | String(50) | Yes |
Name |
Name | String(64) | Yes |
UnitOfMeasure — Units of measure
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(16) | No |
DecimalPlaces |
Decimal places (quantity precision) | Integer | No |
ID |
Code | String(50) | Yes |
Name |
Name | String(256) | Yes |
RatioToBase |
Ratio to base unit | Decimal(18,9) | No |
UnitClass |
Unit class | → UnitClass |
No |