Accounting Module Overview

v0.1.0

Architecture, bounded contexts, and fluent gateway for the Obelawium Accounting & Financial Management suite.

Obelawium Accounting Domain Module

The Obelawium Accounting module (obelaw/ium-accounting) is an enterprise-grade, headless double-entry General Ledger and Financial Management System (FMS) built for the Obelawium (IUM) ecosystem. Designed according to Domain-Driven Design (DDD) principles, it provides mathematical zero-variance integrity, hierarchical Chart of Accounts (COA) governance, immutable Storno reversals, Maker-Checker duty segregation, multi-currency conversions, and an Anti-Corruption Layer (ACL) for cross-domain event ingestion.


Core Philosophy: Headless & Decoupled

In accordance with Obelawium’s core architectural tenets, ium-accounting is 100% headless:

  • Zero UI / Presentation Bias: Contains no Blade templates, JavaScript widgets, controllers, or HTTP routing logic.
  • Pure Domain Orchestration: All double-entry bookkeeping rules, balancing checks, and financial state transitions reside entirely within domain services and aggregates.
  • Universal Consumption: The domain can be consumed from any presentation layer, including Laravel Blade, Filament admin panels, Laravel Nova, mobile apps, scheduled cron daemons, queue workers, or REST/GraphQL APIs.
  • Fluent Gateway Access: Every capability is invoked through Obelawium’s centralized fluent domain gateway:
ium()->accounting()->[service]()->[method]()

The 9 Core Domain Services

The Accounting bounded context is partitioned into nine specialized domain services:

graph TD
    IUM["ium()->accounting() Fluent Gateway"]
    
    subgraph CoreLedger["Core Ledger Services"]
        ACC["accounts()<br/>Hierarchical Chart of Accounts"]
        JRN["journalEntries()<br/>Voucher Drafting, Approval & Storno"]
        LDG["ledger()<br/>General Ledger, Trial Balance & Statements"]
    end

    subgraph Governance["Fiscal Governance Services"]
        PER["fiscalPeriods()<br/>Fiscal Calendar & Hard Period Locks"]
        CLS["closing()<br/>Nominal Sweep & Retained Earnings Close"]
        AUD["audit()<br/>Append-Only Forensic Audit Trail"]
    end

    subgraph FinancialOps["Financial Operations Services"]
        CUR["currencies()<br/>ISO 4217 & Daily FX Snapshots"]
        CST["costCenters()<br/>Multi-Dimensional Cost Centers"]
        ACL["acl()<br/>Anti-Corruption Layer Event Ingestion"]
    end

    IUM --> CoreLedger
    IUM --> Governance
    IUM --> FinancialOps
Service AccessorService ClassPrimary Responsibilities
ium()->accounting()->accounts()AccountServiceHierarchical Chart of Accounts tree, classification, leaf vs aggregator validation.
ium()->accounting()->journalEntries()JournalEntryServiceJournal voucher drafting, zero-sum validation, Maker-Checker workflow, posting, and Storno reversals.
ium()->accounting()->ledger()LedgerServiceGeneral Ledger book of entry, account ledgers, Trial Balance, Balance Sheet, and Income Statement (P&L).
ium()->accounting()->fiscalPeriods()FiscalPeriodServiceFiscal years, monthly/quarterly periods, soft/hard locks, and date-to-period resolution.
ium()->accounting()->costCenters()CostCenterServiceAnalytical accounting dimensions (Cost Centers, Projects, Departments, Business Units).
ium()->accounting()->closing()ClosingServiceFiscal year-end nominal account zeroing and Retained Earnings rollover.
ium()->accounting()->audit()AuditServiceAppend-only forensic accounting audit trail for statutory compliance and regulatory reporting.
ium()->accounting()->acl()AclIngestionServiceAnti-Corruption Layer translating upstream domain events (Billing, Inventory, CRM) into balanced vouchers.
ium()->accounting()->currencies()CurrencyServiceMulti-Currency ISO 4217 registry, daily exchange rate snapshots, conversion, and FX formatting.

Non-Negotiable Domain Invariants (“The Law”)

Financial ledgers must never compromise on mathematical truth. Every write operation through ium()->accounting() is guarded by strict architectural invariants:

  1. Zero-Sum Balance Invariant: Every voucher must satisfy $\sum \text{Debits} = \sum \text{Credits}$ down to the exact minor unit (cent/fils). Imbalanced submissions immediately throw UnbalancedJournalEntryException.
  2. General Ledger Immutability & Storno Reversals: Posted vouchers (POSTED, REVERSED) are strictly immutable. Eloquent hooks prevent any in-place update() or delete(), throwing ImmutableJournalEntryException. Corrections must use the Storno reversal pattern (reverse()), generating an equal-and-opposite posted voucher.
  3. Leaf-Only Account Postings: Postings are only permitted on leaf accounts (is_leaf = true). Aggregator parent nodes throw NonPostingAccountException. Adding a child to an account automatically converts the parent to a non-leaf aggregator.
  4. Fiscal Period Hard Locks: Transactions must resolve to an open period (status === OPEN). Postings into locked periods throw FiscalPeriodClosedException.
  5. Maker-Checker Segregation of Duties: When strict governance is active, a voucher’s creator (creator_id) cannot approve that voucher (approver_id). Violations throw MakerCheckerViolationException.
  6. Base Functional Currency Rate Immutability: The organization’s functional currency exchange rate is locked to 1.000000. Attempting to set an exchange rate on the base currency throws BaseCurrencyRateImmutableException.
  7. Functional Currency General Ledger Denomination: While vouchers can be drafted in any active ISO currency, General Ledger postings (GeneralLedgerPosting) are recorded in the functional reporting currency using amount_functional = (int) round($amount * $exchangeRate).
  8. Anti-Corruption Layer (ACL) Isolation: External modules (Billing, Sales, Inventory, CRM) never write directly to accounting tables. They communicate exclusively via the ACL (ium()->accounting()->acl()->ingest(...)) or registered domain event mappings.

Quick Example: Drafting & Posting a Voucher

use Obelaw\Ium\Accounting\Data\CreateJournalEntryDto;

// 1. Prepare a balanced journal voucher
$dto = CreateJournalEntryDto::fromMajor([
    'entry_date'    => '2026-03-31',
    'currency_code' => 'USD',
    'memo'          => 'Client Invoice Settlement & Sales Tax',
    'lines' => [
        ['account_code' => '1120', 'side' => 'debit',  'amount' => 1150.00, 'memo' => 'Accounts Receivable'],
        ['account_code' => '4100', 'side' => 'credit', 'amount' => 1000.00, 'memo' => 'SaaS Subscription Revenue'],
        ['account_code' => '2150', 'side' => 'credit', 'amount' =>  150.00, 'memo' => 'Sales Tax Payable'],
    ],
]);

// 2. Draft the voucher
$entry = ium()->accounting()->journalEntries()->createDraft($dto);

// 3. Maker-Checker approval workflow
ium()->accounting()->journalEntries()->submit($entry->id, $accountantId);
ium()->accounting()->journalEntries()->approve($entry->id, $controllerId);
$postedEntry = ium()->accounting()->journalEntries()->post($entry->id, $controllerId);

Repositories & Resources