Skip to content

40% off the solution priceReserve early access

NG One
Platform

Five decisions that cannot be added later, made before the first posting

An ERP is chosen for ten years and judged in the second. What decides how the system looks in year ten is not its screens but the assumptions built into the data model before the first document is posted: how analysis is carried, how data is isolated, who may do what, and what happens to history when a rule changes. NG One has those decisions behind it. Everything standing on them — nine business spaces, some sixty technical domains, and the eight end-to-end processes that thread through them from lead to cash — shares one data model, one isolation boundary and one authorisation stack.

  • Modular monolith
  • Isolation in the database
  • Six layers of authorisation
  • OpenAPI over everything
  • Versioned configuration
  • Control/Data Plane boundary

Why architecture, and not a feature list

Every ERP on the market has invoices, stock and a general ledger. The difference does not show in the demo; it shows in year three, when someone asks for something the model never anticipated.

Features get added. Assumptions do not. When a system has run for years on a model with no analytical dimension on the posting line, dimensions are not introduced through a setting — they are introduced through a new chart of accounts and a re-posting of history, which nobody does. When a tax rate is written into code as a number, the system does not know it was different last year, and the first correction to a prior-period document quietly produces the wrong amount. When authorisation stayed on the front end, every new channel — a handheld, an integration, an API key — opens a door nobody locked. Three different problems, one cause: a decision deferred until it set.

NG One made those decisions at the start, wrote them down in public, and enforces them in the schema. The core is a modular monolith — one deployment, modules with explicit boundaries, communication strictly through named interfaces and domain events. That is a choice, not a compromise: microservices would introduce a distributed transaction where one transaction belongs, and module boundaries exist perfectly well without a network between them. Customer data shares a database but never shares a row — isolation is a PostgreSQL policy the application cannot bypass, and the build fails if a table departs from it. Legal entities are a dimension inside a customer, not a second installation.

Above that sits a layer most of the local market does not have as a product: configuration is data — versioned and audited. Document types, state machines, settings, posting rules and price books carry effective dates and a change history, and every transaction keeps a reference to the version of the rules it was created under. The practical consequence is simple: a consultant changes how the system behaves through the console without a developer, and a change made today never changes what was posted yesterday. Everything that follows on this page is derived from those three sentences.

Five decisions that cannot be retrofitted

They come out of mapping Pantheon, Business Central, Odoo, SAP B1 and the regional vendors against Serbian legislation for 2026. None of them is a feature a user can see — all five are assumptions in the data model, and that is exactly why they cannot be added afterwards.

  1. Dimensional posting

    Every posting line carries an unlimited set of analytical dimensions — cost centre, cost object, project, department, region, channel — instead of pushing analysis into the chart of accounts. Why it cannot be added later: a dimension that does not exist at the moment of posting does not exist in history either. A system that solved analysis through account codes holds it in the chart, not on the line; introducing dimensions then means a new chart of accounts and re-posting everything already closed. NG One posts dimensionally from the first line, so controlling and project accounting are not a new module — they are one more dimension over data that already carries it.

  2. Effective-dated statutory parameters

    Tax-free thresholds, contribution bases, the minimum wage, interest rates and VAT rates are always a register with a valid-from and valid-to date, never a constant in code. Why it cannot be added later: a system that stored a rate as a number does not know it was different last year. The first correction to a prior-period document applies today's rate to last year's turnover, and the error stays invisible until an inspection finds it. In Serbia these parameters change at least once a year, often mid-year — payroll and VAT break at the first amendment without this rule.

  3. A tenant model ready for agency mode

    The identity model anticipates one user belonging to several customers with separate roles in each — an accountant at a firm works a client portfolio from a single sign-in, without logging out and back in, while data isolation between clients stays intact. Why it cannot be added later: a system that assumed one user means one company solves the agency case with a separate installation per client. From that point on every upgrade is paid for as many times as the firm has clients, and that cost never stops. In NG One an agency portfolio is a mode of the same identity model — one installation, one upgrade, the isolation boundary untouched.

  4. A connected domain graph with separated integrations

    Internally, the delivery note, the invoice, the VAT record and the collection are connected entities in one flow — and in 2026 the state connected them formally as well, so the graph is modelled from the beginning. Externally the picture is different: e-invoicing, e-delivery, the central invoice registry, fiscalisation and VAT records are distinct systems with distinct lifecycles, so each gets its own adapter, its own synchronisation states and its own retry, with no assumption of shared state. Why it cannot be added later: connectedness baked into the integration layer means an outage at one government service halts flows that have nothing to do with it — and separating them then requires rewriting the domain, not the adapter. The connection belongs in the domain; the integrations are deliberately apart.

  5. Clean-core extensions and versioned configuration

    Adapting to a customer never touches the core: extensions attach to domain events, and configuration — document types, state machines, settings, rules — is data with a version, a history and a diff view. Why it cannot be added later: once a customer modification enters the core, every upgrade is negotiated against every modification. That price is familiar from local and global vendors alike — customisations hold upgrades back, and an upgrade that keeps slipping becomes a frozen version nobody dares touch. Versioning also means a settings change made today never retroactively alters what was posted yesterday.

All five are recorded in the architecture decision register (ODLUKE.md §19), each with its reasoning, the alternatives weighed against it, and its consequence for the data model. A decision of this kind is never reversed, so it has to be readable — and visible in the data: open any general ledger line and the dimensions are on it; open a document from a closed period and the version of the rules it was created under is on it.

The core

The technology choices are fixed and public. None was made for fashion — each answers a problem we have seen in the systems customers are leaving.

Tenant Data Plane
Intelligence layer
  1. Interface

    React 19 SPA · typed clients from OpenAPI

    One application, server data separated from interface state, two themes and two densities. Permissions on the client only display — they never decide.

  2. API layer

    REST · OpenAPI · ProblemDetail (RFC 9457)

    The specification is the source of truth rather than documentation written afterwards: typed clients are generated from it. The same resources carry the bridge to MCP tools.

  3. Identity and authorization

    Spring Authorization Server · OAuth 2.1 · OIDC · Argon2id

    Six layers of authorization on the server, never on the client: roles, data scope, limits, segregation of duties, maker-checker and delegation — with an immutable log.

  4. Document framework and workflow kernel

    State machines · automation rules · outbox

    Document lifecycle, approvals with limits and event → condition → action rules live in the core, so every module inherits the same behaviour instead of writing its own.

  5. Modular monolith

    Spring Boot 4.1 · Spring Modulith 2.1 · Java 25

    One deployment, modules with hard boundaries: communication runs strictly through named interfaces and domain events via an outbox, and architecture tests fail the build when a boundary is crossed.

  6. Data and isolation

    PostgreSQL 18 · Row Level Security (FORCE)

    Every table holding customer data carries a tenant identifier and a policy the runtime role cannot bypass; a meta-test over the schema fails the build if one is left without it.

Control Plane
  • Modular monolith

    Spring Boot 4.1 on Java 25, Spring Modulith 2.1, one deployment. Modules have hard boundaries: communication runs strictly through named interfaces and domain events via an outbox, and architecture tests fail the build when someone crosses a boundary. Microservices are not ruled out forever — communication is already shaped so a module can be lifted out when there is a reason, and not before.

  • Customer isolation in the database itself

    PostgreSQL 18. Every table holding customer data carries a tenant identifier and a Row Level Security policy in FORCE mode; the runtime role has no right to bypass it. The tenant context is set at the start of every transaction, and a meta-test over the schema fails the build if a table with a tenant identifier has no policy. Isolation is therefore a property of the database, not a discipline of developers.

  • Multiple legal entities inside one customer

    A legal entity is a dimension, not a second installation: every transactional table carries its legal entity, document numbering runs per legal entity, and a user's access to a legal entity is validated by the server on every command. The hierarchy is customer → legal entities → organisational units → documents and warehouses. Multi-currency operation is supported, with the National Bank rate list as the source.

  • Identity on the standard

    An embedded Spring Authorization Server: OAuth 2.1 and OpenID Connect, authorization code with PKCE for the SPA, refresh token rotation, ES256 with published JWKS. QR sign-in for warehouse scanners is a dedicated grant on the same server, not a detour around it. Passwords go through Argon2id. A homegrown auth protocol was never on the table.

  • Document framework and state machines

    A document is not a generic table with a magic type but a table per module, alongside a type registry and a configurable state machine: states, transitions, guards and effects are configured per document type, while named guards and effects are registered in code. Everything with accounting or tax meaning is a typed column; JSONB carries additional fields only. Invariants are always on.

  • Workflow kernel

    The state machine runs the life of a document; the workflow kernel runs the work of people — tasks, single and multi-step approvals, conditional branching by amount or type, deadlines with escalation, delegation and cover. The kernel is our own and deliberately small: a full BPM engine brings someone else's data model, someone else's UI and an operational burden that approving an invoice does not justify.

  • Carried by AI or automation

    Automation rules

    Event → condition → action, configured through the console, with named actions registered in code. The system creates the task, sends the notification, drafts the document or performs the transition when conditions are met — and every such move stays visible in the execution history, answering what the system did on its own and under which rule.

  • Business calendar and periods

    A fiscal year need not be a calendar year. Periods are kept per module, accounting separately from tax, with soft and hard close — and reopening runs only through approval and stays in the journal. A document carries five dates separated from day one: document date, delivery date, posting date, due date and tax date, each with its own role in the calculation.

  • Rule versioning and “explain this posting”

    Posting schemes, tax rates, price books, exchange rates and document configuration are effective-dated and versioned. Every posting keeps a reference to the exact rule version plus a snapshot of the key values, so “explain this posting” can show, for any line, which document, which scheme at which version, which rate, which exchange rate, and who changed the configuration and when. An old transaction reconstructs identically after any later change.

  • Configurability without developers

    Settings carry metadata — value type, scope, validation and guidance — so the console renders them itself: a new setting does not require a new screen. Through that console a consultant opens a customer, selects modules, applies a predefined configuration set, creates the first legal entity and the admin account. Every configuration change lands in an append-only journal with the old and the new value.

  • Import and export as a framework

    Import is not a script per module but a platform framework: a template builder the consultant uses to build the template, source-to-field mapping that is saved and reused, validation with per-row errors, a dry-run diff before commit, and idempotency — a repeated import does not duplicate. Excel export exists on every list in the system, not only on reports.

Versions follow the technology lock file and move through a controlled quarterly window; every major jump carries a recorded decision and a compatibility check.

NG One Platform Control Center

Running the platform is a product of its own, not the largest account in the system.

The usual answer is a “super tenant” — an account that sees everything, because somebody needed to open a customer and look into a complaint. It is quiet and practical right up to the question of who was reading your contracts and margins at eleven last night. There is usually no answer, because a system that left the boundary to a role has nothing to record.

NG One splits the platform into two planes with a hard boundary between them. The Control Plane holds metadata about customers: subscriptions, licences, consumption, health, support, the platform journal. The Data Plane holds the customer's business data. Platform staff live in the first plane, with mandatory multi-factor sign-in, and by default have no access whatsoever to the second — they cannot read business data even with a direct query, because they hold no rights over it in the database. Everything the Control Plane knows about a customer's work arrives as aggregated, anonymised metrics.

The only route across the boundary is Support Access: an engineer requests access with a reason, a scope and a duration, the customer's administrator approves it inside their own system, and the session carries a banner in the interface, expires by itself and is recorded in both journals — the platform's and the customer's. The customer then receives a report of what was done. Ahead of any such request stands a diagnostic bundle with no personal data, which resolves most cases without entering the data at all.

Two planes, one boundary

Control Plane

Customers and environments, catalogue and price books, subscriptions, licences, consumption, health, support, the platform journal. Separate schema, separate roles, mandatory multi-factor sign-in. No default access to business data — through any API.

Tenant Data Plane

Documents, partners, the general ledger, stock, payroll — the customer's data. Isolated per customer by Row Level Security. It never reaches toward the Control Plane; from the Control Plane it is touched only through an approved, time-boxed, fully audited Support Access session.

  • Customers and environments

    A customer list and detail, with environments (production, test, sandbox), application and schema version, and lifecycle status. The lifecycle is a guarded state machine, from prospect and provisioning through trial and active operation to dormancy and controlled termination — every transition leaves a record.

  • Catalogue, editions and versioned price books

    Product → edition → package → price book with effective dates. A price book is never overwritten; it gets a new version, so any contract can be read against the prices in force when it was signed. Changes are reviewed as a diff before they take effect.

  • Subscriptions and contracts

    A subscription with items, a contract with discounts and special terms, renewal with notices at several intervals. Moving up or down a package carries an effective date, and a downgrade checks whether current consumption actually fits the new package before it is confirmed.

  • Licences and entitlements

    A customer's rights are computed from their packages with controlled exceptions, and checked at the API layer alongside the permission check — a capability is not the same thing as a module. Licences are allocated by user type (full, limited, handheld, read-only). Licence expiry leads into a grace period with degradation, never an abrupt shutdown. A signed offline licence for on-premise operation is supported.

  • AI credits

    AI consumption is kept as a ledger: allocation, consumption and overage, append-only, with a budget per customer and visible cost. A customer knows at any moment how much has been spent and on what, and a manual allocation above a threshold requires a second person.

  • Usage metering and quotas

    Usage events — API calls, documents, storage, active users, AI, notifications, automations — arrive asynchronously through an outbox, with no impact on the customer's transactions; dimensions that would reveal business data are hashed. A daily roll-up feeds quotas: a soft limit warns, a hard limit degrades one specific thing. A hard limit on AI credits stops AI calls — never posting.

  • Customer health

    A periodic snapshot: application and schema version, the last migration and its outcome, integration status, stuck or failed jobs, database growth, error rate, p95 latency — and, just as importantly, backup status and the date of the last proven restore drill. The result is a score per customer and a fleet view with alerts.

  • Support Access on the customer's approval

    The request carries a reason, a scope and a duration of at most eight hours. The customer's administrator approves it in their own system. The session runs with a restricted scope, shows a visible banner, expires by itself and records every action in both the platform journal and the customer's. The customer receives a full session report.

  • Platform journal and maker-checker

    Every action by platform staff lands in an append-only journal. Critical actions — suspension, reactivation, termination, an entitlement override, a price change on an active contract, emergency access, any deletion — require a second person of higher standing: propose, review, execute, record.

The Control Center is a product of its own, with its own schema and its own database roles — not a module inside the customer's application. The boundary is therefore not a setting anyone can switch off, nor a role anyone can widen: it follows from where the data sits and who holds rights over it. The same boundary applies to our own team.

Six layers of authorisation

A role answers what a user may open. The other five layers answer the questions that actually hurt in business: which data, up to what amount, in which combination of powers, with whose confirmation, and on whose authority while someone is away.

  • Roles and permissions

    The chain of role → functionality → permission, with explicit action codes: view, create, sign, unsign, confirm, cancel, print. The check runs on the server from the very first use case; the front end receives the permission list only so it knows what to show, never so it can decide.

  • Data scope

    Restriction by legal entity, warehouse, department, organisational unit or cost centre. This is part of the authorisation decision on the server, not a filter in the interface — a user who sees one warehouse does not see another through a report, through search, or through the API.

  • Approval limits

    Up to what amount a user approves, by document type and process. The limit is enforced by the workflow kernel: a document above the threshold does not wait on goodwill — it automatically requests approval at the next level, recording who decided, when, and what they were looking at.

  • Segregation of duties

    A matrix of incompatible powers: the same person does not open a supplier and approve its payment, does not enter and confirm a stock count. The check runs in two places — when a role is granted, so the conflict is never created, and at the action itself, so it cannot be assembled from temporary rights.

  • Maker-checker

    Critical actions require a second person. What counts as critical is configuration, not an assumption: payments as a rule, master-data changes at the customer's choice, platform actions always. The flow is the same — propose, review by another person, execute, and record the decision together with what was reviewed.

  • Delegation and cover

    Temporary authority with an end date and cover during absence, carrying the same limits as the original and a clear trace of who decided on whose behalf. This layer also holds the separate dimension of prohibitions: access to payroll, margins and other sensitive data is withheld independently of the role.

The journal: a rule with no record is not a rule

Authorisation is proven by a record, not by a description. NG One therefore keeps two separate trails that cannot be altered after the fact — one for what happened to the data, one for how the system was configured at that moment.

  • An append-only journal of business actions: who, what, when, against what and from which context
  • A configuration change log: every settings change with the old and new value, consultants included
  • Document state history: every transition with the guard that allowed it and the approval that accompanied it
  • A record of every data export — who, what, when and at what scope
  • The decision record inside an approval: what the approver saw at the moment of the decision, not what the document says today

All six layers are enforced by the server, on every command, across all sixty-odd technical domains — none of them optional, none of them relying on the interface having hidden a button. That is why there are six from the first use case: a layer that does not exist at the start means every use case written until then was written without it, and that is not repaired by adding the layer — it is repaired by reading everything underneath it again.

API-first

The interface is the API's first client, not the other way round. Anything a user can do through a screen can be done through the API — with the same authorisation and the same journal.

  • REST and OpenAPI over everything

    Resources under a single versioned base path, with no envelope around the response: success is data with a real HTTP status, an error is an RFC 9457 ProblemDetail carrying a code and a trace identifier. The OpenAPI specification is the source of truth, not documentation written afterwards: typed clients are generated from it, so hand-written calls do not exist.

  • Commands rather than generic CRUD

    The API exposes business actions, not tables: sign, confirm, cancel, approve. Search runs through an explicit filtering and paging contract with a mandatory allow-list of fields per resource. An idempotency key sits on sensitive calls, so a repeated request does not create a second document.

  • Job queue and asynchronous work

    Long-running work — imports, calculations, report generation, sending to external systems — runs through a queue with retries, visible status and history. Domain events are published through an outbox, so a message is not lost when the receiver falls over, and the transaction that produced it stays a single transaction.

  • Notifications through one port

    Email, SMS, Viber and push run through the same channel with templates, delivery rules and per-user subscription. The channel is configuration: adding a new one does not touch the module that raises the notification, because the module publishes an event, not a message.

  • Keys, limits and versions

    Public API keys and service accounts with safe storage, rate limiting and versioning are part of the platform rather than an afterthought. An external system receives its rights the same way a person does — through the same permissions and the same data scope.

  • Carried by AI or automation

    MCP-ready

    Resources and actions are modelled as tools: the same contract the interface calls exposes them to an AI agent over MCP, with no second, parallel API carrying different authorisation rules. An agent acts on behalf of a person and inherits that person's identity whole — permissions, data scope, tenant isolation — so data that person has no right to does not reach the agent through a tool either. Every call lands in the AI journal like any other.

An enterprise interface

An ERP is used eight hours a day, not glanced at once in a demo. The interface decisions follow from that fact.

  • NgDataTable

    One enterprise table for the whole system, on AG Grid Community, exposed strictly through our own component — so a change in table behaviour happens in one place rather than across a hundred screens. Lists are described by a registry: a new list is a registry entry and a thin page, not new code for sorting and filtering.

  • The standard every table meets

    Every list in the system offers the same: show, hide, reorder, resize and pin columns; quick and advanced filters with operators; saved filters and views per user; Excel export; bulk actions; footer totals; a full-screen mode. Learn one table and you know them all.

  • Three screen strategies, on purpose

    Complex screens — the general ledger, bank statements, planning, bulk entry — are desktop-first: information density, keyboard, multiple monitors, a minimum supported width of 1280 pixels. Overviews, approvals and indicators are responsive. Warehouse and field operations get a dedicated handheld interface at 390 pixels, with large targets, the scanner as the first input, and QR sign-in.

  • React 19 SPA

    One application, typed clients generated from OpenAPI, server data kept apart from interface state. Forms share a single generic document pattern with dirty tracking by difference from the last saved state, and per-field messages. Permissions never decide on the client — they only display.

  • Two themes and two densities

    Light and dark are both first-class from day one, with light as the default because that is where people work all day; every token is defined for both, and tests pass in both. Density is chosen between comfortable and compact — the difference is real, not cosmetic, and it is remembered per user. Serbian and English from the start.

  • Welcome & Workspace Setup

    The first sign-in runs a personalisation panel with a live preview drawn from the system's real components: language, theme and density change instantly and are seen before they are confirmed, with no flash on load. It also sets the default legal entity and the starting workspace — strictly from what the user is entitled to. Preferences are stored on the server and can be revisited from the profile; the panel does not pop up on every sign-in.

Why a modular monolith rather than microservices?

Because microservices solve problems NG One does not have and create ones it would. Posting an invoice touches the partner, the item, the stock, the tax record and the general ledger in a single transaction — in a distributed system that transaction becomes a saga with compensations, and that is work you do instead of business logic. A modular monolith gives you what people actually want from microservices: hard module boundaries, communication through named interfaces and events, and the option to lift a module out. The difference is that the price is paid when the need appears, not up front and by everyone. Architecture tests enforce the boundaries, so they do not live only in a document.

One database for all customers — how isolated is that really?

The isolation does not rest on the application; it rests on the database. Every table holding customer data carries a tenant identifier and a Row Level Security policy in FORCE mode, and the role the application runs under has no right to bypass it — this is not a filter a developer may forget in a query, it is a rule the database enforces for every row. The tenant context is set at the start of each transaction. On top of that, two tests fail the build: one checks that no table with a tenant identifier is missing its policy, the other attempts to read and modify another customer's data from one customer's context and requires the attempt to fail. Both run today.

What is the difference between a customer (tenant) and a legal entity?

A customer is the boundary of data isolation — what older systems handled as an entire separate installation or database. A legal entity is a dimension inside a customer: one group with three companies is one customer with three legal entities, sharing registers and users while keeping separate document numbering, separate bank accounts and separate reporting. The distinction matters because of cost: systems without legal entities as a dimension solve this with a third installation, so every upgrade is paid for three times, and the consolidated view is assembled in a spreadsheet outside the system.

What does it mean that platform staff cannot see business data?

Exactly that, and at the level of database rights rather than a promise. The Control Plane and customer data are separate schemas with separate roles; platform roles hold no rights over the business tables, so a query that would read them fails even if someone wrote it. Everything the platform sees about a customer's operation is aggregated, anonymised metrics — how many documents, how many calls, how large the database — without content. When access is genuinely required it runs through Support Access: the customer approves it, the session has a scope and an expiry, actions are recorded in two journals, and the customer receives a report. What holds the boundary is not a promise but a test: it attempts to open business data as a platform administrator and requires the attempt to fail. Until it passes, no release ships.

How do I change how the system behaves without a developer?

Through the console. Document types, numbering, state machines with guards and effects, mandatory fields, print templates, approval rules, automation rules and settings are configuration with metadata — the console draws the control for each setting itself, so a new setting does not require a new screen. The boundary is clear and deliberate: you configure combinations, you do not invent new kinds of behaviour. The guards and effects a state machine calls are named functions registered in code — you select and wire them, you do not write them in a setting. Every change lands in a journal with the old and the new value.

What happens to posted documents when a rule changes?

Nothing — and that is the point. Rules are effective-dated and versioned, and every posting keeps a reference to the version it was created under, along with a snapshot of the key values. A new posting scheme applies from the date you give it; a prior-period document still shows the scheme, rate and exchange rate that were in force then. “Explain this posting” shows precisely that: which document, which scheme at which version, which rate, which exchange rate, who changed the configuration and when. Without this, a settings change would quietly rewrite the past — an error usually discovered during an audit.

Can I get my data out of NG One?

Yes, at three levels. Every list in the system has an Excel export. The API is open across all resources, with an OpenAPI specification, so data can be read programmatically under the same permissions that apply to a person. And there is a complete export of a customer's data, which is part of the termination procedure: a customer leaving receives their data before anything is deleted, and export keeps working even while an account is suspended for non-payment. Every export is recorded — who, what, when and at what scope.

How do I verify this is true rather than a document?

By asking us to show you the place where the system fails if it is not. Isolation: a meta-test over the schema fails the build if a table with a tenant identifier is left without an RLS policy, and a second test tries to read and modify another customer's data from one customer's context and requires it to fail. Module boundaries: architecture tests fail the build on any call that did not go through a named interface or event. Versioning: “explain this posting”, run on a document from a closed period after the configuration has changed, shows the old scheme, the old rate and the old exchange rate. Dimensions: open any general ledger line and see whether it carries them. A technical review walks all four, against the code and against the data — architecture that cannot be shown on a screen is a slide.

Review the architecture before you review the screens

Book a technical review of the platform with our team. We go through the data model, isolation, authorisation and the boundary around your data — and answer the questions your IT function asks, not the ones that are easier to ask.