#dukasync#readiness-sprint#systems-design

DukaSync - Readiness Sprint Documentation

Target: Inventory & Restocking Coordinator for Small Kenyan Retailers

01. Problem Statement

Small electronics, hardware, and pharmacy shops in Kenya track inventory by memory or a paper notebook. There is no reliable trigger for reordering — staff usually notice a shelf is empty only after a customer has already walked away, and by the time someone remembers to call the supplier, the sale (and sometimes the next few) is already lost. The owner has no visibility into which items are moving fast, which are dead stock, or whether a supplier has even acknowledged a restock request.

02. Solution Overview

DukaSync is a lightweight Ecommerce-lite system. When a user orders something the quantity of the order is reduced from the product database that single action does double duty: When the product goes under the quantity threshold the supplier receives a restocking message via whatsapp as the main channel, closing the loop from "shelf is empty" to "supplier has been asked" without anyone needing to remember to make a call.

03. System Architecture

3.1 Stack, and why each piece was chosen

LayerChoiceWhy
FrontendReact + TailwindA fast, thumb-friendly customer "checks out" screen and an owner Dashboard (stock levels, order status, sales trend).
BackendFastAPI (NodeJS)Async I/O so an outbound SMS/webhook call never blocks someone logging a sale at the till; Pydantic validation because a silent off-by-one in stock math means either a missed reorder or a wasted one; fast to iterate on in a one-week build.
Database & Async JobsMongoDB | BullMQ + RedisThe core relationships (shop product → supplier order) are relational by nature, and need transactional guarantees to avoid overselling. BullMQ + Redis is the v2 path once notification volume justifies a dedicated worker; not worth the operational overhead at pilot scale.
NotificationsTwilio, WhatsApp APISMS works on any phone with no data plan required matches how the target shops actually operate.
AuthEmail + PIN, scoped to order_idStaff don't reliably have email; a phone number is the one identifier every staff member already uses to coordinate over WhatsApp today.

3.2 Data Model (ERD)

Entities: Shop, User, Product, StockLevel, Sale, Supplier, RestockOrder

EntityKey FieldsNotes
Shopid, name, phone, locationevery other table is Root tenant scoped to a shop
Userid, shop_id, name, phone, pin_hash, role (staff / owner)Role is currently just two values
Productid, shop_id, name, sku, unit_price, reorder_threshold, reorder_quantitySlow-changing master data
StockLevelid, product_id, current_quantity, last_updatedKept separate from Product on purpose; it's fast-changing, a frequently-written value
Saleid, shop_id, product_id, staff_id, quantity, sale_price, timestampAppend-only doubles as the audit log
Supplierid, shop_id, product_id, phone, name, webhook_secretOne supplier per product in v1
RestockOrderid, shop_id, product_id, supplier_id, quantity_requested, status, created_at, confirmed_atstatus ∈ {Pending, Sent, Confirmed, Fulfilled}

3.3 Core Workflow: How status updates flow

Step 01

Sale Logged

StockLevel decremented automatically

Step 02

Threshold Check

current_quantity <= reorder_threshold?

Step 03

Restock Order Triggered

If breached AND no open order exists: Create RestockOrder (status = Pending)

Step 04

Supplier Notification Sent

Background task sends SMS/WhatsApp message (status = Sent)

Step 05

Webhook Confirmation

Supplier replies with keyword (e.g. "YES 4521") → HMAC-verified → status → Confirmed

Step 06

Fulfillment & Restock

Owner marks Fulfilled once physical stock arrives → StockLevel manually topped up

3.4 What Happens Outside the App

Physical delivery of goods, price negotiation, and any confirmation that doesn't come through the structured SMS keyword all happen outside DukaSync. The system is deliberately not trying to be the source of truth for the physical handoff—it flags and tracks the request, it doesn't replace the phone call for anything nuanced.

3.5 Security

Inbound supplier-confirmation webhook is HMAC-signed and checked against a per-supplier secret, so a third party can't spoof a "Confirmed" status on someone else's order. PINs are stored hashed (bcrypt), never in plaintext. Every order is tied to a user id.

04. Trade-off Log (One-Page)

1. No dedicated barcode scanning

Trade-off #1

What it is: Staff pick products from a searchable list or type the SKU manually; no camera-based reliable scan.

Why accepted: A camera-scanning integration was a multi-day hardware investment; manual/search entry gets staff logging sales on day one.

2. Stock concurrency race conditions

Trade-off #2

What it is: Two staff logging the last unit of the same product at nearly the same moment can both succeed stock can go negative.

Why accepted: Target shops run 1-2 tills; sequential sales are the overwhelming case, and row-locking wasn't worth the complexity for v1.

3. Supplier confirmation depends on rigid format SMS

Trade-off #3

What it is: If a supplier replies in free text instead of the expected keyword, DukaSync can't parse it.

Why accepted: A full NLP parser or dedicated app was out of scope for a one-week build; a fixed keyword was the fastest path.

4. No role permissions beyond PIN

Trade-off #4

What it is: Any staff PIN can do anything a staff account can do.

Why accepted: These shops run 2-5 person teams who trust each other.

05. Roadmap

Near-term: Camera-based barcode scan fast-path; concurrency-safe stock writes; minimal supplier web confirmation portal.

Mid-term: Multi-branch support for one owner with several shops; sales analytics (fast vs. slow movers, margin by product); customer history tied to phone number.

Long-term: A supplier-side marketplace to compare prices across multiple suppliers before a restock request goes out; financing/credit-line integration for restocking.

06. Cross-Examination Prep (State Evidence)

Q: Why FastAPI over Django or Node/Express?

State: We picked FastAPI.

Context: We needed async I/O so sale-logging never blocks on a third-party SMS call, plus Pydantic validation to catch inventory-math bugs.

Q: Why PostgreSQL instead of a NoSQL store like Firebase?

State: We chose PostgreSQL.

Context: Our core relationships are relational, and a stock decrement plus a threshold check need to happen atomically.

Q: What actually happens today if two staff sell the last unit at the same time?

State: Both sales succeed today, and stock can go negative.

Context: We accepted this because our target shops run 1-2 tills, making it a low-probability event.

Written by Rosalind Nduta

Ivan

kubasuivansakwa23@gmail.com

Mobile developer and Front-end Developer

Socials

© Copyright 2025. Made By Kubasu Ivan Sakwa