Case study

Fiscplus

A multi-tenant SaaS portal that replaces a Belgian accounting firm's email and spreadsheet workflow with one auditable system of record. Three role-scoped portals, real-time collaboration, and a complete Belgian anti-money-laundering module, built and run by one engineer.

Type
SaaS · Web App
Role
Solo Full-Stack Build
Built
December 2025 to present
Client
Confidential, Belgian accounting firm

Stack

React 19 · Vite · NestJS 11 · Prisma 7 · PostgreSQL · Redis / BullMQ · Socket.io

Private client project, source and URL withheld
01

How it started

A Belgian accounting firm found me on Fiverr in December 2025 and described how they worked. Fiscal periods ran on email threads, shared drives, spreadsheets and manual chasing. Client onboarding, document collection, VAT and tax cycles, and anti-money-laundering compliance each lived in a different tool. The real state of any client was whatever someone could piece together from their inbox that morning.

In regulated work that costs more than time. The professional body for Belgian accountants and the Belgian anti-money-laundering law require auditable identification, screening and record-keeping. Scattered tools cannot guarantee that trail. A missed recheck or an unrecorded decision is exposure, and the accountant is personally liable for it.

So the brief was one system of record. The firm, its staff and its clients working from the same data, with every meaningful action leaving a trace. My job was to make the compliant path the easiest one to take.

02

What it does

One codebase serves three role-scoped portals. Admin runs the firm: clients, companies, staff, periods, deadlines, reports, settings and the audit log. Assistant is a scoped mirror for delegated work, limited to the clients and companies a person is assigned to. Client is a self-service space where a business owner switches between their companies, uploads what is missing, answers questionnaires and follows their own deadlines.

The day-to-day work of an accounting firm runs through it. A guided onboarding wizard with a deadline. Missing-document checklists with item-level status, comments and a review queue. Versioned questionnaires. Period instances for monthly and quarterly VAT cycles, from open to approved to archived. Inventory counts, a document vault, configurable deadline types, reminder generators, scheduled reports, an internal knowledge base and bulk data import.

It also connects to the tools the firm already pays for. The Belgian company register and the EU VAT service validate every company. Billit handles invoicing. Wolters Kluwer Adsolut syncs accounting documents. Validation rules compare all three sources and surface the differences as something a person can act on.

03

The anti-money-laundering module

This is the largest part of the system by a wide margin, and the reason the firm needed bespoke software at all. Belgian accountants are gatekeepers under the anti-money-laundering law, and the professional body inspects their files. The module implements that obligation as a workflow rather than a folder of PDFs.

Every client gets a dossier that moves through a defined lifecycle. The firm creates it from a company number, and the system pulls the company from the Belgian register, checks its VAT identity, reads the official gazette for dissolutions and statute changes, and runs acceptance checks against refused sectors and high-risk countries. The client then completes their own onboarding: identity documents read by OCR, declarations, beneficial owners, a politically-exposed-person self-declaration, evidence upload and an electronic signature. The same person can appear in several uploaded documents, so the module merges them into one record before anything is screened.

Screening runs each person against sanctions lists, politically-exposed-person lists and adverse media through an external provider. A four-pillar risk model then combines geography, customer type, services and transactions into a risk level, with every contributing factor recorded so the answer is explainable later. High-risk decisions go through a compliance officer and a second reviewer before they take effect.

Compliance is not a one-time form, so the module treats it as a living process. It generates engagement letters and the professional body's model forms, schedules periodic rechecks at a frequency set per dossier, runs continuous vigilance in the background, and raises alerts that must be resolved rather than dismissed. There is an annual report, a whistleblowing channel, and an investigation file per dossier with automated retention.

All of it is built to be defended. Every external lookup is stored with its full response, including the failures, because being unable to screen is itself a fact the firm must prove it recorded. A separate compliance audit log captures every decision with the data it was made on. An interceptor stops a review from being disclosed to the person under review.

04

Real time and background work

Three portals work the same data at once, so the system has to keep them honest. An administrator approves a document, an assistant updates a client's status, a client uploads a missing file. Each change has to appear everywhere it matters without anyone hitting refresh.

Every mutation emits a domain event. A WebSocket gateway broadcasts it to the portal rooms that care, and each portal refreshes only the data that changed. The person who made the change does not get an echo. The same channel carries permission changes, so revoking someone's access takes effect in their browser immediately rather than on their next login.

The slow work runs in queues and scheduled jobs. Onboarding side effects, document generation, email and SMS delivery, accounting sync and compliance checks all leave the request cycle. Scheduled jobs send deadline reminders, sweep dossiers for vigilance, purge expired records and back up the database nightly, all on Brussels time.

05

Architecture

Fiscplus is a React 19 and Vite single-page app talking to a modular NestJS API over REST and WebSocket. TanStack Query holds server state and Zustand holds session state. Data lives in PostgreSQL through Prisma, with Redis for cache, queues and pub/sub, BullMQ for background jobs, and S3-compatible storage for files behind signed URLs.

Each backend feature is a vertical slice that owns its controller, service, validation and tests. The compliance module is a set of such slices under one roof. The frontend mirrors the split, with a shared core and one folder per portal.

It is type-safe end to end. TypeScript strict on both sides, Prisma-generated types flowing from the database through the service layer, and the same shapes enforced at the API boundary at runtime and at compile time. A shape change is a compile error rather than a production surprise. Migrations are idempotent, and a drift check between schema and migrations blocks a pull request.

The interface ships in English and Dutch. Every user-facing string goes through translation, and Dutch runs a quarter longer than English, which shaped the layouts.

06

Security and access

Access control is granular. Permissions are grouped by category and resolved in three tiers: a per-user override first, then the person's system role, then a coarse fallback. A firm can delegate exactly. One senior assistant may approve documents but not touch compliance decisions, without inventing a new role for every exception. The backend enforces every permission, and the interface hides what a person cannot do.

Authentication uses short-lived access tokens with rotated refresh tokens, optional two-factor authentication with backup codes, trusted devices, phone verification for sensitive flows, revocable sessions and login alerts on new devices. Sensitive fields are encrypted at rest. Every public endpoint is rate limited, with tighter limits on login and the whistleblowing channel.

Every sensitive mutation writes to an append-only audit trail. Client records, compliance decisions, document approvals, permission changes and period transitions. Each entry captures the actor, the timestamp, the before and after state and the originating request, so a compliance review reads straight off it. Nothing in the application updates or deletes those rows.

07

Key decisions

Four choices shaped the whole system. Each one serves the same goal, software a regulated firm can trust.

Auditability by default

The audit trail is not a feature that some modules opted into. Every sensitive write goes through it, and the compliance module keeps a second log of its own. When the professional body inspects a file, the evidence is already assembled.

Defensible, not just compliant

Every external lookup is stored with its response, every risk score keeps its contributing factors, and high-risk decisions need a second pair of eyes. The question the module is built to answer is not whether the check ran, but whether the firm can prove what it knew at the time.

One data model, three views

Admin, assistant and client are not three apps. They are three portals over the same records and the same API, which is what keeps them from drifting apart as the system grows.

Type-safe boundary

The same schema validates a form in the browser and the request on the server, and database types flow through to the query hooks without manual casting. A shape change fails the build instead of a customer's afternoon.

08

The outcome

The build started in December 2025. The first production release went out in the middle of 2026, and the system has shipped regular updates since, each one recorded in a changelog and rolled out through staging first.

The firm now runs its client work, its fiscal periods and its anti-money-laundering obligations on a single system it owns outright, code included. When an inspector asks for a record, the answer is a search rather than a hunt through inboxes.

Software like this usually comes from a large vendor on a long timeline. This came from one engineer working directly with the people who use it, without cutting the corners regulation does not allow.

Up Next

Kernel

A Mac app that turns Claude Code into a small software team.

Mac App · Developer Tool