Mungai Plan: Building an FMCG Distribution Management System with Python OOP
An FMCG distribution company deals with many products, multiple product categories, suppliers, customers, sales representatives, warehouses, stock movements, orders, deliveries, and payments. Managing all these activitie
An FMCG distribution company deals with many products, multiple product categories, suppliers, customers, sales representatives, warehouses, stock movements, orders, deliveries, and payments. Managing all these activities manually becomes difficult as the business grows. Your project, Mungai Plan, will be a Python application designed to manage these operations in one system. It will also be a practical way for you to apply the Object-Oriented Programming concepts you have been learning this week. The goal is to understand not only how to write Python classes, but also how to decide which classes a real business system needs, how they interact, and how to build the application in manageable stages. The examples below use fictional products, prices, and business records. They are for learning and do not represent the actual internal operations of Gilani's Distributors.
1. Introduction to the Mungai Plan system
Mungai Plan is a proposed FMCG (Fast-Moving Consumer Goods) Distribution Management System. It will help a distribution business organise its products, inventory, purchases, sales, customers, suppliers, and deliveries. FMCG products are goods that consumers purchase frequently, such as food, drinks, personal care products, and household cleaning supplies.
Examples include:
- Cooking oil, flour, sugar, rice, and packaged foods.
- Bottled water, juices, soft drinks, and other beverages.
- Soap, toothpaste, shampoo, and personal care products.
- Detergents, dishwashing liquids, and cleaning products.
- Snacks, biscuits, confectionery, and other packaged goods
Imagine a distributor managing 1,500 products supplied by 30 suppliers and sold to hundreds of retail shops.
Without an organised system, employees might struggle to answer questions such as:
- How many cartons of cooking oil are available?
- - Which products are running out of stock?
- - Which products will expire soon?
- - How many units were sold today?
- - Which customers owe the company money?
- - Which supplier supplied a particular batch?
- - Which orders have been dispatched but not delivered?
- - Which products generate the most revenue?
**
Applying the four main OOP principles
**
Mungai Plan gives you practical examples of all four major OOP principles.
** Encapsulation:** protecting business data
Suppose a product has 50 units in stock. You should not allow an employee to accidentally set the quantity to -100 or increase it without recording a stock receipt.
Encapsulation allows you to control how important information is changed.
class InventoryItem:
def __init__(self, sku, quantity=0):
if quantity < 0:
raise ValueError("Stock cannot be negative.")
self.sku = sku
self.__quantity = quantity
@property
def quantity(self):
return self.__quantity
def receive_stock(self, quantity):
if quantity <= 0:
raise ValueError("Received quantity must be positive.")
self.__quantity += quantity
def sell_stock(self, quantity):
if quantity <= 0:
raise ValueError("Sale quantity must be positive.")
if quantity > self.__quantity:
raise ValueError("Insufficient stock.")
self.__quantity -= quantity
Inheritance: sharing common employee behaviour
A distributor may have sales representatives, warehouse staff, delivery drivers, and administrators.
They all share basic employee information, but each has different responsibilities.
class Employee:
def __init__(self, employee_id, name):
self.employee_id = employee_id
self.name = name
def display_details(self):
print(self.employee_id, self.name)
class SalesRepresentative(Employee):
def take_order(self, customer):
print(f"{self.name} is taking an order for {customer}.")
class WarehouseClerk(Employee):
def receive_delivery(self, supplier):
print(f"{self.name} is receiving goods from {supplier}.")
The subclasses reuse Employee behaviour while adding their own operations.
In the finished system, inheritance should not be used simply to make the design look advanced. It is useful when there is a genuine shared type, and composition is often better for connecting business entities.
Polymorphism: producing reports in different ways
Mungai Plan might eventually export sales reports in different formats.
class CSVExporter:
def export(self, report):
print("Exporting report to CSV.")
class PDFExporter:
def export(self, report):
print("Exporting report to PDF.")
class ExcelExporter:
def export(self, report):
print("Exporting report to Excel.")
Create a Product class
Create product.py.
from decimal import Decimal
class Product:
def __init__(
self,
sku,
name,
category,
cost_price,
selling_price,
reorder_level=10
):
self.sku = sku.strip()
self.name = name.strip()
self.category = category
self.cost_price = Decimal(str(cost_price))
self.selling_price = Decimal(str(selling_price))
if not self.sku or not self.name:
raise ValueError("SKU and name are required.")
if self.cost_price < 0 or self.selling_price < 0:
raise ValueError("Prices cannot be negative.")
if reorder_level < 0:
raise ValueError("Reorder level cannot be negative.")
self.reorder_level = reorder_level
def calculate_margin(self):
return self.selling_price - self.cost_price
def calculate_margin_percentage(self):
if self.selling_price == 0:
return Decimal("0")
return (
self.calculate_margin()
/ self.selling_price
* Decimal("100")
)
def needs_reordering(self, current_stock):
return current_stock <= self.reorder_level
def display_details(self):
print(f"SKU: {self.sku}")
print(f"Name: {self.name}")
print(f"Category: {self.category.name}")
print(f"Cost: KSh {self.cost_price}")
print(f"Selling price: KSh {self.selling_price}")
print(f"Margin: KSh {self.calculate_margin():.2f}")
What happens when there are thousands of products?
You should not create a separate class for each product.
Instead, create one Product class and make many product objects from it.
products = [
Product(
"OIL001", "Cooking Oil 1 L",
oil_category, 180, 210
),
Product(
"OIL002", "Cooking Oil 500 ml",
oil_category, 100, 120
),
Product(
"OIL003", "Cooking Oil 5 L",
oil_category, 850, 980
)
]
Managing suppliers and purchase
Distributors often purchase products from manufacturers, importers, and other wholesalers. Mungai Plan should record who supplied the goods, what was ordered, the purchase cost, and how much was received.
Create a Supplier class
Create supplier.py.
class Supplier:
def __init__(
self,
supplier_id,
name,
phone,
email=""
):
self.supplier_id = supplier_id
self.name = name
self.phone = phone
self.email = email
def display_details(self):
print(f"Supplier: {self.name}")
print(f"Phone: {self.phone}")
print(f"Email: {self.email}")
Understand the purchase workflow
A purchase order represents what the company intends to buy. It does not necessarily mean the goods have arrived.
The usual workflow is:
- Create a purchase order.
- Specify products and quantities.
- Confirm the supplier and agreed costs.
- Receive the goods and record actual quantities.
- Record batches and expiry dates where applicable.
- Update inventory through a goods-received transaction.
- Record the supplier invoice and payment status. This distinction matters because a supplier might deliver 95 cartons when the purchase order requested 100. The system must record the actual receipt, not automatically add 100 cartons to stock.
A purchase order could contain multiple purchase order lines, each identifying a product, quantity, and agreed unit cost. This is another example of composition: the purchase order contains its lines.
Create a Customer class
Create customer.py.
class Customer:
def __init__(
self,
customer_id,
name,
phone,
location,
credit_limit=0
):
self.customer_id = customer_id
self.name = name
self.phone = phone
self.location = location
self.credit_limit = credit_limit
def display_details(self):
print(f"Customer: {self.name}")
print(f"Phone: {self.phone}")
print(f"Location: {self.location}")
print(f"Credit limit: KSh {self.credit_limit}")
Now create an order using the product and customer objects:
`from category import Category
from product import Product
from customer import Customer
from sales_order import SalesOrder
category = Category("CAT004", "Cooking Oil")
oil = Product(
"OIL001",
"Cooking Oil 1 L",
category,
180,
210
)
customer = Customer(
"CUS001",
"Nakuru Retail Shop",
"0711111111",
"Nakuru"
)
order = SalesOrder("SO001", customer)
order.add_line(oil, 10)
order.display_order()`
**
Invoices, payments, and customer balances
**
Recording a sale and receiving payment are different activities.
For example, a customer might receive goods worth KSh 20,000 but pay only KSh 12,000 immediately. The remaining KSh 8,000 is an outstanding balance, subject to the agreed terms and any applicable adjustments.
Mungai Plan should distinguish:
- Sales orders: what the customer requested.
- - Deliveries: what was dispatched and delivered.
- - Invoices: what the customer has been billed.
- - Payments: what the company has received.
- - Credit notes and returns: adjustments to amounts due. A simple invoice could have an invoice number, customer, issue date, due date, line items, total amount, and payment status.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.


