01 / OVERVIEW
FrankLucy gives a multi branch distributor one place to sell, watch stock, manage staff, and balance the cash drawer. Stock, money, and permissions are all resolved on the server inside database transactions, so the numbers in the interface are the same numbers in the ledger. Every sale, receipt, transfer, payout, and correction leaves a trail showing who did it, when, and what changed.
02 / PROBLEM
Distributors typically run sales, stock, and cash in separate notebooks or spreadsheets, so no single number can be trusted. A crate sold from the shelf never reaches the inventory file, the cashier counts the drawer days later and the variance cannot be explained, and a customer who owes money exists only as a line in a book. When disputes happen there is no record of who adjusted a price, moved stock between branches, or approved a correction. Manual numbering of invoices also collides the moment two branches issue numbers on the same day.
03 / SOLUTION
The platform puts point of sale, a per branch inventory ledger, inter branch transfers, attendance, customer credit, and drawer reconciliation under one login with one identity model. Stock is held in base pieces, so selling one crate and one bottle from the same cart can never double count, and a partially available crate is reported honestly as full crates plus loose pieces. Money is handled with decimal arithmetic and prices are read from the database at the moment of sale, so a tampered request cannot change what a customer is charged. Each branch runs its own atomic sequence for invoice, transfer, and drawer numbers, so identifiers stay unique and gap free without a locking service.
04 / ARCHITECTURE
A Next.js App Router frontend renders server components for data heavy screens and client workspaces for the interactive ones. Those screens call route handlers, which do nothing but validate input and delegate, so all business logic lives in service modules under src/modules. Services check the caller's permissions and branch scope on every call, then run writes inside a single Prisma transaction against PostgreSQL, so a sale cannot record an invoice without also decrementing stock and writing its audit entry. zod validates every payload at the boundary, decimal.js keeps all money exact, and idempotency keys make a retried checkout return the original invoice rather than charging twice. Sessions are HMAC signed cookies compared in constant time, with bcrypt password hashing and a permissions table queried fresh on each request rather than trusted from a token.
05 / TECHNICAL DECISIONS
- Resolve prices, stock, permissions, and branch scope on the server for every request, so a modified browser cannot change what is charged or which rows it can read.
- Hold all inventory in base pieces and derive crate quantities through a per product conversion ratio, so mixed unit sales reconcile to one number.
- Run every multi table write in a single transaction, so an invoice, its stock movements, and its audit entry either all land or none do.
- Treat the audit log as a first class table written inside those transactions, rather than as logging added later, because an unbalanced drawer is only explainable if the trail is trustworthy.
- Keep business rules in framework free service modules that take plain objects and return plain objects, so the whole domain can be exercised against a real database without a browser.
06 / FEATURES
- Point of sale that sells by crate or by piece from the same cart, with server resolved pricing and a piece price derived from the crate price when no explicit one exists
- Idempotent checkout keyed per request, so a double click or a network retry returns the original invoice instead of creating a second sale
- Per branch atomic sequences for invoice numbers, transfer references, and drawer session numbers
- Goods receiving that captures supplier and delivery reference and posts an immutable movement per line
- Damage and write off logging with a mandatory reason, deducted from available stock and attributed to the employee who logged it
- A movement ledger that separates opening stock, purchases, sales, transfers in and out, damage, expiry, and adjustments per branch and product
- Reorder levels per product per branch, with low stock and healthy stock filters and a low stock count on the dashboard
- Inter branch transfers that decrement the source and increment the destination in one transaction and log movement on both sides
- Cash drawer sessions opened against a declared opening float, with only one open session allowed per cashier
- Authorized drawer payouts for petty cash, safe drops, logistics, supplies, and owner withdrawals, blocked when they exceed the physical cash in the drawer
- Server computed expected cash and expected transfers at close, presented as two separate variances so a banking shortfall is never confused with a cash shortfall
- Cash handover from one cashier to the next with pending and received states recorded against the reconciliation
- Customer accounts with credit limits, due dates, outstanding balance, and recorded repayments that clear debt against the ledger
- Attendance clock in and clock out evaluated against each branch opening time, closing time, and grace period in Nigerian business time, producing present, late, incomplete, or early leave statuses
- Correction requests for attendance that preserve the original time and require a manager to approve or reject before the status is recomputed
- Five staff roles across 36 granular permissions, with both the navigation and the endpoints filtered per role, and owners granted every branch
- Branch scoping so a user only reads and writes inside branches they are actively assigned to, enforced in the service layer
- Price change history capturing the old price, the new price, who changed it, and why, whenever a manager or owner adjusts a branch price
- Settings for branches, employees, roles, transfers between branches, product catalog, per branch bulk and piece pricing, and eleven units of measurement from crates to kegs
- Printable invoices in A4 and thermal layouts carrying the company registration number, tax id, bank remittance details, and payment terms
- A forensic audit log storing actor, action, entity, branch, reason, and before and after values, written at 29 separate points across the domain
- A daily dashboard showing revenue, cash in till, low stock alerts, attendance, and the most recent activity needing attention
- An eight phase integration suite that exercises sales, receiving, transfers, damage, attendance, credit, and the audit trail against a real database