Read from the package's own PHP files at the commit af442f79ca5e: its namespaces, classes, interfaces and enums, their public constants, properties and methods with their PHPDoc, and the options its factories take. Nothing of it is run.
LedgerBundleclassDouble-entry accounting on top of omnibase: books (one per legal entity), a French chart of accounts, journals, parties and their lettering, bank feeds through glitchr/omnibank (statement files or bank APIs), the reconciliation of bank lines against open items with ranked suggestions, and the reports a French accountant expects - trial balance, general ledger, open items and the FEC.
3 methods
BankWidgetTypeclassThe banks at a glance, on the back office's dashboard: each account with its balance in the books (its 512) and at the bank, when it last synced, its consent ("Renouveler" when the bank asks), what waits to be reconciled; then the latest lines across accounts. Without any account yet: the buttons to connect a bank (the gateways configured in Omnibank) or to import a statement.
4 methods
BooksWidgetTypeclassEach book's current fiscal year at a glance: income (class 7) and expenses (class 6) to date and the result they make, what tenants still owe (open debits on 411), what is still to pay (open credits on 401), and the cash in the bank accounts (512). Each tile opens its report - the compte de résultat for the first three - and the bilan to date is a link away.
5 methods
ConnectionsclassA book's bank connections and Omnibank: the gateway behind each, its Connection read from and written back to the sealed state, the bank's pages to go through to give consent (start(), complete()), and the bank accounts it gives access to, each with its ledger account (5121, 5122...) and bank journal (BQ, BQ2...).
12 methods
ImporterclassOmnibank's transactions into a bank account's lines, each once: a transaction already imported (same id on the same account) is updated while it waits for reconciliation - banks correct pending lines - and left alone once reconciled or ignored.
2 methods
StatementUploadclassA statement file (CAMT.053, OFX, CSV) typed into a bank account through Omnibank's "files" gateway: its lines imported (each once, however many overlapping statements are uploaded) and the statement's closing balance kept. The file itself is not stored.
2 methods · 1 constant
SyncclassBrings a bank account's new lines in through its connection's gateway: since the last sync, minus a few days (banks book late; lines already there are matched on their id), with the bank's balance when the gateway gives one. A consent the bank wants renewed is recorded on the connection - the back office then shows "Renouveler" - and the sync stops there.
3 methods
BankSyncCommandclassBrings the bank accounts' new lines in (all books, or one) and reconciles what is sure. Accounts fed by statement files are skipped: they are uploaded. A connection whose consent needs renewing is reported and skipped. Suggested cron: a few times a day.
1 method
ChartSeedCommandclassGives a book the accounts and journals of a chart (config/charts/), keeping what it has.
1 method
FecExportCommandclassWrites a fiscal year's FEC, <SIREN>FEC<closing date>.txt, into a directory (the current one by default).
1 method
WebhookControllerclassWhere a bank aggregator tells us something changed (Powens, Bridge): public, checked by the gateway against its signature (notify()). The connection it is about gets its consent updated, then its accounts synced and reconciled.
2 methods
AbstractLedgerPageControllerclassThe ledger's back-office pages that are not a CRUD (reconciliation, statement upload, bank connection, reports): rendered in the admin's layout with its menus, as forge's refund page is.
1 method
BankControllerclassBanks in the back office: connecting one (the bank's or aggregator's consent page, then back here), renewing a consent, syncing an account, and uploading a statement file - after which what is sure is reconciled.
6 methods
LedgerAdminTraittraitThe ledger screens' permissions (LedgerVoter), for every ledger CRUD: reading takes LEDGER_VIEW, changing LEDGER_MANAGE - ledger.admin_role (ROLE_ADMIN) by default, rather than the admin's blanket ROLE_SUPERADMIN.
2 methods
ReconcileControllerclassA bank account's reconciliation: the lines still to place with what they may settle, best first - accept a suggestion, pick the open items (or a rule, or a party) by hand, ignore the line -, and the lines placed lately, which can be undone. Every change is a POST with its token.
6 methods
ReportControllerclassA book's reports - trial balance, general ledger, open items, income statement and balance sheet (on screen, printed or as CSV), the FEC to download - and its buttons: seeding the chart, validating or reversing an entry.
10 methods
Base\Ledger\Controller\Admin\Crud
AccountCrudControllerclassThe chart of accounts, by number. An account with lines is not deleted.
7 methods
BankAccountCrudControllerclassThe bank accounts, each with its ledger account (512x) and bank journal: reconcile its lines, upload a statement, sync it from the bank.
5 methods
BankConnectionCrudControllerclassThe accesses to banks: opened from the "connect" page (the bank's own consent page), renewed when the bank asks. Their state - tokens - is sealed and never shown.
6 methods
BankLineCrudControllerclassThe bank statements' lines, as imported, filtered by where they stand; they are reconciled from the reconciliation page.
6 methods · 1 constant
BookCrudControllerclassThe books - one per legal entity - with their reports: trial balance, open items, the FEC, and seeding the chart.
7 methods
EntryCrudControllerclassThe entries, with their lines. They are posted by the application and the reconciliation (Posting), not typed here: a draft's date, label and piece can be corrected, then validated; a validated entry is read-only - it is reversed, never edited.
7 methods
FiscalYearCrudControllerclassA book's fiscal years: entry numbers run per year; a closed year takes no entry. Without any, calendar years.
5 methods
JournalCrudControllerclassThe journals: BQ (bank), VT (rent notices), AC (purchases), OD, AN (opening balances).
6 methods
PartyCrudControllerclassTenants, suppliers, associates: their auxiliary account (411U3), the IBAN their transfers come from, the application's reference. Usually created by the application (Parties::partyFor()); one typed here gets its auxiliary account when saved.
7 methods
ReconciliationRuleCrudControllerclassWhat recurring bank lines are posted against when nothing open matches: bank fees to 627, the loan instalment split between 164 and 6611, Stripe payouts to 5112. The highest priority matching rule wins.
5 methods
Base\Ledger\DependencyInjection
LedgerConfigurationclass1 method
LedgerExtensionclass2 methods
AccountclassAn account of a book's chart, by its PCG number: 512 (bank), 706 (rents), or an auxiliary account under a collective one (411U3 for a tenant, under 411).
14 methods
BankAccountclassOne account at the bank, as its connection's gateway sees it ($externalId), tied to the book's ledger account for it (a 512) and the journal its movements are posted in (BQ).
22 methods
BankConnectionclassA book's access to a bank through one of Omnibank's gateways ("files", "qonto", "powens", "bridge"): the gateway's Connection state (tokens, the provider's user) sealed with sodium's secretbox (Bank\Connections reads and writes it, never in clear), the consent the bank gave and until when, and the last error a sync met.
19 methods
BankLineclassA transaction on a bank account's statement, as the bank booked it - negative when money leaves. Imported once (its $externalId is unique per bank account), then reconciled: posted in the bank journal ($entry) and lettered with what it pays, or ignored.
31 methods
BookclassOne legal entity's accounts (a company, an SCI): its chart, journals, parties, entries and bank accounts all hang off it. Entries dated on or before $lockedUntil can no longer be posted, changed or reversed - the period is closed, declared, or handed to the accountant.
14 methods
EntryclassA journal entry: balanced lines (debits = credits) on one date, in one journal. $source says where it comes from ("bank:42", "notice:2026-10-U3") and is unique per book, so posting the same thing twice gives the same entry back. Validated, it gets its number (sequential per book and fiscal year) and becomes immutable: a mistake is undone by a reversal entry ($reversalOf), never by an edit.
21 methods
EntryLineclassOne line of an entry: an amount on the debit or the credit side of an account, maybe for a party. Lettering ties lines of one account and party whose debits and credits cancel out (a rent notice and its payment): they share a $letter and are no longer open items. Only the lettering fields change once the entry is validated.
15 methods
FiscalYearclassAn accounting period of a book, usually twelve months. Entry numbers run per fiscal year; once closed, nothing is posted in it any more. A book without fiscal years works on calendar years.
15 methods
JournalclassA book's journal: BQ (bank), VT (sales: rent notices), AC (purchases), OD (miscellaneous), AN (opening balances).
9 methods · 5 constants
PartyclassSomeone the book owes or is owed by - a tenant, a supplier, an associate - with their own auxiliary account (411U3 under 411). $reference is the application's handle on them (a lease, a supplier's code), unique per book; $iban is how their transfers are recognised on the bank statement.
17 methods
ReconciliationRuleclassWhat to post a recurring bank line against when nothing open matches it: bank fees to 627, the loan instalment split between 164 (capital) and 6611 (interest), a Stripe payout to the 5112 clearing account. A rule matches on the label ($pattern: words that must all appear, or a /regular expression/), the counterparty's IBAN, and the direction; the highest priority wins.
24 methods
AccountTypeenumWhat an account is in the balance sheet or the income statement - which side its balance normally sits on (debit for assets and expenses, credit for the others) and whether it is carried forward at year end.
3 methods · 5 cases
BankLineStatusenumWhere a bank line stands in the reconciliation.
1 method · 4 cases
ConsentStateenumA bank connection's consent as the ledger keeps it: Omnibank's ConsentStatus, stored by value so the entity does not depend on the package being installed.
1 method · 4 cases
PartyKindenumWho a party is to the book: the collective account its own account sits under.
1 method · 4 cases
RuleSignenumWhich bank lines a reconciliation rule looks at: money coming in, going out, or both.
1 method · 3 cases
BankLineReconciledclassA bank line posted and lettered with what it settles (Reconciler::apply): what the application owes or is owed can follow - a rent call paid, an invoice settled - from the open items it cleared, without posting again.
1 method · 3 properties
ValidatedEntryGuardclassA validated entry is part of the books for good: whatever tries to change it, or one of its lines, or to delete either, or to add a line to it, is refused at flush time - the back office, a fixture, a careless service.
1 method
BankExceptionclassA bank access that failed: Omnibank missing, a gateway or an account not found, the provider refusing.
LedgerExceptionclassWhat the ledger refused to do; nothing was written.
LetteringExceptionclassLines that cannot be lettered together: they do not cancel out, or not on one account and party.
LockedEntryExceptionclassA change to a validated entry or its lines (other than their lettering): reverse it instead.
LockedPeriodExceptionclassA date on or before the book's lockedUntil, or in a closed fiscal year.
PostingExceptionclassAn entry Posting refuses: unbalanced, a negative or two-sided line, an unknown account or journal.
ReconciliationExceptionclassA bank line that cannot be reconciled (or undone) that way.
FecWriterclassThe Fichier des Écritures Comptables (article A47 A-1 du LPF) of a fiscal year: one row per line of every validated entry, the 18 columns in their order, tab-separated, CRLF, dates as YYYYMMDD, amounts with a decimal comma and no thousands separator. A line on a party's auxiliary account (411U3) is filed under its general account (411) with the auxiliary account and the party's name in CompAuxNum and CompAuxLib.
6 methods · 1 constant
EntryDraftclassAn entry to post, as the application describes it - account numbers and a journal code rather than entities:
5 methods · 6 properties
TextclassHow bank labels, names and references are compared: upper-case, without accents nor punctuation.
2 methods
MatcherclassWhat a bank line may settle, ranked. In order of trust:
3 methods · 17 constants
ReconcilerclassTurns a bank line into the books: apply() posts it in its bank account's journal - the bank account (512x) against what the suggestion says: the open items' accounts, the rule's account(s), or the party's - and letters it with the open items it settles. Its entry's source is "bank:<line id>", so applying it twice posts it once.
7 methods
SuggestionclassWhat a bank line may be (Matcher), or what someone in the back office says it is: the open items it pays - lettered with it when they add up to its amount -, else the rule it falls under, else the party it comes from (a payment on account). $score runs from 0 to 1; Reconciler applies a suggestion on its own only when it is the single one at or above ledger.reconciliation.auto_threshold.
5 methods · 5 properties
BalanceSheetclassThe bilan at a date: every account's balance from the book's first entry to that day, the assets (actif) on one side, what finances them (passif) on the other - and, when asked, the same day a year earlier beside it.
4 methods · 4 constants
GeneralLedgerclassThe grand livre: every line of every account over a period, with the running balance and the account's totals.
3 methods
IncomeStatementclassThe compte de résultat over a period: income (class 7) and expenses (class 6) in the headings an SCI's accounts are read under, their totals and the result (bénéfice when positive, perte when negative) - and, when asked, the same period a year earlier beside it (N-1).
4 methods · 2 constants
OpenItemsclassWhat is still owed, party by party: the unlettered lines of the accounts under a prefix (411 - tenants - by default; 401 for suppliers), their balance and how old the oldest is.
3 methods
TrialBalanceclassThe balance générale: per account, the debit and credit totals over a period and the balance they leave, with totals per class and overall (debits = credits, or the books are wrong).
4 methods
AccountRepositoryclass4 methods
BankAccountRepositoryclass3 methods
BankConnectionRepositoryclass2 methods
BankLineRepositoryclass5 methods
BookRepositoryclass2 methods
EntryLineRepositoryclass8 methods
EntryRepositoryclass5 methods
FiscalYearRepositoryclass4 methods
JournalRepositoryclass2 methods
PartyRepositoryclass4 methods
ReconciliationRuleRepositoryclass2 methods
BookAccessInterfaceinterfaceWho else may reach a book than ledger.admin_role: an application's service implementing this (autoconfigured, tag ledger.book_access) is asked by LedgerVoter - an associate viewing their company's book, an accountant managing their clients'. Any service saying yes is enough.
2 methods
StateCipherclassSeals what a bank connection keeps (the provider's tokens, its user) with sodium's secretbox (XSalsa20-Poly1305), under a key derived from `ledger.secret` (the kernel secret by default): "v1:" + base64(nonce + ciphertext). A sealed state that was tampered with, or sealed under another secret, does not open.
3 methods
LedgerVoterclassLEDGER_VIEW and LEDGER_MANAGE, on a Book or on anything of one (an entry, an account, a bank line: whatever has getBook()):
2 methods · 2 constants
ChartclassGives a book its chart of accounts and journals from a file of config/charts/ (sci: the PCG accounts a French real-estate SCI uses). It adds what is missing and leaves alone what the book already has, so it can run again after the chart grows.
3 methods
LetteringclassLettering: lines of one account and one party whose debits and credits cancel out - a rent notice and the transfer paying it - share a letter (AAA, AAB...) and stop being open items. Partial payments stay open until the rest arrives.
6 methods
PartiesclassThe book's parties as the application knows them: partyFor() finds one by the application's reference (a lease, a supplier code) or creates it, with its auxiliary account under its kind's collective account - 411 + the reference for a tenant (411U3), 401 for a supplier, 455 for an associate, 467 otherwise.
3 methods
PostingclassThe one way entries get into a book. post() checks what double-entry bookkeeping requires - balanced, one-sided non-negative lines, accounts and a journal the book has, a date outside the locked period - and refuses anything else with a PostingException, before writing a thing.
6 methods
LedgerExtensionclass{{ 123456|ledger_money }} → 1 234,56 € (minor units, the locale's way); {{ 123456|ledger_amount }} → 1 234,56 (no currency: a column of a report); {{ iban|ledger_iban }} → FR76 •••• 4321.
3 methods
Base\Ledger
LedgerBundle class
class LedgerBundle extends AbstractBaseBundle LedgerBundle.php:17
Double-entry accounting on top of omnibase: books (one per legal entity), a French chart of accounts, journals, parties and their lettering, bank feeds through glitchr/omnibank (statement files or bank APIs), the reconciliation of bank lines against open items with ranked suggestions, and the reports a French accountant expects - trial balance, general ledger, open items and the FEC.
LedgerBundle::__construct()
public function __construct()LedgerBundle::getPath()
public function getPath(): stringModern bundle layout: the class lives in src/, the bundle root is the package root - so TwigBundle picks up ./templates as @Ledger and Doctrine's auto_mapping ./src/Entity.
LedgerBundle::build()
public function build(ContainerBuilder $container): voidBase\Ledger\Admin\Widget
BankWidgetType class
final class BankWidgetType implements DashboardWidgetTypeInterface BankWidgetType.php:28
The banks at a glance, on the back office's dashboard: each account with its balance in the books (its 512) and at the bank, when it last synced, its consent ("Renouveler" when the bank asks), what waits to be reconciled; then the latest lines across accounts. Without any account yet: the buttons to connect a bank (the gateways configured in Omnibank) or to import a statement.
MenuItem::block('ledger_bank', 'Banque', 'fa-solid fa-building-columns')
BankWidgetType::__construct()
public function __construct(readonly BankAccountRepository $bankAccounts, readonly BankLineRepository $bankLines, readonly EntryLineRepository $entryLines, readonly BookRepository $books, readonly Connections $connections, readonly AuthorizationCheckerInterface $authorization)BankWidgetType::getName()
public static function getName(): stringBankWidgetType::getTemplate()
public function getTemplate(): stringBankWidgetType::getTemplateVars()
public function getTemplateVars(MenuItem $widget): arrayBooksWidgetType class
final class BooksWidgetType implements DashboardWidgetTypeInterface BooksWidgetType.php:25
Each book's current fiscal year at a glance: income (class 7) and expenses (class 6) to date and the result they make, what tenants still owe (open debits on 411), what is still to pay (open credits on 401), and the cash in the bank accounts (512). Each tile opens its report - the compte de résultat for the first three - and the bilan to date is a link away.
MenuItem::block('ledger_books', 'Comptabilité', 'fa-solid fa-book')
BooksWidgetType::__construct()
public function __construct(readonly BookRepository $books, readonly FiscalYearRepository $fiscalYears, readonly EntryLineRepository $lines, readonly IncomeStatement $incomeStatement, readonly AuthorizationCheckerInterface $authorization)BooksWidgetType::getName()
public static function getName(): stringBooksWidgetType::getTemplate()
public function getTemplate(): stringBooksWidgetType::getTemplateVars()
public function getTemplateVars(MenuItem $widget): arrayBooksWidgetType::figures()
public function figures(Book $book, \DateTimeImmutable $today): arrayBase\Ledger\Bank
Connections class
class Connections Connections.php:31
A book's bank connections and Omnibank: the gateway behind each, its Connection read from and written back to the sealed state, the bank's pages to go through to give consent (start(), complete()), and the bank accounts it gives access to, each with its ledger account (5121, 5122...) and bank journal (BQ, BQ2...).
Connections::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly StateCipher $cipher, readonly AccountRepository $accounts, readonly JournalRepository $journals, readonly BankAccountRepository $bankAccounts, readonly BankConnectionRepository $connections, readonly ?Registry $registry = null)Connections::isAvailable()
public function isAvailable(): boolConnections::gateways()
public function gateways(): array- Returns
the gateways the application configured (omnibank.gateways)
Connections::gateway()
public function gateway(string $name): GatewayInterfaceConnections::open()
public function open(BankConnection $connection): ConnectionThe connection's Omnibank Connection, unsealed.
Connections::store()
public function store(BankConnection $connection, Connection $omnibank): voidSeals $omnibank into the connection, with its consent. Flushed by the caller.
Connections::start()
public function start(Book $book, string $gateway, string|callable $returnUrl, ?string $name = null): arrayOpens a new connection of $book through $gateway: the connection, saved, and the page the user gives consent on (null when there is none, as for statement files - its accounts are then discovered).
$returnUrl may be a callable given the saved connection: the return route usually names it.
Connections::renew()
public function renew(BankConnection $connection, string $returnUrl): ?stringThrough the bank's page again: an expired consent, a new password. Null when nothing is needed.
Connections::complete()
public function complete(BankConnection $connection, string $returnUrl, array $query = []): ?stringThe user came back from the bank's page: what the provider put in the return URL's query is handed to the gateway (state "return") and connect() asked again. Null when nothing more is needed - the accounts are then discovered -, else the next page to go through.
Connections::discover()
public function discover(BankConnection $connection): intAsks the bank for the connection's accounts and adds those the book does not have yet; returns how many.
Connections::attach()
public function attach(BankConnection $connection, BankAccountModel|string $account, ?string $name = null, ?string $iban = null, ?string $currency = null): BankAccountA bank account of the connection, with its ledger account and journal: the book's first bank account posts on 5121 in BQ, the second on 5122 in BQ2... Persisted, not flushed.
Connections::findByProviderId()
public function findByProviderId(string $gateway, ?string $providerId): ?BankConnectionThe connection a provider's notification is about: the one of that gateway whose state holds the provider's connection id, or the only one of that gateway.
Importer class
class Importer Importer.php:17
Omnibank's transactions into a bank account's lines, each once: a transaction already imported (same id on the same account) is updated while it waits for reconciliation - banks correct pending lines - and left alone once reconciled or ignored.
Importer::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly BankLineRepository $lines)Importer::import()
public function import(BankAccount $account, iterable $transactions): int- Returns
how many new lines
StatementUpload class
class StatementUpload StatementUpload.php:19
A statement file (CAMT.053, OFX, CSV) typed into a bank account through Omnibank's "files" gateway: its lines imported (each once, however many overlapping statements are uploaded) and the statement's closing balance kept. The file itself is not stored.
const GATEWAY = 'files'
StatementUpload::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly Connections $connections, readonly Importer $importer)StatementUpload::upload()
public function upload(BankAccount $account, string $name, string $content): int- Returns
how many new lines
Sync class
class Sync Sync.php:22
Brings a bank account's new lines in through its connection's gateway: since the last sync, minus a few days (banks book late; lines already there are matched on their id), with the bank's balance when the gateway gives one. A consent the bank wants renewed is recorded on the connection - the back office then shows "Renouveler" - and the sync stops there.
Sync::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly Connections $connections, readonly Importer $importer, readonly int $overlapDays = 5)Sync::sync()
public function sync(BankAccount $account, ?\DateTimeImmutable $until = null): int- Returns
how many new lines
Sync::syncConnection()
public function syncConnection(BankConnection $connection): intEvery account of a connection (a webhook said something changed); returns how many new lines.
Base\Ledger\Command
BankSyncCommand class
class BankSyncCommand extends Command BankSyncCommand.php:25
Brings the bank accounts' new lines in (all books, or one) and reconciles what is sure. Accounts fed by statement files are skipped: they are uploaded. A connection whose consent needs renewing is reported and skipped. Suggested cron: a few times a day.
BankSyncCommand::__construct()
public function __construct(readonly BookRepository $books, readonly BankAccountRepository $bankAccounts, readonly Sync $sync, readonly Reconciler $reconciler)ChartSeedCommand class
class ChartSeedCommand extends Command ChartSeedCommand.php:16
Gives a book the accounts and journals of a chart (config/charts/), keeping what it has.
ChartSeedCommand::__construct()
public function __construct(readonly BookRepository $books, readonly Chart $chart)FecExportCommand class
class FecExportCommand extends Command FecExportCommand.php:17
Writes a fiscal year's FEC, <SIREN>FEC<closing date>.txt, into a directory (the current one by default).
FecExportCommand::__construct()
public function __construct(readonly BookRepository $books, readonly FiscalYearRepository $fiscalYears, readonly FecWriter $writer)Base\Ledger\Controller
WebhookController class
class WebhookController extends AbstractController WebhookController.php:24
Where a bank aggregator tells us something changed (Powens, Bridge): public, checked by the gateway against its signature (notify()). The connection it is about gets its consent updated, then its accounts synced and reconciled.
WebhookController::__construct()
public function __construct(readonly Connections $connections, readonly Sync $sync, readonly Reconciler $reconciler, readonly EntityManagerInterface $entityManager)WebhookController::__invoke()
public function __invoke(Request $request, string $gateway): ResponseBase\Ledger\Controller\Admin
AbstractLedgerPageController class
abstract class AbstractLedgerPageController extends AbstractController AbstractLedgerPageController.php:21
The ledger's back-office pages that are not a CRUD (reconciliation, statement upload, bank connection, reports): rendered in the admin's layout with its menus, as forge's refund page is.
AbstractLedgerPageController::setLedgerServices()
public function setLedgerServices(EntityManagerInterface $entityManager, AdminContext $adminContext, MenuBuilder $menuBuilder): voidBankController class
class BankController extends AbstractLedgerPageController BankController.php:26
Banks in the back office: connecting one (the bank's or aggregator's consent page, then back here), renewing a consent, syncing an account, and uploading a statement file - after which what is sure is reconciled.
BankController::__construct()
public function __construct(readonly Connections $connections, readonly StatementUpload $statements, readonly Sync $sync, readonly Reconciler $reconciler)BankController::upload()
public function upload(Request $request, int $bankAccount): ResponseBankController::connect()
public function connect(Request $request, string $gateway): ResponseConnecting a bank: for statement files, the account's name and IBAN (nothing to consent to); for a bank API or an aggregator, off to its consent page, back on ledger_admin_bank_return.
BankController::return()
public function return(Request $request, int $connection): RedirectResponseBack from the bank's consent page: the gateway finishes the connection, the accounts are discovered.
BankController::renew()
public function renew(Request $request, int $connection): RedirectResponseBankController::sync()
public function sync(Request $request, int $bankAccount): RedirectResponseLedgerAdminTrait trait
trait LedgerAdminTrait LedgerAdminTrait.php:20
The ledger screens' permissions (LedgerVoter), for every ledger CRUD: reading takes LEDGER_VIEW, changing LEDGER_MANAGE - ledger.admin_role (ROLE_ADMIN) by default, rather than the admin's blanket ROLE_SUPERADMIN.
Asked about a record, the voter looks at its book.
LedgerAdminTrait::configureCrud()
public function configureCrud(Crud $crud): CrudLedgerAdminTrait::configureActions()
public function configureActions(Actions $actions): ActionsReconcileController class
class ReconcileController extends AbstractLedgerPageController ReconcileController.php:31
A bank account's reconciliation: the lines still to place with what they may settle, best first - accept a suggestion, pick the open items (or a rule, or a party) by hand, ignore the line -, and the lines placed lately, which can be undone. Every change is a POST with its token.
ReconcileController::__construct()
public function __construct(readonly Matcher $matcher, readonly Reconciler $reconciler, readonly BankLineRepository $bankLines, readonly ReconciliationRuleRepository $rules, readonly PartyRepository $parties)ReconcileController::index()
public function index(int $bankAccount): ResponseReconcileController::apply()
public function apply(Request $request, int $line): RedirectResponseA suggestion accepted, or the open items / rule / party picked by hand.
ReconcileController::ignore()
public function ignore(Request $request, int $line): RedirectResponseReconcileController::undo()
public function undo(Request $request, int $line): RedirectResponseReconcileController::auto()
public function auto(Request $request, int $bankAccount): RedirectResponseReportController class
class ReportController extends AbstractLedgerPageController ReportController.php:31
A book's reports - trial balance, general ledger, open items, income statement and balance sheet (on screen, printed or as CSV), the FEC to download - and its buttons: seeding the chart, validating or reversing an entry.
ReportController::__construct()
public function __construct(readonly FiscalYearRepository $fiscalYears)ReportController::trialBalance()
public function trialBalance(Request $request, int $book, TrialBalance $trialBalance): ResponseReportController::generalLedger()
public function generalLedger(Request $request, int $book, GeneralLedger $generalLedger): ResponseReportController::openItems()
public function openItems(Request $request, int $book, OpenItems $openItems): ResponseReportController::incomeStatement()
public function incomeStatement(Request $request, int $book, IncomeStatement $incomeStatement): Response?n1=0 leaves out the N-1 column; ?format=csv downloads it.
ReportController::balanceSheet()
public function balanceSheet(Request $request, int $book, BalanceSheet $balanceSheet): Response?at= (Y-m-d), else the fiscal year's end - or today, while it runs; ?n1=0 and ?format=csv as for the income statement.
ReportController::fec()
public function fec(int $book, int $year, FecWriter $writer): ResponseReportController::seed()
public function seed(Request $request, int $book, Chart $chart): RedirectResponseReportController::validate()
public function validate(Request $request, int $entry, Posting $posting): RedirectResponseReportController::reverse()
public function reverse(Request $request, int $entry, Posting $posting): RedirectResponseBase\Ledger\Controller\Admin\Crud
AccountCrudController class
class AccountCrudController extends AbstractCrudController AccountCrudController.php:19
The chart of accounts, by number. An account with lines is not deleted.
AccountCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringAccountCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringAccountCrudController::configureCrud()
public function configureCrud(\Base\Admin\Config\Crud $crud): \Base\Admin\Config\CrudAccountCrudController::configureFilters()
public function configureFilters(Filters $filters): FiltersAccountCrudController::configureFields()
public function configureFields(string $pageName): iterableAccountCrudController::createEntity()
public function createEntity(string $entityFqcn): objectAccountCrudController::isDeletable()
public function isDeletable(object $entity): boolBankAccountCrudController class
class BankAccountCrudController extends AbstractCrudController BankAccountCrudController.php:22
The bank accounts, each with its ledger account (512x) and bank journal: reconcile its lines, upload a statement, sync it from the bank.
BankAccountCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudBankAccountCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringBankAccountCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringBankAccountCrudController::configureActions()
public function configureActions(Actions $actions): ActionsBankAccountCrudController::configureFields()
public function configureFields(string $pageName): iterableBankConnectionCrudController class
class BankConnectionCrudController extends AbstractCrudController BankConnectionCrudController.php:23
The accesses to banks: opened from the "connect" page (the bank's own consent page), renewed when the bank asks. Their state - tokens - is sealed and never shown.
BankConnectionCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudBankConnectionCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringBankConnectionCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringBankConnectionCrudController::configureActions()
public function configureActions(Actions $actions): ActionsBankConnectionCrudController::configureRecordNote()
public function configureRecordNote(object $entity): ?stringBankConnectionCrudController::configureFields()
public function configureFields(string $pageName): iterableBankLineCrudController class
class BankLineCrudController extends AbstractCrudController BankLineCrudController.php:20
The bank statements' lines, as imported, filtered by where they stand; they are reconciled from the reconciliation page.
const STATUS_LABELS = ['unmatched' => 'À rapprocher', 'suggested' => 'Suggestion', 'reconciled' => 'Rapprochée', 'ignored' => 'Ignorée']
BankLineCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringBankLineCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringBankLineCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudBankLineCrudController::configureFilters()
public function configureFilters(Filters $filters): FiltersBankLineCrudController::configureActions()
public function configureActions(Actions $actions): ActionsBankLineCrudController::configureFields()
public function configureFields(string $pageName): iterableBookCrudController class
class BookCrudController extends AbstractCrudController BookCrudController.php:18
The books - one per legal entity - with their reports: trial balance, open items, the FEC, and seeding the chart.
BookCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudBookCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringBookCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringBookCrudController::configureActions()
public function configureActions(Actions $actions): ActionsBookCrudController::configureFields()
public function configureFields(string $pageName): iterableBookCrudController::createEntity()
public function createEntity(string $entityFqcn): objectBookCrudController::isDeletable()
public function isDeletable(object $entity): boolA book with entries is part of the accounts: it stays.
EntryCrudController class
class EntryCrudController extends AbstractCrudController EntryCrudController.php:25
The entries, with their lines. They are posted by the application and the reconciliation (Posting), not typed here: a draft's date, label and piece can be corrected, then validated; a validated entry is read-only - it is reversed, never edited.
EntryCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringEntryCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringEntryCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudEntryCrudController::configureActions()
public function configureActions(Actions $actions): ActionsEntryCrudController::updateEntity()
public function updateEntity(EntityManagerInterface $entityManager, object $entity): voidThe form is closed on a validated entry; a direct request is refused before the flush guard would.
EntryCrudController::isDeletable()
public function isDeletable(object $entity): boolEntryCrudController::configureFields()
public function configureFields(string $pageName): iterableFiscalYearCrudController class
class FiscalYearCrudController extends AbstractCrudController FiscalYearCrudController.php:15
A book's fiscal years: entry numbers run per year; a closed year takes no entry. Without any, calendar years.
FiscalYearCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudFiscalYearCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringFiscalYearCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringFiscalYearCrudController::configureFields()
public function configureFields(string $pageName): iterableFiscalYearCrudController::createEntity()
public function createEntity(string $entityFqcn): objectJournalCrudController class
class JournalCrudController extends AbstractCrudController JournalCrudController.php:15
The journals: BQ (bank), VT (rent notices), AC (purchases), OD, AN (opening balances).
JournalCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudJournalCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringJournalCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringJournalCrudController::configureFields()
public function configureFields(string $pageName): iterableJournalCrudController::createEntity()
public function createEntity(string $entityFqcn): objectJournalCrudController::isDeletable()
public function isDeletable(object $entity): boolPartyCrudController class
class PartyCrudController extends AbstractCrudController PartyCrudController.php:24
Tenants, suppliers, associates: their auxiliary account (411U3), the IBAN their transfers come from, the application's reference. Usually created by the application (Parties::partyFor()); one typed here gets its auxiliary account when saved.
PartyCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudPartyCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringPartyCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringPartyCrudController::configureFields()
public function configureFields(string $pageName): iterablePartyCrudController::createEntity()
public function createEntity(string $entityFqcn): objectPartyCrudController::persistEntity()
public function persistEntity(EntityManagerInterface $entityManager, object $entity): voidPartyCrudController::isDeletable()
public function isDeletable(object $entity): boolReconciliationRuleCrudController class
class ReconciliationRuleCrudController extends AbstractCrudController ReconciliationRuleCrudController.php:25
What recurring bank lines are posted against when nothing open matches: bank fees to 627, the loan instalment split between 164 and 6611, Stripe payouts to 5112. The highest priority matching rule wins.
ReconciliationRuleCrudController::configureCrud()
public function configureCrud(Crud $crud): CrudReconciliationRuleCrudController::getEntityFqcn()
public static function getEntityFqcn(): stringReconciliationRuleCrudController::getPreferredIcon()
public static function getPreferredIcon(): ?stringReconciliationRuleCrudController::configureFields()
public function configureFields(string $pageName): iterableReconciliationRuleCrudController::createEntity()
public function createEntity(string $entityFqcn): objectBase\Ledger\DependencyInjection
LedgerConfiguration class
class LedgerConfiguration implements ConfigurationInterface LedgerConfiguration.php:8
LedgerConfiguration::getConfigTreeBuilder()
public function getConfigTreeBuilder(): TreeBuilderLedgerExtension class
class LedgerExtension extends AbstractBaseExtension LedgerExtension.php:11
LedgerExtension::getConfiguration()
public function getConfiguration(array $config, ContainerBuilder $container): LedgerConfigurationLedgerExtension::load()
public function load(array $configs, ContainerBuilder $container): voidBase\Ledger\Entity
Account class
class Account Account.php:16
An account of a book's chart, by its PCG number: 512 (bank), 706 (rents), or an auxiliary account under a collective one (411U3 for a tenant, under 411).
Account::__construct()
public function __construct(Book $book, string $number, string $label, ?AccountType $type = null)Account::normalize()
public static function normalize(string $number): stringAccount numbers are kept upper-case, without spaces: "411 u3" is 411U3.
Account::__toString()
public function __toString(): stringAccount::getId()
public function getId(): ?intAccount::getBook()
public function getBook(): BookAccount::setBook()
public function setBook(Book $book): selfAn account is given its book and its number when it is created: the back office's "new" form writes them, its "edit" form shows them.
Account::getNumber()
public function getNumber(): stringAccount::setNumber()
public function setNumber(?string $number): selfAccount::getLabel()
public function getLabel(): stringAccount::setLabel()
public function setLabel(?string $label): selfAccount::getType()
public function getType(): AccountTypeAccount::setType()
public function setType(AccountType $type): selfAccount::getGeneralNumber()
public function getGeneralNumber(): stringThe general account this one sits under in the FEC: its leading digits (411 for 411U3).
Account::isUnder()
public function isUnder(string $prefix): boolBankAccount class
class BankAccount BankAccount.php:13
One account at the bank, as its connection's gateway sees it ($externalId), tied to the book's ledger account for it (a 512) and the journal its movements are posted in (BQ).
BankAccount::__construct()
public function __construct(BankConnection $connection, string $externalId, string $name, Account $account, Journal $journal, ?string $iban = null, ?string $currency = null)BankAccount::__toString()
public function __toString(): stringBankAccount::mask()
public static function mask(?string $iban): stringFR76 •••• 4321: what a screen shows of an IBAN.
BankAccount::getId()
public function getId(): ?intBankAccount::getConnection()
public function getConnection(): BankConnectionBankAccount::getBook()
public function getBook(): BookBankAccount::getExternalId()
public function getExternalId(): stringBankAccount::getIban()
public function getIban(): ?stringBankAccount::setIban()
public function setIban(?string $iban): selfBankAccount::getMaskedIban()
public function getMaskedIban(): stringBankAccount::getName()
public function getName(): stringBankAccount::setName()
public function setName(string $name): selfBankAccount::getCurrency()
public function getCurrency(): stringBankAccount::getAccount()
public function getAccount(): AccountBankAccount::setAccount()
public function setAccount(Account $account): selfBankAccount::getJournal()
public function getJournal(): JournalBankAccount::setJournal()
public function setJournal(Journal $journal): selfBankAccount::getLastSyncedAt()
public function getLastSyncedAt(): ?\DateTimeImmutableBankAccount::setLastSyncedAt()
public function setLastSyncedAt(?\DateTimeImmutable $lastSyncedAt): selfBankAccount::getBankBalance()
public function getBankBalance(): ?intBankAccount::getBankBalanceAt()
public function getBankBalanceAt(): ?\DateTimeImmutableBankAccount::setBankBalance()
public function setBankBalance(?int $balance, ?\DateTimeImmutable $at = null): selfBankConnection class
class BankConnection BankConnection.php:18
A book's access to a bank through one of Omnibank's gateways ("files", "qonto", "powens", "bridge"): the gateway's Connection state (tokens, the provider's user) sealed with sodium's secretbox (Bank\Connections reads and writes it, never in clear), the consent the bank gave and until when, and the last error a sync met.
BankConnection::__construct()
public function __construct(Book $book, string $gateway, ?string $name = null)BankConnection::__toString()
public function __toString(): stringBankConnection::getId()
public function getId(): ?intBankConnection::getBook()
public function getBook(): BookBankConnection::getGateway()
public function getGateway(): stringBankConnection::getName()
public function getName(): ?stringBankConnection::setName()
public function setName(?string $name): selfBankConnection::getState()
public function getState(): ?stringBankConnection::setState()
public function setState(?string $state): selfThe sealed state, as Bank\Connections::store() writes it.
BankConnection::getConsentStatus()
public function getConsentStatus(): ConsentStateBankConnection::setConsentStatus()
public function setConsentStatus(ConsentState $consentStatus): selfBankConnection::getExpiresAt()
public function getExpiresAt(): ?\DateTimeImmutableBankConnection::setExpiresAt()
public function setExpiresAt(?\DateTimeImmutable $expiresAt): selfBankConnection::getLastError()
public function getLastError(): ?stringBankConnection::setLastError()
public function setLastError(?string $lastError): selfBankConnection::getCreatedAt()
public function getCreatedAt(): \DateTimeImmutableBankConnection::getAccounts()
public function getAccounts(): CollectionBankConnection::addAccount()
public function addAccount(BankAccount $account): selfBankConnection::needsRenewal()
public function needsRenewal(?\DateTimeImmutable $now = null): boolWhether the bank wants its consent given again (expired, or said so).
BankLine class
class BankLine BankLine.php:15
A transaction on a bank account's statement, as the bank booked it - negative when money leaves. Imported once (its $externalId is unique per bank account), then reconciled: posted in the bank journal ($entry) and lettered with what it pays, or ignored.
BankLine::__construct()
public function __construct(BankAccount $bankAccount, string $externalId, \DateTimeImmutable $bookedOn, int $amount, string $label, ?string $currency = null)BankLine::__toString()
public function __toString(): stringBankLine::getId()
public function getId(): ?intBankLine::getBankAccount()
public function getBankAccount(): BankAccountBankLine::getBook()
public function getBook(): BookBankLine::getExternalId()
public function getExternalId(): stringBankLine::getBookedOn()
public function getBookedOn(): \DateTimeImmutableBankLine::setBookedOn()
public function setBookedOn(\DateTimeImmutable $bookedOn): selfBankLine::getValueOn()
public function getValueOn(): ?\DateTimeImmutableBankLine::setValueOn()
public function setValueOn(?\DateTimeImmutable $valueOn): selfBankLine::getAmount()
public function getAmount(): intBankLine::setAmount()
public function setAmount(int $amount): selfBankLine::getCurrency()
public function getCurrency(): stringBankLine::getLabel()
public function getLabel(): stringBankLine::setLabel()
public function setLabel(string $label): selfBankLine::getCounterpartyName()
public function getCounterpartyName(): ?stringBankLine::setCounterpartyName()
public function setCounterpartyName(?string $name): selfBankLine::getCounterpartyIban()
public function getCounterpartyIban(): ?stringBankLine::setCounterpartyIban()
public function setCounterpartyIban(?string $iban): selfBankLine::getReference()
public function getReference(): ?stringBankLine::setReference()
public function setReference(?string $reference): selfBankLine::getRaw()
public function getRaw(): arrayBankLine::setRaw()
public function setRaw(array $raw): selfBankLine::getStatus()
public function getStatus(): BankLineStatusBankLine::setStatus()
public function setStatus(BankLineStatus $status): selfBankLine::getEntry()
public function getEntry(): ?EntryBankLine::setEntry()
public function setEntry(?Entry $entry): selfBankLine::getReconciliations()
public function getReconciliations(): intBankLine::countReconciliation()
public function countReconciliation(): selfBankLine::getCreatedAt()
public function getCreatedAt(): \DateTimeImmutableBankLine::getSource()
public function getSource(): stringThe entry source of its next reconciliation: "bank:<id>", then "bank:<id>/2" after an undo.
Book class
class Book Book.php:15
One legal entity's accounts (a company, an SCI): its chart, journals, parties, entries and bank accounts all hang off it. Entries dated on or before $lockedUntil can no longer be posted, changed or reversed - the period is closed, declared, or handed to the accountant.
Book::__construct()
public function __construct(string $name, ?string $siren = null, string $currency = 'EUR')Book::__toString()
public function __toString(): stringBook::getId()
public function getId(): ?intBook::getName()
public function getName(): stringBook::setName()
public function setName(?string $name): selfBook::getSiren()
public function getSiren(): ?stringBook::setSiren()
public function setSiren(?string $siren): selfBook::getCurrency()
public function getCurrency(): stringBook::setCurrency()
public function setCurrency(?string $currency): selfBook::getLockedUntil()
public function getLockedUntil(): ?\DateTimeImmutableBook::setLockedUntil()
public function setLockedUntil(?\DateTimeImmutable $lockedUntil): selfBook::getCreatedAt()
public function getCreatedAt(): \DateTimeImmutableBook::isLocked()
public function isLocked(\DateTimeInterface $date): boolWhether $date falls in the locked period (on or before lockedUntil).
Book::getBook()
public function getBook(): selfEntry class
class Entry Entry.php:18
A journal entry: balanced lines (debits = credits) on one date, in one journal. $source says where it comes from ("bank:42", "notice:2026-10-U3") and is unique per book, so posting the same thing twice gives the same entry back. Validated, it gets its number (sequential per book and fiscal year) and becomes immutable: a mistake is undone by a reversal entry ($reversalOf), never by an edit.
Entry::__construct()
public function __construct(Book $book, Journal $journal, \DateTimeImmutable $date, string $label, string $source, ?string $piece = null)Entry::__toString()
public function __toString(): stringEntry::getId()
public function getId(): ?intEntry::getBook()
public function getBook(): BookEntry::getJournal()
public function getJournal(): JournalEntry::getDate()
public function getDate(): \DateTimeImmutableEntry::getLabel()
public function getLabel(): stringEntry::getPiece()
public function getPiece(): ?stringEntry::getSource()
public function getSource(): stringEntry::getNumber()
public function getNumber(): ?stringEntry::getValidatedAt()
public function getValidatedAt(): ?\DateTimeImmutableEntry::isValidated()
public function isValidated(): boolEntry::getReversalOf()
public function getReversalOf(): ?EntryEntry::setReversalOf()
public function setReversalOf(?Entry $reversalOf): selfEntry::getCreatedAt()
public function getCreatedAt(): \DateTimeImmutableEntry::validate()
public function validate(string $number, ?\DateTimeImmutable $at = null): selfNumbered and frozen - by Posting, which picks the number.
Entry::getLines()
public function getLines(): CollectionEntry::addLine()
public function addLine(EntryLine $line): selfEntry::getTotalDebit()
public function getTotalDebit(): intEntry::getTotalCredit()
public function getTotalCredit(): intEntry::isBalanced()
public function isBalanced(): boolEntryLine class
class EntryLine EntryLine.php:15
One line of an entry: an amount on the debit or the credit side of an account, maybe for a party. Lettering ties lines of one account and party whose debits and credits cancel out (a rent notice and its payment): they share a $letter and are no longer open items. Only the lettering fields change once the entry is validated.
EntryLine::__construct()
public function __construct(Entry $entry, Account $account, int $debit, int $credit, ?Party $party = null, ?string $label = null)EntryLine::__toString()
public function __toString(): stringEntryLine::getId()
public function getId(): ?intEntryLine::getEntry()
public function getEntry(): EntryEntryLine::getBook()
public function getBook(): BookEntryLine::getAccount()
public function getAccount(): AccountEntryLine::getParty()
public function getParty(): ?PartyEntryLine::getDebit()
public function getDebit(): intEntryLine::getCredit()
public function getCredit(): intEntryLine::getLabel()
public function getLabel(): ?stringEntryLine::getLetter()
public function getLetter(): ?stringEntryLine::getLetteredOn()
public function getLetteredOn(): ?\DateTimeImmutableEntryLine::isLettered()
public function isLettered(): boolEntryLine::getNet()
public function getNet(): intDebit minus credit: positive when the line is owed to the book (a rent notice on 411).
EntryLine::setLetter()
public function setLetter(?string $letter, ?\DateTimeImmutable $on = null): selfBy Lettering only: it checks the lines cancel out.
FiscalYear class
class FiscalYear FiscalYear.php:16
An accounting period of a book, usually twelve months. Entry numbers run per fiscal year; once closed, nothing is posted in it any more. A book without fiscal years works on calendar years.
FiscalYear::__construct()
public function __construct(Book $book, \DateTimeImmutable $start, \DateTimeImmutable $end)FiscalYear::calendar()
public static function calendar(Book $book, int $year): selfThe calendar year $year of a book, unsaved: what a book without fiscal years works on.
FiscalYear::__toString()
public function __toString(): stringFiscalYear::getId()
public function getId(): ?intFiscalYear::getBook()
public function getBook(): BookFiscalYear::setBook()
public function setBook(Book $book): selfFiscalYear::getStart()
public function getStart(): \DateTimeImmutableFiscalYear::setStart()
public function setStart(\DateTimeInterface $start): selfFiscalYear::getEnd()
public function getEnd(): \DateTimeImmutableFiscalYear::setEnd()
public function setEnd(\DateTimeInterface $end): selfFiscalYear::validatePeriod()
public function validatePeriod(ExecutionContextInterface $context): voidWhat the constructor refuses, said to a form instead of thrown.
FiscalYear::getClosedAt()
public function getClosedAt(): ?\DateTimeImmutableFiscalYear::setClosedAt()
public function setClosedAt(?\DateTimeImmutable $closedAt): selfFiscalYear::isClosed()
public function isClosed(): boolFiscalYear::contains()
public function contains(\DateTimeInterface $date): boolJournal class
class Journal Journal.php:11
A book's journal: BQ (bank), VT (sales: rent notices), AC (purchases), OD (miscellaneous), AN (opening balances).
const BANK = 'BQ'const SALES = 'VT'const PURCHASES = 'AC'const MISCELLANEOUS = 'OD'const OPENING = 'AN'
Journal::__construct()
public function __construct(Book $book, string $code, string $label)Journal::__toString()
public function __toString(): stringJournal::getId()
public function getId(): ?intJournal::getBook()
public function getBook(): BookJournal::setBook()
public function setBook(Book $book): selfA journal is given its book and its code when it is created: the back office's "new" form writes them, its "edit" form shows them.
Journal::getCode()
public function getCode(): stringJournal::setCode()
public function setCode(?string $code): selfJournal::getLabel()
public function getLabel(): stringJournal::setLabel()
public function setLabel(?string $label): selfParty class
class Party Party.php:17
Someone the book owes or is owed by - a tenant, a supplier, an associate - with their own auxiliary account (411U3 under 411). $reference is the application's handle on them (a lease, a supplier's code), unique per book; $iban is how their transfers are recognised on the bank statement.
Party::__construct()
public function __construct(Book $book, PartyKind $kind, string $name, string $accountNumber, ?string $reference = null, ?string $iban = null)Party::normalizeIban()
public static function normalizeIban(?string $iban): ?stringParty::__toString()
public function __toString(): stringParty::getId()
public function getId(): ?intParty::getBook()
public function getBook(): BookParty::setBook()
public function setBook(Book $book): selfParty::getKind()
public function getKind(): PartyKindParty::setKind()
public function setKind(PartyKind $kind): selfParty::getName()
public function getName(): stringParty::setName()
public function setName(?string $name): selfParty::getIban()
public function getIban(): ?stringParty::setIban()
public function setIban(?string $iban): selfParty::getReference()
public function getReference(): ?stringParty::setReference()
public function setReference(?string $reference): selfParty::getAccountNumber()
public function getAccountNumber(): stringParty::setAccountNumber()
public function setAccountNumber(?string $accountNumber): selfLeft empty (a form gives null), the back office numbers it from the party's kind and reference when it is saved.
Party::getCreatedAt()
public function getCreatedAt(): \DateTimeImmutableReconciliationRule class
class ReconciliationRule ReconciliationRule.php:24
What to post a recurring bank line against when nothing open matches it: bank fees to 627, the loan instalment split between 164 (capital) and 6611 (interest), a Stripe payout to the 5112 clearing account. A rule matches on the label ($pattern: words that must all appear, or a /regular expression/), the counterparty's IBAN, and the direction; the highest priority wins.
$split, when set, spreads the amount over several accounts: [{"account": "164", "amount": 85000}, {"account": "6611", "rest": true}] - "amount" a fixed part in minor units, "percent" a share, "rest" what remains (the rule's own account takes the rest when no part says so).
ReconciliationRule::__construct()
public function __construct(Book $book, string $name, ?string $pattern, Account $account, RuleSign $sign = RuleSign::ANY)ReconciliationRule::__toString()
public function __toString(): stringReconciliationRule::getId()
public function getId(): ?intReconciliationRule::getBook()
public function getBook(): BookReconciliationRule::setBook()
public function setBook(Book $book): selfReconciliationRule::getName()
public function getName(): stringReconciliationRule::setName()
public function setName(?string $name): selfReconciliationRule::getPattern()
public function getPattern(): ?stringReconciliationRule::setPattern()
public function setPattern(?string $pattern): selfReconciliationRule::getIban()
public function getIban(): ?stringReconciliationRule::setIban()
public function setIban(?string $iban): selfReconciliationRule::getSign()
public function getSign(): RuleSignReconciliationRule::setSign()
public function setSign(RuleSign $sign): selfReconciliationRule::getAccount()
public function getAccount(): AccountReconciliationRule::setAccount()
public function setAccount(Account $account): selfReconciliationRule::getParty()
public function getParty(): ?PartyReconciliationRule::setParty()
public function setParty(?Party $party): selfReconciliationRule::getPriority()
public function getPriority(): intReconciliationRule::setPriority()
public function setPriority(int $priority): selfReconciliationRule::getSplit()
public function getSplit(): ?arrayReconciliationRule::setSplit()
public function setSplit(?array $split): selfReconciliationRule::isEnabled()
public function isEnabled(): boolReconciliationRule::setEnabled()
public function setEnabled(bool $enabled): selfReconciliationRule::matches()
public function matches(string $label, ?string $iban, int $amount): boolWhether a bank line of this label, counterparty IBAN and signed amount is one of this rule's.
Base\Ledger\Enum
AccountType enum
enum AccountType AccountType.php:10
What an account is in the balance sheet or the income statement - which side its balance normally sits on (debit for assets and expenses, credit for the others) and whether it is carried forward at year end.
case ASSET = 'asset'case LIABILITY = 'liability'case EQUITY = 'equity'case INCOME = 'income'case EXPENSE = 'expense'
AccountType::isDebitNormal()
public function isDebitNormal(): boolAccountType::isResult()
public function isResult(): boolIncome and expenses: closed into the result at year end, not carried forward.
AccountType::guess()
public static function guess(string $number): selfThe PCG's default for an account number: its class, and for class 4 the usual side.
BankLineStatus enum
enum BankLineStatus BankLineStatus.php:6
Where a bank line stands in the reconciliation.
case UNMATCHED = 'unmatched'Nothing found for it yet.
case SUGGESTED = 'suggested'Suggestions exist, none sure enough to apply on its own: someone decides.
case RECONCILED = 'reconciled'Posted in the bank journal (its entry) and lettered with what it pays.
case IGNORED = 'ignored'Deliberately left out of the accounts (an internal transfer already posted, a test).
BankLineStatus::isPending()
public function isPending(): boolConsentState enum
enum ConsentState ConsentState.php:10
A bank connection's consent as the ledger keeps it: Omnibank's ConsentStatus, stored by value so the entity does not depend on the package being installed.
case ACTIVE = 'active'case NEEDS_RENEWAL = 'needs_renewal'case REVOKED = 'revoked'case NONE = 'none'
ConsentState::of()
public static function of(\UnitEnum|string|null $status): selfFrom Omnibank's ConsentStatus (a backed or pure enum), by its value or its name.
PartyKind enum
enum PartyKind PartyKind.php:6
Who a party is to the book: the collective account its own account sits under.
case TENANT = 'tenant'case SUPPLIER = 'supplier'case ASSOCIATE = 'associate'case OTHER = 'other'
PartyKind::collective()
public function collective(): stringThe PCG collective account a party of this kind gets an auxiliary account under.
RuleSign enum
enum RuleSign RuleSign.php:6
Which bank lines a reconciliation rule looks at: money coming in, going out, or both.
case ANY = 'any'case IN = 'in'case OUT = 'out'
RuleSign::accepts()
public function accepts(int $amount): boolBase\Ledger\Event
BankLineReconciled class
final class BankLineReconciled BankLineReconciled.php:14
A bank line posted and lettered with what it settles (Reconciler::apply): what the application owes or is owed can follow - a rent call paid, an invoice settled - from the open items it cleared, without posting again.
public readonly BankLine $linepublic readonly Entry $entrypublic readonly array $openItems
BankLineReconciled::__construct()
public function __construct(public readonly BankLine $line, public readonly Entry $entry, public readonly array $openItems)Base\Ledger\EventListener
ValidatedEntryGuard class
class ValidatedEntryGuard ValidatedEntryGuard.php:21
A validated entry is part of the books for good: whatever tries to change it, or one of its lines, or to delete either, or to add a line to it, is refused at flush time - the back office, a fixture, a careless service.
The lettering fields of its lines are the only exception: lettering is done after the fact. A mistake is corrected by a reversal entry (Posting::reverse()).
ValidatedEntryGuard::onFlush()
public function onFlush(OnFlushEventArgs $args): voidBase\Ledger\Exception
BankException class
class BankException extends LedgerException BankException.php:6
A bank access that failed: Omnibank missing, a gateway or an account not found, the provider refusing.
LedgerException class
class LedgerException extends RuntimeException LedgerException.php:6
What the ledger refused to do; nothing was written.
LetteringException class
class LetteringException extends LedgerException LetteringException.php:6
Lines that cannot be lettered together: they do not cancel out, or not on one account and party.
LockedEntryException class
class LockedEntryException extends LedgerException LockedEntryException.php:6
A change to a validated entry or its lines (other than their lettering): reverse it instead.
LockedPeriodException class
class LockedPeriodException extends PostingException LockedPeriodException.php:6
A date on or before the book's lockedUntil, or in a closed fiscal year.
PostingException class
class PostingException extends LedgerException PostingException.php:6
An entry Posting refuses: unbalanced, a negative or two-sided line, an unknown account or journal.
ReconciliationException class
class ReconciliationException extends LedgerException ReconciliationException.php:6
A bank line that cannot be reconciled (or undone) that way.
Base\Ledger\Export
FecWriter class
class FecWriter FecWriter.php:21
The Fichier des Écritures Comptables (article A47 A-1 du LPF) of a fiscal year: one row per line of every validated entry, the 18 columns in their order, tab-separated, CRLF, dates as YYYYMMDD, amounts with a decimal comma and no thousands separator. A line on a party's auxiliary account (411U3) is filed under its general account (411) with the auxiliary account and the party's name in CompAuxNum and CompAuxLib.
The file is named <SIREN>FEC<closing date YYYYMMDD>.txt (filename()).
const COLUMNS = ['JournalCode', 'JournalLib', 'EcritureNum', 'EcritureDate', 'CompteNum', 'CompteLib', 'CompAuxNum', 'CompAuxLib', 'PieceRef', 'PieceDate', 'EcritureLib', 'D…
FecWriter::__construct()
public function __construct(readonly EntryRepository $entries, readonly AccountRepository $accounts)FecWriter::write()
public function write(Book $book, FiscalYear $year): stringFecWriter::filename()
public static function filename(Book $book, FiscalYear $year): stringFecWriter::render()
public function render(Book $book, iterable $entries): stringFecWriter::amount()
public static function amount(int $minor): string123456 -> "1234,56"
FecWriter::clean()
public static function clean(?string $value): stringNo tab, pipe nor line break inside a field: they separate fields and rows.
Base\Ledger\Model
EntryDraft class
final class EntryDraft EntryDraft.php:19
An entry to post, as the application describes it - account numbers and a journal code rather than entities:
$posting->post((new EntryDraft($book, 'VT', $date, 'Loyer octobre U3', 'notice:2026-10-U3', 'AVIS-2026-10-U3')) ->line('411U3', 85000, 0, $tenant) ->line('706', 0, 85000));
$source identifies what the entry records, once: posting the same source again returns the entry already there.
public readonly Book $bookpublic readonly string $journalpublic readonly \DateTimeImmutable $datepublic readonly string $labelpublic readonly string $sourcepublic readonly ?string $piece
EntryDraft::__construct()
public function __construct(public readonly Book $book, public readonly string $journal, public readonly \DateTimeImmutable $date, public readonly string $label, public readonly string $source, public readonly ?string $piece = null)EntryDraft::line()
public function line(string $account, int $debit, int $credit, ?Party $party = null, ?string $label = null): selfA line: amounts in minor units, one of them zero.
EntryDraft::getLines()
public function getLines(): arrayEntryDraft::getTotalDebit()
public function getTotalDebit(): intEntryDraft::getTotalCredit()
public function getTotalCredit(): intText class
final class Text Text.php:6
How bank labels, names and references are compared: upper-case, without accents nor punctuation.
Text::fold()
public static function fold(?string $text): string"Loyer d'Octobre - Dupont" -> "LOYER D OCTOBRE DUPONT"
Text::squash()
public static function squash(?string $text): string"AVIS-2026-10/U3" -> "AVIS202610U3": references survive the bank's own spacing and punctuation.
Base\Ledger\Reconciliation
Matcher class
class Matcher Matcher.php:37
What a bank line may settle, ranked. In order of trust:
1. a reference of an open item found in the line's label or reference (the piece of a rent notice, "AVIS-2026-10-U3"; a Stripe payout's id) - 0.99 when the amounts agree; 2. the counterparty's IBAN, a party's, with an open item of exactly the amount (0.95), several adding up to it (0.92-0.94), or none (0.5: a payment on account); 3. a party's name in the label, with an open item of exactly the amount (0.9), several adding up to it (0.85-0.89); 4. nobody recognised, but open items of one party adding up to the amount (0.7), or one open item of exactly the amount (0.75); 5. a reconciliation rule (0.9), with the open items of its account when they add up (0.92: a Stripe payout against the 5112 clearing account).
The same amount owed by two parties, nothing else to tell them apart, scores 0.5 each; two parties recognised by the same IBAN or name, 0.6. Under the auto threshold (0.9), someone decides.
const REFERENCE = 0.99const REFERENCE_PARTIAL = 0.6const IBAN_EXACT = 0.95const IBAN_ALL = 0.9399999999999999const IBAN_OLDEST = 0.93const IBAN_GROUP = 0.92const IBAN_ONLY = 0.5const NAME_EXACT = 0.9const NAME_ALL = 0.89const NAME_OLDEST = 0.88const NAME_GROUP = 0.85const AMOUNT_ONLY = 0.75const GROUP_ONLY = 0.7const RULE_ITEMS = 0.92const RULE = 0.9const AMBIGUOUS_PARTY = 0.6const AMBIGUOUS = 0.5
Matcher::__construct()
public function __construct(readonly EntryLineRepository $lines, readonly PartyRepository $parties, readonly ReconciliationRuleRepository $rules, readonly int $maxGroupItems = 12)Matcher::openItems()
public function openItems(Book $book): arrayWhat a bank line can settle in $book: the open lines of the third-party accounts (class 4) and of the clearing accounts (511).
Matcher::suggest()
public function suggest(BankLine $line, ?array $openItems = null): array$openItemsthe book's open items, when the caller has them (autoReconcile)
- Returns
best first
Reconciler class
class Reconciler Reconciler.php:36
Turns a bank line into the books: apply() posts it in its bank account's journal - the bank account (512x) against what the suggestion says: the open items' accounts, the rule's account(s), or the party's - and letters it with the open items it settles. Its entry's source is "bank:<line id>", so applying it twice posts it once.
Stripe and the like go through the 5112 clearing account: the payment (5112 / 411) and its fee (6278 / 5112) are posted by the application; the payout on the statement (512 / 5112) settles the 5112 open items, by its reference or by a rule on 5112.
Reconciler::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly Posting $posting, readonly Lettering $lettering, readonly Matcher $matcher, readonly BankLineRepository $bankLines, readonly float $threshold = 0.9, readonly ?EventDispatcherInterface $dispatcher = null)Reconciler::getThreshold()
public function getThreshold(): floatReconciler::apply()
public function apply(BankLine $line, Suggestion $suggestion): Entry- Throws
ReconciliationException|\LedgerException
Reconciler::autoReconcile()
public function autoReconcile(BankAccount $account): intMatches the account's pending lines: a line with exactly one suggestion at or above the threshold is applied; the others are left SUGGESTED (or UNMATCHED, nothing found) for someone to decide. Returns how many were applied.
Reconciler::ignore()
public function ignore(BankLine $line): voidLeaves a line out of the books (an internal transfer posted otherwise, a test).
Reconciler::undo()
public function undo(BankLine $line): voidBack to unmatched: an ignored line simply, a reconciled one by reversing its entry (dated as it was) and unlettering what it settled - refused when that date is in the locked period.
Reconciler::split()
public function split(ReconciliationRule $rule, int $amount): arrayA rule's parts of $amount: [account number, minor units, label] - fixed amounts first, then percentages, the rest to the part saying "rest" or to the rule's own account.
Suggestion class
final readonly class Suggestion Suggestion.php:17
What a bank line may be (Matcher), or what someone in the back office says it is: the open items it pays - lettered with it when they add up to its amount -, else the rule it falls under, else the party it comes from (a payment on account). $score runs from 0 to 1; Reconciler applies a suggestion on its own only when it is the single one at or above ledger.reconciliation.auto_threshold.
public float $scorepublic string $reasonpublic array $openItemspublic ?ReconciliationRule $rulepublic ?Party $party
Suggestion::__construct()
public function __construct(public float $score, public string $reason, public array $openItems = [], public ?ReconciliationRule $rule = null, public ?Party $party = null)Suggestion::total()
public function total(): intWhat the open items add up to, debit minus credit: the bank amount they settle exactly.
Suggestion::settles()
public function settles(int $amount): boolWhether it settles $amount exactly: its open items are then lettered with the bank entry.
Suggestion::key()
public function key(): stringIdentity of what it proposes, whatever its score: two ways to the same items are one suggestion.
Suggestion::withScore()
public function withScore(float $score, ?string $reason = null): selfBase\Ledger\Report
BalanceSheet class
class BalanceSheet BalanceSheet.php:30
The bilan at a date: every account's balance from the book's first entry to that day, the assets (actif) on one side, what finances them (passif) on the other - and, when asked, the same day a year earlier beside it.
- Class 1 is always passif: capital and reserves (10-14), loans (16), tenants' deposits (165); what it holds on the wrong side shows negative.
- Class 2 is always actif, net: depreciation (28) shows negative under the fixed asset group.
- The other balance sheet accounts go by their balance: a tenant who owes (411 debit) is a receivable, one who overpaid (411 credit) a debt; the bank (51, 53) is cash, overdrawn a debt; suppliers (40) and associates' current accounts (455) are debts, or receivables when debit.
- The income and expense accounts (by type) are not closed in the books: their balance is the result, in the capitaux propres - the current fiscal year's, and before it whatever earlier years left unallocated (no closing entry posted to 120/129 and the report à nouveau).
Actif and passif then add up to the same total, or the books are wrong. Draft entries count, as in the trial balance.
const ASSETS = ['fixed_assets' => 'Immobilisations nettes', 'receivables' => 'Créances locataires et clients', 'other_assets' => 'Autres créances', 'cash' => 'Disponibilités']key => heading, in the order of the bilan
const LIABILITIES = ['equity' => 'Capitaux propres', 'loans' => 'Emprunts', 'deposits' => 'Dépôts de garantie reçus', 'suppliers' => 'Dettes fournisseurs', 'associates' => 'Comp…key => heading, in the order of the bilan
const RESULT = 'Résultat de l\'exercice'const EARLIER_RESULTS = 'Résultats antérieurs non affectés'
BalanceSheet::__construct()
public function __construct(readonly EntryLineRepository $lines, readonly FiscalYearRepository $fiscalYears)BalanceSheet::compute()
public function compute(Book $book, \DateTimeImmutable $at, bool $withPrevious = false): arrayAmounts are in minor units, positive on their normal side. The result lines have no account number. previous is null throughout unless $withPrevious.
BalanceSheet::fromTotals()
public static function fromTotals(array $totals, ?array $yearTotals = null, ?array $previous = null, ?array $previousYear = null): array$totalsevery entry up to the date
$yearTotalsthe fiscal year's entries up to it: the result they make is the year's, the rest of the result earlier years'; null, all of it is the year's
$previousthe same a year earlier (N-1)
BalanceSheet::place()
public static function place(string $number, int $balance): array- Returns
where a balance sheet account's balance (debit minus credit) goes
GeneralLedger class
class GeneralLedger GeneralLedger.php:10
The grand livre: every line of every account over a period, with the running balance and the account's totals.
GeneralLedger::__construct()
public function __construct(readonly EntryLineRepository $lines)GeneralLedger::compute()
public function compute(Book $book, ?\DateTimeImmutable $from = null, ?\DateTimeImmutable $to = null, ?string $accountPrefix = null): arrayGeneralLedger::fromLines()
public static function fromLines(iterable $lines): array$linesby account then date
IncomeStatement class
class IncomeStatement IncomeStatement.php:23
The compte de résultat over a period: income (class 7) and expenses (class 6) in the headings an SCI's accounts are read under, their totals and the result (bénéfice when positive, perte when negative) - and, when asked, the same period a year earlier beside it (N-1).
An account goes to the group of the longest prefix it starts with on its side (7083 to the rents, 708 to the recovered charges), and to "Autres produits" / "Autres charges" when none matches. Which side is the account's type (income, expense), as for the trial balance's result.
Draft entries count, as in the trial balance: the statement is what the books say today, before the accountant validates them.
const INCOME = ['rents' => ['Loyers', ['706', '7083']], 'recovered_charges' => ['Charges récupérées', ['708']], 'other_income' => ['Autres produits', ['7088']]]key => [heading, account prefixes]
const EXPENSES = ['rental_charges' => ['Charges locatives et de copropriété', ['614', '6061']], 'repairs' => ['Entretien et réparations', ['615', '6063']], 'insurance' => ['A…key => [heading, account prefixes]
IncomeStatement::__construct()
public function __construct(readonly EntryLineRepository $lines)IncomeStatement::compute()
public function compute(Book $book, \DateTimeImmutable $from, \DateTimeImmutable $to, bool $withPrevious = false): arrayAmounts are in minor units, income and expenses both positive when normal (a refund larger than the charge makes it negative); previous is null throughout unless $withPrevious.
IncomeStatement::fromTotals()
public static function fromTotals(array $totals, ?array $previous = null): array$previousthe N-1 period's
IncomeStatement::yearBefore()
public static function yearBefore(\DateTimeImmutable $date): \DateTimeImmutableThe same day a year earlier, a month's last day staying its last day (28/02/2029 → 29/02/2028, 29/02/2028 → 28/02/2027): the N-1 period of a twelve-month fiscal year is the fiscal year before.
OpenItems class
class OpenItems OpenItems.php:15
What is still owed, party by party: the unlettered lines of the accounts under a prefix (411 - tenants - by default; 401 for suppliers), their balance and how old the oldest is.
OpenItems::__construct()
public function __construct(readonly Lettering $lettering)OpenItems::compute()
public function compute(Book $book, ?Party $party = null, ?string $accountPrefix = '411', ?\DateTimeImmutable $at = null): arrayOpenItems::fromLines()
public static function fromLines(iterable $lines, ?\DateTimeImmutable $at = null): arrayTrialBalance class
class TrialBalance TrialBalance.php:14
The balance générale: per account, the debit and credit totals over a period and the balance they leave, with totals per class and overall (debits = credits, or the books are wrong).
TrialBalance::__construct()
public function __construct(readonly EntryLineRepository $lines)TrialBalance::compute()
public function compute(Book $book, ?\DateTimeImmutable $from = null, ?\DateTimeImmutable $to = null): arrayTrialBalance::fromTotals()
public static function fromTotals(array $totals): arrayTrialBalance::typeOf()
public static function typeOf(array $row): AccountTypeA totals row's account type: the enum, its value, or the PCG's guess from the number.
Base\Ledger\Repository
AccountRepository class
class AccountRepository extends ServiceEntityRepository AccountRepository.php:11
AccountRepository::__construct()
public function __construct(ManagerRegistry $registry)AccountRepository::findOneByNumber()
public function findOneByNumber(Book $book, string $number): ?AccountAccountRepository::chart()
public function chart(Book $book): array- Returns
the book's chart, by number
AccountRepository::findParent()
public function findParent(Book $book, string $number): ?AccountThe account $number belongs under: the book's account with the longest number $number starts with (401 for 4011, 41 for 4199).
BankAccountRepository class
class BankAccountRepository extends ServiceEntityRepository BankAccountRepository.php:11
BankAccountRepository::__construct()
public function __construct(ManagerRegistry $registry)BankAccountRepository::forBook()
public function forBook(?Book $book = null): array- Returns
every bank account, or a book's
BankAccountRepository::countForBook()
public function countForBook(Book $book): intBankConnectionRepository class
class BankConnectionRepository extends ServiceEntityRepository BankConnectionRepository.php:11
BankConnectionRepository::__construct()
public function __construct(ManagerRegistry $registry)BankConnectionRepository::forBook()
public function forBook(?Book $book = null): arrayBankLineRepository class
class BankLineRepository extends ServiceEntityRepository BankLineRepository.php:12
BankLineRepository::__construct()
public function __construct(ManagerRegistry $registry)BankLineRepository::findOneByExternalId()
public function findOneByExternalId(BankAccount $account, string $externalId): ?BankLineBankLineRepository::pending()
public function pending(BankAccount $account): array- Returns
the lines still to reconcile (unmatched or suggested), oldest first
BankLineRepository::latest()
public function latest(?BankAccount $account = null, int $limit = 50, ?BankLineStatus $status = null): array- Returns
a bank account's lines, newest first
BankLineRepository::countByStatus()
public function countByStatus(BankAccount $account): array- Returns
status value => count, for one bank account
BookRepository class
class BookRepository extends ServiceEntityRepository BookRepository.php:10
BookRepository::__construct()
public function __construct(ManagerRegistry $registry)BookRepository::resolve()
public function resolve(string $handle): ?BookA book by its id, SIREN or exact name: what the commands accept.
EntryLineRepository class
class EntryLineRepository extends ServiceEntityRepository EntryLineRepository.php:14
EntryLineRepository::__construct()
public function __construct(ManagerRegistry $registry)EntryLineRepository::openItems()
public function openItems(Book $book, ?Party $party = null, ?string $accountPrefix = null): arrayThe unlettered lines - what is still owed, either way - of the accounts under $accountPrefix (all when null), maybe one party's, oldest first.
EntryLineRepository::findByLetter()
public function findByLetter(Book $book, string $letter): arrayEntryLineRepository::lastLetter()
public function lastLetter(Book $book): ?stringThe last letter given in the book: letters run AAA, AAB... ZZZ, AAAA.
EntryLineRepository::totalsByAccount()
public function totalsByAccount(Book $book, ?\DateTimeImmutable $from = null, ?\DateTimeImmutable $to = null, bool $validatedOnly = false): arrayDebit and credit totals per account over [$from, $to] (either open).
EntryLineRepository::forLedger()
public function forLedger(Book $book, ?\DateTimeImmutable $from = null, ?\DateTimeImmutable $to = null, ?string $accountPrefix = null): arrayThe lines of the accounts under $accountPrefix over [$from, $to], by account then date: the general ledger's rows.
EntryLineRepository::sumUnder()
public function sumUnder(Book $book, string $accountPrefix, ?\DateTimeImmutable $from = null, ?\DateTimeImmutable $to = null, bool $openOnly = false): arrayDebit and credit totals of the accounts under $accountPrefix, all lines or only the unlettered ones.
EntryLineRepository::balance()
public function balance(Account $account, ?\DateTimeImmutable $to = null): intAn account's balance (debit minus credit), all dates or up to $to.
EntryRepository class
class EntryRepository extends ServiceEntityRepository EntryRepository.php:11
EntryRepository::__construct()
public function __construct(ManagerRegistry $registry)EntryRepository::findOneBySource()
public function findOneBySource(Book $book, string $source): ?EntryEntryRepository::lastNumber()
public function lastNumber(Book $book, string $prefix): ?stringThe highest number given with this prefix ("2026-"): numbers are zero-padded, so the greatest string is the last.
EntryRepository::findReversal()
public function findReversal(Entry $entry): ?EntryThe entry reversing $entry, if one does.
EntryRepository::forPeriod()
public function forPeriod(Book $book, \DateTimeImmutable $from, \DateTimeImmutable $to, bool $validatedOnly = true): arrayThe book's entries dated in [$from, $to], with their lines, in the order the FEC lists them (date, then number).
FiscalYearRepository class
class FiscalYearRepository extends ServiceEntityRepository FiscalYearRepository.php:11
FiscalYearRepository::__construct()
public function __construct(ManagerRegistry $registry)FiscalYearRepository::containing()
public function containing(Book $book, \DateTimeInterface $date): ?FiscalYearThe fiscal year of $book that $date falls in, if the book has one.
FiscalYearRepository::forYear()
public function forYear(Book $book, int $year): FiscalYearThe fiscal year closing in $year, else the calendar year $year (unsaved).
FiscalYearRepository::current()
public function current(Book $book, ?\DateTimeImmutable $date = null): FiscalYearThe fiscal year $date falls in, or its calendar year.
JournalRepository class
class JournalRepository extends ServiceEntityRepository JournalRepository.php:11
JournalRepository::__construct()
public function __construct(ManagerRegistry $registry)JournalRepository::findOneByCode()
public function findOneByCode(Book $book, string $code): ?JournalPartyRepository class
class PartyRepository extends ServiceEntityRepository PartyRepository.php:11
PartyRepository::__construct()
public function __construct(ManagerRegistry $registry)PartyRepository::findOneByReference()
public function findOneByReference(Book $book, string $reference): ?PartyPartyRepository::findByIban()
public function findByIban(Book $book, ?string $iban): array- Returns
the book's parties paying from (or paid to) this IBAN
PartyRepository::accountNumberTaken()
public function accountNumberTaken(Book $book, string $number): boolReconciliationRuleRepository class
class ReconciliationRuleRepository extends ServiceEntityRepository ReconciliationRuleRepository.php:11
ReconciliationRuleRepository::__construct()
public function __construct(ManagerRegistry $registry)ReconciliationRuleRepository::forBook()
public function forBook(Book $book): array- Returns
a book's enabled rules, highest priority first
Base\Ledger\Security
BookAccessInterface interface
interface BookAccessInterface BookAccessInterface.php:15
Who else may reach a book than ledger.admin_role: an application's service implementing this (autoconfigured, tag ledger.book_access) is asked by LedgerVoter - an associate viewing their company's book, an accountant managing their clients'. Any service saying yes is enough.
BookAccessInterface::canView()
public function canView(UserInterface $user, Book $book): boolBookAccessInterface::canManage()
public function canManage(UserInterface $user, Book $book): boolStateCipher class
class StateCipher StateCipher.php:15
Seals what a bank connection keeps (the provider's tokens, its user) with
sodium's secretbox (XSalsa20-Poly1305), under a key derived from
ledger.secret (the kernel secret by default): "v1:" + base64(nonce +
ciphertext). A sealed state that was tampered with, or sealed under
another secret, does not open.
StateCipher::__construct()
public function __construct(string $secret)StateCipher::seal()
public function seal(array $data): stringStateCipher::open()
public function open(string $sealed): array- Throws
BankException when it was not sealed by this secret, or was changed since
Base\Ledger\Security\Voter
LedgerVoter class
final class LedgerVoter extends Voter LedgerVoter.php:26
LEDGER_VIEW and LEDGER_MANAGE, on a Book or on anything of one (an entry, an account, a bank line: whatever has getBook()):
- ledger.admin_role (ROLE_ADMIN by default) views and manages every book;
- the application's BookAccessInterface services may grant a user a book (managing implies viewing).
Without a subject - the back office's ledger screens as a whole - only the admin role is granted.
const VIEW = 'LEDGER_VIEW'const MANAGE = 'LEDGER_MANAGE'
LedgerVoter::__construct()
public function __construct(readonly AccessDecisionManagerInterface $accessDecisionManager, readonly iterable $access = [], readonly string $adminRole = 'ROLE_ADMIN')LedgerVoter::bookOf()
public static function bookOf(mixed $subject): ?BookBase\Ledger\Service
Chart class
class Chart Chart.php:20
Gives a book its chart of accounts and journals from a file of config/charts/ (sci: the PCG accounts a French real-estate SCI uses). It adds what is missing and leaves alone what the book already has, so it can run again after the chart grows.
Chart::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly AccountRepository $accounts, readonly JournalRepository $journals, readonly string $defaultChart = 'sci', readonly ?string $chartsDir = null)Chart::seed()
public function seed(Book $book, ?string $chart = null): voidChart::definition()
public function definition(string $chart): arrayLettering class
class Lettering Lettering.php:18
Lettering: lines of one account and one party whose debits and credits cancel out - a rent notice and the transfer paying it - share a letter (AAA, AAB...) and stop being open items. Partial payments stay open until the rest arrives.
Lettering::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly EntryLineRepository $lines)Lettering::letter()
public function letter(iterable $lines, ?\DateTimeImmutable $on = null): stringLetters $lines together and returns their letter.
- Throws
LetteringException unless they balance, on one account and one party, none lettered yet
Lettering::unletter()
public function unletter(string $letter, Book $book): intTakes a letter off the lines carrying it in $book; returns how many.
Lettering::openItems()
public function openItems(Book $book, ?Party $party = null, ?string $accountPrefix = '411'): arrayThe unlettered lines of the accounts under $accountPrefix (411: what tenants still owe, and their payments not matched yet), maybe one party's, oldest first.
Lettering::nextLetter()
public function nextLetter(Book $book): stringThe letter after the book's last one: AAA, AAB... AAZ, ABA... ZZZ, AAAA.
Lettering::increment()
public static function increment(string $letter): stringParties class
class Parties Parties.php:20
The book's parties as the application knows them: partyFor() finds one by the application's reference (a lease, a supplier code) or creates it, with its auxiliary account under its kind's collective account - 411 + the reference for a tenant (411U3), 401 for a supplier, 455 for an associate, 467 otherwise.
Parties::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly PartyRepository $parties, readonly AccountRepository $accounts)Parties::partyFor()
public function partyFor(Book $book, PartyKind|string $kind, string $reference, string $name, ?string $iban = null): PartyParties::accountNumber()
public function accountNumber(Book $book, PartyKind $kind, string $reference): string411 + the reference, upper-case and alphanumeric (411U3), made unique in the book.
Posting class
class Posting Posting.php:31
The one way entries get into a book. post() checks what double-entry bookkeeping requires - balanced, one-sided non-negative lines, accounts and a journal the book has, a date outside the locked period - and refuses anything else with a PostingException, before writing a thing.
The same (book, source) twice gives back the entry already posted.
Validated, an entry is numbered "<fiscal year>-<000001>", in sequence per book and fiscal year, and frozen (ValidatedEntryGuard): it is corrected by reverse(), which posts its mirror image.
Posting::__construct()
public function __construct(readonly EntityManagerInterface $entityManager, readonly AccountRepository $accounts, readonly JournalRepository $journals, readonly EntryRepository $entries, readonly FiscalYearRepository $fiscalYears, readonly bool $autoAccounts = false, readonly ?LockFactory $locks = null)Posting::post()
public function post(EntryDraft $draft, bool $validate = true): Entry- Throws
PostingException the draft is not a valid entry of its book
- Throws
LockedPeriodException its date is in the locked period or a closed fiscal year
Posting::reverse()
public function reverse(Entry $entry, string $source, ?\DateTimeImmutable $date = null): EntryCancels a validated entry by its mirror image (debits and credits swapped), dated $date or the entry's own date, and returns it. The same $source twice gives back the reversal already posted.
Posting::validate()
public function validate(Entry $entry): EntryNumbers and freezes a draft entry.
Posting::assertOpen()
public function assertOpen(Book $book, \DateTimeInterface $date): voidThrows when nothing may be posted on $date in $book.
Posting::nextNumber()
public function nextNumber(Book $book, \DateTimeInterface $date): stringThe number the next validated entry of $book dated $date gets.
Base\Ledger\Twig
LedgerExtension class
final class LedgerExtension LedgerExtension.php:13
{{ 123456|ledger_money }} → 1 234,56 € (minor units, the locale's way); {{ 123456|ledger_amount }} → 1 234,56 (no currency: a column of a report); {{ iban|ledger_iban }} → FR76 •••• 4321.
LedgerExtension::money()
public function money(int|string|null $minor, string $currency = 'EUR', ?string $locale = null): stringLedgerExtension::amount()
public function amount(int|string|null $minor): stringLedgerExtension::iban()
public function iban(?string $iban): string