accountifie-svc
v1.1.5
Published
A simple REST ledger service with running balances. Perfect for use with accountifie.
Downloads
37
Readme
To install: npm install accountifie-svc -g
To run: PORT=5124 MONGO_URL=mongodb://localhost:27017/accountifie accountifie-server
To test: npm test
A few-frills REST ledger server built to support Accountifie, but also works as a standalone server.
Basic features:
- stores ledgers for multiple companies.
- stores transactions.
- groups a transaction's balancing entries across multiple accounts.
- generates balances for an account at a given date.
Special features:
- balance caching for faster balance retrieval.
- multi-day transactions for cases like depreciation.
- fetching of transaction-list state at a given point in time. Useful for troubleshooting discrepencies. in a report for a given date caused by back-dated or updated transactions.
- filtering of balances to exclude transactions for selected counterparties or contra accounts.
Accountifie is a django frontend with advanced permission and reporting features. It provides a platform for you to create custom objects for your business's domain that result in generated transactions, and tools for tailoring your own financial reports with advanced export options. More information is available on github.
Ledgers and Accounts and Transactions, oh my!
The world consists of General Ledgers, also known as GLs. Each GL contains sets of Transactions and Accounts.
A Transaction contains Lines, each with a balance and an Account. All a Transaction's Lines should balance to 0. A Line's Contra Accounts are the Accounts of all other Lines for that transaction. A Transaction may occur on a single date or, to avoid highly repetitive transactions like depreciation, may be spread across a period (referred to as a multi-date transaction).
An Account contains references to all Transactions that have at least one Line for that Account. An Account also has a Balance at any date. The Balance is calculated by summing the Account's Line amounts up to that date.
The models also contain references to Counterparties and Business Model Objects (BMOs) which are stored and returned for convenience, but not really used by the service (except for filtering).
How it works
The service's focus has been on operational speed, especially when calculating balances. All objects are kept in memory, sometimes at the cost of startup time. Changes are persisted to mongo using the event sourcing pattern provided by sourced. Most implementation details have been spec'd out in the tests.
Persistence with sourced
The system uses sourced for persistence, a library that persists and replays method calls with their passed params to restore an entity's (in our case GeneralLedger) state. This can have some impact on startup time, though uses snapshotting to minimise the effect.
Sourced has the advantages of leaving a comprehensive audit trail, and enabling us to recreate the state of the system
at a given timestamp - something we've built upon with the
/gl/:LEDGER_ID/snapshot/transactions
endpoint.
The best way to see a ledger's state via the database is in the GeneralLedger.snapshots
collection. To get the latest
snapshot, query by {id: LEDGER_ID}
, sort by {version: -1}
and limit to 1 result.
Balance generation
Generating balances based on thousands of transactions has some impact on performance, especially as we're using BigNumber to
prevent floating point arithmetic issues. To improve performance, we use a
Balance Cache for each
account at the monthly anniversary of the account's first transaction. Generating a balance for a date then involves only
adding the transactions between the date and the Balance Cache immediately prior to the date. To make things even snappier
we also cache the result of every balance request that's processed, so the second time you hit /gl/myco/balances?date=2015-01-13
will probably be faster than the first.
Whenever a transaction is added, updated, or deleted, all caches with dates after the transaction are invalidated. Regenerating the
caches can take seconds and, since node is unithreaded, get in the way of processing requests. To overcome this, the task is split
into microtasks (one per cache) which are placed on the
Low Priority Queue
(aka the LPQ). The LPQ processes a microtask then sleeps for a bit so requests can be processed. This means the balance caches
could take a few minutes to generate after CUDing some old transactions, resulting in slowed balance fetching, but this is unlikely
to have much real-world impact. You can monitor the status of the LPQ at
/lpq/stats
.
Balances may be requested with a Filter that excludes Counterparties or Conta Accounts, or limit to a set of Counterparties. Filtered transactions are unable to use the cached balances generated for unfiltered transactions, nor is a filter able to use the cached balances for a different filter, as a different set of transactions may be used to calculate the transactions. Because of this, each balance cache is also associated with a filter.
By default, only unfiltered transactions have balance caches generated automatically, though all requests will have the balance
cached against the filter provided in the request. You can get caches to generate automatically for a filter by using the
/gl/:LEDGER_ID/add-filter
endpoint.
Multi-day transactions
Some Transactions, like depreciation, occur over a period instead of on a given date. These transactions have a dateEnd
as well
as a date
. When a balance is calculated on a date that falls part-way through the period, it will contain only the portion of
the transaction's amount for the period that has lapsed. For instance, for a transaction with
date='2015-01-01', dateEnd='2015-01-04', amount='4.00'
:
- a balance on
2015-01-01
would include only $1 for that transaction. - a balance on
2015-01-02
would include $2 - a balance on
2015-01-03
would include $3 - balances on
2015-01-04
and beyond would include all $4
When requesting a list of transactions for a period that contains only a portion of the transaction, the transaction's amount will
reflect the amount during the overlap and the transaction's date
or endDate
will be adjusted so the transaction fits within
the specified period. E.g. a transaction with date='2015-01-01', dateEnd='2015-01-04', amount='4.00'
:
- for
/gl/myco/tranactions?from=2015-01-02&to=2015-01-10
will returndate='2015-01-02', dateEnd='2015-01-04', amount='3.00'
- for
/gl/myco/tranactions?from=2014-12-01&to=2015-01-02
will returndate='2015-01-01', dateEnd='2015-01-02', amount='2.00'
- for
/gl/myco/tranactions?from=2015-01-02&to=2015-01-03
will returndate='2015-01-02', dateEnd='2015-01-03', amount='2.00'
For reporting convenience, you can provide the chunkFrequency=end-of-month
param to
/gl/:LEDGER_ID/transactions
, which
will break a multi-day transaction into multiple transactions at each month's boundary.
Version history
- v1.1.4 - 2016-12-02 - Add /gl/:LEDGER_ID/cpBalances
- v1.1.3 - 2016-05-18 - Verify unique ID when using /gl/:LEDGER_ID/transaction/:TX_ID/create
- v1.1.2 - 2016-04-08 - Prevent snapshot generation if snapshot already exists
- v1.1.1 - 2016-03-17
- Return error if no data available for /gl/:LEDGER_ID/snapshot/create
- Attempt to use semver properly - the last release should have had a minor version bump. This is a patch.
- v1.0.10 - 2016-03-16
- Add /gl/:LEDGER_ID/snapshot/balances
- Move /gl/:LEDGER_ID/snapshot-transactions to /gl/:LEDGER_ID/snapshot/transactions
- Move /gl/:LEDGER_ID/take-snapshot to /gl/:LEDGER_ID/snapshot/create and require a date be provided
- Add 'excludeTags' option to /gl/:LEDGER_ID/transactions
- Accept 'withTags' and 'excludeTags' filter options to /gl/:LEDGER_ID/balances
- v1.0.9 - 2016-03-01 - Fix digest of deleteBmoTransactions.
- v1.0.8 - 2016-02-26 - Fix cconfig bug when globally installed.
- v1.0.7 - 2016-02-26 - Add /gl/:LEDGER_ID/bmo-transactions/:BMO_ID/delete