lifeos handbook
Apps

Finloop

A double-entry ledger with envelopes, bills, projects and a month-end statement.

Finloop answers four questions, in order of how often they get asked: what do I have, where did it go, what do I owe whom, and was that job worth doing.

The ledger underneath everything

Every movement of money is a transaction with postings and lines.

  • Postings say which accounts moved and by how much. They must sum to zero.
  • Lines say what the money was for — the category breakdown.
  • The invariant is sum(postings) == sum(lines), checked on every write.

LedgerService.post() is the only code path that changes an account balance. Not one of several — the only one. A balance that can be written from two places is a balance that will eventually disagree with its own history.

A TRANSFER may carry zero lines: moving money between your own accounts is not spending and has nothing to categorise. Every other kind of transaction requires at least one category line.

Read more: the ledger.

Accounts, including the ones that are not money

Seven account types. Three of them are not spendable and exist to model obligation rather than cash:

TypeWhat it isSpendable
CASH, BANK, MOBILE_MONEY, SAVINGSactual moneyyes
DEBTwhat you owe someoneno
RECEIVABLEwhat someone owes youno
PREPAID_VENDORa balance sitting with a vendorno

Modelling a debt as an account rather than a separate table means repaying it is an ordinary transfer, and its history is the same history everything else has (ADR-0010).

Envelopes

Virtual, over a single pooled balance — not separate accounts. An envelope is a claim on money you already have, so filling one moves nothing and cannot fail. What it changes is what the app is willing to call free to spend (ADR-0012).

Read more: envelopes.

Bills and repayments

A recurring bill produces a pending item on its due date and nags until you confirm it. It never posts the money itself — the system must not claim money moved when it did not (ADR-0014).

A debt repayment plan is a recurring bill that settles an account. Rent and pay Beth 20,000 on the 5th are the same shape: due on a date, nags until you say you paid. So they share one nag mechanism, one escalation job and one confirm dialog. The difference is what gets posted — a bill posts an expense with a category line; a repayment posts a transfer with no lines at all.

That distinction is load-bearing. A repayment booked to a category would inflate every month's spending by the size of your debts, and nothing on screen would look wrong. A test pins it.

Projects

A project is a client job with an agreed value, so the screen can answer was this worth doing rather than merely what did this cost. Profit is shown twice — cleared so far, and expected on completion.

There is no cached profit column. Profit is derived from the postings on every read, so a project cannot drift from the ledger.

Installments are the parts of a project: agree 900,000 in three, and each part becomes an expected income row. Because the allocation wizard already reserves against expected income, installments become obligation-aware for free.

The statement

One page per calendar month, computed live from the postings: opened with X, earned Y, spent Z by category, closed with W, what the envelopes did, and how it compares with last month.

  • There is no stored statement table, so it cannot drift from the ledger.
  • Closing is derived as opening + movement, not queried separately — a page that says "opened with X, Y moved, closed with Z" then cannot print three numbers that fail to add up.
  • The residual is shown, not swallowed. Money leaves your spendable accounts for reasons that are not spending: a debt repayment, an opening balance, money into savings. Without its own line the month would appear to lose money nobody spent.
  • The comparison is uncoloured on purpose. Spending less is not automatically good and earning less is not automatically a failure. Arrows say which way it went; the judgement is yours.

On this page