013 Lead

UBBFY

Lead architect on a multi-tenant ERP, CRM and HR platform in production for many clients.

Project
2025 to present
Stack
Django / DRF / Vue 3 / Celery / PostgreSQL / Stripe / FedaPay

About ten companies run their operations on it, the largest of them managing two hundred employees and contractors. The platform is thirty-three server-side modules, 338 data models and close to four hundred screens, served in three languages: KPS, Planus, Flowplan, Gotolearn and People&Skill are the tenants in production.

I came onto a project already under way, written in Laravel on MySQL, and the first decision was to restructure it onto Django and PostgreSQL. Migrating data is thankless work that bills you late: timestamps carried over from the old database came in an hour behind, and that kind of gap never shows in review, it shows in production on somebody's timesheet.

The parts that decided the architecture were the ones nobody sees. Role-based access built to be configured rather than coded, so a new tenant does not mean a new deployment. An event bus on Celery, so a module can react to another without importing it. An audit trail, because a platform holding several companies' operations has to be able to answer what happened and who did it. Billing runs through Stripe and FedaPay together, which is what serving both European and West African clients actually requires.

What I did not see coming was internationalisation. I did not design for it, and retrofitting it onto a written codebase took the development environment down for days. The problem was not translation, it was the boundary: interface strings are mine, content typed by a tenant is theirs and has to exist in languages I do not choose. A notification now leaves in the language of whoever reads it rather than whoever triggered it. That is a decision I take on the first day of a project now, not in the sixth month.

The floor is held by tests: 3,146 on the server, 468 end-to-end runs through the browser.

Architecture

Business application

Vue 3, 35 modules, close to four hundred screens

Platform administration

Vue 3, tenants, roles, settings

HTTPS, token in an HttpOnly cookie

Django REST API

33 modules, 338 models, per-module permissions configured rather than coded

events published

Celery and Redis bus

a module reacts to another without importing it

Audit trail

old and new value, on every write

writes

PostgreSQL

MinIO

objects, signed URLs

Stripe and FedaPay

Europe and West Africa

Two separate applications in front of one API: an operations team and a business user do not need the same screens, and merging them would have produced one interface compromised for both. Modules talk through the bus instead of importing each other, so one can be added without touching the rest, and every write leaves a trace.

UBBFY dashboard: headcount, absences, net payroll and pending leave, with the month’s payroll close and the week’s calendar.
What an administrator lands on: what they need to know, and the thing they came to do. Closing payroll is a button because it is the task of the month, not a menu entry to go and find.
UBBFY roles screen: ten roles, each with its permission count and its type, and a button to create another.
Permissions configured rather than coded. Each role carries its count of per-module permissions, and a new one is created from this screen: that is what makes a new client something other than a new deployment.

Contents

022

UBBFY v2, the plan

Five workstreams that carry the platform into 2027, estimated in days and ordered by what depends on what.