Accounting for Developers, Part 1: Money

It's been some time now that I want to learn accounting fundamentals.

There are several books and internet articles about the topic, but I never found any study material that was created specifically for developers, that also included code examples.

I've actually found 4 articles called "Accounting for Developers", but they don't provide code examples:

Approach

I'll not try to write about the history of accounting, its ramifications or why it is useful. To learn about those topics, I recommend reading the Wikipedia article about accounting.

This series of articles is focused on explaining accounting principles using code examples writen in Typescript. This approach is good to help developers understand the concepts.

We aim to have a simple, functional accounting system at the end of the series, capable of doing basic tasks.

The following topics are non-goals:

Dealing with Money Programmatically

An accounting system manipulates money. So, it's very important to chose a correct strategy when dealing with it, in order to avoid some common pitfalls.

A very common mistake when dealing with money is the obsession for primitive types. Treating monetary values like they are just simple numbers is a mistake. We also need to consider currency conversions, rounding, internationalization (i18n) and localization (i10n).

Most programming languages have libraries that implement the ISO 4217 standard and solve those issues. As examples, we can use dinero.js for Javascript and Joda-Money for Java. I'll not try to explain how to install and use those libraries, since this is not the focus here.

We will use dinero.js. The first thing we need to do in our system is to create a wrapper around this dependency. This article shows why this is important.

src/money.ts
import Dinero from "dinero.js";

export class MoneyFacade {
    private _wrappedObj: Dinero.Dinero;

    constructor(private _currency: Currency, private _valueInMinorUnits: number) {
        this._wrappedObj = Dinero({
            amount: this._valueInMinorUnits,
            currency: this._currency,
        });
    }

    add(other: MoneyFacade) {
        return this.fromWrappedLibInstance(this._wrappedObj.add(other._wrappedObj));
    }

    subtract(other: MoneyFacade) {
        return this.fromWrappedLibInstance(this._wrappedObj.subtract(other._wrappedObj));
    }

    multiply(factor: number) {
        return this.fromWrappedLibInstance(this._wrappedObj.multiply(factor));
    }

    divide(factor: number) {
        return this.fromWrappedLibInstance(this._wrappedObj.divide(factor));
    }

    isLessThan(other: MoneyFacade) {
        return this._wrappedObj.lessThan(other._wrappedObj);
    }

    isLessThanOrEqualTo(other: MoneyFacade) {
        return this._wrappedObj.lessThanOrEqual(other._wrappedObj);
    }

    isEqualTo(other: MoneyFacade) {
        return this._wrappedObj.equalsTo(other._wrappedObj);
    }

    isGreaterThanOrEqualTo(other: MoneyFacade) {
        return this._wrappedObj.greaterThanOrEqual(other._wrappedObj);
    }

    isGreaterThan(other: MoneyFacade) {
        return this._wrappedObj.greaterThan(other._wrappedObj);
    }

    format(locale: string) {
        return new Intl.NumberFormat(locale, {
            style: "currency",
            currency: this._currency,
            // in order to keep the code simple, let's assume all currencies have
            // minor units that are 1/100 of the major unit
        }).format(this._valueInMinorUnits / 100);
    }

    private fromWrappedLibInstance(obj: Dinero.Dinero) {
        return new MoneyFacade(obj.getCurrency(), obj.getAmount());
    }
}

export type Currency = Dinero.Currency;

export default function money(currency: Currency, valueInMinorUnits: number) {
    return new MoneyFacade(currency, valueInMinorUnits);
}
// example
const aThousandDollars = money(1000_00, "USD");
const twelveHundredDollars = aThousandDollars.add(money(200_00, "USD"));
console.log(twelveHundredDollars.format("en-US")); // $ 1,200.00

Cool! Now that we encapsulated our dependency we can go ahead and start writing our system.

Now, go ahead and read the part 2!