wexample / symfony-accounting
Accounting for Symfony: ledgers, double-entry bookkeeping, invoices, bank reconciliation, VAT, closing and reports, with country rules in jurisdiction packages
Requires
- php: >=8.5
- league/csv: ^9.5
- wexample/php-date: >=2.1.0
- wexample/symfony-check: >=3.0.0
- wexample/symfony-geo: >=5.0.0
- wexample/symfony-helpers: >=15.0.0
- wexample/symfony-money: >=5.0.0
- wexample/symfony-payment: >=3.0.0
- wexample/symfony-remote-payment: >=2.0.0
Requires (Dev)
- phpunit/phpunit: ^13
- wexample/symfony-testing: >=2.0.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 5.0.2
- 5.0.1
- 5.0.0
- 4.0.1
- 4.0.0
- 3.0.6
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.0.0
- 1.0.96
- 1.0.95
- 1.0.94
- 1.0.93
- 1.0.92
- 1.0.91
- 1.0.90
- 1.0.89
- 1.0.88
- 1.0.87
- 1.0.85
- 1.0.84
- 1.0.83
- 1.0.82
- 1.0.81
- 1.0.80
- 1.0.79
- 1.0.78
- 1.0.77
- 1.0.76
- 1.0.75
- 1.0.74
- 1.0.73
- 1.0.72
- 1.0.71
- 1.0.70
- 1.0.69
- 1.0.68
- 1.0.67
- 1.0.66
- 1.0.65
- 1.0.64
- 1.0.63
- 1.0.62
- 1.0.61
- 1.0.60
- 1.0.59
- 1.0.58
- 1.0.57
- 1.0.56
- 1.0.55
- 1.0.54
- 1.0.50
- 1.0.49
- 1.0.48
- 1.0.47
- 1.0.46
- 1.0.45
- 1.0.44
- 1.0.43
- 1.0.42
- 1.0.41
- 1.0.40
- 1.0.39
- 1.0.38
- 1.0.37
- 1.0.36
- 1.0.35
- 1.0.34
- 1.0.33
- 1.0.32
- 1.0.31
- 1.0.30
- 1.0.29
- 1.0.28
- 1.0.27
- 1.0.26
- 1.0.25
- 1.0.24
- 1.0.23
- 1.0.22
- 1.0.21
- 1.0.20
- 1.0.19
- 1.0.18
- 1.0.17
- 1.0.16
- 1.0.15
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- 0.1.48
- 0.1.47
- 0.1.46
- 0.1.45
- 0.1.44
- 0.1.43
- 0.1.42
- 0.1.41
- 0.1.40
- 0.1.39
- 0.1.38
- 0.1.37
- 0.1.36
- 0.1.35
- 0.1.34
- 0.1.33
- 0.1.32
- 0.1.31
- 0.1.30
- 0.1.28
- 0.1.27
- 0.1.26
- 0.1.25
- 0.1.24
- 0.1.23
- 0.1.21
- 0.1.20
- 0.1.19
- 0.1.18
- 0.1.17
- 0.1.16
- 0.1.15
- 0.1.14
- 0.1.13
- 0.1.12
- 0.1.11
- 0.1.10
- 0.1.9
- 0.1.8
- 0.1.7
- 0.1.6
- 0.1.5
- 0.1.3
- 0.1.2
- 0.1.1
This package is auto-updated.
Last update: 2026-10-08 10:58:36 UTC
README
Version: 5.0.2
A ledger
$ledger = $ledgerService->create('Acme SRL', 'BE', fiscalYearStart: new DateTimeImmutable('2026-01-01')); $ledger->setLegalIdentifier('0123.456.749')->setVatNumber('BE0123456749')->setInvoicePrefix('ACM'); $customer = $partyService->create($ledger, 'Client SA'); // gets its auxiliary code, CCLIENTSA
create() loads the jurisdiction's chart and journals. Accounts missing from the chart are created on first use, labelled after their closest parent. The ledger's settings (setSettings()) tune the rest: default_vat_rate, invoice_number_pattern, hours_per_day, late_penalty_rate, auto_post_invoices… — each documented where it is read.
Documents
$invoice = $invoiceFactory->create($ledger, party: $customer); $invoiceFactory->addItem($invoice, 'Development', 50000, '2j'); // 2 days at 500.00 $emissionService->emit($invoice); // numbered, frozen, booked
Emission is the only way a document becomes official: VAT resolved per item (domestic, intra-EU, export, reverse charge, franchise), gap-free number for sales, issuer and party identities (name, identifiers, bank) frozen into snapshots — addresses are read from the entities, their country being a relation, InvoiceEmittedEvent, then the entry in the ledger. Quotations convert to bills (createBillFromQuotation, deducting deposit bills), bills get credit notes and penalties (LatePenaltyCalculator), models renew monthly.
UblInvoiceBuilder writes the structured electronic invoice (UBL 2.1, Peppol BIS Billing 3.0 / EN 16931): mandatory for B2B invoices in Belgium since 2026, one of the formats of the French reform. UblInvoiceReader does the reverse with received ones: a purchase draft, supplier found by VAT number or created with its IBAN, checked against the supplier's stated total. Sending and receiving through a Peppol access point is left to a remote package. Validate the output on the Peppol testbed before going live.
InvoiceDocumentDataBuilder gives everything a printed document shows; PdfFactoryDocumentBuilder turns it into a pdf-factory document, and PdfFactoryRenderer posts it.
Bank
$bankImportService->importContent($bankAccount, $fileContent); // CAMT, OFX, CODA, LBP…: detected $matchingService->run($ledger); // proposals and sure matches $allocationService->allocate($line, $invoice); // or allocateToAccount(), linkTransfer() $letteringService->letterLedger($ledger);
Imports never duplicate a line. Matchers propose (pending) or settle (validated) by provider payment, payment reference, label rules, transfers between the ledger's accounts and exact amounts; refusing a proposal excludes the pair for good. Validated allocations are booked at once, one entry per payment.
A bank account whose provider is set (stripe) is fed by ProviderBalanceImporter through symfony-remote-payment: gross payments, fees (booked on the bank fees account), refunds, payouts (matched as transfers).
Period end
$vatReturn = $vatReturnService->compute($ledger, $from, $to); $vatReturnService->fillForms($vatReturn); // CA3, CA12, BE periodic… from the jurisdiction packages $vatReturnService->settle($vatReturn); $closingService->close($fiscalYear); // checks, result, opening entries of the next year, lock
Before closing, DepreciationService books the year's depreciation of the fixed assets (straight-line, pro rata temporis by day; accounting:depreciation), AccrualService posts adjustments that reverse themselves on the next year's first day, and the checks warn about anything missing.
Reports are computed on demand: TrialBalanceService, GeneralLedgerService, FinancialStatementService (balance sheet, income statement from the jurisdiction's layouts), AgedBalanceService. LedgerExportService writes the books (FEC, CSV), EntryImportService takes another bookkeeping over.
Collections and supplier payments
DunningService finds the reminders due by level (dunning_levels, days late; accounting:dunning --record) and keeps their history on the document; the host sends them on InvoiceReminderRecordedEvent. SepaCreditTransferBuilder writes the pain.001 file paying a batch of purchases, structured references included.
Checks
InvoiceChecker, BankTransactionChecker and FiscalYearChecker report through symfony-check. Errors of the fiscal year block its closing.
Table of Contents
- A ledger
- Documents
- Bank
- Period end
- Collections and supplier payments
- Checks
- Architecture
- Integration in the Suite
- Dependencies
- Versioning & Compatibility Policy
- License
- About us
- Migration Notes
Architecture
Ledger
src/Entity/Ledger.php is a set of books; every other entity belongs to one. src/Entity/JournalEntry.php and src/Entity/EntryLine.php are the double entry: amounts are int minor units in debit/credit columns, a line may carry a party (its auxiliary account), a letter, and a VAT code with its role (base or tax).
src/Service/Ledger/PostingService.php is the only way into the books: it checks balance and fiscal year, numbers entries without gaps per fiscal year (fiscal year row locked), and reverses rather than deletes. Callers describe entries with src/Class/EntryDraft.php, naming accounts by number or by src/Enum/AccountRole.php; src/Service/Ledger/ChartService.php resolves roles through the ledger's overrides, then its jurisdiction.
Entries know their source (sourceType, sourceId): an invoice, an allocation, a transfer, an opening. That is what makes booking idempotent and what lettering follows.
Jurisdictions
src/Interface/JurisdictionInterface.php holds everything a country decides. src/Service/Jurisdiction/AbstractJurisdiction.php implements the EU VAT rules and generates VAT codes (S_DOM_2100, P_RC_2000, S_IEUS_0…); src/Service/Jurisdiction/DefaultJurisdiction.php serves countries without a package. Jurisdiction packages ship data (charts as CSV, statement layouts as PHP arrays) and the mentions' texts as translations.
Invoicing
src/Entity/Invoice.php uses the symfony-money priced traits: a parent summing its items, VAT per item, discount spread over the VAT bases. Status changes go through src/Service/Invoice/InvoiceWorkflow.php; emission through src/Service/Invoice/InvoiceEmissionService.php. src/Service/Invoice/InvoiceAccountingService.php turns a document into an entry: bases split per account and VAT code so that they sum exactly to each rate's base, self-assessed VAT booked twice, VAT on payments parked on pending accounts.
Bank
Parsers (src/Interface/BankStatementParserInterface.php) are pure: content in, src/Class/ParsedStatement.php out. src/Service/Bank/BankImportService.php deduplicates by external id, else by fingerprint counted per occurrence. src/Entity/Allocation.php links a line to a document or an account; src/EventSubscriber/BankAccountingSubscriber.php books it and moves the document's payment status. Matchers (src/Interface/TransactionMatcherInterface.php) only propose; src/Service/Bank/MatchingService.php applies proposals above a threshold and never one that was excluded.
src/Service/Ledger/LetteringService.php letters party accounts by connected components: lines of a document, its write-off and its allocations are connected, and payments covering several documents connect them; a balanced component gets a letter.
Period end and reports
src/Service/Vat/VatReturnService.php sums tagged lines per VAT code — tax only where it is due or deductible, so VAT on debits and on payments need no separate code — and national forms (src/Interface/VatReturnFormInterface.php) place the totals in their boxes. src/Service/Ledger/FiscalYearClosingService.php computes the result on net balances and posts the next year's opening entries, party by party. src/Service/Report/FinancialStatementService.php evaluates the jurisdiction's layouts: an account goes to the first line whose rules take it.
What is not here yet
- An HTTP API: its shape depends on the screens of
symfony-accounting-ds, and access to ledgers (firm staff, clients) is a host decision to make first. - Documents in another currency than their ledger's: refused at emission.
- Partly deductible VAT (cars…), domestic reverse charge (construction), OSS distance sales.
Integration in the Suite
This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.
Related Packages
The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.
Visit the Wexample Suite documentation for the complete package ecosystem.
Dependencies
- php: >=8.5
- league/csv: ^9.5
- wexample/php-date: >=2.1.0
- wexample/symfony-helpers: >=15.0.0
- wexample/symfony-money: >=5.0.0
- wexample/symfony-geo: >=5.0.0
- wexample/symfony-check: >=3.0.0
- wexample/symfony-payment: >=3.0.0
- wexample/symfony-remote-payment: >=2.0.0
Versioning & Compatibility Policy
Wexample packages follow Semantic Versioning (SemVer):
- MAJOR: Breaking changes
- MINOR: New features, backward compatible
- PATCH: Bug fixes, backward compatible
We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Free to use in both personal and commercial projects.
About us
Wexample stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.
This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.
Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.
Migration Notes
When upgrading between major versions, refer to the migration guides in the documentation.
Breaking changes are clearly documented with upgrade paths and examples.