← All work
Case study

Sole Ledger

The bookkeeping app I built to run my own company across two tax systems: time, expenses, invoices and a live tax reserve, with no accountant in the day-to-day loop.

Sole Ledger screenshot
Role
Everything: product, the cross-border tax model, Flutter app
Stack
Flutter · Dart · Riverpod · Drift (SQLite) · pdf · ML Kit
Ships as
Android + macOS desktop + web, from one codebase
Data
Offline-first, single-file SQLite, synced with Syncthing. No server
How it came together
  1. 01

    Why I built it

    I run a Dutch eenmanszaak while living as a tax resident in Japan, invoicing EU business clients. That combination makes 'do your own books' awkward: two tax systems, two currencies, and a VAT treatment that isn't the one most people assume.

    Every off-the-shelf tool solved one slice and left me stitching the rest together by hand each month. I wanted one app that took the recurring friction out of logging time, capturing expenses, cutting invoices and knowing what to set aside, so that running the business stopped eating the hours I'm paid for.

  2. 02

    The build

    One Flutter codebase to Android, macOS and web, offline-first on a local SQLite database. Money is never a float: integer minor units throughout, and tax rounds once per rate group. Time entries flow into invoices: pick a client, and unbilled hours pull in as lines. Those render to a tri-lingual PDF with the correct legal wording for that client's VAT case.

    Expenses are a tap and a photo. On-device OCR pre-fills amount, tax, date and currency for me to confirm; it never auto-saves, because they're my books. A fixed-asset register handles depreciation properly and feeds the right yearly slice into deductions. A dashboard card runs year-to-date profit through a Japanese income-tax estimate, so I always know the reserve.

  3. 03

    What was hard

    The load-bearing part wasn't code, it was the tax model. When a client says 'add tax on top' they mean VAT, not my income tax, and for a Japan-based supplier billing EU businesses that resolves to a 0% reverse-charge invoice, with income tax settled separately in Japan. Getting that right, and having the app teach it rather than quietly do the wrong thing, drove the whole invoicing design.

    The other half was trust and portability. The entire ledger is one SQLite file, so Syncthing keeps laptop and phone in step with no server. That only worked after getting the database path right per platform, and being honest about where it can't work at all (the web build's sandboxed storage).

In screenshots

What this means for your project

Product engineering in a domain with unforgiving rules: a cross-border tax model, offline-first data I trust with my own money, three platforms from one codebase, and the dozens of small friction-removing decisions that make a tool something I open weekly instead of a demo I abandoned.

FlutterDartRiverpodDrift (SQLite)pdf / printingML Kit OCRgen-l10n