Skip to content
ERPNext Data Model
Esc
navigateopen⌘Jpreview
On this page

Multi-Company Bootstrap & Global Defaults Provisioning

How a new legal entity acquires a working accounting skeleton, and what a first-time onboarding flow provisions once for the whole system

1. Requirements

1.1 Functional requirements

  • Support many independent legal entities, each with its own chart of accounts, base currency, warehouse tree, cost center tree, and department tree; entities can form a hierarchy, and a subsidiary must inherit its parent’s accounting structure rather than choosing a template.
  • A first-time onboarding flow must, in order: install a broad catalog of shared reference data not scoped to any one entity, create the first legal entity, and record that entity — with a currency and country — as the system’s active defaults.
  • Saving a legal entity record, whether initial creation or any later edit, must on its own converge the entity’s accounting skeleton to a consistent state: chart of accounts, starter warehouses, a cost center, country tax templates, and departments, with no separate manual step.
  • A single system-wide settings record must expose the active default entity, currency, and country, and propagate that choice into the shared defaults store the rest of the system reads from; choosing a currency must make it usable system-wide immediately.
  • A handful of statutory shipping-term codes must exist system-wide from application install, before any legal entity has been created.

1.2 Non-functional requirements

  • Idempotency: re-saving an existing entity, or re-running any provisioning step, must not duplicate ledger accounts, warehouses, cost centers, or departments.
  • Extensibility: a new chart-of-accounts template or country fixture pack plugs in by name/country lookup, without touching core provisioning code.
  • Graceful degradation: a country with no bundled fixtures skips that step silently; one whose fixture module exists but fails aborts with a clear, entity-naming error rather than a half-configured entity.
  • Low operator overhead: one entity save triggers every downstream master-data dependency automatically.

1.3 Constraints

  • Chart-of-accounts templates are static bundled trees, or a point-in-time clone of another entity’s tree — no live sync back to a source after cloning.
  • Only one system-wide default entity/currency/country can be active at a time, even though many entities can exist.
  • Every auto-created bundle (accounts, warehouses, cost centers, departments) is a hierarchical tree, so provisioning always seeds a root before any leaf makes sense.

2. High-Level Design

2.1 Component diagram

2.2 Provisioning walkthrough — the entity-save reconciler, step by step

This runs on every save of a legal entity record (see § 3.2 for why “every save,” not just the first one, matters):

  1. Tree bookkeeping. The entity is itself a node in a parent/subsidiary hierarchy; nested-set bookkeeping runs first.
  2. Accounting skeleton, gated on “no account exists yet for this entity.” If unset and not suppressed: sync financial-statement report templates (skipped when the chart is cloned from an existing entity, on the assumption it already has them), build the chart of accounts, then the starter warehouse tree.
  3. Cost center, gated on “no non-group cost center exists yet.” Create a root cost center named after the entity plus one posting-capable “Main” leaf, and wire the entity’s cost-center-related default fields to that leaf.
  4. Country fixtures and a default tax template, gated on a country-change signal. Install whatever country fixture module matches (skipped, not failed, if none is bundled), and build tax templates from a country-keyed data file.
  5. Departments, gated on “no department exists yet for this entity.” Create a shared “All Departments” root (only if absent — that root carries no entity scope) and a fixed set of entity-scoped leaves beneath it.
  6. Default account resolution, unless suppressed. Fill cash, bank, round-off, depreciation, cost-of-goods-sold, and (if perpetual inventory is on) stock-related account fields by type lookup under this entity; if a default cash account is now set, wire it into the shared cash payment method’s per-entity account list.
  7. Cross-cutting finalization. Enable the entity’s currency system-wide; refresh a perpetual-inventory flag cache if tracked; rebuild the hierarchy if the parent changed; clear caches.

2.3 Data flow — two producers converge on one reconciler


3. Deep Dive

3.1 Data model

Legal Entity — the company record, hierarchical via its own parent-link field. Key fields: a required abbreviation (auto-derived from initials if blank; rejected on collision with any other entity), a default currency, a country, a chart-of-accounts selection mode (Standard Template vs. Existing Company), and roughly twenty default-account link fields. A parent entity forces the chart-of-accounts mode to Existing Company against that parent, and forces the reporting currency to mirror the parent’s.

Ledger Account tree — one per entity, populated from a bundled template (standard, numbered, or a country-specific verified template) or by walking and re-creating another entity’s existing tree node-for-node — the same mechanism a subsidiary uses against its parent. Insertion is depth-first; ordering is rebuilt once at the end, not per node.

Warehouse tree — a fixed starter set per entity: a group root and four leaves (general storage, work-in-progress, finished goods, an in-transit type), name-plus-entity scoped.

Cost Center tree — two nodes per entity: a group root named after the entity, and one posting-capable “Main” leaf that the entity’s cost-center default fields point at.

Department tree — one shared group root (“All Departments”) with no entity scope of its own, and roughly a dozen entity-scoped leaves (accounts, sales, purchase, production, dispatch, and similar) beneath it per entity.

Tax Templates — built from a country-keyed data file mapping a country to sales/purchase tax-and-charges templates and item tax templates, tied to a chart-of-accounts template name or a wildcard fallback. Building them auto-provisions a “Duties and Taxes” (or “Tax Assets”) group account if missing, then leaf tax accounts beneath it.

Global Defaults — a single, system-wide record (not one per entity) exposing the default entity, currency, country, a default distance unit, and document-display toggles (rounded-total and amount-in-words visibility across eight transaction types, enforced via generated UI overrides rather than editing those types). Saving it writes into the shared defaults store the rest of the system reads, and re-enables the chosen currency.

Terms and Conditions / Shipping Term Codes — two plain, manually-authored masters, not auto-provisioned per entity. A terms template is written by an operator and optionally designated as an entity’s default; nothing in the bootstrap path creates one. Shipping-term codes are the exception that is seeded automatically, but only once, system-wide, at application-install time.

3.2 Algorithm — why this is a reconciler, not a one-shot bootstrap

Every step above runs on every save of a legal entity record, not only on creation. Nothing branches on “is this the first save”; each step is independently gated by its own existence query, so a step that already succeeded is a fast no-op on a later save, and one that failed or was skipped gets another chance the next time the record is saved for any reason — correcting an address typo would re-check every gate. That makes this a convergent, idempotent reconciler, not a bootstrap that fires once: “what happens when you create a legal entity” is the natural but incomplete framing; the accurate one is “what the system continuously converges toward, on every save.”

A cross-branch coupling worth naming. A country-change signal starts false on each save and flips true only if the country field actually changed; that signal gates installing country fixtures and the default tax template. But the accounting-skeleton step unconditionally forces the same signal back to true whenever the entity has no account yet, regardless of whether the country changed. So the “country changed” gate is really two conditions sharing one flag — correct for a first-time entity, but the flag’s name misdescribes what triggers it.

Chart-of-accounts creation is a recursive tree walk: given a bundled template or a tree reconstructed from an existing entity’s current accounts, it inserts one account per node (group vs. leaf inferred from whether a node has non-metadata children) and rebuilds ordering once at the end. Cloning works by reading an existing entity’s live account list back into the same nested shape the template format uses — so “clone” and “load a template” share one insertion routine over two sources of the same shape.

3.3 Two entry points: onboarding flow versus the entity’s own save

Application install (once, before any legal entity exists) seeds a few system-wide singleton settings and the bundled shipping-term codes. Nothing here is entity-scoped.

The onboarding flow runs three stages, only the middle one touching the reconciler:

  1. Installs a large catalog of shared, entity-agnostic master data — item groups, territories, customer/supplier groups, generic payment methods, activity types, unit-of-measure definitions, and similar — exactly once for the whole system. Adding a later entity never re-runs this.
  2. Creates a fiscal year and the first legal entity record. Creating that record is what triggers the reconciler in § 3.2 — the flow itself has no chart-of-accounts, warehouse, or cost-center logic; it delegates all of that to the entity’s own save.
  3. Only after the entity (and its skeleton) exists does the flow add what the reconciler doesn’t: two starter price lists, the Global Defaults singleton pointed at what was just created, a couple of tuned settings singletons, and one default bank ledger account created under whichever “Bank Accounts” group the chart template produced.

Adding a later legal entity by hand skips stages 1 and 3 entirely — it only exercises the reconciler, the only piece wired to the entity record’s own lifecycle rather than the flow’s stage list. A second entity never gets its own starter price lists or default bank account automatically.

Two smaller, superseded helper modules in the source tree duplicate pieces of the company-creation and defaults steps, but nothing in the live flow calls either — dead code, not part of the provisioning story.

3.4 Error handling

  • Unrecognized country: treated as “nothing to install,” not a failure.
  • A country module that exists but raises: logged and surfaced as an explicit, entity-naming error.
  • Default-account integrity: every default-account field must belong to the entity, not be a group account, not be disabled, and carry the entity’s currency — any violation blocks the save.
  • Currency and valuation changes after transactions exist: changing default currency, disabling perpetual inventory, or changing the valuation method is blocked once postings exist.
  • Hierarchy integrity: a parent entity must itself be a group node; an abbreviation collision anywhere in the system is rejected.

4. Scale and Reliability

  • Load pattern: rare and admin-driven — on the order of once per legal entity stood up, never a hot path.
  • Idempotency as the safety net, not a lock. Every step is guarded by an existence check rather than a distributed lock, adequate given low-frequency, effectively single-operator saves; two administrators racing the same entity’s first save could both pass an existence check before either insert completes — an accepted, unaddressed gap, not a designed guarantee.
  • The department root is a shared, cross-entity resource. Because “All Departments” has no entity scope, every entity’s first save contends on the same row — unlike the entity-scoped, contention-free warehouse and cost-center roots.
  • The onboarding flow’s shared-catalog stage is a one-time cost that does not repeat as entities are added; only the reconciler does, and its per-entity cost is small and bounded.
  • Chart-of-accounts insertion is not incremental — every node is inserted individually before one tree-order rebuild; cost scales with template size, once per entity.

5. Trade-off Analysis

Decision Trade-off
One reconciler, re-run every save, gated per step by existence checks Self-heals a partial entity on its next save, at the cost of re-executing several existence queries on every ordinary edit.
One “country changed” signal reused for both a real change and first-time bootstrap No duplicated logic, but the signal’s name no longer describes what actually sets it.
Chart-of-accounts cloning reuses the templating insertion routine No separate subsidiary code path, but the clone is a point-in-time copy with no ongoing link to the source tree.
A shared, entity-agnostic department root instead of one per entity Free shared taxonomy, but a shared write target when concurrent entities’ first saves land together.
Onboarding master data installed once, globally, not re-scoped per entity Fast onboarding, no reseeding — but a later entity gets none of the flow’s finishing touches unless recreated by hand.
Default-account resolution only fills fields still empty Never clobbers a manual override, but a field set incorrectly once will not self-correct later.

6. What to Revisit as the System Grows

  • Split the coupled signal into “country actually changed” and “skeleton doesn’t exist yet,” so a future change to one trigger doesn’t silently change the other.
  • Concurrency on first save. If entity creation becomes bulk or automated, existence-check-only idempotency will need a real lock or a database uniqueness constraint.
  • Decouple the department root from cross-entity contention, or document it as intentionally shared, if many entities start onboarding concurrently.
  • Give later entities feature parity with onboarding — a second entity gets no starter price lists or default bank account today; worth an explicit “finish entity setup” action.

Was this page helpful?