elgibor-solution / laravel-accounting
Enterprise-grade accounting engine for Laravel applications.
Package info
github.com/elgiborsolution/laravel-accounting
pkg:composer/elgibor-solution/laravel-accounting
Requires
- php: >=8.2
- laravel/framework: ^11.0|^12.0
This package is auto-updated.
Last update: 2026-06-09 04:07:10 UTC
README
elgibor-solution/laravel-accounting is a Laravel 11/12 accounting package that provides chart-of-accounts management, journal entry storage, service-to-account mappings, monthly closing, and financial reporting.
For the full technical reference, start with docs/README.md. It documents the architecture, public APIs, services, journal engine, mapping engine, and extension points directly from the source code.
Overview
The package is built around a small set of accounting entities:
- Account categories
- Accounts
- Business services and account mappings
- Journal entries and journal entry details
- Fiscal periods
- Monthly balances
- Report mappings
The package also ships with API controllers, reusable services, seeders, a factory, and package migrations. Tables are prefixed by default with acc_.
Features
- Chart of accounts with hierarchical parent/child accounts
- Account category management with status toggling
- Business service definitions with debit/credit mappings
- Journal storage with validation and auto-posting
- Journal number generation using a configurable format
- Fiscal period locking to prevent posting into closed periods
- Monthly closing and reopening
- General ledger, trial balance, profit & loss, balance sheet, and cash flow reports
- Tenant-aware routes when a
{tenantId}segment is used
What is not in this package
The current codebase does not include invoice, tax, AR/AP, or payment controllers/routes. Those concepts may exist in a larger application, but they are not implemented here.
Installation
- Require the package in your Laravel application.
composer require elgibor-solution/laravel-accounting
- Publish the configuration file if you want to customize defaults.
php artisan vendor:publish --tag=accounting-config
- Publish the package migrations if you want them in your application database folder.
php artisan vendor:publish --tag=accounting-migrations
- Run migrations.
php artisan migrate --path database/migrations/accounting
- Seed the default chart of accounts, account categories, and ERP accounting services.
php artisan db:seed --class="ESolution\LaravelAccounting\Database\Seeders\AccountingSeeder"
The service provider also loads the package migrations automatically when Laravel is running in console.
AccountingSeeder now includes the default ERP accounting service catalog, so a fresh installation starts with standard transaction services in acc_services.
Configuration
The package configuration lives in config/accounting.php.
Available options
return [ 'table_prefix' => 'acc_', 'journal' => [ 'auto_post' => true, 'number_format' => 'JV/{YEAR}/{MONTH}/{SEQ}', ], 'fiscal' => [ 'start_month' => 1, ], 'route' => [ 'prefix' => 'api/accounting', 'middleware' => ['api'], ], ];
table_prefixcontrols every package table name.journal.auto_postdetermines whether a created journal is immediately posted.journal.number_formatis used byJournalService::generateJournalNo()to build the journal number.fiscal.start_monthis present for fiscal-year configuration.route.prefixandroute.middlewarecontrol package API routing.
Usage
Resolve the high-level service
src/Services/AccountingService.php is a convenience wrapper that exposes the package services:
use ESolution\LaravelAccounting\Facades\Accounting; use ESolution\LaravelAccounting\Services\AccountingService; $accounting = app(AccountingService::class); $journalService = $accounting->journal(); $coaService = $accounting->coa(); $mappingService = $accounting->mapping(); $closingService = $accounting->closing(); $reportService = $accounting->report(); $salesCashService = Accounting::service('SALES_CASH'); $catalog = Accounting::catalog();
Use enum-backed service codes
The package now ships with an enum for standard accounting service codes so package logic can avoid hardcoded strings.
use ESolution\LaravelAccounting\Enums\AccountingServiceCode; use ESolution\LaravelAccounting\Facades\Accounting; $service = Accounting::service(AccountingServiceCode::SALES_CASH); $salesServices = Accounting::catalog()->sales();
Create a manual journal
JournalService::journalManual() accepts balanced debit/credit lines and creates a journal entry with details.
use ESolution\LaravelAccounting\Services\JournalService; $journal = app(JournalService::class)->journalManual([ 'trx_date' => '2026-01-15', 'description' => 'Office expense payment', 'items' => [ [ 'account_code' => '5100', 'type' => 'D', 'amount' => 150000, 'description' => 'Office supplies', ], [ 'account_code' => '1000', 'type' => 'K', 'amount' => 150000, 'description' => 'Cash payment', ], ], ]);
Create a journal from a mapped service
JournalService::journalByMapping() validates service_code, checks mapping keys, and can auto-post when enabled in configuration.
use ESolution\LaravelAccounting\Enums\AccountingServiceCode; use ESolution\LaravelAccounting\Services\JournalService; $journal = app(JournalService::class)->journalByMapping([ 'service_code' => AccountingServiceCode::SALES_CREDIT, 'trx_date' => '2026-01-15', 'reference_no' => 'INV-0001', 'description' => 'Sales invoice INV-0001', 'items' => [ [ 'mapping_key' => 'cash_debit', 'amount' => 1000000, ], [ 'mapping_key' => 'revenue_credit', 'amount' => 1000000, ], ], ]);
Read the chart of accounts tree
CoaService::getTree() returns categories with top-level accounts and nested children.
use ESolution\LaravelAccounting\Services\CoaService; $tree = app(CoaService::class)->getTree();
Close a month
ClosingService::closeMonth() validates the fiscal period, checks journal balance, builds monthly balances, and marks the period as closed.
use ESolution\LaravelAccounting\Services\ClosingService; app(ClosingService::class)->closeMonth(2026, 1, auth()->id());
Generate reports
ReportService provides report methods that are also exposed through the API.
use ESolution\LaravelAccounting\Services\ReportService; $reportService = app(ReportService::class); $generalLedger = $reportService->generalLedger($accountId, '2026-01-01', '2026-01-31'); $trialBalance = $reportService->trialBalance(2026, 1); $profitLoss = $reportService->profitLoss(2026, 1); $balanceSheet = $reportService->balanceSheet(2026, 1); $cashFlow = $reportService->cashFlow(2026, 1);
Default ERP Accounting Services
DefaultAccountingServicesSeeder seeds acc_services with a standard ERP-ready service catalog. The seeder is idempotent and uses updateOrCreate() so rerunning seeds updates definitions without creating duplicates.
Module grouping
| Module | Service Codes |
|---|---|
| SALES | SALES_CASH, SALES_CREDIT, SALES_RETURN, SALES_DISCOUNT, SALES_WRITE_OFF |
| PURCHASE | PURCHASE_CASH, PURCHASE_CREDIT, PURCHASE_RETURN |
| INVENTORY | STOCK_OPENING, STOCK_ADJUSTMENT_PLUS, STOCK_ADJUSTMENT_MINUS, STOCK_TRANSFER, STOCK_OPNAME_GAIN, STOCK_OPNAME_LOSS |
| FINANCE | CASH_IN, CASH_OUT, BANK_TRANSFER, JOURNAL_MANUAL, PETTY_CASH |
| EXPENSE | EXPENSE, PREPAID_EXPENSE |
| PAYROLL | PAYROLL, PAYROLL_ACCRUAL |
| ASSET | ASSET_PURCHASE, ASSET_DEPRECIATION, ASSET_DISPOSAL, ASSET_REVALUATION |
| ACCOUNT_RECEIVABLE | CUSTOMER_RECEIVABLE_PAYMENT, CUSTOMER_RECEIVABLE_WRITE_OFF |
| ACCOUNT_PAYABLE | VENDOR_PAYMENT, VENDOR_PAYABLE_WRITE_OFF |
| TAX | TAX_OUTPUT, TAX_INPUT, TAX_PAYMENT |
| CLOSING | MONTH_END_CLOSING, YEAR_END_CLOSING |
Purpose of each service
| Service Code | Purpose |
|---|---|
SALES_CASH |
Cash sales recognized immediately without creating receivables. |
SALES_CREDIT |
Sales invoices that create customer receivables. |
SALES_RETURN |
Reversals for goods returned by customers. |
SALES_DISCOUNT |
Discounts granted on sales transactions. |
SALES_WRITE_OFF |
Sales-related balances written off after approval. |
PURCHASE_CASH |
Purchases paid immediately by cash or bank. |
PURCHASE_CREDIT |
Purchases that create vendor payables. |
PURCHASE_RETURN |
Returns of purchased goods to vendors. |
STOCK_OPENING |
Initial inventory balance recognition. |
STOCK_ADJUSTMENT_PLUS |
Positive inventory adjustments. |
STOCK_ADJUSTMENT_MINUS |
Negative inventory adjustments. |
STOCK_TRANSFER |
Inventory movement between locations. |
STOCK_OPNAME_GAIN |
Surplus stock found during stock count. |
STOCK_OPNAME_LOSS |
Missing stock found during stock count. |
CASH_IN |
General non-sales cash receipts. |
CASH_OUT |
General non-purchase cash disbursements. |
BANK_TRANSFER |
Transfers between cash and bank accounts. |
JOURNAL_MANUAL |
Manual accounting adjustments entered by finance users. |
PETTY_CASH |
Petty cash funding, usage, and replenishment. |
EXPENSE |
Standard operating expense recognition. |
PREPAID_EXPENSE |
Prepaid expense acquisition and amortization flows. |
PAYROLL |
Payroll payment transactions. |
PAYROLL_ACCRUAL |
Payroll accrual and liability recognition. |
ASSET_PURCHASE |
Fixed asset acquisitions and capitalization. |
ASSET_DEPRECIATION |
Periodic depreciation entries. |
ASSET_DISPOSAL |
Disposal or retirement of fixed assets. |
ASSET_REVALUATION |
Approved asset revaluation entries. |
CUSTOMER_RECEIVABLE_PAYMENT |
Customer payments against receivables. |
CUSTOMER_RECEIVABLE_WRITE_OFF |
Write-off of uncollectible receivables. |
VENDOR_PAYMENT |
Vendor payments against payables. |
VENDOR_PAYABLE_WRITE_OFF |
Write-off of payable balances after reconciliation. |
TAX_OUTPUT |
Output tax recognition from taxable sales. |
TAX_INPUT |
Input tax recognition from taxable purchases or expenses. |
TAX_PAYMENT |
Settlement of tax liabilities. |
MONTH_END_CLOSING |
Month-end adjustment and closing entries. |
YEAR_END_CLOSING |
Year-end closing and retained earnings transfer. |
Service catalog registry
ServiceCatalog is the central registry for default services. It groups services by module through all(), sales(), purchase(), inventory(), finance(), expense(), payroll(), asset(), receivable(), payable(), tax(), and closing().
Default ERP Journal Templates
DefaultServiceAccountMappingsSeeder seeds acc_service_accounts with production-ready journal templates for the default ERP services. The seeder resolves account_id from seeded account_code, never hardcodes UUIDs, and uses updateOrCreate() for idempotent installs and upgrades.
What acc_service_accounts does
- Journal template registry
- Account mapping engine
- Auto journal generator blueprint
- Dynamic account resolver
- Validation layer for required mapping keys
Default ERP account codes
| Code | Account |
|---|---|
1001 |
Cash |
1002 |
Bank |
1003 |
Petty Cash |
1101 |
Accounts Receivable |
1201 |
Inventory |
1301 |
Prepaid Expense |
1501 |
Fixed Asset |
1502 |
Accumulated Depreciation |
1601 |
Input VAT |
2001 |
Accounts Payable |
2101 |
Salary Payable |
2201 |
Tax Payable |
2301 |
Output VAT |
3001 |
Opening Balance Equity |
3101 |
Retained Earnings |
3201 |
Revaluation Reserve |
3301 |
Income Summary |
4001 |
Sales Revenue |
4101 |
Other Income |
4201 |
Inventory Gain |
5001 |
Cost Of Goods Sold |
5101 |
Salary Expense |
5201 |
Operational Expense |
5301 |
Inventory Loss |
5401 |
Depreciation Expense |
5501 |
Bad Debt Expense |
5601 |
Sales Discount |
5701 |
Sales Return |
Template registry
ServiceAccountTemplateRegistry centralizes the default mapping blueprints. Each template definition includes:
mapping_keymapping_namepositionaccount_codesequence_nois_dynamicis_requiredis_active
Default mapping matrix
| Service Code | Default Mappings | Dynamic |
|---|---|---|
SALES_CASH |
sales_cash_cash_d, sales_cash_sales_k, sales_cash_cogs_d, sales_cash_inventory_k |
Cash/Bank |
SALES_CREDIT |
sales_credit_ar_d, sales_credit_sales_k, sales_credit_cogs_d, sales_credit_inventory_k |
No |
SALES_RETURN |
sales_return_sales_return_d, sales_return_receivable_k, sales_return_inventory_d, sales_return_cogs_k |
Receivable/Cash |
SALES_DISCOUNT |
sales_discount_discount_d, sales_discount_receivable_k |
Receivable/Cash |
SALES_WRITE_OFF |
sales_writeoff_bad_debt_d, sales_writeoff_ar_k |
No |
PURCHASE_CASH |
purchase_cash_inventory_d, purchase_cash_cash_k |
Cash/Bank |
PURCHASE_CREDIT |
purchase_credit_inventory_d, purchase_credit_ap_k |
No |
PURCHASE_RETURN |
purchase_return_ap_d, purchase_return_inventory_k |
No |
STOCK_OPENING |
stock_opening_inventory_d, stock_opening_opening_balance_k |
No |
STOCK_ADJUSTMENT_PLUS |
stock_adjustment_plus_inventory_d, stock_adjustment_plus_gain_k |
No |
STOCK_ADJUSTMENT_MINUS |
stock_adjustment_minus_loss_d, stock_adjustment_minus_inventory_k |
No |
STOCK_TRANSFER |
No default journal mapping | Future customization |
STOCK_OPNAME_GAIN |
stock_opname_gain_inventory_d, stock_opname_gain_gain_k |
No |
STOCK_OPNAME_LOSS |
stock_opname_loss_loss_d, stock_opname_loss_inventory_k |
No |
CASH_IN |
cash_in_cash_d, cash_in_other_income_k |
Cash/Bank |
CASH_OUT |
cash_out_expense_d, cash_out_cash_k |
Expense Account, Cash/Bank |
BANK_TRANSFER |
bank_transfer_destination_bank_d, bank_transfer_source_bank_k |
Source Bank, Destination Bank |
JOURNAL_MANUAL |
No default mapping | Manual journal engine |
PETTY_CASH |
petty_cash_fund_d, petty_cash_cash_k |
Cash/Bank |
EXPENSE |
expense_expense_d, expense_cash_k |
Expense Account, Cash/Bank |
PREPAID_EXPENSE |
prepaid_expense_asset_d, prepaid_expense_cash_k, prepaid_expense_amortization_d, prepaid_expense_asset_k |
Expense Account on amortization, optional customization |
PAYROLL |
payroll_salary_expense_d, payroll_cash_k |
Cash/Bank |
PAYROLL_ACCRUAL |
payroll_accrual_expense_d, payroll_accrual_payable_k |
No |
ASSET_PURCHASE |
asset_purchase_asset_d, asset_purchase_cash_k |
Asset Account, Cash/Bank/AP |
ASSET_DEPRECIATION |
asset_depreciation_expense_d, asset_depreciation_accumulated_k |
No |
ASSET_DISPOSAL |
asset_disposal_accumulated_d, asset_disposal_asset_k |
No |
ASSET_REVALUATION |
asset_revaluation_asset_d, asset_revaluation_reserve_k |
No |
CUSTOMER_RECEIVABLE_PAYMENT |
receivable_payment_cash_d, receivable_payment_ar_k |
Cash/Bank |
CUSTOMER_RECEIVABLE_WRITE_OFF |
receivable_writeoff_bad_debt_d, receivable_writeoff_ar_k |
No |
VENDOR_PAYMENT |
vendor_payment_ap_d, vendor_payment_cash_k |
Cash/Bank |
VENDOR_PAYABLE_WRITE_OFF |
vendor_writeoff_ap_d, vendor_writeoff_income_k |
No |
TAX_OUTPUT |
tax_output_receivable_d, tax_output_vat_k |
Cash/Receivable |
TAX_INPUT |
tax_input_vat_d, tax_input_payable_k |
Cash/AP |
TAX_PAYMENT |
tax_payment_payable_d, tax_payment_cash_k |
Cash/Bank |
MONTH_END_CLOSING |
month_closing_revenue_d, month_closing_income_summary_k, month_closing_income_summary_d, month_closing_expense_k |
Revenue Accounts, Expense Accounts |
YEAR_END_CLOSING |
year_closing_income_summary_d, year_closing_retained_earnings_k |
No |
API Reference
All endpoints return the package response format from ApiResponse:
{
"status": 200,
"message": "Accounts retrieved successfully",
"data": []
}
Validation failures use the same wrapper:
{
"status": 422,
"message": "Validation Error",
"errors": {
"account": ["Cannot delete account with children"]
},
"data": null
}
Base route
The package registers routes under the configured prefix:
GET|POST /api/accounting/...- Tenant-aware variants:
/{tenantId}/...under the same prefix
The current route set is:
GET /api/accounting/categoriesPOST /api/accounting/categoriesGET /api/accounting/categories/{id}PUT /api/accounting/categories/{id}DELETE /api/accounting/categories/{id}PATCH /api/accounting/categories/{id}/toggle-statusGET /api/accounting/accountsPOST /api/accounting/accountsGET /api/accounting/accounts/{id}PUT /api/accounting/accounts/{id}DELETE /api/accounting/accounts/{id}PATCH /api/accounting/accounts/{id}/toggle-statusGET /api/accounting/servicesPOST /api/accounting/servicesGET /api/accounting/services/{id}PUT /api/accounting/services/{id}DELETE /api/accounting/services/{id}PATCH /api/accounting/services/{id}/toggle-statusGET /api/accounting/journalsGET /api/accounting/journals/{id}GET /api/accounting/reports/general-ledgerGET /api/accounting/reports/trial-balanceGET /api/accounting/reports/profit-lossGET /api/accounting/reports/balance-sheetGET /api/accounting/reports/cash-flow
Accounts API
Create account
POST /api/accounting/accounts
Request body:
{
"category_id": "b3d2f1d0-3a8b-4b42-9d18-3b3e7d1f2f11",
"code": "1002",
"name": "Bank BCA",
"status": true
}
Sample response:
{
"status": 201,
"message": "Account created successfully",
"data": {
"id": "6c3c8b8d-8c3f-4e5c-a8ea-2d8d3ef2f4f1",
"category_id": "b3d2f1d0-3a8b-4b42-9d18-3b3e7d1f2f11",
"code": "1002",
"name": "Bank BCA",
"status": true
}
}
List accounts
GET /api/accounting/accounts?search=1001
The controller loads the related category in the cached result.
Services API
Create service with mappings
POST /api/accounting/services
Request body:
{
"service_code": "TEST_SERVICE",
"service_name": "Test Service",
"module_name": "TEST",
"mappings": [
{
"mapping_key": "test_d",
"mapping_name": "Test Debit",
"position": "D",
"account_id": "UUID-OF-ACCOUNT"
},
{
"mapping_key": "test_k",
"mapping_name": "Test Credit",
"position": "K",
"is_dynamic": true
}
]
}
Sample response:
{
"status": 201,
"message": "Service created successfully",
"data": {
"id": "a11d8b76-1d6a-4d4c-b2cb-7e4f8d8f67c2",
"service_code": "TEST_SERVICE",
"service_name": "Test Service",
"module_name": "TEST",
"mappings": []
}
}
Journal API
List journals
GET /api/accounting/journals
Query parameters supported by the controller:
pageper_pagesearchstart_dateend_datestatus
Get journal detail
GET /api/accounting/journals/{id}
The controller returns the journal header with loaded details.account and service relations.
Reports API
General ledger
GET /api/accounting/reports/general-ledger?account_id={uuid}&start_date=2026-01-01&end_date=2026-01-31
Request validation requires:
account_idas a UUIDstart_dateas a dateend_dateas a date that is on or afterstart_date
Sample response shape:
{
"status": 200,
"message": "General Ledger retrieved successfully",
"data": {
"account": {
"id": "6c3c8b8d-8c3f-4e5c-a8ea-2d8d3ef2f4f1",
"code": "1000",
"name": "Kas"
},
"opening_balance": 0,
"details": []
}
}
Trial balance
GET /api/accounting/reports/trial-balance?year=2026&month=1
Profit & loss
GET /api/accounting/reports/profit-loss?year=2026&month=1
Balance sheet
GET /api/accounting/reports/balance-sheet?year=2026&month=1
Cash flow
GET /api/accounting/reports/cash-flow?year=2026&month=1
Internal service methods
These methods are available through the package but are not exposed as routes in the current codebase:
JournalService::journalByMapping(array $data)JournalService::journalManual(array $data)JournalService::post($id)CoaService::createAccount(array $data)CoaService::getTree()CoaService::activateAccount($id)CoaService::deactivateAccount($id)MappingService::findByKey(string $key)MappingService::getByService($serviceId)ClosingService::closeMonth($year, $month, $userId = null)ClosingService::reopenMonth($year, $month, $userId = null)
Architecture Flow
Business Module
↓
Service Catalog
↓
Account Mapping Engine
↓
Journal Engine
↓
Fiscal Period
↓
Monthly Summary
↓
Financial Reports
Data Flow
- A business module selects a standard service from the default
ServiceCatalogor a custom service inacc_services. - The account mapping engine validates the selected service and loads its debit/credit mapping rules.
JournalServicevalidates that every entry is balanced and that the fiscal period is not closed.- A journal header is created in
acc_journal_entries, with detail lines stored inacc_journal_entry_details. - If
accounting.journal.auto_postis enabled, the journal is immediately marked asposted. ClosingServiceaggregates posted journal activity intoacc_monthly_balancesfor each account.ReportServicereadsacc_monthly_balances,acc_report_mappings, and posted journal details to generate reports.
Package Models
The package defines these main Eloquent models:
AccountCategoryAccountServiceServiceAccountJournalEntryJournalEntryDetailFiscalPeriodMonthlyBalanceReportMapping
All package models use UUID primary keys through the shared HasUuid trait.
Notes for Integrators
- The package uses cache tags for accounts, categories, services, and journals.
AccountingPeriodLockedExceptionis thrown when posting into a closed fiscal period.- Route handlers support an optional tenant context when the path includes
{tenantId}and atenancy()helper is available. - The service layer currently shows a naming mismatch between controller/request fields (
status,mappings) and theServicemodel/schema (is_active,accounts()), so confirm service create/update behavior against the live app before depending on it. - The codebase currently includes only one feature test file for accounts; additional coverage for journals, reports, and closing would be a useful follow-up.