scottlaurent / accounting
Laravel Accounting Journals for Eloquent Models
Installs: 25 865
Dependents: 0
Suggesters: 0
Security: 0
Stars: 193
Watchers: 18
Forks: 71
Open Issues: 0
Requires
- php: ^8.1|^8.2|^8.3
- laravel/framework: ^8.0|^9.0|^10.0|^11.0|^12.0
- moneyphp/money: ^3.3.3
Requires (Dev)
- fakerphp/faker: ^1.23
- mockery/mockery: ^1.6.0
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0|^10.0
- phpunit/phpunit: ^9.0|^10.0|^11.0
This package is not auto-updated.
Last update: 2025-06-25 20:35:11 UTC
README
I am an accountant and a Laravel developer. I wrote this package to provide a simple drop-in trait to manage accruing balances for a given model. It can also be used to create double entry based projects where you would want to credit one journal and debit another.
** This DOES allow you to keep line-item balances historical debits and credits on a per model object (user, account, whatever) basis
** This DOES allow you track per-line-item memos and actually reference a foreign class object directly.
** This DOES (starting with v0.2.0) allow you utilize a "ledger" system that makes it possible to run queries against expense, income.
** This DOES NOT force double-entry bookeeping but does create an environment in which you can build out a double-entry system.
** This DOES NOT replace any type of financial recording system which you may be using (ie if you are tracking things in Stripe for example).
โจ Features
- ๐ฆ Double-Entry Accounting - Proper accounting principles with debits and credits
- ๐ฐ Multi-Currency Support - Handle transactions in different currencies
- ๐ 100% Test Coverage - Thoroughly tested and reliable
- ๐ Laravel 8-12 Support - Works with all modern Laravel versions
- ๐ PSR-12 Compliant - Clean, maintainable code
- ๐ฏ Precise Money Handling - Uses moneyphp/money for accurate calculations
๐ Requirements
- PHP: 8.1, 8.2, or 8.3
- Laravel: 8.x, 9.x, 10.x, 11.x, or 12.x
- Database: MySQL, PostgreSQL, SQLite, or SQL Server
๐ Laravel Version Compatibility
Laravel | PHP | Status |
---|---|---|
12.x | 8.2, 8.3 | โ Fully Supported |
11.x | 8.2, 8.3 | โ Fully Supported |
10.x | 8.1, 8.2, 8.3 | โ Fully Supported |
9.x | 8.1, 8.2 | โ Fully Supported |
8.x | 8.1 | โ Fully Supported |
Contents
Installation
1. Install via Composer
composer require scottlaurent/accounting
The service provider will be automatically discovered by Laravel (5.5+). No manual registration required!
2. Publish Migrations
php artisan vendor:publish --provider="Scottlaurent\Accounting\Providers\AccountingServiceProvider"
This will install 3 new tables in your database:
accounting_ledgers
- For organizing accounts by type (optional)accounting_journals
- For tracking balances per modelaccounting_journal_transactions
- For individual transaction records
3. Run Migrations
php artisan migrate
4. Add the Trait to Your Models
Add the AccountingJournal
trait to any model you want to track balances for:
use Scottlaurent\Accounting\ModelTraits\AccountingJournal; class User extends Model { use AccountingJournal; protected static function boot() { parent::boot(); // Automatically create a journal when a user is created static::created(function ($user) { $user->initJournal(); }); } }
Sign Convention
This package uses the following sign convention for accounting entries:
- Debits are negative: When you debit an account, the balance becomes more negative
- Credits are positive: When you credit an account, the balance becomes more positive
This is the opposite of standard accounting practice but was implemented this way for technical reasons. Keep this in mind when working with account balances.
For example:
- Debiting an asset account (like Cash) will make the balance more negative
- Crediting a revenue account will make the balance more positive
Code Sample
// locate a user (or ANY MODEL that implements the AccountingJournal trait) $user = User::find(1); // locate a product (optional) $product = Product::find(1); // init a journal for this user (do this only once) $user->initJournal(); // credit the user and reference the product $transactionOne = $user->journal->creditDollars(100); $transactionOne->referencesObject($product); // check our balance (should be 100) // Note: getCurrentBalanceInDollars() will return a positive number for credit balances $currentBalance = $user->journal->getCurrentBalanceInDollars(); // debit the user $transactionTwo = $user->journal->debitDollars(75); // check our balance (should be 25) // The balance will be positive if credits > debits, negative if debits > credits $currentBalance = $user->journal->getCurrentBalanceInDollars(); // get the product referenced in the journal (optional) $productCopy = $transactionOne->getReferencedObject();
see /tests for more examples.
How It Works
-
The trait includes functions to a) initialize a new journal for your model object and b) to return that journal.
-
Typically systems will have one journal per user or account and one general journal for the company if doing a double-entry system.
-
IMPORTANT: The accounting system uses the Money PHP class which deals with indivisible currency. For example, the indivisible currency of USD is the penny. So $1 is really Money 100 USD. This prevents loss of currency by division/rounding errors.
Usage Examples
-
SCENARIO A - VERY SIMPLE CASE - You are providing an API Service. Each API hit from a user costs 5 cents. You don't care about double-entry accounting.
a. Add the model trait to your user class.
b. Run a cron at the end of each month to count the API calls and do a $user->journal->debitDollars(1500 * 0.05) as an example where 1500 is the number of API calls from that user at the end of the month.
c. Any time the user makes a payment, post a $user->journal->creditDollars(25.00)
From this point, you can can a balance for this user by doing $balance_due = $user->journal->getBalanceInDollars() (or getBalance() for a Money object);
-
SCENARIO B - You want to track product purchases users and also do a VERY BASIC INCOME ONLY double entry recording for the entire app where users each have an income based jounral (as in SCENARIO A).
a. Add the model trait to your user class. If you don't have one, create a model for the company itself (which you may only have a single entry which is the company/app). Add the trait to that class as well. So, to recap, you will have ONE journal PER USER and then one additional model object overall for the company which has a single journal entry.
b. If you do simple product purchasing, just debit the user journal when the purchase is made, and credit the account journal. You can optionally reference the products when you do the debit and the credits (see the test class).
c. If you do more complex orders which have invoices or orders, you can still do the same thing here: debit a user model. credit the invoice model. then debit the invoice model and credit the account model. This entirely depends on how you want to structure this, but the point here is that you are responsbible for doing the debits and the credits at the same time, and this can be a very simplistic and/or manual way to build out a mini-accounting system.
-
SCENARIO C - You want to assign journals to a ledger type system and enforce a double entry system using the
Transaction
classThe
Transaction
class provides a fluent interface for creating double-entry transactions:use Scottlaurent\Accounting\Transaction; // Create a new transaction group $transaction = Transaction::newDoubleEntryTransactionGroup(); // Add transactions (debit and credit) $transaction->addDollarTransaction( $journal, // Journal instance 'debit', // or 'credit' 100.00, // amount 'Memo text' // optional memo ); // Commit the transaction (will throw if debits != credits) $transaction->commit();
The
Transaction
class ensures that all transactions are balanced (total debits = total credits) before committing to the database. -
SCENARIO D - Advanced: Product Sales with Inventory and COGS
For a complete example of handling product sales with inventory management, cost of goods sold (COGS), and different payment methods, see the ProductSalesTest class in the
tests/ComplexUseCases
directory.For a comprehensive financial scenario demonstrating all ledger types (Assets, Liabilities, Equity, Revenue, Expenses, Gains, Losses) with proper closing entries, see the CompanyFinancialScenarioTest class.
a. Run the migrations. Then look in the tests/BaseTest setUpCompanyLedgersAndJournals() code. Notice where 5 basic ledgers are created. Using this as an example, create the ledgers you will be using. You can stick with those 5 or you can make a full blown chart of accounts, just make sure that each legder entry is assigned to one of the 5 enums (income, expense, asset, liability, equity)
b. You will need multiple company jounrals at this point. If you look at the test migration create_company_journals_table, it is a simple table that allows you to add journals for no other purpose than to record transactions.
c. Each journal that is created, whether it's a user journal, or a cash journal you create in your journals table, you will want to assign the journal to a ledger. $user->journal->assignToLedger($this->companyIncomeLedger);
d. To process a double entry transaction, do something like this:
// this represents some kind of sale to a customer for $500 based on an invoiced amount of 500. $transactionGroup = Transaction::newDoubleEntryTransactionGroup(); $transactionGroup->addDollarTransaction($user->journal, 'credit', 500); // your user journal probably is an income ledger $transactionGroup->addDollarTransaction($this->companyAccountsReceivableJournal, 'debit', 500); // this is an asset ledger $transactionGroup->commit();
// this represents payment in cash to satisfy that AR entry $transactionGroup = Transaction::newDoubleEntryTransactionGroup(); $transactionGroup->addDollarTransaction($this->companyAccountsReceivableJournal, 'credit', 500); $transactionGroup->addDollarTransaction($this->companyCashJournal, 'debit', 500); $transactionGroup->commit(); // at this point, our assets are 500 still and our income is 500. If you review the code you will notice that assets and expenses are on the 'left' side of a balance sheet rollup and the liabilities and owners equity (and income) are rolled up on the right. In that way, the left and right always stay in sync. You could do an adjustment transaction of course to zero out expenses/income and transfer that to equity or however you do year-end or period-end clearances on your income/expense ledgers.
e. Finally note that add up all of your $ledger model objects of type asset/expense then that will always be 100% equal to the sum of the $ledger liability/equity/income objects.
f. Note that the $transactionGroup->addDollarTransaction() allows you to add as many transactions as you want, into the batch, but the sum of ledger-type journals for the assets/expenses must equal that of the income/liability/equity types. This is a fundamental requirement of accounting and is enforced here. But again, remember that you don't have to use ledgers in the first place if you don't want to.
๐งช Testing
Running Tests
To run the test suite:
# Run all tests with coverage make test # Test specific Laravel version locally ./test-versions.sh 11 # Test all Laravel versions (8-12) ./test-versions.sh
Complex Use Cases
The package includes comprehensive test scenarios demonstrating real-world accounting implementations:
๐ฆ Product Sales Scenario
- Complete product sales workflow with inventory management
- Cost of Goods Sold (COGS) calculations
- Cash and credit payment processing
- Multi-product transactions
- Inventory tracking and valuation
๐ข Company Financial Scenario
- Full accounting cycle with all ledger types:
- Assets: Cash, Accounts Receivable, Inventory, Equipment
- Liabilities: Accounts Payable, Loans Payable
- Equity: Common Stock, Retained Earnings
- Revenue: Product Sales, Service Revenue
- Expenses: COGS, Salaries, Rent, Utilities, Depreciation
- Gains/Losses: Asset sales, inventory shrinkage
- Period-end closing entries
- Financial statement preparation
- Accounting equation validation
These test cases serve as documentation and examples for implementing complex accounting scenarios in your applications.
๐ API Reference
Journal Operations
// Basic operations $journal->debit(5000, 'Equipment purchase'); // Amount in cents $journal->credit(2500, 'Payment received'); // Amount in cents // Dollar convenience methods (recommended) $journal->debitDollars(50.00, 'Office supplies'); // Amount in dollars $journal->creditDollars(25.00, 'Refund issued'); // Amount in dollars // Get balances $currentBalance = $journal->getBalance(); // Money object $dollarBalance = $journal->getBalanceInDollars(); // Float $balanceOnDate = $journal->getBalanceOn($date); // Money object // Daily totals $debitedToday = $journal->getDollarsDebitedToday(); // Float $creditedToday = $journal->getDollarsCreditedToday(); // Float
Transaction Operations
use Scottlaurent\Accounting\Transaction; // Create transaction group $transaction = Transaction::newDoubleEntryTransactionGroup(); // Add transactions with proper camelCase parameters $transaction->addDollarTransaction( journal: $journal, method: 'debit', value: 100.00, memo: 'Transaction description', referencedObject: $product, // Optional reference postdate: Carbon::now() // Optional date ); // Commit (validates debits = credits) $transactionGroupId = $transaction->commit();
Ledger Management
use Scottlaurent\Accounting\Models\Ledger; use Scottlaurent\Accounting\Enums\LedgerType; // Create ledgers $assetLedger = Ledger::create([ 'name' => 'Current Assets', 'type' => LedgerType::ASSET ]); // Assign journal to ledger $journal->assignToLedger($assetLedger); // Get ledger balance $totalBalance = $assetLedger->getCurrentBalance('USD');
Sign Convention Reminder
Remember the sign convention used in this package:
- Debits are negative
- Credits are positive
This is particularly important when working with account balances and writing tests. The test suite includes examples of how to work with this convention.
Contribution
Contributions are welcome! Please feel free to submit pull requests or open issues for any bugs or feature requests. When contributing, please ensure that your code follows the existing coding style and includes appropriate tests.
License
This package is open-sourced software licensed under the MIT license.