ekmungai/eloquent-ifrs
Eloquent IFRS adds double-entry accounting to Laravel, generating IFRS-compliant financial statements. Supports multiple entities, account categorization, VAT, opening balances, forex revaluation, tamper-protected ledgers, and configurable receivables aging.
Architecture fit
ekmungai/eloquent-ifrs is not a reporting library bolted onto existing models — it is a full double-entry general ledger implemented as Eloquent models inside your application's database. Adopting it means accepting its schema, its write path, and its domain model as the accounting source of truth for the entities it covers. That is an architectural commitment, not a dependency.
The domain model is credibly designed for the stated scope:
create() does not hit the ledger; addLineItem() and post() do). This is the correct pattern for a ledger and gives you a natural place to hook validation, authorisation, and audit events.Recycling trait and destroyed_at columns indicating soft-delete-by-design rather than hard deletes. Good for auditability; see failure modes below.new IncomeStatement($from, $to)->getSections()), with toString() explicitly documented as debug-only. There is no JSON/API contract — you own the entire presentation and export layer, including any XBRL/iXBRL work.Entity, entity_id), but the ifrs.user_model configuration and the entity_id column added to your users table imply a user belongs to one entity default. True multi-tenant isolation (one user, many entities, per-entity roles) will require work on your side.Integration feasibility
Feasible, but the install path has two sharp edges that are easy to underestimate in estimation:
users table. It adds entity_id and destroyed_at, requires your users migration to be dated before 2014_10_12_000000, and requires your User model to implement Recyclable and use IFRSUser and Recycling traits. The README's own "DB Collision" section acknowledges this. If your users table has a non-standard PK, a UUID, or a custom User namespace, budget real time here.Everything else — composer require, vendor:publish, php artisan migrate, seeding a chart of accounts — is routine. The README's worked example (cash sale, client invoice, cash purchase, supplier bill, client receipt, then Assignment to clear the invoice) is enough to get a spike running in under a day.
Technical risk
| Risk | Severity | Notes |
|---|---|---|
| Single-maintainer package, 0 dependents | High | 367 stars but no downstream packages on Packagist means no ecosystem validation. You are likely the largest consumer. |
| Tight coupling to Laravel majors | High | Laravel 11/12/13 support is a moving target; every application major upgrade becomes a ledger upgrade. |
| Docs contain errors | Medium | The CashPurchase example is booked against $clientAccount rather than the bank account, and the illustrated Balance Sheet figures do not fully reconcile against the transaction values in the same example (bank shown as 50 against an implied 60). Treat the README as illustrative, not as specification. |
| Reporting scope ≠ IFRS compliance | Medium | This is a general ledger with an IFRS-shaped chart of accounts. It does not implement IFRS 15 revenue recognition, IFRS 16 leases, IFRS 9 ECL provisioning, or IFRS 13 fair value. "IFRS compliant reporting" is marketing-adjacent. |
| Tax engine depth | Medium | A single Vat model with rate and account_id plus one input/output code. Multi-jurisdiction VAT, OSS, reverse charge, and US sales tax are out of scope. |
| CI/CD signalling | Low | Travis CI badges and CodeClimate badges indicate older infrastructure conventions; verify the test suite actually runs green on your target PHP/Laravel matrix before committing. |
Key questions to answer before committing
post() run inside a single database transaction with row-level locking? What is the state of a Transaction and its LineItems if post() throws midway?UPDATE? An auditor will ask both "can it be changed" and "can it be changed legitimately, e.g. for a correction entry."Recycling soft-delete? Which one does the package's ledger reconciliation assume?Entity? Is there a role/authorisation story at all, or is access control entirely on the host application?post()-time validation surface — unbalanced entries, closed periods, FX rates missing, VAT account missing?Stack fit
Good fit if — and only if — the host application is already on Laravel 11 or 12 with PHP 8.2+, uses standard Eloquent, and has a conventional users table with an id (or any single-column PK) and a migration dated before the Laravel default. MySQL and PostgreSQL are the realistic targets; verify on your specific engine before assuming, since the package's tests will not cover your configuration.
Poor fit if the application is on Laravel 9/10, uses a non-Eloquent persistence layer for users, is a polyglot stack where accounting is a separate service, or needs real-time high-volume ledger writes. In the last case, a dedicated accounting engine (or a hosted ledger API) is the better architectural choice, and this package becomes a reporting shim.
Migration path
Do not treat this as a greenfield-only decision. The workable path for an existing application:
entity_id mutation of the users table is the only hard cross-boundary coupling; keeping ledger tables on a separate schema limits blast radius on the user table and simplifies the eventual fork-and-extend.Entity, Currency, Vat, and the full chart of accounts from an accountant-reviewed mapping, then load historical balances through ReportingPeriod start-of-year opening balances rather than replaying years of transactions. Replaying history through the posting path is seductive and almost always wrong.ClientInvoice in this package is a receivable movement with VAT, not your Order or Invoice record. Expect a mapping table and a one-way sync (your domain → ledger), never bidirectional.Compatibility
Pin the exact version in composer.json with a conservative constraint, and add a CI job that runs your ledger acceptance tests against the next Laravel major in a scratch branch. Because the package's own support matrix moves with new Laravel majors, your upgrade planning must include "ledger package ready" as a gate item, not an afterthought.
Sequencing
Assignment. Goal: confirm post() semantics, soft-delete behaviour, and whether you can reconcile the resulting Balance Sheet by hand.IncomeStatement and BalanceSheet, and reconcile against the incumbent system monthly.Maintenance
Real and ongoing. You inherit:
Support
Community-only. The package is a microbooks.io community initiative with no commercial SLA, no paid support tier, and no visible governance. Effective triage policy: your team is tier 1 and tier 2; upstream is tier 3 with best-effort latency measured in weeks. Build the fork discipline accordingly, and consider retaining a fractional accountant as an on-call domain reviewer — the failure modes here are accounting failures, not stack traces.
Scaling
The scaling profile is unusual: write volume is low (a company posts tens to low thousands of transactions per month), but read cost for reports grows with total ledger history. getSections() aggregates in PHP, which means report memory and latency scale with the number of entries in the period, and cross-entity consolidation multiplies that. Practical mitigations:
post().getSections() live.Concurrency is the other scaling concern: two simultaneous post() calls against the same account need row-level locking or a serialisation strategy. Confirm the package provides it; if not, serialise postings at the application level (queue with a per-entity lock).
Failure modes
| Failure | Impact | Detection |
|---|---|---|
post() partially succeeds |
Unbalanced ledger; silently wrong reports | Reconciliation assertion after every post; a nightly trial-balance check (debits == credits) as a hard alert |
| Tamper protection blocks a legitimate correction | Stuck data, manual DB surgery required | Define and test the correction workflow (reversal entries) before go-live |
Soft-delete / Recycling leaves orphaned references |
Balances disagree with expectations | Period-close reconciliation against bank and control accounts |
Opening balances drift after ReportingPeriod changes |
Prior-year comparatives break | Freeze closed periods; alert on any write touching a closed period |
| VAT account misconfiguration (rate vs. control account) | Understated/overstated liabilities | Accountant review of the VAT config as a gate item |
| Laravel major upgrade breaks the package | Reporting outage during upgrade window | CI matrix against next Laravel major; freeze reports during upgrade |
| Direct SQL edits bypass model guards | Auditor finding | DB-level permissions: no human write access to ledger tables outside the app |
Ramp-up
Steeper than the install instructions suggest. The install is an afternoon; operational competence is a quarter. Three distinct skill sets are needed:
post(), Assignment, control accounts, and clearing is not obvious to engineers without accounting exposure. Budget a half-day of internal training walking the README's five transactions through to a hand-checked Balance Sheet.A pragmatic first-quarter plan: one engineer at ~50% for the spike and integration, an accountant for two to three days of design and one day per period of reconciliation, and a TPM-owned gate that no write-path cutover happens until two consecutive periods reconcile cleanly in shadow mode. Given the package's substance but thin ecosystem validation (score 31.7, opportunity 18.5, zero dependents), the correct posture is adopt with a fork plan and a one-period shadow run — not adopt-and-hope, and not build-from-scratch, which would cost multiples of the integration effort for a domain that is already reasonably modelled here.
How can I help you explore Laravel packages today?