← All projects

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.

The Spotless employee register, listing staff by department and site

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

Svelte
TypeScript
MySQL
Drizzle
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

Employee register listing staff by department, site and employment status
The staff register: filterable, sortable, exportable, and the entry point to each employee’s leave, salary and documents.
Transaction list showing payments in Ethiopian-calendar dates
Every payment carries its bank, its method, its receipt and the person who recorded it. Dates are Ethiopian throughout.
Supplies inventory with stock levels
Supplies are tracked as owned assets: on hand, reserved, leased out and total owned are four different numbers.
Supply lease records and their status
A lease moves pending → approved → issued → returned → closed, and each step writes stock movement and an event log together.
Pending requests queue awaiting approval
One approval queue serves every entity in the system, because the entity is data rather than a hand-written screen per table.
Customer list with contract status
Clients, the sites they contract for, and the contracts underneath them.
Client site list
Each site links its staff, its contract, its monthly payments and the supplies leased to it.
Role and permission configuration
Roles are built from a permission table, with per-user overrides. Super admin is derived, never a flag.
Back to all projects