Skip to content
VED.EXE

200Finance, cross-platform

Accounic

A multi-currency personal ledger. Three clients, one database, and the database does the maths.

Role
Sole developer.
Year
2026
Status
v1.16.1. Windows installer, portable zip, Android APK and a web client.
Stack
Next.js 15, React 19, Flutter 3.29, Riverpod, PostgreSQL, Supabase, Tailwind v4
Accounic web dashboard: net position, receivable, payable and settled totals, and cash in hand per currency.
clients on one schema: web, Android, Windows
3
CI workflows: web, Flutter, SQL, demo
4
tenant-isolation checks over real HTTP
71
floats in the money path
0

(57) Abstract

Accounic answers four questions and little else: who do I have an account with, how much do they owe me, how much do I owe them, and what has been settled. It runs on the web, Android and Windows from one Supabase schema.

Background

Money lent between friends, family and small businesses lives in notebooks and chat threads. Currencies mix, partial repayments pile up, and nobody agrees on the balance. Accounic makes the balance a fact the database computes, instead of a number each app adds up for itself.

Drawings

  1. FIG. 1One PostgreSQL schema owns balances, validated writes and tenant isolation. Three clients read it with the user's JWT.
  2. FIG. 2From the README. net_balance above zero means they owe you; below zero, you owe them.
  3. FIG. 3The web client's dashboard. Every figure on it is read from the database's views; the page adds nothing up.
  4. FIG. 4The activity journal, day by day. Settlements sit beside the credits they close; neither edits the other.
  5. FIG. 5People, each with their own balance. A real counterparty's name is redacted here; it is their data, not mine.
PostgreSQL / Supabaseprofiles · peopletransactions · settlements200202RLS: tenant isolation204views: balance engine206RPCs: validated writesanon key + user JWT210Next.js web+ service role, admin only212Flutter Androidreal + demo214Flutter Windowsreal + demo
FIG. 1One PostgreSQL schema owns balances, validated writes and tenant isolation. Three clients read it with the user's JWT.
The accounting model
total_credit           = Σ credit transactions       (not void)
total_debit            = Σ debit transactions        (not void)
settled_in             = Σ settlements direction in  (not void)
settled_out            = Σ settlements direction out (not void)

outstanding_receivable = total_credit - settled_in
outstanding_payable    = total_debit  - settled_out
net_balance            = outstanding_receivable - outstanding_payable
FIG. 2From the README. net_balance above zero means they owe you; below zero, you owe them.
Accounic web dashboard with net position, receivable, payable, settled, and cash in hand split by currency.
  1. 220Net position, computed in the database
  2. 222Lent less borrowed, last 30 days
  3. 224Receivable, payable, settled
  4. 226Cash in hand in INR, as entered
  5. 228The same, in AED, never re-priced
FIG. 3The web client's dashboard. Every figure on it is read from the database's views; the page adds nothing up.
Accounic activity journal: credit, debit and settled totals for 30 days, then entries grouped by day.
FIG. 4The activity journal, day by day. Settlements sit beside the credits they close; neither edits the other.
Accounic people list with one account's name redacted, showing a receivable balance and an account that is up to date.
FIG. 5People, each with their own balance. A real counterparty's name is redacted here; it is their data, not mine.

Detailed description

The database computes every balance

No client adds up a column. Balances come from views (person_balances, owner_summary) and every write goes through validated RPCs. money.ts and money.dart only format and parse, and they are mirrored line for line.

Integer minor units, per currency

₹100.50 is stored as 10050. How many minor units make a major one is a property of the currency: the yen has none, the Kuwaiti dinar has three. There is no float anywhere in the money path.

Entries keep the currency they were entered in

An amount in another currency is converted at the door, and the row keeps what was actually handed over: the original amount, its currency, the rate, when it was taken and where it came from. A later rate move never touches a recorded transaction. Live rates come from open.er-api.com with the ECB, via Frankfurter, behind it.

Settlements never edit history

A settlement records money that actually moved and reduces the outstanding side. It never edits or deletes the original transaction. Each transaction's settled or open status is allocated first in, first out, with targeted settlements taking priority.

Isolation the client never decides

Row-level security is forced on every table and the anon role holds no EXECUTE in public. The demo build is the same binary with one dart-define; a profiles.is_demo flag only changes what the interface offers, never what an account may read.

Releases a person approves

CI builds a release on a tag and leaves it as a draft. Installed apps check GitHub Releases on launch and only offer published builds. v1.16.1 fixed an updater that could pick the demo installer, because GitHub lists assets alphabetically and the check took the first .exe it saw.

What is claimed is:

  1. 1.

    A ledger in which every balance is computed by the database and no client adds up a column.

  2. 2.

    The ledger of claim 1, wherein amounts are integer minor units whose scale is a property of the currency.

  3. 3.

    The ledger of claim 2, wherein each entry keeps its original currency, rate and rate source, and later rate changes never alter it.

  4. 4.

    The ledger of claim 1, wherein one schema serves a web client, an Android client and a Windows client through the same validated RPCs.

  5. 5.

    The ledger of claim 4, wherein installed clients only ever offer a release a person has published.