@classytic/assets
v0.5.0
Published
Fixed-asset register + valuation engine on @classytic/mongokit — acquisition, multi-book depreciation (6 methods, half-year/mid-period conventions), IAS 16 revaluation, IAS 36 impairment, IAS 8 estimate changes, market/appraisal value history, disposal wi
Readme
@classytic/assets
Fixed-asset register + valuation engine on @classytic/mongokit.
Acquisition → multi-book depreciation → valuation → disposal, covering the full spectrum from personal net-worth tracking (market value of a property, gold, a vehicle) to IFRS-grade business accounting (IAS 16 revaluation, IAS 36 impairment, IAS 8 estimate changes) — with pure integer-minor-unit calculators that are audit-clean (schedule sums match the depreciable basis to the cent, no floating-point drift).
- Mongokit repositories ARE the domain layer. Inherited CRUD /
pagination / aggregation;
claim()FSM transitions;claimVersion()on every carrying-amount write; arccreateAdapterwiring for free. - Six depreciation methods — straight-line, declining balance, double-declining, sum-of-years-digits, units-of-production, manual — plus first-period conventions (half-year rule, mid-month proration).
- Dual books.
accountingdrives asset totals + the GL bridge;tax(or any host-defined book) is a pure projection with its own method / life / rate. - Valuation layer. Append-only value history (market, insurance,
tax-assessed, liquidation) with appraiser + evidence refs;
currentValue()resolves book or market value at any point in time; revaluation / impairment / estimate changes rebase unposted periods prospectively — posted history is immutable. - Optional ledger bridge (
LedgerPort, rule-23 host adapter) with deterministic idempotency keys — the non-atomic post sequence converges on crash-retry instead of double-posting. - Arc-standard events: 15 events, Zod catalog on
./events, P8 outbox dispatch, §P8.1 unit-of-work with post-commit flush.
Install
npm install @classytic/assetsPeers: @classytic/mongokit >=3.19, @classytic/repo-core >=0.8,
@classytic/primitives >=0.11, mongoose >=9.4, zod >=4.
Quick start
import { createAssets, ensureAssetsReady } from '@classytic/assets';
const assets = await createAssets({
connection: mongoose.connection,
bridges: { ledger: myLedgerBridge }, // optional — runs open-loop without
eventTransport: arcTransport, // optional — in-process bus fallback
// tenant: true, // opt-in; default = company-wide register
});
await ensureAssetsReady(assets);
const ctx = { actorId: 'user-1' };
const asset = await assets.repositories.asset.acquire({
assetNumber: 'VEH-0001',
name: 'Delivery Van',
category: 'vehicle',
cost: { amount: 2_000_000, currency: 'USD' }, // $20,000.00 in cents
salvageValue: { amount: 200_000, currency: 'USD' },
depreciation: { method: 'straight_line', periods: 60 },
}, ctx);
const id = String(asset._id);
await assets.repositories.asset.activate(id, ctx);
await assets.services.depreciation.generate(id, { firstPeriodFraction: 0.5 }, ctx);
await assets.services.depreciation.postPeriod(id, 0, {}, ctx);
// Tax book — DDB over 5 periods, projection-only:
await assets.services.depreciation.generate(id, {
book: 'tax', methodOverride: 'double_declining', periodsOverride: 5,
}, ctx);
// Valuation — personal/insurance tracking and accounting events:
await assets.repositories.valuation!.record(id, {
kind: 'market', value: { amount: 2_400_000, currency: 'USD' },
appraiser: 'ACME Appraisals',
}, ctx);
await assets.services.valuation!.impair(id, {
recoverableAmount: { amount: 1_200_000, currency: 'USD' },
}, ctx);
const { value } = await assets.services.valuation!.currentValue(id, { kind: 'market' }, ctx);
// Disposal with realized gain/loss + GL derecognition:
const { gainLoss } = await assets.repositories.asset.dispose(id, {
proceeds: { amount: 1_000_000, currency: 'USD' },
}, ctx);Domain verbs
| Surface | Verbs |
|---|---|
| repositories.asset | acquire, activate, dispose, retire, writeOff, transfer, loadAsset (tag OR ObjectId) + inherited CRUD |
| services.depreciation | generate (per book, conventions, overrides), postPeriod, rebase |
| repositories.valuation | record (market/insurance/tax_assessed/liquidation), latest + inherited CRUD |
| services.valuation | revalue (IAS 16), impair / reverseImpairment (IAS 36), changeEstimate (IAS 8), currentValue |
| engine | withTransaction (P8.1 UoW), syncIndexes, ensureAssetsReady |
Carrying-amount model
bookValue = grossCarryingAmount − accumulatedDepreciation − accumulatedImpairment- Revaluation (elimination approach): gross ← fair value, accumulated
depreciation and impairment reset to 0, cumulative
revaluationSurplustracked on the asset. Unposted periods rebase over the new base. - Impairment: gross unchanged,
accumulatedImpairmentgrows, book value drops to the recoverable amount; reversals are capped at the accumulated loss. - Posted history is immutable — every prospective change (estimate,
revaluation, impairment) re-projects only
scheduledrows;postedrows and the GL never rewrite.
Ledger bridge (host-side, rule 23)
Implement LedgerPort.postEntry and honor idempotencyKey as a dedupe
key (return the existing entryRef). Kinds: depreciation, disposal
(plus revaluation / impairment if your GL maps them — the domain
events carry before/after/surplus for that decision). Reference adapter:
be-prod/src/resources/assets/asset.adapters.ts.
Arc wiring
defineResource({
name: 'asset',
adapter: createAdapter(assets.models.FixedAsset, assets.repositories.asset),
actions: {
activate: { handler: (id, body, req) => assets.repositories.asset.activate(id, req.scope, body?.placedInServiceAt) },
dispose: { handler: (id, body, req) => assets.repositories.asset.dispose(id, body, req.scope) },
// …retire / writeOff / revalue / impair follow the same shape
},
});Register events: for (const def of assetsEventDefinitions) registry.register(def);
(import from @classytic/assets/events). Zod route schemas live on
@classytic/assets/schemas.
Tests
npm test — 64 tests: pure-calculator unit suites (audit-clean
invariants), convention proration, event-catalog no-drift, and
mongodb-memory-server (replica set) integration: full lifecycle, valuation
scenarios, dual books, tenant probe, ghost-event/unit-of-work.
Trademark
Code is MIT-licensed. "Classytic"/"arc" names + logos are trademarks of Classytic LLC — see TRADEMARK.md.
