ZuloOne
English
Sign in
Menu

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.

← General specification

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 the ExchangeRate window covering the date; if there is no window or the rate is ≤ 0, it returns null, never default 1. 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 returns null. FactorAsync does the same for amount 1.
  • UnitConverter (IUnitConverter) — ConvertAsync(quantity, from, to): identical units are unchanged; otherwise it resolves both UnitOfMeasure records and delegates to the pure static helper ConvertByRatio, which returns null if the classes differ or either unit has no RatioToBase — this is NOT an error, but a signal that this service cannot convert the value. ConvertRoundedAsync rounds by the target unit precision (DecimalPlaces or the global constant QuantityScale).
  • PaymentDueService (IPaymentDueService) — DaysOfAsync(term): 0 if 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 (from CommonSettings or AccountingSettings), LegalEntity (from OrganizationSettings), OutputLocation (from ProductionSettings), PaymentTerm (from PurchasingSettings for purchasing objects — the code checks object names PurchaseOrder, PurchaseCreditNote, VendorPayment, and also non-existent "Vendor" (the dictionary is called Supplier, so this branch is dead) — otherwise from SalesSettings), start dates (EffectiveFrom, DateFrom, HireDate, CountDate, DeliveryDate, TaxPointDate, PeriodFrom — all today) and DueDate through PaymentDueService.

    A trap that cost time. The event handler must call SeedAsync, not ForNewAsync, from OnBeforeCreate: ForNewAsync itself runs the object's OnBeforeCreate before appending values — calling it from inside the same OnBeforeCreate recurses while the card is opened.

  • TradeProfileService (ITradeProfileService) — the only allowed entry point to CommonSettings.TradeProfile; neither Production nor tenant setup reads the field directly. CurrentNameAsync returns Unspecified/Full/BuySell (no record — Unspecified). EnsureBuySellAsync does not downgrade a tenant that has already explicitly selected Full. ProductionBlockReasonAsync returns user-facing refusal text for BuySell, otherwise null.

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.

The Common group → Dictionaries (Country, Region, City, Address, UnitOfMeasure, Currency, UnitClass, Gender, PaymentTerm, DeliveryTerm — ExchangeRate is not shown in the menu) · Settings (CommonSettings).

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
Was this page helpful?