Spotless Enterprise ERP
- Client
- Spotless
- Industry
- Facility management & contract workforce
- Year
- 2025–present
A complete operations system for a contract workforce and facility management business: staff records, Ethiopian-calendar payroll, client contracts, site billing, supply leasing and a maker-checker approval queue over all of it.
1
System of record, not four
0
Re-entry steps between payroll and the bank ledger
50+
Screens under one permission model
What it covers
- HR & employee lifecycle
- Leave accrual & expiry engine
- Ethiopian-calendar payroll
- Contracts & site billing
- Supply leasing & stock
- Bank ledger & expenses
- Maker-checker approvals
- Roles, permissions & audit log
The business, and why spreadsheets ran out
Spotless places cleaning and security staff on client sites across Addis Ababa and manages the supplies those sites consume. Both halves are people-heavy and site-heavy: dozens of contract staff, each tied to a site, a department, an employment status and a salary that moves with bonuses, overtime, deductions and pension.
Every one of those facts used to live in a spreadsheet, and every one of them was also a decision somebody had to remember: which leave days had expired, which site had already been billed, whether the mop stock at Piassa was on hand or out on a site.
What the system does
People. A full employee register: identity documents, guarantors, family, work history, education, position, department and site, with the whole lifecycle from hire to termination. Attendance is recorded per site and per period.
Leave that actually reconciles. Annual leave is accrued, not typed in. Each employee earns a grant on their employment anniversary, sized by a service-length bracket, and each grant carries its own expiry. Days are always spent oldest-grant-first, so the days closest to expiring are used before newer ones. The balance ledger settles every change as the difference between what a leave cost before and what it costs after, which is what stops an already-approved leave from being charged twice when somebody edits its dates.
Payroll on the Ethiopian calendar. Salary runs per month and per site, with overtime types, deduction types, pension classes, income tax bands and VAT/withholding rules all configurable rather than coded. Payslips, bank transfer lists and receipts come out the other end.
Money. A signed bank ledger: every movement is tied to the transaction that caused it, positive in and negative out, and the account's running balance moves by exactly that amount inside the same database transaction. The history of a transaction always sums to what the balance says.
Clients and sites. Customers, the sites they contract for, the contracts themselves, monthly payments, penalties, discounts, commission and renewals, with payment requests and a full payment history per contract.
Supplies as assets, not stock. The company owns its supplies whether they sit in the store or at a client site, so a lease is a location change, not a sale. The system tracks four figures (on hand, reserved, leased out and total owned) and moves a lease through pending → approved → issued → partially returned → returned → closed, writing the movement rows, the running total and the event log together in one transaction.
Approval on everything. A maker-checker queue covers every entity that carries approval fields, from a new employee to a payment request. One registry, one queue, one set of actions: the entity is data, and a single route serves all of them.
Permissions. Role-based, with per-user overrides. A super admin is not a flag but a derivation: someone holding every permission that exists. Add a new permission to the system and anyone who does not also receive it stops being a super admin automatically.
The point
Nothing here is novel on its own. What makes it work is that the same event is recorded once: a leave approval moves the ledger, a lease issue moves the stock, a payment moves the bank balance. Nobody reconciles anything at month end, because there is only one set of books to reconcile against.
How it was built
- Framework
SvelteKit
Server load functions, form actions and endpoints; SSR by default.
Svelte 5
Runes for reactivity ($state, $derived, $effect).
- Language
TypeScript
Strict mode across app and server code.
- Data
MySQL
Primary datastore.
Drizzle Kit
Committed SQL migrations rather than push-to-prod diffing.
Drizzle ORM
Typed schema and queries, with migrations generated from the schema.
- Auth
Argon2
Password hashing.
- Forms
Superforms
Progressive-enhancement forms with server-side validation.
Zod
One schema validating both client and server.
- UI
Chart.js
Reporting charts.
Dark mode
System-aware theme switching.
Lucide
Icon set.
TanStack Table
Sorting, filtering and pagination for admin tables.
Bits UI
Headless primitives behind the component library.
Tailwind CSS
Utility-first styling with a project design token layer.
- Reporting
jsPDF
Server-side PDF generation for reports and receipts.
jsPDF AutoTable
Tabular report export.
- Testing
Vitest
Unit tests over business logic.
- Infrastructure
Client image compression
Uploads compressed in the browser before hitting the server.
Nodemailer
Transactional email over SMTP.
Inside the system