Skip to content

KB-212 – Accounting Engine Architecture

Knowledge Base ID: KB-212
Chapter: 9 - Account Resolution Pipeline
Project: BLACK ERP
Version: 0.9
Status: Draft
Last Updated: 2026-06-26
Applies To: ADempiere 3.9.4


Purpose

This chapter explains the complete account resolution pipeline inside the ADempiere Accounting Engine.

Previous chapters introduced:

  • Doc
  • Fact
  • FactLine
  • Accounting Metadata
  • Account Configuration

This chapter connects them together and follows the actual execution flow used during document posting.

Unlike previous chapters, this document is based directly on the ADempiere 3.9.4 source code.


High Level Flow

When a document is posted, ADempiere executes the following pipeline.

Business Document
        │
        ▼
Doc_*
        │
        ▼
createFacts()
        │
        ▼
Resolve Accounts
        │
        ▼
Fact.createLine()
        │
        ▼
FactLine.setAccount()
        │
        ▼
Currency Conversion
        │
        ▼
Fact.save()
        │
        ▼
FACT_ACCT

Every accounting document follows this architecture.


Step 1 — Posting Starts

The Posting Engine begins inside the corresponding document class.

Examples:

Doc_Invoice
Doc_Payment
Doc_AllocationHdr
Doc_Movement
Doc_MatchInv
Doc_MatchPO

Each implementation overrides:

createFacts(MAcctSchema as)

This method is responsible for building all accounting entries.

Conceptually:

Invoice

↓

Doc_Invoice.createFacts()

Step 2 — Resolving the Required Account

Whenever an accounting line must be created, the document requests an account.

The source code exposes two key methods:

getValidCombinationId(...)

and

getAccount(...)

The first method determines which accounting metadata table must be queried.

The second converts the resulting Account Combination into an MAccount object.


Step 3 — Account Type Resolution

ADempiere defines internal Account Types.

Examples include:

ACCTTYPE_C_Receivable

ACCTTYPE_V_Liability

ACCTTYPE_Charge

ACCTTYPE_UnallocatedCash

ACCTTYPE_BankAsset

ACCTTYPE_CashExpense

ACCTTYPE_WriteOff

ACCTTYPE_ProjectAsset

These constants identify the business meaning of the requested account.

The Posting Engine never requests accounts by General Ledger number.

Instead, it requests them by business purpose.

Conceptually:

Need Vendor Liability

↓

ACCTTYPE_V_Liability

Step 4 — Metadata Lookup

The method

getValidCombinationId()

contains the metadata lookup logic.

Depending on the requested Account Type, it executes SQL against different accounting tables.

Examples:

Vendor Accounting

SELECT V_Liability_Acct
FROM C_BP_Vendor_Acct

Customer Accounting

SELECT C_Receivable_Acct
FROM C_BP_Customer_Acct

Charge Accounting

SELECT CH_Expense_Acct
FROM C_Charge_Acct

Bank Accounting

SELECT B_Asset_Acct
FROM C_BankAccount_Acct

Cash Book Accounting

SELECT CB_Expense_Acct
FROM C_CashBook_Acct

This confirms that ADempiere resolves accounts through metadata instead of hardcoded logic.


Step 5 — Building MAccount

Once the Valid Combination ID has been obtained, the Posting Engine creates an MAccount object.

Conceptually:

C_ValidCombination

↓

MAccount

MAccount represents the complete accounting combination including:

  • Natural Account
  • Organization
  • Business Partner
  • Product
  • Project
  • Campaign
  • Activity
  • User Elements
  • Sub Account

This object is passed to the Fact layer.


Step 6 — Creating the FactLine

The Posting Engine calls

Fact.createLine(...)

This method creates a new FactLine.

Inputs:

  • DocLine
  • MAccount
  • Currency
  • Debit Amount
  • Credit Amount

Conceptually:

DocLine

+

MAccount

↓

Fact.createLine()

↓

FactLine

At this point no database record exists yet.


Step 7 — Expanding the Accounting Dimensions

Immediately after creating the FactLine, ADempiere executes:

FactLine.setAccount()

This method decomposes the MAccount object into individual accounting dimensions.

Examples:

  • Account_ID
  • Sub Account
  • User Element 1
  • User Element 2
  • Organization
  • Project
  • Campaign
  • Activity

The composite accounting combination becomes explicit FactLine fields.


Step 8 — Currency Conversion

FactLine then executes:

convert()

Responsibilities include:

  • Source Amount
  • Accounting Amount
  • Currency Conversion
  • Accounting Currency Precision

After conversion, both document currency and accounting currency are available.


Step 9 — Account Validation

Before saving, Fact executes:

checkAccounts()

Validation includes:

  • Account exists
  • Element Value exists
  • Account is Active
  • Account is not Summary
  • Account can be posted

Only valid accounts continue to persistence.


Step 10 — Persisting FACT_ACCT

Finally,

Fact.save()

stores every FactLine into

FACT_ACCT

The posting process ends here.

Conceptually:

Fact

↓

FactLine

↓

FACT_ACCT

Complete Resolution Pipeline

The entire execution flow can be summarized as follows.

Business Document

↓

Doc_Invoice.createFacts()

↓

Account Type

↓

getValidCombinationId()

↓

Accounting Metadata Tables

↓

C_ValidCombination

↓

MAccount

↓

Fact.createLine()

↓

FactLine.setAccount()

↓

convert()

↓

checkAccounts()

↓

Fact.save()

↓

FACT_ACCT

BLACK ERP Engineering Notes

The Mexican VAT Cash Basis implementation integrates naturally into this pipeline.

The customization extends the posting logic inside:

Doc_AllocationHdr.createFacts()

Additional VAT accounting entries are generated before the standard Fact object is persisted.

The customization therefore respects the standard Accounting Engine architecture.

No modifications were required to:

  • Fact
  • FactLine
  • MAccount
  • FACT_ACCT

The extension simply injects additional accounting lines using the existing infrastructure.

This design minimizes future upgrade risks.


Engineering Principles

The source code demonstrates several important architectural decisions.

  • Business documents never know General Ledger accounts.
  • Metadata resolves accounting configuration.
  • MAccount encapsulates the accounting combination.
  • Fact creates accounting entries.
  • FactLine expands accounting dimensions.
  • Validation occurs before persistence.
  • FACT_ACCT is only the final storage layer.

Key Takeaways

  • Posting begins inside createFacts().
  • Accounts are requested by business purpose, not account number.
  • Metadata determines the correct Valid Combination.
  • MAccount represents the accounting combination.
  • Fact creates accounting entries.
  • FactLine expands dimensions and performs currency conversion.
  • Account validation occurs before persistence.
  • FACT_ACCT stores the final accounting result.

Next Chapter

Chapter 10 explores how individual document implementations (Invoice, Payment, Allocation, Shipment, Inventory and others) generate different accounting flows while sharing the same Posting Engine.


Revision History

Version Date Description
0.9 2026-06-26 Added complete Account Resolution Pipeline based on ADempiere source code walkthrough.