Download the PHP package otim-otim/bookkeeper without Composer

On this page you can find all versions of the php package otim-otim/bookkeeper. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package bookkeeper

BookKeeper — Accounting for Laravel

BookKeeper is a complete double-entry accounting engine for Laravel. Install it and your application gets:

Requires PHP 8.4+ and Laravel 12 or 13. Works on SQLite, MySQL and PostgreSQL.


Contents

  1. Installation
  2. Concepts
  3. Double-entry bookkeeping
  4. Money
  5. Currencies and countries
  6. Data transfer objects
  7. Chart of accounts
  8. Journal entries
  9. Posting templates
  10. Subledgers
  11. Periods and closing
  12. Receivables and payables
  13. Tax
  14. Fixed assets
  15. Budgets
  16. Teller tills
  17. Bank and mobile-money reconciliation
  18. Surplus appropriation and dividends
  19. Reports
  20. Events
  21. Configuration reference
  22. Roles the package uses
  23. Database tables
  24. Exceptions
  25. Multi-tenant applications
  26. Using BookKeeper in Suluhu
  27. Testing your application
  28. Architecture
  29. Development with Docker
  30. Upgrading from 1.x

Installation

bookkeeper:install imports a chart of accounts and opens the current fiscal year. Run php artisan bookkeeper:install --list to see the available templates.

The base currency is chosen once per set of books, using --currency, the chart's currency, or BOOKKEEPER_CURRENCY. It is locked when the first entry posts. See Currencies and countries.


Concepts

Concept Description
Money OtimOtim\BookKeeper\Support\Money is an immutable amount in integer minor units. Money::of('1500.50', 'USD') and Money::ofMinor(150050, 'USD') are the same value. Amounts with more decimals than the currency allows are rejected. Calculated amounts (tax, interest, depreciation) are rounded with the configured mode. Money serialises to a decimal string.
Account reference Anywhere an account is expected, you can pass an Account model, its ID, its code ('1121') or a role ('@bank').
Role The purpose an account plays (cash, accounts_receivable, retained_earnings, ...), so code never hard-codes account numbers. Chart templates assign roles. config('bookkeeper.roles') can override them.
Control account An account whose every line must name a subledger party, such as a member, customer or loan. Party balances are views over the ledger, so they always tie to the control account.
Posted vs draft Posted entries affect balances and are immutable. Drafts do not affect balances and can still be edited or deleted.
Base currency Every line is stored in the base currency. Foreign amounts and rates are kept alongside.

Double-entry bookkeeping

Double entry is the foundation everything else builds on. Invoices, payments, depreciation, tills, dividends and year-end close all post through the same ledger, so every one of them obeys these rules.

Every entry balances. A journal entry has two or more lines. Each line debits or credits one account, and total debits must equal total credits exactly. The check uses integer minor units, so rounding can never let an unbalanced entry through. An unbalanced entry throws UnbalancedEntryException, and nothing is saved.

Balances follow each account type's normal side:

Type Normal balance Increases with Balance = Statement
Asset Debit Debit debits − credits Balance sheet
Expense Debit Debit debits − credits Income statement
Liability Credit Credit credits − debits Balance sheet
Equity Credit Credit credits − debits Balance sheet
Revenue Credit Credit credits − debits Income statement

Contra accounts, such as accumulated depreciation and loan-loss provision, carry the opposite balance. They show as negative within their section, so the section totals net off correctly.

The books always agree with themselves. Because every entry balances, the ledger as a whole stays in balance:

History is permanent. A posted entry, including its lines, cannot be edited or deleted. A mistake is corrected by BookKeeper::reverse(), which posts a mirror-image entry and links the two. Both stay visible in the ledger, giving a complete audit trail. Entries carry a gap-free number, the user who posted them, the branch and the source business record.

One posting, one transaction. An entry and everything recorded with it (document, payment, allocation, till session) are written in a single database transaction. They are posted together or not at all. An idempotency key makes a retried request return the original entry instead of posting twice.


Money

OtimOtim\BookKeeper\Support\Money holds an amount as an integer number of minor units (cents, or whole shillings for UGX), with exact arithmetic from brick/math.

Raw amounts with more decimals than the currency allows (Money::of('10.005', 'USD')) throw InvalidAmountException. Unknown currency codes throw InvalidCurrencyException. Precision comes from ISO 4217: UGX has 0 decimals, USD and KES have 2, KWD has 3.


Currencies and countries

BookKeeper works in any currency and any country. Nothing is hard-coded to one.

Base currency per set of books. The books' base currency is stored in the database, not taken from config at run time:

Every ISO 4217 currency is built in, with its correct minor units. Override one, or add a custom unit, in config:

Currency::all(), Currency::precision('KWD'), Currency::name('TZS') and Currency::cashIncrement('CHF') expose the data.

Foreign-currency accounts. Give an account a currency (['currency' => 'USD']) and every line on it must be in that currency. Its balance is reported both ways:

The general ledger and trial balance add currency_balance columns for these accounts. Bank reconciliation of a foreign account works entirely in that account's currency.

Exchange rates. Rates are stored as base units per 1 foreign unit, by date. Lookups use the latest rate on or before the date.

To fetch rates automatically, set exchange_rates.provider and schedule bookkeeper:rates. Two providers are bundled:

Provider Source
RateProviders\OpenExchangeRatesApiProvider open.er-api.com: free, daily, about 160 currencies including UGX, KES, TZS and RWF. Needs Laravel's HTTP client.
RateProviders\FixedRateProvider exchange_rates.fixed in config, for pegged or board-set rates

To add a source, such as a central bank's feed, implement Contracts\ExchangeRateProvider:

Realised and unrealised exchange differences:

Cash rounding. For currencies whose smallest coin is larger than one minor unit (CHF 0.05, UGX 50, SEK 1, ...), document totals can round to the coin. Set 'cash_rounding' => true per document, or documents.cash_rounding globally. The difference posts to the cash_rounding role.

Formatting. Money::format($locale) uses PHP's intl extension, with the locale taken from bookkeeper.locale or the app locale:

Without intl, it falls back to UGX 50,000.

On Alpine images, install icu-data-full. The default ICU data only covers English.

Country chart templates.

Template Country / standard Currency Languages
standard General business from settings en
ifrs IFRS statement layout (IFRS 16 leases, IAS 37 provisions, IAS 12 deferred tax, OCI) from settings en
sacco-ug Uganda SACCO (UMRA): MTN and Airtel floats, Stabilisation Fund levy UGX en, sw
sacco-ke Kenya SACCO (SASRA): M-Pesa, BOSA/FOSA deposits, excise duty, SHIF, housing levy KES en, sw
sacco-tz Tanzania SACCOS (TCDC): M-Pesa, Mixx by Yas, Airtel, HaloPesa, SDL, WCF TZS en, sw

Template names can be translations ('name' => ['en' => 'Cash', 'sw' => 'Fedha taslimu']). --locale picks the stored name, and Account::localizedName('sw') returns any other. The Swahili names are a first draft and should be reviewed by a native speaker before production use.

Tax rates, fiscal year start and numbering are all configuration, so any country's rules can be expressed without code changes.


Data transfer objects

Every write operation is described by a readonly DTO in OtimOtim\BookKeeper\Data. Services accept the DTO, or the equivalent snake_case array shown elsewhere in this README, which is converted with Dto::fromArray() at the boundary. Inside, services only work with typed, validated values.

DTO Used by
EntryData, LineData record(), draft(), transfer(), reverse()
AccountData accounts()->createFrom(), chart import
DocumentData, DocumentLineData documents()->createFrom() (invoices, bills, notes)
PaymentData, AllocationData payments()->recordFrom()
TaxRateData taxes()->createFrom()
ContactData contacts()->createFrom()
FixedAssetData assets()->register()
BankStatementData, StatementLineData bank()->import()
TillData tills()->createFrom()
PostingTemplateData, TemplateLineData templates()->define() and config templates
AppropriationRuleData appropriation() rules

DTOs validate when they are constructed. For example, a subtype that doesn't match its type, a document without lines, or an unknown appropriation base all throw immediately.


Chart of accounts

Account types and subtypes. The type decides the normal balance and the statement. The subtype refines it:

Type Subtypes
Asset cash, bank, mobile_money, short_term_investment, receivable, loan_portfolio, interest_receivable, loan_loss_provision†, inventory, prepayment, tax_receivable, other_current_asset, investment, fixed_asset, accumulated_depreciation†, intangible_asset, other_asset
Liability payable, member_savings, fixed_deposits, interest_payable, tax_payable, accrued_liability, deferred_income, dividends_payable, short_term_borrowing, long_term_borrowing, other_current_liability, other_liability
Equity share_capital, reserve, retained_earnings, opening_balance_equity, drawings†, other_equity
Revenue operating_revenue, interest_income, fee_income, other_income
Expense cost_of_sales, interest_expense, operating_expense, personnel_expense, provision_expense, depreciation_expense, tax_expense, other_expense

† Contra account. Cash, bank and mobile money count as cash in the cash flow statement. Investments and fixed assets are investing activities. Borrowings, share capital, dividends payable and drawings are financing. Everything else is operating. Override this per account with cash_flow_category.

Templates are PHP arrays in resources/charts. See the bundled templates under Currencies and countries. Publish them with --tag=bookkeeper-charts, or drop your own into resources/bookkeeper/charts. A template may set a currency, and its account names may be translations.


Journal entries

The v1 line format, ['account' => '1000', 'type' => 'debit', 'amount' => 100], still works.

The rules enforced on every entry:


Posting templates

Templates describe a business event once. Application code then posts it by name.

Each line takes three kinds of value:

Lines that add up to zero are skipped. You can also define templates at runtime with BookKeeper::templates()->define().


Subledgers


Periods and closing

The first posting into a date creates its fiscal year and twelve monthly periods automatically. The year starts in the month set by fiscal_year.start_month; set auto_create to false to turn this off.

Each close runs only once per date. Plug in your own end-of-day, month and year work, such as interest accrual, penalties or loan-loss provisioning:

Schedule the artisan commands:

Command Purpose
bookkeeper:install --chart= --currency= --locale= --year= Set the base currency, import a chart, open the fiscal year
bookkeeper:close-day {date?} End of day
bookkeeper:close-month {date?} End of month (default: last month)
bookkeeper:close-year {year} End of year
bookkeeper:depreciate {date?} Post outstanding depreciation
bookkeeper:snapshot {date?} Store daily balance snapshots
bookkeeper:reversals {date?} Post scheduled reversals
bookkeeper:rates {date?} --currency=* Fetch exchange rates from the configured provider

Receivables and payables


Tax

A purchase rate without an account is not recoverable, so its tax is added to the cost.


Fixed assets


Budgets


Teller tills


Bank and mobile-money reconciliation


Surplus appropriation and dividends

Appropriation is idempotent per fiscal year. Rules can also live in config('bookkeeper.appropriation').


Reports

Every report returns arrays of Money values, ready for response()->json().

Balances use aggregate SQL. Daily snapshots, taken at end of day, keep balance queries fast as history grows, and a back-dated posting invalidates the snapshots it affects.


Events

These events are dispatched after the database transaction commits:

They are all in the OtimOtim\BookKeeper\Events namespace. For example, listen to EntryPosted to flag cash transactions above a reporting threshold.


Configuration reference

Publish the file with php artisan vendor:publish --tag=bookkeeper-config.

Key Default Purpose
connection null (default connection) Database connection for BookKeeper tables (BOOKKEEPER_DB_CONNECTION)
run_migrations true Load the package migrations automatically (BOOKKEEPER_RUN_MIGRATIONS)
currency USD Default base currency for new books (BOOKKEEPER_CURRENCY); locked per set of books on first posting
currencies [] Overrides and custom units: 'KES' => ['decimals' => 2, 'cash_increment' => '1']
locale null (app locale) Money formatting and chart name language (BOOKKEEPER_LOCALE)
rounding HalfUp Rounding for calculated amounts (any Brick\Math\RoundingMode)
fiscal_year.start_month 1 First month of the fiscal year
fiscal_year.auto_create true Create fiscal years and periods on first use
journal.number_format JE-{Y}-{seq} Entry numbering; tokens {Y}, {m}, {seq}
journal.sequence_padding 6 Zero padding for {seq}
journal.enforce_subledgers true Require a party on control-account lines
roles [] Role → account code overrides
postings [] Posting templates
documents.number_formats INV-…, CN-…, BILL-…, DN-…, RCPT-…, PAY-… Document and payment numbering
documents.default_payment_terms_days 30 Due date when the contact has no terms
documents.ageing_buckets [0, 30, 60, 90] Ageing report bucket bounds (days overdue)
tills.over_short_role cash_over_short Account role for till overages and shortages
snapshots.enabled true Use daily balance snapshots
exchange_rates.provider null ExchangeRateProvider class for bookkeeper:rates
exchange_rates.currencies [] Currencies to fetch (empty: those in use)
exchange_rates.fixed [] Rates for FixedRateProvider
revaluation.enabled true Revalue foreign monetary balances at month end
revaluation.role fx_gain_loss Account role for unrealised exchange differences
revaluation.reverse true Reverse the revaluation the next day
documents.cash_rounding false Round document totals to the smallest coin
appropriation [] Default year-end appropriation rules
close.day / close.month / close.year [] CloseTask classes run at each close

Roles the package uses

Some features post to accounts by role. Both bundled charts assign all of them. For a custom chart, give these roles to accounts before using the features that need them:

Role Used by
retained_earnings Year-end close, appropriation, dividends (default debit)
opening_balance_equity openingBalances()
accounts_receivable / accounts_payable Invoices, credit notes / bills, debit notes, payments (control accounts)
fx_gain_loss Exchange differences on settlement
withholding_tax Withholding without a tax-rate account
cash_over_short Till overages and shortages
cash_rounding Cash rounding on document totals
cash_on_hand Parent of new till accounts
fixed_assets, accumulated_depreciation, depreciation_expense, asset_disposal Fixed assets
dividends_payable Default credit for dividends

Database tables

Table Holds
bookkeeper_accounts Chart of accounts
bookkeeper_journal_entries / bookkeeper_journal_lines The general ledger: entry headers and their debit and credit lines
bookkeeper_fiscal_years / bookkeeper_periods Fiscal calendar and period locks
bookkeeper_sequences Gap-free numbering
bookkeeper_settings Settings of these books, including the locked base currency
bookkeeper_snapshots / bookkeeper_account_balances Daily cumulative balances
bookkeeper_close_runs Which day, month and year closes have run
bookkeeper_tax_rates / bookkeeper_exchange_rates Tax and currency rates
bookkeeper_contacts Customers and vendors
bookkeeper_documents / bookkeeper_document_lines Invoices, credit notes, bills, debit notes
bookkeeper_payments / bookkeeper_allocations Payments and what they settle
bookkeeper_fixed_assets / bookkeeper_asset_depreciations Asset register and depreciation charges
bookkeeper_budgets / bookkeeper_budget_lines Budgets
bookkeeper_tills / bookkeeper_till_sessions Tills, vaults and teller sessions
bookkeeper_bank_statements / bookkeeper_bank_statement_lines Imported statements and matches

Each table has an Eloquent model in OtimOtim\BookKeeper\Models (Account, JournalEntry, JournalLine, FiscalYear, Period, Document, Payment, ...). Use them freely for reads. For writes, go through the services so the rules are enforced.


Exceptions

Every exception extends OtimOtim\BookKeeper\Exceptions\BookKeeperException.

Exception Thrown when
UnbalancedEntryException Debits do not equal credits
InvalidEntryException A line is invalid: group or inactive account, missing subledger, wrong currency, negative amount
AccountNotFoundException An account code, ID or role does not exist
PeriodClosedException Posting into a closed (or missing) period
ImmutableRecordException Editing or deleting a posted entry or line
InvalidAmountException An amount has too many decimals or mixes currencies
InvalidCurrencyException An unknown currency code (extends InvalidAmountException)
InvalidOperationException A business rule is broken (paying the wrong document, closing twice, voiding a paid invoice, ...)
TemplateNotFoundException A posting template is not defined

Multi-tenant applications

BookKeeper's tables carry no tenant column, so each tenant needs its own database or schema. This is the stancl/tenancy model.

The services are stateless, so they work across tenant switches in queue workers and Octane.


Using BookKeeper in Suluhu

Suluhu, the SACCO platform, keeps each SACCO's books in its own PostgreSQL schema. Set it up as follows:

  1. Install. Add the package from its VCS repository, publish the tenant migrations, and set BOOKKEEPER_RUN_MIGRATIONS=false and BOOKKEEPER_CURRENCY=UGX.
  2. Seed the chart. In the tenant seeder, call BookKeeper::chart()->import('sacco-ug') and BookKeeper::periods()->createFiscalYear(now()->year). The SACCO financial year runs January to December, the default.
  3. Fix the accounting service. Rebuild AccountingService on posting templates. Loan approval is not a ledger event, so drop it. Disbursement, repayment, fees, penalties, interest accrual, provisions and write-offs each become a template in config/bookkeeper.php.
  4. Post through templates. Savings, shares and loans call BookKeeper::post() inside their own DB transaction, passing the member's account or loan as the subledger party and the request's idempotency key. Member balances then become BookKeeper::subledgers()->balance(), not stored counters.
  5. Tellers. Use BookKeeper::tills() for teller sessions, and call assertOpen() before cash postings.
  6. Close tasks. Daily interest accrual, arrears ageing and penalties are CloseTasks registered under close.day. Month-end provisioning goes under close.month.
  7. Reconciliation. BookKeeper::subledgers()->reconcile('@member_savings') (and the same for loans and shares) is the subledger-versus-control check.
  8. Year end. At year end, run closeYear. After the AGM, run appropriation()->appropriate(), then dividends()->calculate() and pay().
  9. UMRA ratios. Build these from balanceByTag('liquid'), balanceByTag('short_term'), balanceByTag('core_capital') and balanceSheet()['totals'].

Testing your application

BookKeeper's tables are created by its migrations, so RefreshDatabase works without extra setup. A typical test seeds a chart and asserts on balances:

Freeze the clock (Carbon::setTestNow()) when you assert on dates or periods.


Architecture

BookKeeper is organised around SOLID principles so it can be extended without being edited:


Development with Docker

The image is PHP 8.5 CLI with Composer, PCOV and the SQLite, MySQL and PostgreSQL drivers.

Without Docker, run composer install and then vendor/bin/pest.


Upgrading from 1.x

Version 2 has a new schema and returns Money instead of floats. See UPGRADE.md.


License

MIT


All versions of bookkeeper with dependencies

PHP Build Version
Package Version
Requires php Version ^8.4
brick/math Version ^0.14.2|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20|^1.0
illuminate/console Version ^12.0|^13.0
illuminate/database Version ^12.0|^13.0
illuminate/events Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package otim-otim/bookkeeper contains the following files

Loading the files please wait ...