Skip to content

Accounting

Location: dg_smart_pos_api/modules/Accounting. Owns the double-entry ledger: a chart of accounts, journal entries and their lines, and fiscal years that get closed at year-end into Retained Earnings. It is meant to be the automatic bookkeeping layer behind Sales, expenses, supplier payments, debt collection, and GRNs, posting a balanced journal entry whenever one of those happens.

Accounting is also one of the modules under the core architecture change freeze.

  • Account: one chart-of-accounts row: account_number, account_name, account_type (AccountType enum: asset/liability/equity/income/expense), parent_id for hierarchy, balance (a cached convenience figure; currentBalance() recomputes the authoritative value from posted journal lines), is_system (seeded default accounts, cannot be renamed or deleted).
  • JournalEntry: date, reference_number, description, status (JournalEntryStatus enum: draft/posted/reversed), created_by, reversed_by (self-referencing link to the reversal entry).
  • JournalEntryLine: one debit or credit line on an entry: account_id, debit, credit, source_type/source_id (polymorphic-by-convention pointer back to the record that caused it, e.g. 'sale'/123).
  • FiscalYear: year, start_date, end_date, status (open/closed), closing_journal_entry_id.

AccountingService::seedDefaultAccounts() inserts eleven system accounts per store the first time it runs (Cash 1001, Bank 1003, Accounts Receivable 1004, Inventory 1005, Accounts Payable 2001, Owner’s Equity 3001, Retained Earnings 3002, Revenue 4001, Cost of Goods Sold 5001, Salary Expense 5002, General Expense 5003). It runs on every new store via StoreObserver regardless of the module’s enabled flag (see caution above).

Manual bookkeeping (what’s actually reachable today)

Section titled “Manual bookkeeping (what’s actually reachable today)”

AccountController gives full CRUD over the chart of accounts (index/store/show/update/destroy, with system accounts protected from renumbering and deletion) plus balanceSheet, which aggregates posted journal_entry_lines as of a date into assets/liabilities/equity/income/expenses, computing net income and checking Assets = Liabilities + Equity + Net Income.

JournalEntryController::index/show list and read entries (with filters for date range, status, account). reverse creates a mirrored reversal entry (debits and credits swapped) via AccountingService::reverseEntry(), only for entries currently posted.

FiscalYearController::seedCurrent idempotently ensures the current and previous calendar year exist. close (and the scheduler-driven autoClose) call AccountingService::closeFiscalYear(), which zeroes every income/expense account’s balance for the year into Retained Earnings via a generated closing entry, then marks the year closed. It refuses to close before end_date or if already closed.

What would run if the observers were re-enabled

Section titled “What would run if the observers were re-enabled”

AccountingJournalObserver (currently unregistered, see caution) defines the posting rules that were designed for this module: Sale -> debit Cash/Bank per payment method, credit Revenue (4001); business Expense -> debit the chosen expense account, credit Cash/Bank; InvoicePayment -> debit cash collected, credit Accounts Receivable (1004); DebtHistory (credit sale) -> debit AR for the unpaid portion and cash for any immediate payment, credit Revenue; GrnHeader -> debit Inventory (1005), credit Accounts Payable (2001); SupplierHistory payments -> debit Accounts Payable, credit cash/bank; Deposit -> debit Bank, credit Cash. All of this logic exists and is unit-testable in AccountingService, it simply isn’t wired to fire yet.

All under auth:sanctum, prefix v1_accounting/stores/{store}, registered via modules/Accounting/Routes/accounting.php (required unconditionally from routes/tenant.php, see caution above):

Method Path Controller method
GET balance-sheet AccountController::balanceSheet
GET fiscal-years FiscalYearController::index
POST fiscal-years/seed-current FiscalYearController::seedCurrent
POST fiscal-years/auto-close FiscalYearController::autoClose
POST fiscal-years/{year}/close FiscalYearController::close
GET accounts AccountController::index
POST accounts AccountController::store
GET accounts/{account} AccountController::show
PUT accounts/{account} AccountController::update
DELETE accounts/{account} AccountController::destroy
GET journal-entries JournalEntryController::index
GET journal-entries/{journalEntry} JournalEntryController::show
POST journal-entries/{journalEntry}/reverse JournalEntryController::reverse
  • Sales: the intended source of automatic sale journal entries (posting currently disabled)
  • Inventory: GRNs are the intended source of inventory/payable journal entries (posting currently disabled)
  • HR: PayrollService checks config('modules.enabled') for Accounting before attempting a salary journal entry, so it correctly no-ops while Accounting stays commented out of that list
  • Analytics: reports derived from raw transaction tables rather than from this ledger, since nothing posts to it yet