ZuloOne
English
Sign in
Menu

🧱 Global Core and country localization

Updated

How if (country == …) does not appear in the platform, and where to put the law, contributions, a bank or government portal, and statutory forms.

← Developer wiki · the product meaning — direction · the reference package — 20-Localization

Two different "kernels"#

Layer What it is Repository
The platform Metadata, code generation, schema, scripts, forms, API zulo.one (src/ZuloOne.Core, …)
The business Global Core The universal Base + Solution models zuloone-workspace (Common … Sales, Tax, Accounting)
Localization A country package, the Customization(3) layer the same folder: LocalizationSaudiArabia, LocalizationUkraine, …

The platform knows nothing about Ukraine or Saudi Arabia. The business kernel does not branch by country. A country is a model: objects, seed data, extensions, tx/events with next(), and tests.

The extension mechanisms (these are the "providers")#

Do not introduce a C# ICountryLocalization / ITaxProvider / IPricingProvider just to implement a switch. The existing mechanisms are:

  1. A model plus dependencies in model.json. A model that is not installed is not in the EffectiveModelSet — its fields, scripts and menu items disappear without a DROP.
  2. The extension aggregate <Target>.<Model> — fields and tx on another model's document (a live example: SalesInvoice.LocalizationSaudiArabia / SaudiVatTx).
  3. Chain of Command — next / nextAsync on events, services, commands, tx, drivers. See chain-of-command.
  4. Rule data — TaxRule plus conditions/actions; the rate by date on TaxRate. The law changes through the rows of a package, not through a branch in TaxService.
  5. A host connector / a Job — the network, crypto, files. A workspace script does not write this (the Enforce policy).

Pricing is not a country provider. IPricingService lives in Inventory and serves any country. An individual contract is Global Core (a SalesContract of an outlet: Outlet is required), see pricing. Incoterms are the kernel too: Common.DeliveryTerm on the contract, not in the country package.

What goes into a country package#

As in the 20-Localization-Overview.md template. A package is a whole country, not a VAT module:

  • the seed of tax codes, rates, rules and the mapping of the declaration's boxes;
  • the contributions that do not fit into the shared SocialInsurance as rates (the Ukrainian ПДФО/ВЗ are objects of the package; ЄСВ and GOSI are HR rates);
  • the fields and print forms the law requires;
  • the channels to the country's services: the tax authority, a bank, a government portal, an e-invoice. The implementation is IOutboundGateway / IOutboundCall plus a host, not an HttpClient in a script. Fatoora and the future PrivatBank / Diia / ДПС cabinet are one mechanism;
  • the statutory accounts as a seed into the existing ChartOfAccounts, not as a new ledger engine;
  • the package's tests.

Do not put there: universal Sales, Stock or Pricing, the mobile client, a shared Tax Engine, shared social insurance as a separate "Ukrainian register" when the HR rates are enough. ABC analysis goes into Sales (IAbcClassifier), not into a country package and not into Core: ABC/XYZ. A country's print forms are files of the package; the assistant edits LayoutJson in the database through set_print_form_layout (not a PDF) — see print forms.

i18n ≠ localization pack#

UI translations and of the metadata captions (the language object caption: { "en": …, "ru": …, "ar": … }) are the platform and the metadata of all models. RTL is the SPA. This is not the KSA package.

How a caption actually reaches the user in the request's language — and the two ways an endpoint silently falls out of that — is metadata captions.

The KSA package is VAT, ZATCA and the country's own services beyond that, not the translation of the UI. The status of translations and RTL — the Arabic UI. E-invoice compliance is process 15 in process map.

A new country#

  1. Create a model with the zuloone-new-model skill, layer 3, dependencies on Tax / Sales / Accounting (at a minimum); a bank or a government portal will add its own objects and a channel, not a new countryless "integration" model.
  2. Create a seed DataPackage, not percentages in the Sales code.
  3. Extensions only if fields or a country reporting register are needed.
  4. Bump the package's modelVersion in the same change.
  5. A tenant without this model sells and computes tax with the Tax kernel — the package is not mandatory.
  6. The wiki pages go into wiki/business/localization/, wiki/work/localization/, wiki/developer/localization/, not as leaves at the root of the section.

Removing a package from a tenant#

Removing the folder from git does not remove the model: the installer only installs and updates. In the tenant's designer a Zulo model (publisher ZuloOne) cannot be deleted — protected by a write lock.

Remove it from the panel, the Models page: expand the tenant → the bin next to the model → type the model name. This is a Core cascade (DELETE /api/metadata/models/{id}): the metadata, the extensions in other models' objects, the scripts, the menu, the physical tables. Take a snapshot before that — the container is not recreated, and the snapshot file is the only undo.

If other models depend on this one (LocalizationUkraine on Sales) — 409, remove the leaves first. Core and the tenant's stand model are not removable. After success the tenant.Models pin is rewritten with an explicit list of what is left, otherwise a recreate will bring the package back.

A live sample: LocalizationSaudiArabia/ and LocalizationUkraine/ in the workspace. The packages' pages are localizations, not leaves at the root of the business wiki.

Deployment to a tenant is per model, not in a batch#

MetaPackages.PackageVersion moves only for a model that imported without an error of its own and compiled Ok. A neighbour that Failed or did not pass MinVersion gets no stamp and is tried again on the next start. The other, already installed versions are neither rolled back nor stuck because of it.

Where next#

Was this page helpful?