🧱 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:
- 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. - The extension aggregate
<Target>.<Model>— fields and tx on another model's document (a live example:SalesInvoice.LocalizationSaudiArabia/SaudiVatTx). - Chain of Command —
next/nextAsyncon events, services, commands, tx, drivers. See chain-of-command. - Rule data —
TaxRuleplus conditions/actions; the rate by date onTaxRate. The law changes through the rows of a package, not through a branch inTaxService. - 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
SocialInsuranceas 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/IOutboundCallplus a host, not anHttpClientin 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#
- Create a model with the
zuloone-new-modelskill, 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. - Create a seed DataPackage, not percentages in the Sales code.
- Extensions only if fields or a country reporting register are needed.
- Bump the package's
modelVersionin the same change. - A tenant without this model sells and computes tax with the Tax kernel — the package is not mandatory.
- 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.