Finance Reference Public

Module Research Notes

Evidence gathered from the current Finance module files — controllers, models, configs, and migrations — before writing these user guide pages.

Guide version: r1 Module version: 1.6.0 Updated: 2026-07-22 Estimated time: 11 min 2 views
Reference

Module Research Notes

Evidence gathered from the current module files before writing the user guide pages.

Slug: financeVersion: 1.6.0Category: FinanceReviewed: 2026-07-11

Research Sources Inspected

Source path

modules/Finance

Feature flags marked true

  • Controllers
  • Models
  • Views
  • Language
  • Permissions
  • Left Menu
  • Default Menu
  • Routes
  • Migrations
  • Cron Jobs
  • Dashboard Widgets
  • Api Endpoints

Evidence Summary

Manifest

module.json defines slug finance, version 1.6.0, category Finance, and the module capability flags used in this guide.

Runtime surface

31 controller file(s), 72 view file(s), and 55 route mapping(s) were found.

Data surface

27 model file(s), 22 declared table(s), and 18 migration file(s) were found.

Operations surface

69 permission key(s), 1 job(s), 1 seeder(s), and 1 test file(s) were found.

Detailed File Evidence

Controllers and methods

  • Account_transactions - modal_form, list_data
  • Account_transfers
  • Account_types - index, modal_form, save, list_data, delete
  • Accounts
  • Accounts - index, view
  • Accounts - index, view
  • Aging
  • Alphalist
  • Balance_sheet
  • BankReconciliation
  • Banks - index, modal_form, save, list_data, delete, view
  • Bir_reports
  • Books
  • Cheque_booklets - index, modal_form, save, list_data, view, update_status
  • Cheques - list_data, issue_cheque_modal_form, save_cheque_issue, update_status
  • Detail_types - index, modal_form, save, list_data, delete
  • Employee_bir
  • Expense_categories
  • Expenses
  • Expenses_payments
  • Fiscal_periods
  • Gov_contributions
  • Invoice_payments
  • Journals - index, listData
  • Payment_methods
  • Tax_reports
  • Taxes
  • Transaction_classifier - index, modal_form, save, list_data, delete
  • Trial_balance
  • Vouchers - index, modal_form, save, list_data
  • VoucherTypes - index, modalForm, save, listData, delete

Jobs and schedules

  • Generate recurring expenses (20 * * * *) - Creates new expense entries from recurring templates whose next_recurring_date has arrived.

Routes sampled

RouteTarget
fas/summaryExpenses/cash_flow_comparison
fas/balance_sheet/(:any)Balance_sheet/$1
fas/trial_balanceTrial_balance/index
fas/trial_balance/(:any)Trial_balance/$1
fas/booksBooks/index
fas/books/(:any)Books/$1
fas/tax_reportsTax_reports/index
fas/tax_reports/(:any)Tax_reports/$1
fas/agingAging/index
fas/aging/(:any)Aging/$1
fas/gov_contributionsGov_contributions/index
fas/gov_contributions/(:any)Gov_contributions/$1
fas/gov_contributions/(:any)/(:any)Gov_contributions/$1/$2
fas/fiscal_periodsFiscal_periods/index
fas/fiscal_periods/(:any)Fiscal_periods/$1
fas/fiscal_periods/(:any)/(:any)Fiscal_periods/$1/$2
fas/paymentsInvoice_payments/index
fas/payments/(:any)Invoice_payments/$1
fas/payments/(:any)/(:any)Invoice_payments/$1/$2
fas/expensesExpenses/index
35 additional route(s) omitted for readability.

README topics

  • Features
  • What lives here vs. core
  • REST API (read-only)
  • Routing
  • Migrations
  • Demo seeder
  • Fixes applied during migration

Operating Implications

  • Permission coverage is part of the module contract, so every workflow should be tested with a role-limited user and an administrator.
  • The module has migrations, so fresh installs and tenant databases need migration validation before go-live.
  • The module has API documentation or routes, so response examples and query filters should stay aligned with user-facing screens.
  • Scheduled jobs can change statuses, notifications, or summaries after users leave the screen; support should check cron status during troubleshooting.
  • Dashboard widgets summarize module state and should be verified against the source list before management reporting.
  • Seeders exist for sample or starter data; operators should know whether seeded records are demo-only or operational defaults.

Known Risks from the Manifest

  • Finance-domain MODELS were moved into the module (modules/Finance/models/) and remain globally loadable via module package paths. The EARLIEST registration is application/config/autoload.php (the autoloader's first phase, before any controller constructs and before $autoload['model'] runs); it is re-registered later by the base Controller constructor, the init_settings post_controller_constructor hook, and the cron Executor. The autoload.php phase is what keeps RAW CI_Controller subclasses that load finance models IN THEIR CONSTRUCTOR working - verified for the public payment callbacks Stripe_redirect/Paypal_ipn (-> Invoice_payments_model) and Pay_invoice (-> Payment_methods_model). Other cross-consumers OUTSIDE finance also keep working: App_Controller base income/expense dropdown -> Invoice_payments_model; traits/PayrollsTrait -> Expenses_model/Accounts_model/Payment_methods_model/Expense_categories_model; payroll & purchase controllers (Payrolls, Payslips, Payroll_imports, Payslip_bank_exports, PurchaseOrders, PurchaseReturns, Cheque_booklets); modules/Sales Invoices/Estimates -> Taxes_model/Payment_methods_model; Stripe/Paypal libraries -> Payment_methods_model; widget_helper -> Expenses_model; CreateRecurringExpensesJob -> Expenses_model.
  • Invoice_payments (the fas/payments controller) renders invoices/* views that are owned by modules/Sales/ and loads the core Invoices_model; it resolves at runtime via global module package paths. Finance and Sales ship together, so the payments feature has a soft runtime coupling to Sales' invoice views. v1.6.0 added a NON-REGRESSIVE presence guard in the constructor: if the Sales module is not INSTALLED (not discovered by erpat_get_module_bootstrap_data) the controller degrades gracefully (show_error, lang sales_module_required_for_payments) instead of fataling on a missing view. Deliberately NOT gated on the module_sales active SETTING - the Sales views resolve via package paths regardless of that setting, so a setting gate would wrongly break payments where Sales is present-but-disabled.
  • Amount_In_Words library intentionally STAYS in core (application/libraries/Amount_In_Words.php) because payroll depends on it heavily (Payslips, Payroll_imports, Payslip_bank_exports, Payslip_preview, Payslip_mailing). Vouchers loads it from core.
  • STANDALONE PERMISSIONS (features.permissions=true): the 19 finance RBAC keys were MOVED to modules/Finance/config/permissions.php (parents with default_level:module + explicit X_create/X_update/X_delete children, since the module bootstrap does not reproduce Roles.php's auto-CRUD flag) and REMOVED from application/controllers/Roles.php. Roles.php::_append_bootstrapped_permissions() merges them, skipping any key still in core, so there are no duplicate rows. COUPLING ACCEPTED: the merge is gated on module_enabled('module_finance') (Roles.php:397-401), so disabling Finance removes its permission rows from the editor - including expense/payment/accounting_summary which a few CORE gates still read (Dashboard income/overdue-expense + payment widgets, Team_members expense tab, the client-portal payments menu). Existing grants still resolve (with_permission reads stored user permissions, not this list); only NEW UI assignment is blocked while Finance is off. loan*/cheque*/payroll* keys stay in core Roles.php (other subsystems).
  • STANDALONE LANGUAGE (features.language=true): 132 finance-EXCLUSIVE keys were MOVED to modules/Finance/language/english/finance_lang.php (loaded globally every request by erpat_load_module_languages) and removed from application/language/english/default_lang.php. SHARED vocabulary intentionally STAYED in core: the bare tax/account/payment/finance words, payment_method (Sales/Stripe), the account/auth login block, the BIR tax-TABLE strings reused by payroll, and the Cheques subsystem's cheque/booklet strings (the cheque/Alphalist/BIR FEATURES moved into this module in v1.3.0, but their lang keys stayed in core default_lang.php and load globally). Module lang loads regardless of enabled state, so moved keys remain app-wide available. v1.5.0 additionally moved the Finance submenu labels submenu_fas_summary/_accounts/_expenses and 'journals' (and removed the now-inert Finance block from the DEPRECATED default_staff_menu fallback in application/config/left_menu.php, which filter_items_by_active_modules already dropped when Finance is off). submenu_fas_payments, 'vouchers' and 'banks' stay in core because live NON-Finance paths still render them regardless of module_finance: the client sidebar (Left_menu.php, gated on module_payment), the core settings/vouchers page + customization tab (gated on module_vouchers), and the Manage-Modules module_list_data 'banks' label (shown while Finance is inactive).
  • SECURITY FIX during migration: Balance_sheet previously had NO module/permission gate (any authenticated staff could read fas/balance_sheet/list_data). It is now gated on with_permission_any(balance_sheet, accounting_summary), matching the accounting-summary report it backs.
  • ROUTING FIX during migration: BankReconciliation is a mixed-case controller linked via lowercase bankreconciliation/* URLs with no explicit route; it now ships explicit module routes so it resolves on case-sensitive (Linux) filesystems, not only case-insensitive Windows.
  • BUG FIX during migration: application/config/settings.php cron_list_data miscategorized 'expenses' => 'Sales'; corrected to 'Finance' (vouchers was already 'Finance').
  • DASHBOARD WIDGETS MIGRATED (v1.5.0, features.dashboard_widgets=true): the income_vs_expenses and overdue_expenses widgets are now module-owned. Render functions + the compound gate moved to modules/Finance/helpers/finance_widget_helper.php; registration is modules/Finance/config/widgets.php; the hardcoded Dashboard.php gating/render branches and the two core widget_helper.php functions were removed. To express the gates, erpat_apply_module_dashboard_widgets() gained two GENERIC (non-finance) registry extensions - permission_any (array; user needs at least one permit) and gate (a module-owned callable predicate invoked as gate($controller)) - and App_Controller::get_access_info()/can_view_invoices() were widened protected->public so the income_vs_expenses gate callback can read expense access level + invoice visibility. Both widgets stay staff-only (no client gate), matching legacy behaviour; their palette labels already lived in finance_lang.php.
  • CONFIG-SURFACE MERGE (v1.6.0): the previously-blocked core config gaps are now module-owned. GENERIC (non-finance) merge plumbing was added to application/helpers/module_compatibility_helper.php - erpat_collect_module_config_arrays($basename) (ungated discovery of modules/<M>/config/<basename>.php) plus erpat_get_bootstrapped_settings_list($which), erpat_get_bootstrapped_system_logs(), erpat_get_bootstrapped_user_guides(), and erpat_merge_module_control_tokens($ci). Finance contributes: config/settings_lists.php (10 module_list_data + 2 cron_list_data + 5 adsense_list_data rows), config/system_logs.php (6 finance-owned log defs), config/control_tokens.php (allowed_labels 'expenses' - NOT named control.php, which would collide with the autoloaded core 'control' config on CI's config search path), config/user_guides.php (8 help entries incl. reports-accounting-summary + finance-payments). Wiring by read-timing: module_list_data folds into erpat_get_bootstrapped_module_list_data (already merged by Settings/Tenancy/tenant_helper); cron_list_data + adsense_list_data merge in the Settings/AdSense constructors (they read in the constructor, before the hook); control.php (autoloaded) + system_logs.php (loaded first so a later consumer config->load can't clobber the set_item) merge in the init_settings post_controller_constructor hook; user_guides merges in the Documentation + Assistant consumers. All ungated by active state so Manage-Modules lists, historical-log labels/filters, and the help center still include an inactive module. COMPENSATION rows (loan/payroll/salary_history/deminimis/payslip/earnings, plus finance-loans help) intentionally STAY in core - they belong to the Compensation domain, not Finance.
  • Pre-existing items left untouched: the dangling core route fas -> summary/index (no Summary controller); Taxes has no with_module(tax) gate (menu still gates module_tax) preserved as-is; the model class VoucherTypesModel keeps its non-standard name; the client-portal 'payments' entry remains in application/libraries/Left_menu.php (intentional, per config/menu.php header).
  • FINANCE CONSOLIDATION (v1.3.0): Cheques (Cheques + Cheque_booklets controllers, Cheques_model + Cheque_booklets_model, bank_helper, views/booklets), Alphalist (controller + Payrolls_alphalists_model + views/alphalist), and BIR Reports (Bir_reports + Employee_bir controllers + views/bir_reports + views/employee_bir) were MOVED into this module. The old application/controllers/finance/ directory (which held Alphalist + Loans) was removed; Loans was relocated to a core-root controller (application/controllers/Loans.php), NOT this module, as it is HR/Compensation. App_Router resolves the existing core routes (alphalist/*, bir_reports/*, employee_bir/*) to the moved module controllers; Cheques gained explicit cheques/* + cheque_booklets/* routes in config/routes.php (it was path-routed). KEPT IN CORE intentionally: Government_reports_model + Payslip*_model (payroll data, loaded globally), payroll_helper (BIR tax fns), the payrolls/view_alphalist.php + alphalist_modal_form.php shared payroll views, the cheque_booklets/cheques + cheque permission keys in Roles.php and the cheque/BIR lang keys in default_lang.php (all loaded globally; gating works regardless of definition location). Payroll cross-callers (Payrolls batch alphalist regen, Wage_summary BIR-compliance tab rendering bir_reports/_content, Contributions_model reading payrolls_alphalists) keep working via globally-registered module package paths + global model loading.
  • MODULE-OWNED REST API (v1.4.0, features.api_endpoints=true): the Finance module now ships BOTH a read-only integration ('Client') API (api/v1/finance/accounts[/{id}], OAuth2 client-credentials, scope finance:read) and a read-only End-User API (v1/api/finance/accounts[/{id}], employee JWT, gated on module_finance + the 'account' permit). Controllers live IN the module (modules/Finance/controllers/api/finance/ and .../v1/api/finance/), route definitions in config/api.php + config/v1_api.php, the OAuth scope in config/api_scope.php, and per-endpoint OpenAPI enrichment in config/api_openapi.php + config/euapi_openapi.php. GENERIC (non-finance) core enablers were added so ANY module can do this: (a) App_Router now resolves module controllers at a routed SUB-directory (api/finance/, v1/api/finance/), not only top-level, and descends into module-owned controller directories that don't exist under application/controllers/ (faithful additive superset of CI_Router::_validate_request; core always wins on collision); (b) application/config/api.php + v1_api.php require modules/*/config/{api,v1_api}.php into the shared Route registrar (so both runtime routing AND the `php erpat api:openapi` / `euapi:openapi` generators, which evaluate those core files in a sandbox, pick them up); (c) application/config/api_scope.php merges modules/*/config/api_scope.php; (d) the two openapi generators merge modules/*/config/{api_openapi,euapi_openapi}.php sidecars. Re-run `php erpat api:openapi --postman` and `php erpat euapi:openapi --postman` after changing any finance endpoint or shape. ACCEPTED COUPLING: the Client-API Postman category for finance endpoints falls to 'General' (the core api:openapi $resolvePostmanCategory has no finance branch - intentionally left generic); the OpenAPI tag is correctly 'finance'.
Was this guide helpful?

Report a content problem