ZuloOne
English
Sign in
Menu

🗺️ Overview and the map of modules

Updated

← Business process wiki

The ZuloOne business layer is 14 models. A model is a workspace folder with its own model.json, its own version and explicitly declared dependencies. A model sees the layers below it and extends their objects additively — adding fields and attaching handlers on top — but never edits what belongs to another model. A new country or an industry-specific solution is a new model on the Customization layer, not a patch to the kernel.

Below is what each model contains and which questions its registers answer. The full object specifications are in docs/reference/business-layer/.

What the business layer is made of#

Four object kinds appear throughout the business layer:

  • A dictionary — something that exists in its own right and is referenced: an item, a customer, a store, a tax rate. It lives long and changes rarely.
  • A document — an event of economic life, with a date: an order, a realization, a payment, an accrual. A document has subtypes — states. The transition between them is made by a command (a button). The subtype change runs the postings; there is no state dropdown on the card. See commands and subtypes.
  • A register — a derived table of movements. Nobody enters rows by hand and nobody copies rows into it: movements are created by posting documents and rebuilt when a document is reposted. A balance is computed from the movements.
  • A service — shared logic called from more than one place (GetService<IPricingService>()). The bodies of handlers and commands are thin orchestration on top of services.

The map of models#

The layers: Base(1) is the foundation, Solution(2) the applied contours, Customization(3) the country packages. A model sees only what is below it and what it declared in its dependencies.

Module Layer Depends on Holds More
Common Base — 11 dictionaries, 3 services 00-Common
Organization Base Common 4 dictionaries 01-Organization
Accounting Base Common, Organization 4 dict., 1 document, the GL register 02-Accounting · the ledger
Tax Base Accounting, Common, Organization 20 dict., 4 documents, TaxLedger 03-Tax
Inventory Solution Accounting, Common, Organization, Tax 15 dict., 6 documents, Stock 04-Inventory · stores
Purchasing Solution Accounting, Common, Inventory, Organization, Tax 1 dict., 4 documents, Payable 05-Purchasing
Sales Solution Accounting, Common, Inventory, Organization, Tax, Costing 10 dict., 9 documents, 6 registers 07-Sales · the process
Production Solution Common, Costing, Inventory 6 dict., 1 document, WorkInProgress 06-Production · the process
HR Solution Accounting, Common, Organization 4 dict., 5 documents, 3 registers 08-HR · the timesheet
CRM Solution Inventory, Sales 3 dict., 3 documents, LoyaltyPoints 09-CRM
Costing Solution Inventory, Purchasing 2 dict., 2 registers 10-Costing · the process
LocalizationSaudiArabia Customization Inventory, Sales, Tax, Organization 2 dict., VatPayable, 8 services 21 KSA · the package · ZATCA
LocalizationUkraine Customization Organization, Inventory, Sales, Tax, Purchasing, HR 6 dict., 8 registers Ukraine

Two things a reader of older material will expect and not find: store, zone and cell live as dictionaries inside Inventory — there is no separate warehouse application — and the field agent is a role of the mobile client, not a model of its own.


The modules one by one#

Common — the shared building blocks#

Everything that other models reference and that belongs to no single contour.

Dictionaries (11): Country, Region, City, Address, Currency, UnitOfMeasure, UnitClass, PaymentTerm, DeliveryTerm (Incoterms), Gender, CommonSettings.

Services: UnitConverter — converting quantities between units; PaymentDueService — the due date out of the payment terms; TradeProfileService — a counterparty's trade profile (customer / supplier / both).

A country's currencies are stored in the CountryCurrency link table rather than in one field: a country may have several currencies. Unit conversion works through a class plus a ratio (and packaging on the item itself) rather than pairwise rules — see stores.

Organization — who exactly keeps the books#

Dictionaries (4): LegalEntity, Division, DivisionType, OrganizationSettings.

A legal entity is not decoration: it is a dimension in the tax ledger, in the general ledger and in every Ukrainian register. The tax regime, the registrations and the connections to the tax authority are all attached to it. A document without a legal entity cannot be posted.

Accounting — the chart of accounts and the ledger#

Dictionaries (4): ChartOfAccounts, FiscalYear, FiscalPeriod, AccountingSettings. Document: JournalEntry — a manual posting. The GL register — double entry; ΣDebit = ΣCredit is checked on the document. Three pairs of resources: Debit/Credit (financial), TaxDebit/TaxCredit (tax) and ManagementDebit/ManagementCredit (management) — one fact, three views of accounting, with no duplicated documents.

Services: GeneralLedgerService (the mechanics of a ledger leg — accounts by code, period by date, the three books), FiscalPeriodService (deriving the period from the document's date).

Every business document writes its own ledger legs, the way a 1C document writes its own accounting-register records: the sale posts revenue and cost of sales, the receipt posts inventory and payables, payroll posts the expense. The legs are the document's own movements, so cancelling the document takes them out of the book. Who writes the general ledger lists every leg.

The period is taken from the document's date, not from "today". Otherwise a backdated document drifts away from the subledger across periods, and it is only visible at the close. Separately from the fiscal periods there is a posting boundary — one tenant-wide stop date, in the Administrator menu.

The model provides the sales order print form. It extends another model's document; it is not a separate object.

Tax — tax as an engine, not a rate in a field#

The largest model by dictionary count: 20. Tax here is computed by rules held as data, so a new country does not require an if (country) branch.

The core: Tax, TaxCode, TaxRate, TaxCategory, TaxDirection, TaxJurisdiction. Rate determination: TaxRule + TaxRuleCondition + TaxRuleAction, TaxProfile + TaxProfileAttribute, TaxMapping. Registrations and exemptions: TaxRegistration (dated), TaxExemptionReason. Reporting: TaxPeriod, TaxSubmission, TaxReportMapping, TaxAuthority, TaxAuthorityConnection, plus TaxSettings. Documents (4): TaxCalculation, TaxDocument, TaxPayment, TaxReturn. The TaxLedger register — the base and the amount of tax broken down by code, direction and legal entity, plus RecoverableAmount. The return is assembled from it.

The return is assembled by the Build return button on a TaxPeriod row (BuildTaxReturn → ITaxReturnService.BuildFromPeriodAsync): the period window is copied to TaxReturn, and the amounts and lines are computed from the ledger. A closed tax or financial period refuses with a message. There is no need to create a return by hand and type OutputTax / InputTax: before the button existed, that was the only way in, because the service had no entry point of its own.

Services: TaxService (the calculation), TaxReturnService, TaxAuthoritySubmitService, TaxPackInstaller, EInvoiceRelease.

A trap that cost time: tax determination requires a context. Rules, profiles and mappings are skipped silently when no context is passed — and then a credit note is reversed at a different rate from the one its own invoice was issued at.

Inventory — items, stores and prices#

Master data (14): Item, ItemGroup, ItemUnit, Brand, Store / StoreZone / StoreCell / StoreCellType, Customer, CustomerContact, CustomerOutlet, Supplier, PriceType, InventorySettings. Documents (6): PutAwayTask, PickTask, StockTransfer, GoodsIssue, StockAdjustment, StockCount (a stocktake). The Stock register — quantity broken down by cell and item, single entry, Qty only. There is no value here: that lives in Costing.

Services: PricingService (a price with its provenance), StockAvailabilityService (may this be written off), ItemQuantityConverter, StoreCellService, ItemClassification, TenantSetup.

Counterparties sit here for a reason: customers and suppliers are master data, and in the menu they are placed where they are used (Sales and Purchasing). PriceTypeHistory is the link table of price type history.

The balance is per cell, and that matters: goods cannot be teleported, movements are paired (receiving → storage → picking), and a write-off comes strictly out of the picking cell.

Purchasing — goods in and money to the supplier#

Documents (4): PurchaseOrder (Draft → Ordered → Received), PurchaseReturn, PurchaseCreditNote (reverses input VAT), VendorPayment. The Payable register — the debt to suppliers, broken down by supplier. Service: PurchaseReceiptService.

It has almost no dictionaries of its own — one PurchasingSettings: the supplier and the items come from Inventory. SupplierInvoice books only the difference from what the receipt already accrued — process map §8.

Sales — the largest model of the contour#

Dictionaries (10): SalesContract, SalesPerson, DeliveryRoute, DeliveryRouteStop, DeliverySchedule, Driver, Vehicle, AbcProfile, AbcClassBand, SalesSettings. Documents (9): SalesOrder (Draft → Submitted → Confirmed), SalesRealization (Reserved … Issued), CustomerPayment, SalesReturn, SalesCreditNote, SalesDebitNote, VisitRoute, AgentVisit, DeliveryTrip. Registers (6):

  • Receivable — what customers owe: it arises when a sales realization is released and is settled by a payment; the slice is customer and contract. The mirror of Payable.
  • ReservedStock — the quantity held by confirmed orders. Free = Stock − Reserved; it is released by rolling the Confirmed subtype back.
  • Revenue — recognised revenue by item, customer and contract.
  • AbcClassification — the ABC/XYZ slice by profile and subject; a manual classification (ManualAbc/ManualXyz) survives a recalculation.
  • AgentGpsLog — an information register: a point on an agent's track.
  • MobileCommandReceipt — the idempotency information register for mobile commands: for each key it holds the action, the status and the stored answer; a repeated key returns the same answer rather than a second document.

Services: SalesLinePricing, SalesContractService, SalesFulfillmentService, DeliveryService, GrossMarginService, AbcClassifier.

A contract belongs to a trading outlet (Outlet is mandatory, and the customer is copied from the outlet), and it is the contract that carries the currency, the settlement kind, the payment term, the Incoterms, the price type, the limit and the date window. The contract's prices are the SalesContractPrice and SalesContractQtyBreak link tables. The ABC/XYZ recalculation is a job, RecalcAbcClassifications.

Production — bills of materials and output#

Dictionaries (6): BillOfMaterials, BomComponent, Routing, RoutingOperation, WorkCenter, ProductionSettings. Document: ProductionOrder (Released → Finished). The WorkInProgress register — the quantity of the product in progress, not its value: FIFO has already left the store when the raw material was written off. Services: BomService, RoutingService.

HR — people, the timesheet and payroll#

Dictionaries (4): Employee, Position, TimeOff, HRSettings. Documents (5): TimeSheet, PayrollAccrual, PayrollPayment, SocialInsuranceAccrual, SocialInsurancePayment. Registers (3): Payroll (accrued), PayrollLiability (Amount + Withheld, negatives forbidden), SocialInsurance (EmployeeContribution / EmployerContribution). Services: PayrollCalculationService, PayrollVoidService, SocialInsuranceService, TimeSheetService.

The Withheld resource on PayrollLiability is the contract for any withholding from pay: the Ukrainian personal income tax (ПДФО) and military levy (військовий збір) write into exactly that one, and the localization introduces no withholding mechanism of its own. Voiding an accrual is a separate command (VoidPayrollAccrual), not deleting the document.

CRM — the lead, the quotation and loyalty#

Dictionaries (3): LoyaltyTier, LoyaltyCampaign, CRMSettings. Documents (3): SalesLead (New → Qualified → Quoted), SalesQuotation (Draft → Issued → Converted), LoyaltyRedemption. The LoyaltyPoints register — points by customer, negatives forbidden: the engine will not let more be redeemed than was accrued. Services: LeadService, QuotationService, LoyaltyCampaignService.

CRM is built on top of the order axis additively and does not replace it: the lead and the quotation are optional, and an order stands on its own. More — the lead, the quotation, loyalty.

Costing — what it cost#

Dictionaries (1): CostingSettings (the standard cost is a price type: CostingSettings.StandardPriceType). Registers (2): ItemCostFifo — FIFO lots by item (Quantity + Amount); InventoryValue — the quantity and the value of stock. Services: StandardCostService, SurplusCostingService.

The split from Inventory is deliberate: Stock answers "how much is in stock", InventoryValue "worth how much". The sales margin is computed in Sales (GrossMarginService), not here.

LocalizationSaudiArabia — Saudi law and services#

VAT and the e-invoice are built today; the package does not end there.

Dictionaries (2): LocalizationSaudiArabiaSettings, FatooraStubDelivery. The VatPayable register — VAT payable. Services (8): SaudiEInvoice, ZatcaQr (the TLV QR), ZatcaXades (the signature), ZatcaCsr, ZatcaOnboarding, ZatcaComplianceSamples, FatooraSigner, EInvoiceRelease. Job: ApplyFatooraOutcomes.

The model carries the print forms for the realization and the credit and debit notes — those are extensions of Sales documents, not objects of its own. The E and O categories with their VATEX codes live on the TaxCategory.LocalizationSaudiArabia extension. A standard invoice cannot be handed to the customer before the Cleared status. GOSI is computed by the HR kernel at the stand's rates. Mudad, Qiwa, Nafath, SADAD and Saudi banks are not in the model. More — the package · ZATCA.

LocalizationUkraine — Ukrainian law and services#

VAT (ПДВ), the regimes, single tax (єдиний податок) and payroll withholdings are built today; the package does not end at tax.

Dictionaries (6): LocalizationUkraineSettings, UaRegimeChange, UaSingleTaxLimit, UaBankConnection, UaLandPlot, UaTaxFilingExport. Registers (8) — more than any other localization has, and each answers its own question:

  • UaVatFirstEvent — the accumulations on a contract (Shipped, Accrued, Paid, EpAccrued) from which the first event on the output side is derived (ПКУ 187.1). The key is the legal entity / customer / contract dimensions rather than analytics: a totals driver sees coordinates only.
  • UaVatPayable — the liability itself, by the first event.
  • UaPurchaseFirstEvent — the mirror on the input side (Received, PaidOut, Credited), Tax Code of Ukraine (ПКУ) 198.2.
  • UaVatCredit — the right to credit on an input first event.
  • UaPayrollLevy — personal income tax (ПДФО) and military levy (військовий збір) broken down by employee: TaxLedger is sliced by direction, legal entity and tax code — enough to pay the budget, and not enough to show a withholding per person.
  • UaBankStatement — imported Autoclient bank-statement entries (an information register, not a payment document).
  • UaFopEsv — the social insurance contribution (ЄСВ) accrued by a sole proprietor (ФОП) "for self", by legal entity.
  • UaVatRefundClaim — the declared amount of VAT budget refund.

Services: UaFirstEvent, UaTaxFiling, UaPayrollLevies, UaEInvoice, UaBank. Jobs: ApplyUaRegimeChanges, PullUaTaxInvoiceReceipts, PullUaBankStatements.

Social insurance (ЄСВ) requires no separate code: it is the social insurance contour in HR. The tax invoice (податкова накладна) goes to the Unified Register of Tax Invoices (ЄРПН) through an operator: the TaxDocument envelope is ours, and the XML and qualified electronic signatures (КЕП) stay with M.E.Doc. The MakeDoc adapter is in the package, but there is no live exchange in CI. The PrivatBank statement uses the pb-statements channel. There is no Diia or State Tax Service (ДПС) cabinet. More — Ukraine.


Every register in one list#

Which question each of them answers. Register movements are derived: they are not entered by hand, and they are rebuilt when a document is reposted.

A breakdown has two kinds. For an analyst, the question is: "which fields can I use in a report?" A dimension is the register's own column. An analytic is a dynamic breakdown alongside; the set of analytics grows without touching the register itself. For a developer the difference matters more: code that computes balances during posting sees dimensions only. If you slice the register only by analytics, that code matches zero rows — silently, with no error. That is why the UaVatFirstEvent key is dimensions (legal entity × customer × contract), without analytics that duplicate those names. The outlet slice lives on UaVatPayable, where the driver writes it. Details — the developer wiki.

Register Module Dimensions Analytics Resources Answers the question
Stock Inventory Cell, Item — Qty how much is in stock, and where
ReservedStock Sales — Cell, Item Qty how much is held by orders
InventoryValue Costing — Item Qty, Value worth how much
ItemCostFifo Costing Item — Quantity, Amount at what price to write off
WorkInProgress Production — Cell, Item Qty how much is in progress
Receivable Sales — Customer, SalesContract Amount how much is owed to us
Payable Purchasing — Supplier Amount how much we owe
Revenue Sales — Customer, Item, SalesContract Amount how much revenue is recognised
GL Accounting — Account, FiscalPeriod, LegalEntity 3 Dr/Cr pairs what is in the ledger
TaxLedger Tax — TaxCode, Direction, LegalEntity TaxBase, TaxAmount, RecoverableAmount what goes into the return
Payroll HR — Division, Employee Amount how much is accrued
PayrollLiability HR — Employee Amount, Withheld how much is payable and how much withheld
SocialInsurance HR — Division, Employee employee / employer contribution social insurance
LoyaltyPoints CRM Customer — Points how many points
AbcClassification Sales Profile, Subject — class, share, Cv which ABC/XYZ class the item or customer has
AgentGpsLog Sales Agent — coordinates, speed, battery where the agent has been
MobileCommandReceipt Sales Key — status, answer is this a replay
VatPayable KSA — Customer, CustomerOutlet, SalesContract Amount VAT payable
UaVatFirstEvent Ukraine LegalEntity, Customer, SalesContract — Shipped, Accrued, Paid, EpAccrued has the first event occurred
UaVatPayable Ukraine — Customer, CustomerOutlet, SalesContract Amount the VAT (ПДВ) liability
UaPurchaseFirstEvent Ukraine LegalEntity, Supplier — Received, PaidOut, Credited the first event on the input side
UaVatCredit Ukraine LegalEntity, Supplier — Amount the tax credit
UaPayrollLevy Ukraine Employee, LegalEntity, TaxCode — Base, Amount personal income tax (ПДФО) and military levy (ВЗ) per person
UaBankStatement Ukraine LegalEntity, BankRef — Amount, TranType, CounterpartyEdrpou what came in or went out on the bank statement
UaFopEsv Ukraine LegalEntity — Amount the social insurance contribution (ЄСВ) accrued by a sole proprietor (ФОП) "for self"
UaVatRefundClaim Ukraine LegalEntity — Amount the declared amount of VAT budget refund

ReservedStock is sliced by the same coordinates as Stock (cell + item) — and that is precisely why the subtraction "free = Stock − Reserved" means anything at all.

A negative on Stock is allowed by the engine (allowNegativeBalance: true) — the refusal lives in the events and commands (IStockAvailabilityService), not in the engine. LoyaltyPoints and PayrollLiability, by contrast, forbid a negative at the register level.

Three numbers that show how it is built#

The free balance is computed per cell, not per store. Stock and ReservedStock are sliced identically (cell + item) — which is why the subtraction means something, and why it does not sum across the store:

There are 200 pcs in the store: 100 in picking cell A, 100 in storage cell B. An order for 40 has not been picked yet: the reservation is in B, so free in B is 60 and free in A is 100. Picking more than sixty from B is not possible, even though another hundred pieces are in A. After picking is confirmed, the 40 move to A together with the reservation.

The first event is shifted by a maximum, not a sum. A prepayment of ₴120 at a rate of 20% closes a base of ₴100; a subsequent shipment of ₴100 accrues nothing, because max(Shipped, Paid) has not grown. The walk-through with numbers and by regime — Ukraine.

One fact, three pairs in the ledger. A sale writes into GL simultaneously Debit/Credit (the financial view), TaxDebit/TaxCredit (the tax one) and ManagementDebit/ManagementCredit (the management one). These are not three postings and not three documents: the amounts in the pairs diverge where the recognition rules themselves diverge, and a report picks the pair it needs.

What's not there yet#

Limits a consultant may look for in setup and not find:

  • Lots, serial numbers and expiry dates as a stock contour — not in the product; a barcode on the print form exists.
  • A live M.E.Doc / State Tax Service (ДПС) cabinet — the M.E.Doc adapter is in the package; this is REST on the accountant's PC plus an agent, not a cloud. How to seat it without pushing to the State Tax Service (ДПС) — M.E.Doc. There is no State Tax Service (ДПС) cabinet.
  • Diia — not in the delivery. Sign-in is platform identity, not a country package. There is no Diia signing or sharing channel. Why it is separate.
  • Opportunities in CRM — the lead and the quotation exist; there is no opportunity object.

How the processes connect (the end-to-end flow)#

Purchase → Receipt into the store → (Production: raw material out → output) → Order → Realization
    │                │                                   │                              │
    ▼                ▼                                   ▼                              ▼
 Payable          Stock                          WorkInProgress                  ReservedStock,
                  ItemCostFifo                   ItemCostFifo                    then Stock,
                  InventoryValue                                                 Revenue, Receivable,
                                                                                 TaxLedger
                                  every document above writes its own legs into GL
  • Master data (items, counterparties, stores) is supplied by Inventory/Common.
  • Movements go through documents, which move registers in pairs and by the scripts' rules. From the UI a document is posted by a command (a change of subtype) — not a state dropdown; see commands and subtypes.
  • Cost is carried over onto the output and the shipment (FIFO), and tax is computed by the tax engine at the points of sale and purchase.
  • The ledger is filled by the documents themselves: each posts its own legs, and loses them when it is cancelled.

The invariants an analyst should know#

  • Goods cannot be "teleported" — only paired movements between cells.
  • More cannot be written off than is in the cell: the refusal comes from IStockAvailabilityService, not from the register engine.
  • Register movements are derived: not entered by hand, not copied, regenerated by reposting.
  • A posting's date is the document's date, not "today". Otherwise a backdated document ends up in a different period.
  • A subtype is frozen once saved: it can only be moved by a command, and only along a permitted edge.
  • The tax integration is universal (TaxAuthorityConnection), and a country is connected by a localization rather than by rewriting the kernel.
  • In the kernel (Core / Sales / Tax / Pricing) if (country == …) is forbidden: a new law is a seed and rules held as data in the country's package.

The menu ribbon#

Every ribbon tab is a module. Its columns are: Documents, Dictionaries, Totals, Settings. A menu item appears where users need it, not necessarily where the object's metadata lives. A user command (an import, a processing run) can be placed as a targetType: UserCommand item — a click runs the command rather than opening a page.

Tab What is here
Common countries, addresses, currencies, units of measure, payment and delivery terms
Organization legal entities and divisions
Accounting the chart of accounts, the journal, the general ledger, fiscal periods
Tax kinds/codes/rates, rules, calculations, returns, payments
Inventory items, stores and cells, put-away/picking, balances, write-offs, stocktakes
Production bills of materials, routings, work centres, production orders
Costing the value of stock, standard cost
Sales customers, trading outlets, contracts, orders, realizations, payments, returns, routes and visits, ABC profiles
Purchasing suppliers, purchase orders, payments, payables
HR employees, timesheets, payroll, social insurance
CRM leads, quotations, loyalty tiers and points
Saudi Arabia VAT payable, ZATCA settings
Ukraine regimes, single-tax limit, declaration export

Price list rows, bill-of-materials components and tax rule conditions are not shown in the menu: they open from the parent dictionary.

Where next#

How each contour runs — the process map. How Global Core and a country package split — product direction.

The contours in detail (for the analyst): stores, sales and purchasing, the ledger, prices, costing, production, the timesheet, ABC/XYZ, loyalty, KSA, Ukraine.

Where to click (for the user): getting started, working with data, the form designer, payroll, the mobile app.

How it works inside (for the developer): the architecture, information registers, Chain of Command, the forms system, what scripts can do, the posting boundary.

The object specifications — docs/reference/business-layer/.

Was this page helpful?