You buy a laptop for €2,160 and register it as an asset with a three-year life. In most accounting tools, the fixed-asset module now owns that laptop: it stores the cost, generates a 36-line amortization table, and posts €60 of depreciation into the general ledger each month. The register is the source of truth, and the books are made to match it.
This works until someone touches the books directly. Your accountant posts a year-end depreciation entry themselves. An opening-balance import books accumulated depreciation the module never heard about. Someone deletes one of the generated entries. Now the register says the laptop is worth €1,440 and the trial balance says €1,380, and both are presented as the truth. Tools built this way all grow a "reconcile register to GL" report to manage the disagreement, but the disagreement comes from the architecture: two tables both claim to own the same number.
We have added a fixed-asset register and a depreciation engine to Financica, and we wanted to rule this failure mode out. The rule: the double-entry ledger stays the only source of truth, so the register stores no amounts. No cost column, no accumulated depreciation, no net book value; all three are computed from the ledger when read.
Two kinds of facts
A fixed asset is two kinds of facts.
Amounts are ledger facts. What the laptop cost, how much has been depreciated, what remains. The ledger can express all of these, so the register must not store them. In our data model the asset is a dimension on ledger lines: each journal-entry leg can be attributed to an asset, the way a leg already belongs to an account. The asset's cost is the sum of attributed legs on its asset account. Accumulated depreciation is the sum of attributed legs on the contra account. Net book value is one minus the other.
Policy facts describe intent the ledger cannot express: useful life, depreciation method, first-year convention, residual value, the date the asset entered service. That is what the register stores, along with the asset's name and its link to the purchase invoice. Residual value is an amount, but it is an estimate of the future, and the ledger records what happened, not what you expect to recover, so it belongs on the policy side.
Attribution is editable state, and it is the one place drift could hide: re-attributing a leg changes an asset's derived history. The difference is that attribution is visible on the ledger rows themselves, and balances on asset accounts that are not attributed to any asset surface in the books health check, the same way uncategorized transactions do.
The engine is a reconciler
Each month, per asset, the engine derives a schedule from the policy: anchored at the in-service date, over the cost basis the attributed legs add up to, using the asset's method and first-year convention. From that schedule it knows the expected accumulated depreciation to date. It compares that against the accumulated depreciation actually in the books and posts one entry for the shortfall, dated the last day of the period. It never reverses anything: if the books hold more than the schedule expects, the engine posts nothing and waits for the schedule to catch up. And it only posts automatically for open periods; a shortfall that belongs to a closed period is a prior-period question, so the engine flags it for review instead of posting it.
When you change the policy (a revised useful life, a new residual value), the schedule re-anchors from the current book value over the remaining life. That is the prospective treatment accounting standards prescribe for changes in estimate. A genuine error, like a materially wrong posting in a filed year, is a prior-period correction and stays your accountant's call; the engine never blocks a correcting entry, and it carries on from whatever the ledger says afterwards.
An example. The €2,160 laptop enters service on 15 January with a monthly convention, so January counts as a full month and the schedule says €60 per month. You only register the asset in Financica in April, and your accountant already posted a €180 first-quarter dotation attributed to it. The April run compares expected accumulated depreciation (€240 through April) with the books (€180) and posts €60 dated 30 April. No January-to-March catch-up entries, and the accountant's €180 is left alone.
Now the divergent case. Had the accountant booked €150 instead, the April run would post the full €90 shortfall, and that is deliberate: the schedule follows the statutory rate, and quietly under-depreciating against it is a mistake more often than a choice. When it is a choice, the fix is the policy, not a fight over entries: extend the useful life and the schedule re-anchors from the current book value. Delete a generated entry in an open period and the next run finds the shortfall and posts it again, for the same reason. And in the closed-quarter variant, where the first quarter was filed with only €120 booked, the engine posts April's own €60 and flags the €60 that belongs to the closed quarter for review.
The last posting for an asset is whatever brings accumulated depreciation to exactly cost minus residual, so rounding never leaves a cent behind. Concurrent engine runs are serialized per organization, so the engine cannot race itself; a person racing it in the same period at worst produces an over-depreciation, which resolves through the engine's own quiescence rule. The examples here are straight-line; declining-balance, where a country allows it, runs through the same comparison.
Because everything above reads from the ledger and only appends to it, migration also comes free: attribute the legs of your opening-balance entry to the asset and the engine picks up from wherever your previous books left off.
What the entries look like
Ordinary journal entries, nothing else: a debit on the depreciation expense account (a 6302 dotation for tangible assets on the Belgian PCMN), a credit on the accumulated-depreciation contra account (the matching 2x9 "amortissements actés" account), one entry per month covering all assets, each leg attributed to its asset. The accounts come from your own chart, picked by the asset’s category. In an open period you can delete or adjust these entries like any others; in a closed one the database refuses, whoever is asking. Disposal generates the full unwind (depreciation up to the disposal date, derecognition of cost and accumulated depreciation, and the gain or loss on the 663/763 result accounts) and shows you the entry before posting.
The trade-offs
There are two downsides.
First, there is no "edit the cost" field, and we could not give you one. If amounts live only in the ledger, changing an amount means posting a journal entry. The register’s own actions work this way too: you give an asset its cost by attaching the purchase line to it, and you dispose of it by posting the unwind. That is a real constraint, not a hidden one, and it is stricter than a form field would be. A cost change that skips the ledger is the same silent edit that caused the drift described above, so we kept it.
Second, reading the register means aggregating ledger lines instead of selecting a row. For a business with two hundred assets this is not measurable. If it ever becomes so, the fix is a derived cache that can be thrown away and rebuilt from the ledger at any time.
The general rule
Developers will recognize the shape: derived views over a primary record, instead of copies that need syncing. We would not call the ledger an event log; entries in an open period are editable, and that is by design. The rule is narrower and older than event sourcing: a feature added to an accounting system should be entries in the books or a view over them, never a second place amounts live. Depreciation looks like it demands its own amount tables. It needs a policy record and a reconciler.
What shipped
The fixed-asset register and depreciation engine are live on the Scale and Professional Plus plans. Register an asset in three fields, attach the purchase line that gives it its cost, and the monthly entries post themselves. Defaults for the useful life, the method and the first-year proration come from a per-country rule set, Belgium first.
One note on those defaults, because it is the kind of thing that decides whether a feature is useful or merely present. Our design document said Belgian small companies could take a full first-year annuity. That was true until the 2020 corporate-tax reform, which removed it: small companies now pro-rate the first year like large ones, in principle by day, and declining balance was abolished for companies while surviving for sole traders. We only caught it because the rule that a country rate must be researched and cited before it is written down forced us to look. A rate remembered rather than sourced is a bug with a plausible face.
The practical guide is in the documentation.