Case study · Operations software

Deptmaster

A debt-collection platform where no one — not even the administrators — can quietly change the numbers.

Dozens of field collectors, multiple wallets, thousands of client accounts. Every action recorded for good, a mobile app that works with no signal, and code the client owns.

RustFlutterEvent sourcingOffline mobile
Admin panel overview: totals and debt over time
App home: total balance, chart and outstanding balances
Automated tests
230206 are full scenarios: several people posting events at once, plus stress tests.
Built and live in
3 monthsFrom our first meeting to the system running their business.
In the field
No signal neededCollectors post payments anywhere; the app syncs when it's back online.
Since go-live
No major bugs reportedBy the client. They've asked only for interface tweaks, which we made.

The problem

Money moving fast, through many hands, with no memory.

Collectors handled cash; some managed accounts; some did both. Wallets were shared, approvals were verbal. When something looked off, nobody could answer: who changed that, when, and from what?

What they asked us for
A permanent history Every transaction, kept for good. Nothing editable after the fact.
Fine-grained permissions A team posts for its own clients and can't even see another team's wallet.
Works with no signal A mobile app for the field that syncs when it reconnects.
Owned outright Full source code. No SaaS, no vendor to depend on.
Before we wrote a line of code
“They came in asking for logs everywhere. We told them: make the data structure the log.”
What we found · balances only (example)
ClientBalance
#48211,450,000 1,200,000
#3307860,000

Change a number and the old one is gone. Who, when, why: unknown.

What we built · every action, kept (example)
  1. Tue 14:32 · Collector A+250,000#4821
  2. Tue 16:05 · Finance−500,000#4821
  3. Wed 09:10 · Collector C+120,000#3307

Balances are computed from this list. Nothing is overwritten, ever.

How it works

The history is the ground truth.

Events in, balance out
+250,000 · Collector A−500,000 · Finance+1,450,000 · opening
Balance #4821 1,200,000 computed · cached

A cached balance looks wrong? Replay the events and rebuild it. Corrections are new events, never edits.

Rules written once, run everywhere
Shared Rust coreevery business rule, one source file
ServerAxum · Postgres · the final word
Mobile appFlutter · SQLite · works offline

Two codebases drift apart; one can't. The client doubted Rust at first, so we proved it: 230 tests, most of them full scenarios with several people acting at once, plus stress tests.

In the office

The dashboard

Wallets, teams and clients in one place, every number traceable back to the actions that produced it.

Admin panel: totals, debt over time and the events log
The admin panel. Every number at the top traces back to the events log below it.
In the field

The collector's app

Collectors post payments where they stand, signal or not. Work queues on the phone and syncs the moment it reconnects.

App home: total balance, chart and outstanding balances Contacts list with what each one gave or received One client's balance and transaction history Adding a transaction with amount, date and due date
Home 1 / 4 The balance at a glance.
Wallet
Team · e.g. Sellers
Contact group · e.g. North district
readcreateupdatedeleteclose
Example: this team can read, create and close, but not delete.
Permissions

The part nobody thinks about until they need it.

  1. Wallet-wide — who adds members, renames, edits the rules.
  2. Team to team — "Sellers Admin" runs Sellers, not Finance.
  3. Contact groups — which teams can file clients where.
  4. Action-level — read, create, update, delete, close: per team, per group.

Every permission change is itself a recorded event.

Beyond the audit log

Servers that check each other.

Several servers compare cryptographic fingerprints of their event history. To forge a record you'd have to rewrite every server and every device at once, and even then every node raises the alarm, stops taking new events and alerts the admins. The apps keep working offline until the servers agree.

Available in our custom builds.

a3f9…c2 a3f9…c2 a3f9…c2
Going live

How we switched without putting their business at risk.

Why not just switch over?A one-day switch is a bet: if anything breaks on day one, collections stop. Running both systems took the bet off the table and made launch day a non-event.
  1. 1Move the dataA migration script copied everything out of the old database and reshaped it for the new system.
  2. 2Run both, side by sideFor almost a week the old system stayed on alongside the new one. If anything looked wrong (a bug, a mismatch, a case we hadn't seen), staff could go straight back to it while we fixed it.
  3. 3Switch the old one offNobody ever needed to fall back. The old system was retired, and Deptmaster runs the business.
From first contact to delivery
  1. Day oneFirst contact
  2. Week oneAudit of the existing system
  3. Month oneClient reviews a working build
  4. Month threeFull delivery, in production
What the client got

Six things they didn't have before.

  • Full ownershipOne binary on their own server. No cloud, no subscription, no vendor.
  • Permanent recordEvery action recorded for good. Disputes have an answer: replay the history.
  • Multiple walletsEach with its own teams, contacts and rules, fully isolated.
  • Offline mobile appQueues work and syncs when reconnected.
  • Fine permissionsA team lead runs their own team and nothing else.
  • Add-on servicesDue-date reminders, SMS and WhatsApp statements, office tools: beside the core, not inside it.
For developers
  • Rust — server (Postgres + Axum) and client logic layer
  • Flutter — mobile UI via Flutter Rust Bridge (FFI)
  • SQLite — local client database
  • Event sourcing — every action recorded; balances computed, not stored
  • Shared Rust crates — business logic written once, compiled into both server and app
Open source on GitHubThe open version of this system: the full code, to read or run yourself.github.com/dark-cli/deptmasterLicensePolyForm Noncommercial 1.0.0