Case Study

RetailMS — Motorcycle Dealership Management System

Django · HTMX · Tailwind CSS · SQLite · Python

Production web application · Solo project · ~6 months · Status: Live

// the problem

The Problem

My client runs a motorcycle dealership in Nigeria. Before this system existed, the entire business ran on paper ledgers, phone calls, and memory.

Customers in this business don't typically walk in and pay the full price for a motorcycle in one transaction. They pay installments over weeks or months — deposits toward an agreed price. The dealer had to manually track how much each customer had deposited, how much of that was committed to a specific model, and how much was available for withdrawal. This was being done in notebooks.

On the inventory side, motorcycles arrive from suppliers in crates. Before they can be sold individually, they need to be uncrated and assigned unique engine and chassis numbers. There was no system linking this assembly process to the stock count or to the cost that should be attributed to each unit.

The financial picture was equally fragmented. Profit margins per sale were guesswork — the business had no way to know the actual cost of the specific units sold because all inventory was valued at a blended average. When a sale was reversed or voided, restoring the books accurately was nearly impossible.

The result was a business operating with no real-time visibility into deposits, inventory, or profitability — and no audit trail for who changed what and when.

// what i built

What I Built

RetailMS is a full-stack web application that manages the complete operational lifecycle of the dealership. It runs locally on a Windows desktop — no internet dependency, no server costs, no IT staff required.

01

Customer & Deposit Accounts

Every customer gets a deposit account created automatically on signup. The account tracks total balance, allocated funds (committed to a purchase agreement), and available balance in real time. Customers can deposit, withdraw, or allocate money toward a reservation for specific models at agreed prices.

02

Purchase Agreements

When a customer wants to reserve a motorcycle model, an agreement locks a portion of their deposit at a fixed price. The system tracks units ordered vs. delivered and prevents withdrawal of committed funds. If an agreement line item is amended, a new version is created — preserving history rather than overwriting it.

03

Two Sales Paths

A normal sale handles direct cash or bank transfer transactions. An agreement fulfillment converts a reservation into a sale, automatically deducting the agreed price from the customer's deposit and generating the withdrawal audit trail. Both paths produce the same structured records, so reporting is consistent.

04

FIFO Inventory Costing

Every goods receipt creates a cost layer — a record of units received at a specific unit cost (purchase price + apportioned delivery charges). When units are sold, the system depletes the oldest layers first, records exactly which layers were consumed and in what quantity, and stores the total cost basis against the sale. Every sale has a precise, traceable profit figure.

05

Transformations (Uncrating & Assembly)

When a boxed motorcycle is uncrated and assembled, the system creates a serialized unit with unique engine and chassis numbers. The FIFO cost from the source batch carries forward to the assembled unit, plus any assembly service fee. These units can then be sold individually with full cost traceability.

06

Supply Chain & Audit

Purchase orders are raised against suppliers, goods are received (with delivery costs apportioned across units), and supplier payments are tracked. Every action in the system is logged with user, timestamp, IP address, and a structured detail record.

// technical decisions

Technical Decisions Worth Explaining

01

Why HTMX instead of React or Vue

This system runs on a single machine for a small team. A full JavaScript frontend framework would add build complexity, a separate API layer, and a larger maintenance surface — none of which serve this deployment context. HTMX gives a SPA-like experience with Django doing all the rendering. The entire body is boosted, so navigation and form submissions are AJAX by default. Modals, inline formsets, search filters, tab switching, and dashboard refreshes all work through HTMX partial rendering. The result: a fast, responsive interface with roughly 490 lines of custom JavaScript.

02

Why FIFO and not Weighted Average Cost

Motorcycle imports are batch-based. A shipment in March may cost significantly more than the same model received in January, because exchange rates and shipping costs fluctuate. A blended average obscures this — you might think you made ₦50,000 profit on a sale when the real margin on that batch was ₦30,000. FIFO tracks the actual cost of the exact units sold. Weighted Average Cost still runs in parallel as a summary metric for dashboards — the two coexist, serving different purposes.

03

Why a bridge table for layer consumption

The BoxedSaleLayerConsumption table records exactly which FIFO layers were consumed by each sale line. This exists specifically to make sale voids accurate. Without it, reversing a sale would require reconstructing which batches were consumed — impossible if other sales happened in between. With the bridge table, voiding a sale restores each consumed layer's remaining quantity precisely, without affecting any other transaction.

04

Why SQLite and not PostgreSQL

Single-machine deployment. SQLite is a single file, needs zero configuration, and is entirely sufficient for a local-only application with one concurrent writer. The system uses select_for_update() on deposit accounts and inventory rows to prevent race conditions. If the client ever needs multi-machine access, migration to PostgreSQL is straightforward.

// challenges

Challenges That Were Worth Solving

Select2 + HTMX Integration

Select2 creates its own DOM elements outside the select tag it enhances. HTMX swaps DOM content without knowing about those generated elements. The combination produced three bugs: orphaned dropdown containers after swaps, dropdowns closing immediately due to scroll events in formset rows, and ghost event listeners blocking search on newly added rows. The solution required destroying Select2 instances before each HTMX swap, mounting all dropdowns to document.body, and using requestAnimationFrame with a deduplication flag to control re-initialization timing.

Retroactive FIFO Migration

FIFO costing was introduced partway through development. Inventory already existed in the system — units received before cost layers existed. I added a synthetic layer fallback: if the depletion algorithm finds insufficient layer coverage, it creates a temporary layer at the current Weighted Average Cost to bridge the gap. The void system also handles three code paths — sales with full layer consumption records, sales with only a cost basis figure, and sales with neither — to support records created before, during, and after the migration.

Dashboard Profit Calculation

The dashboard went through three rounds of bug fixes: using current WAC instead of sale-time cost basis (inflating or deflating profit depending on price changes); double-counting assembly service fees in COGS for serialized units; and treating supplier payments as operating expenses instead of inventory asset reclassifications. Each fix required understanding the distinction between cash flow and profit-and-loss in an inventory-based business.

// by the numbers

By the Numbers

~24.5k

Lines of Python

27

Database models

63

View functions

70

HTML templates

82

HTMX partials

62

Migration files

1,065

Lines of tests

6

Django apps

// outcome

What the Business Can Do Now

  • See every customer's deposit balance, allocated amount, and available funds in real time
  • Track exactly which motorcycle units came from which supplier batch, at what cost
  • Know the precise profit on every individual sale — not an approximation
  • Reverse any transaction cleanly, with the books staying accurate
  • Start the entire system by double-clicking a desktop icon, with automatic updates when internet is available
  • See who did what and when, for every action taken in the system
Interested in working together?

Let's build something that actually works.

I build systems like this for businesses across Nigeria. If you have a workflow problem or a product idea, let's talk.