Skip to content

Accounting — Developer Guide ​

Mirrors the Leads/Expenses module layers under flat app/ namespaces. Slug accounting, middleware module:accounting, permissions accounting.*. Catalog version 1.10.0.

Domain ​

ModelTableNotes
AccountaccountsCoA; is_system starter rows; is_cash_bank (asset only); is_header (non-postable); optional parent; list/show include balance
JournalEntryjournal_entriesdraft | posted | void; JE- numbers
JournalEntryLinejournal_entry_linesdebit XOR credit; FK to postable (non-header) accounts
AccountTransferaccount_transfersposted | void; TRF- numbers; linked journal_entry_id
AccountBalanceAdjustmentaccount_balance_adjustmentsposted | void; ADJ- numbers; previous/target/delta + linked JE
AccountingPeriodaccounting_periodsfiscal periods; lock/unlock; year-end close
BankReconciliationbank_reconciliationsstart against cash/bank; clear lines; complete

Services: AccountService, JournalEntryService, GeneralLedgerService, ChartOfAccountsSeederService, OpeningBalanceService, CashMovementJournalService, InventoryCostJournalService, AccountTransferService, AccountBalanceAdjustmentService, AccountingPeriodService, YearEndCloseService.

Periods: setting fiscal_year_start_month (1–12); journal post/void blocked in locked periods; year-end close zeros P&L into Retained Earnings 3100 (multi-line JE) and locks the FY period atomically / idempotently. Balance Sheet Net Income uses fiscal YTD (zero after a proper close). Bank rec: start / clear lines / complete against cash/bank accounts — book balance as of statement date, exclude prior cleared lines, require cleared = statement to complete. Invoice send/void and credit-note apply are transactional with period-lock checks.

ChartOfAccountsSeederService seeds starter CoA (incl. Tax Payable 2100, Salaries Payable 2200, Salary Expense 6400, Retained Earnings 3100, Fixed Assets 1500, Accumulated Depreciation 1550, Depreciation Expense 6500) and ensureMissingSystemAccounts backfills missing system codes for existing workspaces.

OpeningBalanceService posts a balanced multi-line opening journal (POST /accounts/opening-balances). Header accounts are rejected on journal lines.

CashMovementJournalService creates+posts two-line journals and voids linked entries. Used by Payments post/void, Expenses pay, Payroll pay (payment JE), Account Transfers, balance adjustments, and invoice/credit accrual hooks. System codes include Inventory 1200, Fixed Assets 1500, Accumulated Depreciation 1550, Accounts Payable 2000, Owner Equity 3000, Cost of Goods Sold 5000, and Depreciation Expense 6500. AssetDepreciationService capitalizes an asset once (Dr 1500 / Cr 3000) and posts straight-line months (Dr 6500 / Cr 1550) when Accounting is entitled.

InventoryCostJournalService posts when Accounting is entitled and stores journal_entry_id on purchase_order_receipts and stock_movements. A purchase-order receipt debits 1200 for tracked lines and 6000 for every other line, and credits 2000, at quantity × unit_price (tax_rate ignored). It skips the journal when an expense already references the purchase order, and a second call on the same receipt does not post again. Manual stock in debits 1200 and credits 3000 at product.cost. Stock out debits 5000 and credits 1200. Adjust uses the signed quantity change. Transfers and purchase-order receipt movements post no second journal. PurchaseOrderService::convertToExpense rejects a purchase order that already has a receipt journal. Locked fiscal periods fail the journal inside the receive or stock transaction. No backfill.

AccountBalanceAdjustmentService::create reads current balance via FinancialReportService::currentBalancesFor, posts the delta, and stores an ADJ- row (default offset Equity 3000).

Events → AccountingEventSubscriber → PlatformAuditService + Spatie LogsActivity.

Soft dependents ​

Optional catalog deps (is_optional=true): invoices → accounting, credit-notes → accounting, payments → accounting, expenses → accounting, payroll → accounting, assets → accounting.

When Accounting is entitled:

  • Invoice send (CustomerInvoiceService::send): Dr AR 1100 / Cr Sales Revenue 4000 (REVENUE_CODE) for invoice total; stores journal_entry_id. Cancel/void voids the JE.
  • Credit note apply (CustomerCreditNoteService::apply): Dr Revenue 4000 / Cr AR 1100 for credit total; stores journal_entry_id (applied remains irreversible — no JE void path).
  • Payment post: allocations must sum to amount; Dr deposit_account_id (or Cash 1000) / Cr AR 1100 (settles receivable booked on send); void voids the JE.
  • Expense pay: require paid_from_account_id; default expense account 6000; Dr expense / Cr paid-from for amount+tax.
  • Payroll pay: require paid_from_account_id; ensure posted accrual (6400/2200 defaults); then Dr Salaries Payable / Cr paid-from. Optional post creates draft accrual only.
  • No historical backfill for invoices/credits issued before entitlement.

API ​

See tenant-v1-accounting.md. Payments/expenses account fields: tenant-v1-payments, tenant-v1-expenses.

Frontend ​

  • Routes: /accounts, /accounts/opening-balances, /journals, /account-transfers, /general-ledger
  • Accounts: parent picker + Header (is_header) on create/edit; headers excluded from journal account pickers
  • Journals: classic Account / Debit / Credit / Memo grid (create/edit/view); list Amount column; currency formatting with blank zero Dr/Cr cells
  • Account view: Set balance dialog + Balance adjustments list/void for cash/bank; opening TB via /accounts/opening-balances
  • General Ledger: currency columns, journal deep links, Export CSV (GET /general-ledger/export)
  • Nav group Finance, dual-gated module: accounting + PERMISSIONS.accounting.view
  • Production notes: JournalEntryService::post / void use DB::transaction + lockForUpdate(); system account code/type immutable; GL inquiry paginated (100/500); cash movements auto-post (Payroll Mark paid payment JE auto-posts; optional Payroll Post to journal may leave accrual as draft until pay).
  • Playwright (tenant project, one login session per suite):
    • Full module: npm run test:e2e:accounting:modules / :headed — validation, CoA CRUD, journal unbalanced/post/void, transfers smoke, GL
    • Authz: npm run test:e2e:accounting:authz / :headed — /403, API 401/403, unbalanced 422
    • Smoke + all: npm run test:e2e:accounting / :headed

Tests ​

Pest: tests/Feature/Tenant/Accounting/ (CRUD, cash/bank + balances, transfers, balance adjustments, journal post/void, GL), plus CustomerInvoiceAccountingTest, CustomerCreditNoteAccountingTest, CustomerPaymentAccountingTest, ExpenseAccountingTest.

Official documentation for the EloSync SaaS Platform.