npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

a.l.e

v1.0.0

Published

Double accounting package based on node.js + sequelize

Downloads

43

Readme

A=L+E

Multi-currency double-entry accounting system based on node.js + Sequelize

Ale is based on the basic formula Assets = Liabilities + Equity and is insipred by Medici

Basics

ALE divides itself into "books", each of which store journal entries and their child transactions. The cardinal rule of double-entry accounting is that "everything must balance out to zero", and that rule is applied to every journal entry written to the book. If the transactions for a journal entry do not balance out to zero, the system will return a rejected promise.

Books simply represent the physical book in which you would record your transactions - on a technical level, the "book" attribute simply is added as a field in Transactions and JournalEntry tables to allow you to have multiple books if you want to.

Each transaction in ALE operates on a single account. Accounts are arbitrary string tags, but you can subdivide accounts using colons (or any other separator). Transactions in the Assets:Cash account will appear in a query for transactions in the Assets account, but will not appear in a query for transactions in the Assets:Property account. This allows you to query, for example, all expenses, or just "office overhead" expenses (Expenses:Office Overhead).

In theory, the account names are entirely arbitrary, but you will likely want to use traditional accounting sections and subsections like assets, expenses, income, accounts receivable, accounts payable, etc. But, in the end, how you structure the accounts is entirely up to you. Sub-accounts are also not matched explicitly, but by comparing the account name to query against the beginning of the account name. Thus Trades will match Trades:USD, but not Assets:Trades.

Configuration

ALE tries to be agnostic as to which RDMS you have running on the back-end. Therefore, you need to ensure that the relevant DB bindings are installed and part of your package.json file.

For PostgreSQL, this would entail

yarn add pg

You must set an ALE_CONNECTION environment variable which holds the connection string to connect to the underlying database. To date, ALE has been tested against PostgreSQL, but any DB supported by Sequelize (SQLite, MySQL etc) should work.

export ALE_CONNECTION=postres://ale_user@localhost:5432/trading_db

The user and database must exist before importing ALE. For an example of how to ensure this in code (for PostgreSQL), see the test setup function.

Coding Standards

ALE is written in JavaScript. All database queries return promise objects instead of using the traditional node function(err, result)callback.

Writing journal entries

Writing a journal entry is very simple. First you need a book object:

const { Book } = require('a.l.e');

// The first argument is the book name, which is used to determine which book the transactions and journals are queried from.
// The second argument is the currency to report all queries in.
return Book.getOrCreateBook('ACME inc', 'USD').then(book => {
    ...
});

Now write an entry:

// You can specify a Date object as the second argument in the book.entry() method if you want the transaction to be for a different date than right now
const newEntry= book.newJournalEntry('Received payment');
newEntry.debit('Assets:Receivable', 500)     // The currency and exchange rate default to 'USD' and 1.0 if omitted
        .credit('Income:Rent', 500, 'USD');  // debit and credit return the entry to allow chained transactions
entry.commit().then((e) => {                 // Validate and commit the entry to the DB, returning a promise
   assert.equal(e.memo, 'Received payment');
});

You can continue to chain debits and credits to the journal object until you are finished. The entry.debit() and entry.credit() methods both have the same arguments: (account, amount, currency, exchangeRate).

Querying Account Balance

To query account balance, just use the book.balance() method:

myBook.balance({ account:'Assets:Accounts Receivable' }).then((balance) => {
    console.log("Joe Blow owes me", balance.total);
});

balance returns a promise that resolves to the the following creditTotal - the sum of credits (in quote currency), debitTotal, balance - The current balance, in quote currency, currency - the currency the results are reported in, numTransactions - The total number of transactions making up the balance calculation.

Retrieving Transactions

To retrieve transactions, use the book.ledger() method (here I'm using moment.js for dates):

const startDate = moment().subtract('months', 1).toDate(); // One month ago
const endDate = new Date(); //today

myBook.ledger({
    account: ['Income', 'Expenses'] // Both sets of accounts will be included
    start_date: startDate,
    end_date: endDate
}).then((result) => {
    // Do something with the returned transaction documents
    result.count // The number of transactions found
    result.transactions // An array of transactions found
});

Voiding Journal Entries

Sometimes you will make an entry that turns out to be inaccurate or that otherwise needs to be voided. Keeping with traditional double-entry accounting, instead of simply deleting that journal entry, ALE instead will mark the entry as "voided", and then add an equal, opposite journal entry to offset the transactions in the original. This gives you a clear picture of all actions taken with your book.

To void a journal entry, you can either call the voidEntry(book, void_reason) method on a JournalEntry instance document, or use the book.voidEntry(journalId, void_reason) method if you know the journal document's ID.

myBook.void("123456", "I made a mistake").then(() => {
    // Do something after voiding
})

The original entry will have its voided field set to true and counteracting transctions will be added with the original memo suffixed with [REVERSED]

Calculating unrealized profit with markToMarket

You can calculated unrealised profit/losses due to changes in exhange rates by calling markToMarket and giving it a hash of exchange rates:

const book = new Book('Forex test');
book.markToMarket({ account: ['Trading', 'Assets:Bank'] }, { ZAR: 20, USD: 1 }).then(result => {
   assert.equal(result['Trading:ZAR'], 500);
   assert.equal(result['Trading:USD'], 600);
   assert.equal(result['Assets:Bank'], -1650);
   assert.equal(result.unrealizedProfit, -550);
});

Changelog

  • v0.1.0 First release